GOT、PLTとIAT
動的リンクされたライブラリのアドレス解決の際、ELFではGOT、PLTが、PEではIATがそれぞれ用いられる。たまにどっちがどっちでどうだったのか分からなくなるのでメモしておく。
GOTとPLT
GOT(Global Offset Table)はシンボルへのポインタの配列で、 プログラムが使用するすべてのライブラリ関数はGOTにエントリを持ち、そこで実際の関数のアドレスを管理する。 これにより、ライブラリをプロセスメモリ空間の中で容易に再配置できるようになる。
遅延リンク
ELFではPLT(Procedure Linkage Table)を用いて遅延リンクできるようにしている。 遅延リンクとは、共有ライブラリのロード時にシンボル解決を行うのではなく、関数が実際に呼び出されるときにシンボル解決を行うことである。 関数が最初に呼ばれるとき、飛び先は関数の本体ではなく、PLT上の対応するコードとなり、 そのPLTには、GOTの対応するスロットから飛び先アドレスを取得し、そこに飛ぶようなコードが格納されている。 呼びだされたときには、PLT経由でRTL(RunTime Linker: 実行時リンカ)のシンボル解決のコードが呼ばれ、 GOTに設定してある自分自身の飛び先を、本来の関数に設定しなおす。 関数が次に呼び出されたときには、今度はRTLは関与せずにGOTのエントリを通じて直接実行される。
確認する
以下のコードを実行し、puts()を呼ぶ前と呼んだ後でGOTのエントリを確認する。
コード
#include <stdio.h> int main() { puts("test"); }
gdbにより確認
$ gdb ./a.out Reading symbols from ./a.out...(no debugging symbols found)...done. gdb-peda$ start gdb-peda$ disas main Dump of assembler code for function main: 0x0000000000400526 <+0>: push rbp 0x0000000000400527 <+1>: mov rbp,rsp => 0x000000000040052a <+4>: mov edi,0x4005c4 0x000000000040052f <+9>: call 0x400400 <puts@plt> 0x0000000000400534 <+14>: mov eax,0x0 0x0000000000400539 <+19>: pop rbp 0x000000000040053a <+20>: ret End of assembler dump. gdb-peda$ disas 0x400400 Dump of assembler code for function puts@plt: 0x0000000000400400 <+0>: jmp QWORD PTR [rip+0x200c12] # 0x601018 0x0000000000400406 <+6>: push 0x0 0x000000000040040b <+11>: jmp 0x4003f0 End of assembler dump. gdb-peda$ x/x 0x601018 // GOTを確認 0x601018: 0x0000000000400406 // 書き換え前 gdb-peda$ break 0x400534 Function "0x400534" not defined. gdb-peda$ break *0x400534 Breakpoint 2 at 0x400534 gdb-peda$ c Continuing. test Breakpoint 2, 0x0000000000400534 in main () gdb-peda$ x/x 0x601018 // GOTを確認 0x601018: 0x00007ffff7a7d690 // 書き換え後
こうして遅延リンクを用いたアドレス解決は位置独立性と共有性を損なわずに行われる。
遅延リンクを用いるときGOTを動的に書き換えるため、GOTは書き込み可能である必要がある。 そのため、format string bugやROPを用いてGOT overwriteするといった形で攻撃に用いられることがある。
この攻撃に対するセキュリティ機構としてFull RELRO(RELocation ReadOnly)というものがある。 これは、遅延リンクを行わずに、ローダがプログラム実行開始時に実際の関数アドレスをGOTのエントリに挿入することで、 GOTを書き込み禁止するというものである。
IAT
Windows PEにもGOTと似たIAT(Import Address Table)というのがある。IATはプログラム中で使用するAPIのコードの開始位置のアドレスを格納したテーブルである。
IATのエントリにはローダがメモリ空間上に各DLLが読み込まれたアドレスをベースにして、実際の関数開始アドレスを書き込む。ローダが書き込むので実行前にIATのエントリは書き換えられる。これによって、プログラムはIATを参照するだけでAPIを呼び出せるようになる。IATのエントリは実行時には書き換える必要性がないので、デフォルトで書き込み禁止にすることができ、GOTより実行中に書き換えられるリスクは軽減されている。
IATが書き込み禁止になっているからといって、書き換えられない訳ではなく、Windows APIのVirtualProtect()を用いて書き込めるようにできる。 これはAPIのIATエントリを書き換え、任意の関数を指すようにするAPIフックに利用される。
参考資料
- C/C++ セキュアコーディング
- リンカ・ローダ実践開発テクニック
- Practical Malware Analysis
- ld -z relro で GOT overwrite attack から身を守る - memologue