Sigreturn Oriented Programming,依然是一种很好用的rop技术啦!
srop
只需要一个syscall指令地址,一个足够长的buffer overflow与可控的eax(eax是函数的返回值,所以控制起来比较容易),就可以达到控制任意寄存器的目的!
srop原理
当内核将signal dispatch给user mode,会将所有寄存器入栈,在执行sigreturn system call时会恢复寄存器:
恢复时,即返回地址为rt_sigreturn
,它内部再调用sigreturn
,后者会将存储在栈上的寄存器值填回寄存器,此时栈上的结构如下:
1 | struct sigcontext_32 { |
于是可以构造sigcontext结构,ax,bx等存参数,pc存系统调用指令地址,于是就可以控制全部寄存器并进行系统调用了~
vdso
上面已经提了原理,现在实现起来还要做些事–找到syscall gadget
,动态链接时syscall一般不会出现在给的binary中,libc里面的一般位置都是不固定的,于是就需要这个知识点了:
这个图中是sigreturn
这个gadget的位置,可以看到在32位下是vdso,64位下只有libc了,但是其实sigreturn本来就是一个系统调用,而且没有参数,于是只需要在32位下将eax设置为119,64位下将rax设置为15,再使用syscall指令即可,于是须要的就是syscall,下图就是比较通用的情况了:
在低版本的64位Linux中,vsyscall这片区域的位置是固定的,当然是最好用的了,而高版本的就是位置不固定的vdso了。vdso和vsyscall都是用来加速一些并不需要太高权限的系统调用的,他们最大的区别就是前者的位置是不固定的,于是前者更加安全,可以使用gdb的dumpmem vd vdso
与dumpmem vs vsyscall
将其dump出来观察。
现在主要的问题就是vdso位置不固定了,但是幸运的是,vdso的伪随机是很比较弱的:
x86: 只有⼀個 byte 是 random 的,所以有 1/256 之⼀的機率猜
x64:在有 pie 的情況下,只有 11 bit 是 random 的
CVE-2014-9585
linux kernel 3.18.2 後已修正到 18 bit
于是爆破总是最粗暴的方法!当然另外的方法就是通过泄露 __libc_stack_end
得到栈起始地址再得到auxv再得到vdso的地址。
pwntools工具
emmm,比直接构造要简单一点点,只需要注意下部分寄存器的值不能随便改动即可:
1 | ## 在有明显控制eax,有int80时优先使用srop |
也可以手动来:
1 | ## 这里的寄存器不能随便填,emmm,没填完 |
small-bin
emmmmm,名字是乱取的,因为这不是某道题[4]:
1 | ;;;安全:ASLR+NX |
观察发现功能很简单的,缓冲8bytes,最大读入1024bytes,明显的溢出但是利用起来似乎有些困难,思路是:
- 第一次输入
sh\0
,并且溢出ROP为:Addr1 + Addr2 + Addr1 这样可以再次输入并且返回Addr2 - 第二次输入一个字符,此时rax置为1,返回到Addr2
- Addr2时参数是
syscall(rax=1,rdi=0,rsi=rsp,rdx=1024)
,即从当前栈输出1024字节,这里有个知识点就是stdin与stdout指向同一个字符设备: - 这样就能够读到
sh\0
的地址啦,因为栈上有一些指向栈的地址 - 此时再次到Addr1,可以输入一个sigFrame到栈上,它的作用是执行execv,即
rip=syscallAddr,rax=constants.SYS_execve,rdi=shAddr,rsi=0,rdx=0
,这次输入的返回地址写为Addr1+Addr3 - 到这里,再次再次输入15个字符,返回到Addr3,此时
rip=syscallAddr,rax=15
即调用sigreturn,调用时将之前的fake sigFrame放入寄存器,并且执行rip就可以拿到shell啦。
这就是总体思路,但是有一些细节需要注意,就是写入的时候可能会覆写掉之前的数据,这里要么尽量靠下写要么尽量靠上写即可。
另外,这里坐着的另一种思路也是很巧妙的,答题读写思路一样,但是他直接读到了auxv,从那里面找到了栈地址与vdso的地址,有了vdso就可以利用里面的代码构造gadgets啦,由此来构造普通的rop。
defcon-2015-fuckup
defcon的题就是直接,明说了漏洞在那里,限制是静态编译,NX保护,ASLR,代码与数据的地址是伪随机的,但是可以知道上一次的地址,经过分析可以直接带text里面找到execv的gadget,但是不知道它的地址,于是首先需要得到text的地址,就是第一种解法,使用z3约束求解,然后对于aslr的另一种通用解法是部分覆盖,在这里也能用:
1 | do |
现在记录第三种解法,即srop,这里最关键的就是需要知道vdso的地址,不过还有个问题就是溢出位数不够:
1 | unsigned int bufOverFlow() |
于是溢出的第16h字节才是返回地址,再之上能用的空间不足以容纳一个sigcontext(80bytes),于是通用的方法是先调用read,将后续的写入一个位置可知的地方,再迁移栈到那一个位置继续rop,
参考
[1]http://ytliu.info/blog/2015/12/02/sigreturn-oriented-programming-srop-attack-gong-ji-yuan-li/
[2]https://tc.gtisc.gatech.edu/bss/2014/r/srop-slides.pdf
[3]http://www.newsmth.net/bbsanc.php?path=%2Fgroups%2Fcomp.faq%2FKernelTech%2Finnovate%2Fsolofox%2FM.1222336489.G0
[4]http://v0ids3curity.blogspot.jp/2014/12/return-to-vdso-using-elf-auxiliary.html