読者です 読者をやめる 読者になる 読者になる

GOT、PLTとIAT

reversing

動的リンクされたライブラリのアドレス解決の際、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フックに利用される。

参考資料