一开始 Linux 是通过 int 0x80 中断的方式进入系统调用,它会先进行调用者特权级别的检查,然后进行压栈、跳转等操作,这无疑会浪费许多资源。从 Linux 2.6 开始,就出现了新的系统调用指令 sysenter/sysexit,前者用于从 Ring3 进入 Ring0,后者用于从 Ring0 返回 Ring3,它没有特权级别检查,也没有压栈的操作,所以执行速度更快。
signal 机制
如图所示,当有中断或异常产生时,内核会向某个进程发送一个 signal,该进程被挂起并进入内核(1),然后内核为该进程保存相应的上下文,然后跳转到之前注册好的 signal handler 中处理相应的 signal(2),当 signal handler 返回后(3),内核为该进程恢复之前保存的上下文,最终恢复进程的执行(4)。
一个 signal frame 被添加到栈,这个 frame 中包含了当前寄存器的值和一些 signal 信息。
一个新的返回地址被添加到栈顶,这个返回地址指向 sigreturn 系统调用。
signal handler 被调用,signal handler 的行为取决于收到什么 signal。
signal handler 执行完之后,如果程序没有终止,则返回地址用于执行 sigreturn 系统调用。
sigreturn 利用 signal frame 恢复所有寄存器以回到之前的状态。
最后,程序执行继续。
不同的架构会有不同的 signal frame,下面是 32 位结构,sigcontext 结构体会被 push 到栈中:
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;
};
下面是 64 位,push 到栈中的其实是 ucontext_t 结构体:
// defined in /usr/include/sys/ucontext.h
/* Userlevel context. */
typedef struct ucontext_t
{
unsigned long int uc_flags;
struct ucontext_t *uc_link;
stack_t uc_stack; // the stack used by this context
mcontext_t uc_mcontext; // the saved context
sigset_t uc_sigmask;
struct _libc_fpstate __fpregs_mem;
} ucontext_t;
// defined in /usr/include/bits/types/stack_t.h
/* Structure describing a signal stack. */
typedef struct
{
void *ss_sp;
size_t ss_size;
int ss_flags;
} stack_t;
// difined in /usr/include/bits/sigcontext.h
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];
};
$ file funsignals_player_bin
funsignals_player_bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
这是一个 64 位静态链接的 srop,可以说是什么都没开。。。
$ checksec -f funsignals_player_bin
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
No RELRO No canary found NX disabled No PIE No RPATH No RUNPATH No 0 0 funsignals_player_bin
gdb-peda$ disassemble _start
Dump of assembler code for function _start:
0x0000000010000000 <+0>: xor eax,eax
0x0000000010000002 <+2>: xor edi,edi
0x0000000010000004 <+4>: xor edx,edx
0x0000000010000006 <+6>: mov dh,0x4
0x0000000010000008 <+8>: mov rsi,rsp
0x000000001000000b <+11>: syscall
0x000000001000000d <+13>: xor edi,edi
0x000000001000000f <+15>: push 0xf
0x0000000010000011 <+17>: pop rax
0x0000000010000012 <+18>: syscall
0x0000000010000014 <+20>: int3
End of assembler dump.
gdb-peda$ disassemble syscall
Dump of assembler code for function syscall:
0x0000000010000015 <+0>: syscall
0x0000000010000017 <+2>: xor rdi,rdi
0x000000001000001a <+5>: mov rax,0x3c
0x0000000010000021 <+12>: syscall
End of assembler dump.
gdb-peda$ x/s flag
0x10000023 <flag>: "fake_flag_here_as_original_is_at_server"
$ python2 exp_funsignals.py
[*] '/home/firmy/Desktop/funsignals_player_bin'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x10000000)
RWX: Has RWX segments
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] Switching to interactive mode
fake_flag_here_as_original_is_at_server\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[*] Got EOF while reading in interactive