PLT(Procedure Linkage Table)とGOT(Global Offset Table)
GOT Overwriteの問題をやる前に、共有ライブラリの関数呼び出しはどのようなものなのか?PLTとGOTについて勉強する。
PLTとGOTを具体的にみる
以下のプログラムを使う:
// gcc test.c -fcf-protection=none -z norelro -no-pie -g #include <stdio.h> int main(void) { printf("Hello, "); puts("World!"); printf("printf-2"); }
PLTセクションは以下のようになっている:
┌──(shoebill㉿shoebill)-[~/test_GOT-PLT] └─$ objdump --no-show-raw-insn -M intel -j .plt -d ./a.out セクション .plt の逆アセンブル: 0000000000401020 <puts@plt-0x10>: 401020: push QWORD PTR [rip+0x22c2] # 4032e8 <_GLOBAL_OFFSET_TABLE_+0x8> 401026: jmp QWORD PTR [rip+0x22c4] # 4032f0 <_GLOBAL_OFFSET_TABLE_+0x10> 40102c: nop DWORD PTR [rax+0x0] 0000000000401030 <puts@plt>: 401030: jmp QWORD PTR [rip+0x22c2] # 4032f8 <puts@GLIBC_2.2.5> 401036: push 0x0 40103b: jmp 401020 <_init+0x20> 0000000000401040 <printf@plt>: 401040: jmp QWORD PTR [rip+0x22ba] # 403300 <printf@GLIBC_2.2.5> 401046: push 0x1 40104b: jmp 401020 <_init+0x20>
gdbで解析
一番最初のprintf
が呼ばれる様子を、si
コマンドで一つ一つの命令を追いながらみていく。
最初のprintf
にブレイクポイントを打って走らせると
一命令実行すると(上で示したPLTセクションも参考に)
このタイミングでGOTエントリをみると、printf
のアドレスではなくprintf@plt+6
になっている:
telescope 0x403300 3 gdb-peda$ telescope 0x403300 3 0000| 0x403300 --> 0x401046 (<printf@plt+6>: push 0x1) 0008| 0x403308 --> 0x0 0016| 0x403310 --> 0x0
二命令進めると
さらに二命令進めると
_dl_runtime_resolve_xsave
という関数は、libc内にあるprintf
のアドレスを解決し、最終的にそのアドレスに遷移する。
ここで二番目のputs
のにブレイクポイントを打つ。その後continue
コマンドでcall 0x401030 <puts@plt>
の前までいく。
このタイミングで再度GOTエントリをみてみると
gdb-peda$ telescope 0x403300 3 0000| 0x403300 --> 0x7ffff7c58380 (<__printf>: sub rsp,0xd8) 0008| 0x403308 --> 0x0 0016| 0x403310 --> 0x0
先ほどprintf@plt+6
と書いてあった部分が、今はprintf
の実体のアドレスになっている。
上記でみてきたjmp
命令たちをまとめると以下のように動いてる:
call 0x401040 <printf@plt> [PLT] → 0x403300 <printf@GLIBC_2.2.5> [GOT] → 0x401046 <printf@plot+6> → 0x401020 <.plt> → 0x4032f0 <_dl_runtime_resolve_xsave>
共有ライブラリの関数が呼ばれる時は、初回にアドレス解決を行い、次回以降はその解決結果を利用して関数に遷移する。
そこで、GOTエントリを編集してジャンプする先を制御する。これがGOT Overwrite攻撃。