GOT overwrite(弊研究室の某課題について考える13日目)
はじめに
この記事は弊研究室の某課題について考えるの13日目の記事です。
今日はよく使われるGOT overwriteの仕組みについてやります
GOTの流れ
ret2pltのところでpltとgotの説明をしましたが、pltからgotにジャンプしたところで説明を終えました。今日はその後から話します。
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>
pltに飛んだ後に201018
というグローバルオフセットにジャンプしますがこの201018
が指す値はdynamic linkerによって決定します。
このdynamic linkerがやることですが次のどちらかになります。
- オブジェクトがロードされた時(プログラムの起動時)に、dynamic linkerが全てのGOTのエントリに本当の関数のアドレス(libc.soのputsなど)を埋める
- オブジェクトがロードされた時(プログラムの起動時)には、GOTに特別な値を入れておき、本当の関数のアドレス調査を、その関数の初回呼び出し時まで遅延する
それでデフォルトの動きは2になります。ではその流れを見ます。
GOTの流れを確認する
サンプルプログラム
#include<stdio.h> int main(){ puts("first"); puts("next"); return 0; }
Dump of assembler code for function main: 0x000000000000064a <+0>: push rbp 0x000000000000064b <+1>: mov rbp,rsp 0x000000000000064e <+4>: lea rdi,[rip+0x9f] # 0x6f4 0x0000000000000655 <+11>: call 0x530 <puts@plt> 0x000000000000065a <+16>: lea rdi,[rip+0x99] # 0x6fa 0x0000000000000661 <+23>: call 0x530 <puts@plt> 0x0000000000000666 <+28>: mov eax,0x0 0x000000000000066b <+33>: pop rbp 0x000000000000066c <+34>: ret End of assembler dump.
11行目と23行目にブレークポイントを設置します。
1回目のputs関数
gdb-peda$ si [-------------------------------------code-------------------------------------] 0x555555554521: xor eax,0x200ae2 0x555555554526: jmp QWORD PTR [rip+0x200ae4] # 0x555555755010 0x55555555452c: nop DWORD PTR [rax+0x0] => 0x555555554530 <puts@plt>: jmp QWORD PTR [rip+0x200ae2] # 0x555555755018 | 0x555555554536 <puts@plt+6>: push 0x0 | 0x55555555453b <puts@plt+11>: jmp 0x555555554520 | 0x555555554540 <_start>: xor ebp,ebp | 0x555555554542 <_start+2>: mov r9,rdx |-> 0x555555554536 <puts@plt+6>: push 0x0 0x55555555453b <puts@plt+11>: jmp 0x555555554520 0x555555554540 <_start>: xor ebp,ebp 0x555555554542 <_start+2>: mov r9,rdx gdb-peda$ si --snip-- gdb-peda$ si [-------------------------------------code-------------------------------------] 0x55555555451d: add BYTE PTR [rax],al 0x55555555451f: add bh,bh 0x555555554521: xor eax,0x200ae2 => 0x555555554526: jmp QWORD PTR [rip+0x200ae4] # 0x555555755010 | 0x55555555452c: nop DWORD PTR [rax+0x0] | 0x555555554530 <puts@plt>: jmp QWORD PTR [rip+0x200ae2] # 0x555555755018 | 0x555555554536 <puts@plt+6>: push 0x0 | 0x55555555453b <puts@plt+11>: jmp 0x555555554520 |-> 0x7ffff7dede30 <_dl_runtime_resolve_xsave>: push rbx 0x7ffff7dede31 <_dl_runtime_resolve_xsave+1>: mov rbx,rsp 0x7ffff7dede34 <_dl_runtime_resolve_xsave+4>: and rsp,0xffffffffffffffc0 0x7ffff7dede38 <_dl_runtime_resolve_xsave+8>: sub rsp,QWORD PTR [rip+0x20eb09] # 0x7ffff7ffc948 <_rtld_local_ro+168> JUMP is taken
1回目ですがdl_runtime_resolve_xsaveやその次のdl_fixupで動的に決定しています。
次に2回目のputs関すを見てみます
gdb-peda$ c Continuing. first [-------------------------------------code-------------------------------------] 0x55555555464e <main+4>: lea rdi,[rip+0x9f] # 0x5555555546f4 0x555555554655 <main+11>: call 0x555555554530 <puts@plt> 0x55555555465a <main+16>: lea rdi,[rip+0x99] # 0x5555555546fa => 0x555555554661 <main+23>: call 0x555555554530 <puts@plt> 0x555555554666 <main+28>: mov eax,0x0 0x55555555466b <main+33>: pop rbp 0x55555555466c <main+34>: ret 0x55555555466d: nop DWORD PTR [rax] Guessed arguments: arg[0]: 0x5555555546fa --> 0x1b0100007478656e [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 2, 0x0000555555554661 in main () gdb-peda$ si [-------------------------------------code-------------------------------------] 0x555555554521: xor eax,0x200ae2 0x555555554526: jmp QWORD PTR [rip+0x200ae4] # 0x555555755010 0x55555555452c: nop DWORD PTR [rax+0x0] => 0x555555554530 <puts@plt>: jmp QWORD PTR [rip+0x200ae2] # 0x555555755018 | 0x555555554536 <puts@plt+6>: push 0x0 | 0x55555555453b <puts@plt+11>: jmp 0x555555554520 | 0x555555554540 <_start>: xor ebp,ebp | 0x555555554542 <_start+2>: mov r9,rdx |-> 0x7ffff7a8f130 <puts>: push r13 0x7ffff7a8f132 <puts+2>: push r12 0x7ffff7a8f134 <puts+4>: mov r12,rdi 0x7ffff7a8f137 <puts+7>: push rbp JUMP is taken [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0x0000555555554530 in puts@plt () gdb-peda$ si [-------------------------------------code-------------------------------------] 0x7ffff7a8f125 <popen@@GLIBC_2.2.5+133>: call QWORD PTR [rip+0x342e6d] # 0x7ffff7dd1f98 0x7ffff7a8f12b <popen@@GLIBC_2.2.5+139>: jmp 0x7ffff7a8f0ff <popen@@GLIBC_2.2.5+95> 0x7ffff7a8f12d: nop DWORD PTR [rax] => 0x7ffff7a8f130 <puts>: push r13 0x7ffff7a8f132 <puts+2>: push r12 0x7ffff7a8f134 <puts+4>: mov r12,rdi 0x7ffff7a8f137 <puts+7>: push rbp 0x7ffff7a8f138 <puts+8>: push rbx
pltの後にすぐにlibcのputs関数に飛んでいることが確認できます。
GOT overwrite
動的に決定しているということはプログラム実行中にGOTを書き換えてしまえば任意の関数を実行できるようにすることができます
FSBでは任意4byteを書き換えることができるのでGOT overwriteで攻撃できます。
GOT overwriteの実験
#include <stdio.h> #include <string.h> int test_puts(){ puts("test"); } int main(int argc, char *argv[]) { char buf[100]; strncpy(buf, argv[1], 100); printf(buf); putchar('\n'); return 0; }
ここではputscharのGOTを書き換えてtest_putsにしてみます。
gdb-peda$ disas main Dump of assembler code for function main: 0x08048511 <+0>: push ebp 0x08048512 <+1>: mov ebp,esp 0x08048514 <+3>: and esp,0xfffffff0 0x08048517 <+6>: add esp,0xffffff80 0x0804851a <+9>: mov eax,DWORD PTR [ebp+0xc] 0x0804851d <+12>: mov DWORD PTR [esp+0xc],eax 0x08048521 <+16>: mov eax,gs:0x14 0x08048527 <+22>: mov DWORD PTR [esp+0x7c],eax 0x0804852b <+26>: xor eax,eax 0x0804852d <+28>: mov eax,DWORD PTR [esp+0xc] 0x08048531 <+32>: add eax,0x4 0x08048534 <+35>: mov eax,DWORD PTR [eax] 0x08048536 <+37>: mov DWORD PTR [esp+0x8],0x64 0x0804853e <+45>: mov DWORD PTR [esp+0x4],eax 0x08048542 <+49>: lea eax,[esp+0x18] 0x08048546 <+53>: mov DWORD PTR [esp],eax 0x08048549 <+56>: call 0x80483f0 <strncpy@plt> 0x0804854e <+61>: lea eax,[esp+0x18] 0x08048552 <+65>: mov DWORD PTR [esp],eax 0x08048555 <+68>: call 0x8048390 <printf@plt> 0x0804855a <+73>: mov DWORD PTR [esp],0xa 0x08048561 <+80>: call 0x80483e0 <putchar@plt> 0x08048566 <+85>: mov eax,0x0 0x0804856b <+90>: mov edx,DWORD PTR [esp+0x7c] 0x0804856f <+94>: xor edx,DWORD PTR gs:0x14 0x08048576 <+101>: je 0x804857d <main+108> 0x08048578 <+103>: call 0x80483a0 <__stack_chk_fail@plt> 0x0804857d <+108>: leave 0x0804857e <+109>: ret End of assembler dump. gdb-peda$ b *main+80 Breakpoint 1 at 0x8048561 gdb-peda$ r AAAA [----------------------------------registers-----------------------------------] EAX: 0x4 EBX: 0xb7fc2000 --> 0x1acda8 ECX: 0x0 EDX: 0xb7fc3898 --> 0x0 ESI: 0x0 EDI: 0x0 EBP: 0xbffff648 --> 0x0 ESP: 0xbffff5c0 --> 0xa ('\n') EIP: 0x8048561 (<main+80>: call 0x80483e0 <putchar@plt>) EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x8048552 <main+65>: mov DWORD PTR [esp],eax 0x8048555 <main+68>: call 0x8048390 <printf@plt> 0x804855a <main+73>: mov DWORD PTR [esp],0xa => 0x8048561 <main+80>: call 0x80483e0 <putchar@plt> 0x8048566 <main+85>: mov eax,0x0 0x804856b <main+90>: mov edx,DWORD PTR [esp+0x7c] 0x804856f <main+94>: xor edx,DWORD PTR gs:0x14 0x8048576 <main+101>: je 0x804857d <main+108> Guessed arguments: arg[0]: 0xa ('\n') [------------------------------------stack-------------------------------------] 0000| 0xbffff5c0 --> 0xa ('\n') 0004| 0xbffff5c4 --> 0xbffff847 ("AAAA") 0008| 0xbffff5c8 --> 0x64 ('d') 0012| 0xbffff5cc --> 0xbffff6e4 --> 0xbffff818 ("/home/tsugumiyagi/Documents/adventor/got/a.out") 0016| 0xbffff5d0 --> 0xbffff684 --> 0x1389ee32 0020| 0xbffff5d4 --> 0xbffff5f8 --> 0x0 0024| 0xbffff5d8 ("AAAA") 0028| 0xbffff5dc --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1, 0x08048561 in main () gdb-peda$ disas test_puts Dump of assembler code for function test_puts: 0x080484fd <+0>: push ebp 0x080484fe <+1>: mov ebp,esp 0x08048500 <+3>: sub esp,0x18 0x08048503 <+6>: mov DWORD PTR [esp],0x8048610 0x0804850a <+13>: call 0x80483b0 <puts@plt> 0x0804850f <+18>: leave 0x08048510 <+19>: ret End of assembler dump. gdb-peda$ disas 0x080483e0 Dump of assembler code for function putchar@plt: 0x080483e0 <+0>: jmp DWORD PTR ds:0x804a020 0x080483e6 <+6>: push 0x28 0x080483eb <+11>: jmp 0x8048380 End of assembler dump. gdb-peda$ set {int}0x804a020 = 0x080484fd gdb-peda$ c Continuing. AAAAtest
putcharのコードを確認し0x804a020
の部分をtest_puts
の始まりである0x080484fd
に変更した結果出力がAAAA\n
となるものがAAAAtest
となっていることが確認できます。