有用的ROP技巧
__libc_csu_init
从stack中将值加载到寄存器的通用gadget
x86和x86_64中都存在,x86_64中这个会很方便
- 经常找不到
pop rdi; ret
这类,我们可以通过这种方式设置rdi
- 经常找不到
可以从stack经过r13设置rdi的值,但是需要注意只能设置32位,因为这里是edi而不是rdi
alarm(x)
- 在x86/x64中,想要通过ROP设置EAX/RAX为任意数值
- 可以写一个直接调用systemcall的ROP
- 通常搜索rop gadgets时无法找到
pop eax/rax
这种- 这是一个罕见的命令
- 可以使用alarm()来代替
- CTF问题一般都能很好实现alarm()
- 通过ret2plt来调用两次alarm(x) -> alarm(0),EAX/RAX的值将被设置为x
- 更多信息可以参考man alarm文档
- 可以使用相同的技术来修改其他函数(例如write)的返回值
Repeat-code
考虑系统调用受到沙箱限制
在例如libc中灵活使用
\xEB\xFE(jmp $-2)
这样的字节挺好- 到达此处时,会发生无限循环
- 在指令中间,可以进行控制
例如执行memcmp(key,input,n),根据返回值进行分支ROP
让它跳到
jmp $-2
或者会导致SIGSEGV的gadget- 如果key和input相等,则会进入无限循环导致连接无响应
- 如果key和input不同,则会产生SIGSEGV导致断开连接
- 因为这种差异,即使write/send有限,也可以指定一个字节
One-gadget-RCE
x64中要调用system(“/bin/sh”)的话,有几个必要条件
- pop rdi
- “/bin/sh”
- return system()
需要8*3=24字节的ROP
可以用一种有条件限制的8字节来代替
- 8字节只是一个ROP,无需带入到ROP中(只需要控制RIP就可以)
- 只对x64和xinetd类型有效
核心
- libc中存在自动执行
execve("/bin/sh",0,0)的
地址 - 参考DragonSector的资料(http://j00ru.vexillium.org/?p=2485)
- 但是有条件限制,lea带入到rsi的值需要是NULL
- libc中存在自动执行
x86不存在One-gadget-RCE
- 因为libc中有PIC,将ebx保存为基指针
- 需要以ebx+offset的形式访问字符串
- 如果ebx不包含libc的基地址,则它无法正常工作
- 如果ebx包含libc的基地址,则可能存在One-gadget-RCE的地址
- 但是,栈上对应第二个参数的部分需要为NULL(与x64相同)
- 所以不太现实
- 因为libc中有PIC,将ebx保存为基指针
在x64中没有基指针这样的东西,它由RIP的相对地址表示
- 寄存器不必包含特定数字
- 使用这种技术的概率非常高
进阶ROP类型
- JOP (Jump-Oriented Programming)
- 基于jmp指令而不是return的ROP
- COP (Call-Oriented Programming)
- 基于call指令的ROP
- 习惯构造ROP后,无意中就会用到JOP和COP
- 在x64中,可以只使用heap伤的JOP/COP进行攻击,而无需使用任何stack数据
- 基于call指令的ROP
- SROP(Sigreturn-Oriented Programming)
- 利用信号中断恢复重写寄存器并简化ROP
- x86和x64都可以使用,但要注意需要有足够的栈溢出空间
- 利用信号中断恢复重写寄存器并简化ROP
- BROP(Blind Return-Oriented Programming)
- 手边没有二进制原文件的ROP
- 详细可以参考ntddk的博客
GOT overwrite技术
将某个函数的GOT修改为system()
- 如果存在接受用户输入的函数就太好了
- 将想要执行的命令作为字符串传递成为函数参数
- 常见的一些
- strlen()
- 使用strlen(user_input)判断哟ing胡输入长度的情况
- strcmp()/memcmp()
- 使用strcmp(user_input,xxx)或者memcmp(user_input,xxx)将用户输入作为第一个参数的情况
- atoi()/strtol()
- 使用atoi(user_input)或者strtol(user_input,xxx,xxx)将用户输入转换成整数的情况
- free()
- 使用free(user_input_buffer)对用户输入进行free的情况
- strlen()
- 上述的情况,如果将GOT修改为system(),那么调用时将成为system(user_input)
ASLR地址相关技术
- 通过预先调查,将system()或者”/bin/sh”的地址加载到stack上
- 。。。
- 但是,有一个问题
- 如果对方环境的libc版本不是特定的就不行了
- 单纯绕过NX的话,可以采用上述方式,但是,如果有ASLR呢
- 真的不知道libc吗?
- libc.so大体上是公开的
- deb, rpm之类的都包含有
- 也就是说,你拥有很多可能的libc
- 将libc.so转换成DB
- http://libcdb.com/
- 在ASLR下,libc以0x1000为单位被随机化加载
- 也就是说,末尾12比特(0x00000FFF)不是随机的
- 使用泄漏的GOT最后12位作为关键字在libcdb中搜索以缩小libc版本范围
- 如果可以确定libc版本,就可以计算出其他地址,例如”/bin/sh”,DROP gadget等
- libc.so大体上是公开的
针对libc DB的对策
- 当然,如果libc不在DB中就无法搜索
- CTF出题人可能会使用自定制libc
- 这是针对libcdb的对策
绕过”针对libc DB的对策”
方式一
- 使用Gentoo的stage3.tar.bz2中包含的libc.so
- Gentoo的stage3是每周从源代码构建更新
- 每周的镜像也会公开,可以获取到
- 出题人自定义libc的话,一般会从最新源码构建
- 因此与Gentoo的libc.so没有太大区别
方式二
- 从GOT等中删除地址的末尾12+α位,之后计算libc base
- 从 libc base开始泄漏大概0x200000字节的数据
- 根据”exit 0”之类的字符串作为参照查找system地址
方式三
- 使用ret2dl_runtime_resolve