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 = &syscallSTART 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; ret
gadget :- 因為 stack (rsp) 已經被修改了,所以
syscall; ret
之後會跳到下一個 Signal Frame 的__restore_rt
(會執行下一個sigreturn
) 。
- 因為 stack (rsp) 已經被修改了,所以
加上以上動作便可以把很多 System Call 連接起來!