はじめに
この記事は弊研究室の某課題について考えるの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となっていることが確認できます。