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がやることですが次のどちらかになります。

  1. オブジェクトがロードされた時(プログラムの起動時)に、dynamic linkerが全てのGOTのエントリに本当の関数のアドレス(libc.soのputsなど)を埋める
  2. オブジェクトがロードされた時(プログラムの起動時)には、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となっていることが確認できます。

参考文献