基础栈溢出复习 三 之 SROP
最近出现SROP的题目,就是XCTF -NJCTF中的 Pwn300-233
当然,虽然出题人是这么出的,但是也还是有非预期做法的。比如Joker师傅的针对这个题目的强行解决方案,强行猜libc base 然后暴力跑,用ROP 解决。
那么 SROP是什么,与普通的ROP有什么区别呢?我们可以开始学习了。
什么是SROP
SROP: Sigreturn Oriented Programming 系统Signal Dispatch之前会将所有寄存器压入栈,然后调用signal handler,signal handler返回时会将栈的内容还原到寄存器。 如果事先填充栈,然后直接调用signal handler,那在返回的时候就可以控制寄存器的值。
首先,我们得先了解一下signal的调用流程,那么我就能大概了解SROP的利用原理。
正如mctrain,在他的《Sigreturn Oriented Programming (SROP) Attack攻击原理》文章里所提到的,当内核向某个进程发起(deliver)一个signal,该进程会被暂时挂起(suspend),进入内核(1),然后内核为该进程保存相应的上下文,跳转到之前注册好的signal handler中处理相应signal(2),当signal handler返回之后(3),内核为该进程恢复之前保存的上下文,最后恢复进程的执行(4)。
在这四步过程中,第三步是关键,即如何使得用户态的signal handler执行完成之后能够顺利返回内核态。在类UNIX的各种不同的系统中,这个过程有些许的区别,但是大致过程是一样的。
那么,我们是如何利用这个系统调用来做一些不可告人的事情的呢?
在singnal中可以说是,有两个层次,一个是用户,一个是内核层次,我们也可以将这个过程简单的看作。
- User code
- singnal handler
- sigreturn
如果在mctrain文章中看懂了,signal的调用流程,那么我们就可以讲讲,如何去利用攻击,即我们可以讲讲他的攻击流程。攻击流程
注: 以下图片内容均来自https://www.slideshare.net/AngelBoy1/sigreturn-ori 的PDF
- 当内核发起signal
- 这个时候,我们可以看到栈还并未没push数据,以及ip仍然在User code上。
- 将数据push到栈中时
- 将sigreturn syscall的位置 push 进栈
- 紧接着程序流程跳转至signal handler
- 从signal handler 返回
- 然后流程又跳转至 sigreturn code
- 执行 singreturn syscall
- stack 即栈上的内容全部 pop 回register ,流程又重新回到 user code
- 至此,我们基本完成了攻击,我们可以大概总结下,
我们需要的攻击条件
第一,攻击者可以通过stack overflow等漏洞控制栈上的内容;
第二,需要知道栈的地址(比如需要知道自己构造的字符串/bin/sh
的地址);
第三,需要知道syscall
指令在内存中的地址;
第四,需要知道sigreturn
系统调用的内存地址。
当然,更详细的,如利用SROP构造系统调用串(System call chains)依旧可以从mctrain,在他的《Sigreturn Oriented Programming (SROP) Attack攻击原理》文章找到,我们这里的重点并不是SROP,而是做SROP CTF题。
SROP构造,及攻击流程概括的来讲就是:
- 伪造sigcontext 结构,push进stack中
- 设置ret address在sigreturn syscall的gadget
- 将signal fram中的rip(eip)设置在syscall(int 0x80)
- 当sigreturn返回时,就可以执行syscall
需要说明的是sigretrun gadget的寻找是有前人总结的
- x86
- vdso 正常的 syscall handler也会使用的
- x64
- kernel <3.3
- vsyscall (0xffffffff600000) <= 位置一直固定
- kernel >= 3.3
开销更小比较容易理解, 那么路径更好指的是什么呢? 拿x86下的系统调用举例, 传统的int 0x80有点慢, Intel和AMD分别实现了sysenter, sysexit和syscall, sysret, 即所谓的快速系统调用指令, 使用它们更快, 但是也带来了兼容性的问题. 于是Linux实现了vsyscall, 程序统一调用vsyscall, 具体的选择由内核来决定. 而vsyscall的实现就在VDSO中.
Linux(kernel 2.6 or upper)环境下执行ldd /bin/sh, 会发现有个名字叫linux-vdso.so.1(老点的版本是linux-gate.so.1)的动态文件, 而系统中却找不到它, 它就是VDSO. 例如:
1 | wings@sw:~$ ldd /bin/sh |
为什么要用VDSO 来做ROP?
在X86系统中,传统的system call:int 0x80并不是由很好的效果的,因此在intel 新型的cpu提供了新的syscall指令。
- sysenter
- sysexit
(Linux kernel 》= 2.6后的版本支持新型syscall机制)
VDSI可以降低在传统的 int 0x80的overhead 以及提供了sigreturn 方便在signal handler结束后返回到user code
如何利用 VDSO 做ROP
我们需要知道 sysenter其参数传递方式和int 0x80是一样的,但是我们需要事前自己做好funcion prologpush ebp;mov ebp,sp
以及需要一个 “A good gadgaet for stack pivot”,因为如果没做function prolog可以利用ebp去改变stack位置
Retrun to vDSO
如何找到vdso 地址?
基本上里利用方法就是:
- 要么暴力解决
- 利用 信息泄露 即我们所受的information leak
- 使用ld.so _libc_stack_end找到 stack其实位置,计算ELF Auxiliary vector offset 并从中取出AT_SYSINFO_EHDR
- 使用ld.so中的_rtld_global_ro的某个offset也有vdso的位置。
我们需要尤其注意的是在开了ASLR的情况下,VDSO的利用是有一定优势的
在x86环境下:
只有一个字节是随机的,所以我们可以很容易暴力解决
在x64环境下
在开启了pie的情形 有 11字节是随机的 例如:CVE-2014-9585
但是在linux kernel 3.182.2版本之后,这个已经增加到了18个字节的随机重头戏来了:Defcon 2015 Qualifier fuckup
题目可以在这里下载: this
我们照旧来分析程序:
总体上来说
程序应该是开启了ASLR 的,每次
用户执行命令时,FUCKUP会根据类似于WELL512的生成算法生成的随机数,改变二进制映射的存储器的基址。
当我们运行程序时,可以看到有一个菜单
1 | $ ./fuckup |
当运行函数,以及反编译程序之后,我们可以了解程序功能。
当我们选择功能2的时候,“App moved to new random location”,text段和stack会被修改,重新指向新的内存地址
当我们选择3的时候,会告诉我们最后一个随机数(其当前determienstextbase)再次随机化text。这可以用于PRNG的预测
选项4:
1 | Input buffer is 10 bytes in size. Accepting 100 bytes of data. |
我们在功能3找到一个mmap 地址映射函数:
change_random(sub_80481A6)
1 | do |
所以寻常的思路,我们基本是做不了了
大概是这样的,做了不一样的地址映射,所以其实这个题目还是要回归于VDSO以及SROP。
思路如下:
32位下vdso 只有1字节是随机的,我们这里可以brute force然后利用其gadget
可以直接利用overflow return address,只有100个字节
先利用vdso的gadget做出read sys call 并加大input的大小
read 读入的内容放到tls
tls位置在vdso前一个page
使用sysenter 将stack 换到tls段
然后,我们在第二次输入的时候 可以将 /bin/sh 放入到tls段,这里要注意但是,这个时候tls已经在栈了紧接着,我们sigreturn gadget 以及 fack signal frame一并放进,然后可以直接execve执行 /bin/sh
进行循环,知道成功getshell
最后的exp,我没能搞定,这里可以参考 hastebin.com的脚本
1 | #!/usr/bin/env python3 |