SROP
SROP Note
SROP Sigreturn-Oriented Programming
Signal 機制
- Signal 機制 (軟中斷訊號、軟中斷) 是類 UNIX 系統中 process 之間相互傳遞信息的一種方法。比方說: process 之間可以通過 system 調用 kill 來發送值軟中斷信號。 
- 信號機制常見步驟: - 步驟 1 : - Kernel 向某 process 發送 signal 機制,該 process 會暫時被 hang (掛起) ,進入 kernel 態。
 
- 步驟 2 : - Kernel 會為該 process 保存一些資訊,會把以下資訊壓入 stack 中: - 所有 register 值。 
- Signal 信息。 
- 指向 sigreturn 的系統調用位址。 
 
- 此部分是在 user process 的 address area 。 
- 之後會跳轉到 Signal Handler 中處理相應的 signal 。 
- Signal Frame 會因為架構的不同而不同。 
 
- 步驟 3 : - 當 Signal Handler 執行完之後, Signal Handler 返回。
 
- 步驟 4 : - Signal Handler 返回後, Kernel 會執行 return to sigreturn所指向的地方的程式,為原先之 process 恢復之前所保存的資訊,像是將先前壓入 stack 的 register 值重新 pop 回對應的 register 。最後恢復 process 的執行。
 
- Signal Handler 返回後, Kernel 會執行 
 
攻擊原理
- 一些關鍵: - Signal Frame 被保存在 user process 的 address area 中,所以 user process 是可以讀寫的。 
- 由於 "Kernel agnostic (不可知) about signal handlers." , Kernel 並不會去紀錄某 signal 對應的 Signal Frame ,所以,當執行 - return to sigreturn時,此刻的 Signal Frame 並不一定是之前 Kernel 為 user process 保存的 Signal Frame 。
 
- DEMO : Get Shell - 這邊以 64 bit 為例。 
- 假設 attacker 可以控制 user process 的 stack ,他就可以 fake 一個 Signal Frame 。 
- 一些可能相關的結構: - 主要是 - rt_sigframe結構:- 1 
 2
 3
 4
 5
 6
 7
 8
 struct rt_sigframe {
 char __user *pretcode;
 struct ucontext uc;
 struct siginfo info;
 /* fp state follows here */
 };- https://elixir.bootlin.com/linux/latest/source/arch/x86/include/asm/sigframe.h
 
- 在 - rt_sigframe結構裡出現的- ucontext結構:- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13- /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 
 struct ucontext {
 unsigned long uc_flags;
 struct ucontext *uc_link;
 stack_t uc_stack;
 struct sigcontext uc_mcontext;
 sigset_t uc_sigmask; /* mask last for extensibility */
 };- https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/ucontext.h
 
- 在 - ucontext結構裡出現的- sigcontext結構:- 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
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 struct sigcontext {
 __u64 r8;
 __u64 r9;
 __u64 r10;
 __u64 r11;
 __u64 r12;
 __u64 r13;
 __u64 r14;
 __u64 r15;
 __u64 rdi;
 __u64 rsi;
 __u64 rbp;
 __u64 rbx;
 __u64 rdx;
 __u64 rax;
 __u64 rcx;
 __u64 rsp;
 __u64 rip;
 __u64 eflags; /* RFLAGS */
 __u16 cs;
 /*
 * Prior to 2.5.64 ("[PATCH] x86-64 updates for 2.5.64-bk3"),
 * Linux saved and restored fs and gs in these slots. This
 * was counterproductive, as fsbase and gsbase were never
 * saved, so arch_prctl was presumably unreliable.
 *
 * These slots should never be reused without extreme caution:
 *
 * - Some DOSEMU versions stash fs and gs in these slots manually,
 * thus overwriting anything the kernel expects to be preserved
 * in these slots.
 *
 * - If these slots are ever needed for any other purpose,
 * there is some risk that very old 64-bit binaries could get
 * confused. I doubt that many such binaries still work,
 * though, since the same patch in 2.5.64 also removed the
 * 64-bit set_thread_area syscall, so it appears that there
 * is no TLS API beyond modify_ldt that works in both pre-
 * and post-2.5.64 kernels.
 *
 * If the kernel ever adds explicit fs, gs, fsbase, and gsbase
 * save/restore, it will most likely need to be opt-in and use
 * different context slots.
 */
 __u16 gs;
 __u16 fs;
 union {
 __u16 ss; /* If UC_SIGCONTEXT_SS */
 __u16 __pad0; /* Alias name for old (!UC_SIGCONTEXT_SS) user-space */
 };
 __u64 err;
 __u64 trapno;
 __u64 oldmask;
 __u64 cr2;
 struct _fpstate __user *fpstate; /* Zero when no FPU context */
 __u32 __fpstate_pad;
 __u64 reserved1[8];
 };- https://elixir.bootlin.com/linux/latest/source/arch/x86/include/uapi/asm/sigcontext.h
 
- 這邊注意到 - rt_sigframe結構裡還有一個- siginfo結構, SROP 主要是可以 call 一系列的 syscall ,只有涉及到控制那些 register ,- ucontext-->- sigcontext裡面可以控制 register ,既然已經到可以控制 register 的地方,再去追 siginfo 結構應該也沒有甚麼意義了。 (2022/10/22)
 
- 有了以上的結構資訊,我們便可以 "大致上" 排出在 stack 上的 Signal Frame 樣貌 (會說 "大致上" 是因為目前我只了解到會把相關資訊放到 stack 上,並未實際測試程式是如何放置這些值的,所以還是有些欄位參考網路上的 paper ) : - START BYTE - DATA (0x0~0x7) - DATA (0x8~0xF) - 0x00 - points to - __restore_rt- uc_flags- 0x10 - &uc- uc_stack.ss_sp- 0x20 - uc_stack.ss_flags- uc_stack.ss_size- 0x30 - r8- r9- 0x40 - r10- r11- 0x50 - r12- r13- 0x60 - r14- r15- 0x70 - rdi- rsi- 0x80 - rbp- rbx- 0x90 - rdx- rax- 0xA0 - rcx- rsp- 0xB0 - rip- eflags- 0xC0 - cs / gs / fs- err- 0xD0 - trapno- oldmask(unused)- 0xE0 - cr2(segfault addr)- fpstate- 0xF0 - __reserved- sigmask- 表格說明: - START BYTE : - 每 16 (0x10) 個 byte 一數。
 
- DATA (0x0~0x7) : - 設該列的 START BYTE 為 - SB。
- DATA (0x0~0x7) 為 - SB + 0x0~- SB + 0x7的 data 。
 
- DATA (0x8~0xF) : - 設該列的 START BYTE 為 - SB。
- DATA (0x8~0xF) 為 - SB + 0x8~- SB + 0xF的 data 。
 
- __restore_rt:- 此程式碼片段應該是會 call sigreturn(編號0xf) 這個 function 。
 
- 此程式碼片段應該是會 call 
 
 
- 偽造 Signal Frame : - 1 
 2
 3
 4- 偽造的部分: 
 rdi = &"/bin/sh"
 rax = 0x3b (execve)
 rip = &syscall- START BYTE - DATA (0x0~0x7) - DATA (0x8~0xF) - 0x00 - points to - __restore_rt- uc_flags- 0x10 - &uc- uc_stack.ss_sp- 0x20 - uc_stack.ss_flags- uc_stack.ss_size- 0x30 - r8- r9- 0x40 - r10- r11- 0x50 - r12- r13- 0x60 - r14- r15- 0x70 - rdi = &"/bin/sh"- rsi- 0x80 - rbp- rbx- 0x90 - rdx- rax = 0x3b(execve)- 0xA0 - rcx- rsp- 0xB0 - rip = &syscall- eflags- 0xC0 - cs / gs / fs- err- 0xD0 - trapno- oldmask(unused)- 0xE0 - cr2(segfault addr)- fpstate- 0xF0 - __reserved- sigmask
- 之後程式會執行一系列的 - pop指令,用來恢復 register 的值,當執行到- rip時,就會指向 syscall 的 address ,在對應到 register 裡面的值,便會開 shell ,這是因為我們偽造了 Signal Frame 。
 
System Call Chains
- 如果要執行 "一系列" 的函數,只要再修改我們偽造的 Signal Frame 就可以達成: - 控制 - rsp指向下一個 Signal Frame :- 讓很多 Signal Frame "連接" 起來。
 
- rip指向的 gadget 要是- syscall; retgadget :- 因為 stack (rsp) 已經被修改了,所以 syscall; ret之後會跳到下一個 Signal Frame 的__restore_rt(會執行下一個sigreturn) 。
 
- 因為 stack (rsp) 已經被修改了,所以 
 - 加上以上動作便可以把很多 System Call 連接起來! 

