SROP
signal机制
基本流程就是当有中断或者异常产生时,内核会向某个进程发送一个signal,该进程挂起进入内核,内核为其保存上下文,然后在signal handle中进行处理哦,退出后内核依据之前保存的上下文恢复进程原来的状态
这里有几个比较重要的地方
-
进程的上下文保存的结构叫signal frame,保存在用户的地址空间中,用户具有读写的权限
-
signal frame中主要保存寄存器的状态和值,栈上的局部变量和函数调用信息,结构体如下
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
// 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; };
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
// x64 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]; };
假设可以控制一个signal frame那么就可以随意控制寄存器的值,从而执行任意命令
最终就是要调用execve("/bin/sh", 0, 0)
在64位下要求rax=59,rdi=/bin/sh,rsi=0,rdx=0
smallest
检查
反汇编
|
|
程序是64位的,rax置0进行syscall调用的是read,对应的三个参数通过寄存器传递
|
|
read往栈顶读取地址作为返回值,同时栈顶的地址取决于外界的输入,因此可以多输几次入口地址多次进行系统调用
控制程序流程
|
|
gdb.attach(io, 'b *0x4000be; continue')
中需要下断点的原因
库函数本质上是对系统调用的多层封装,库函数的实现本身就调用了其它很多函数进行中断等待输入;在反汇编的代码中只有syscall,直接使用syscall 进行系统调用时,程序会进入内核态,执行系统调用。这个过程通常是同步的,不涉及中断。
write(1, buf,0x400)
泄露栈地址
sigreturnFrame写入栈
触发sigreturn调用read(0,leak_stack_addr,0x400)
,再把execve的sigreturn写入栈
再次输入15字节getshell
整体流程
read(0,buf,0x400) –> write(1,buf,0x400) –> sigreturn –> read(0,leak_stack_addr,0x400) –> sigreturn –> execve("/bin/sh",0,0)
exp
|
|