有用的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类型有效
  • 核心

    • 但是有条件限制,lea带入到rsi的值需要是NULL
  • x86不存在One-gadget-RCE

    • 因为libc中有PIC,将ebx保存为基指针
      • 需要以ebx+offset的形式访问字符串
    • 如果ebx不包含libc的基地址,则它无法正常工作
      • 如果ebx包含libc的基地址,则可能存在One-gadget-RCE的地址
      • 但是,栈上对应第二个参数的部分需要为NULL(与x64相同)
      • 所以不太现实
  • 在x64中没有基指针这样的东西,它由RIP的相对地址表示

    • 寄存器不必包含特定数字
    • 使用这种技术的概率非常高

进阶ROP类型

  • JOP (Jump-Oriented Programming)
    • 基于jmp指令而不是return的ROP
  • COP (Call-Oriented Programming)
    • 基于call指令的ROP
      • 习惯构造ROP后,无意中就会用到JOP和COP
      • 在x64中,可以只使用heap伤的JOP/COP进行攻击,而无需使用任何stack数据
  • SROP(Sigreturn-Oriented Programming)
    • 利用信号中断恢复重写寄存器并简化ROP
      • x86和x64都可以使用,但要注意需要有足够的栈溢出空间
  • 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的情况
  • 上述的情况,如果将GOT修改为system(),那么调用时将成为system(user_input)

ASLR地址相关技术

  • 通过预先调查,将system()或者”/bin/sh”的地址加载到stack上
  • 。。。
  • 但是,有一个问题
    • 如果对方环境的libc版本不是特定的就不行了
    • 单纯绕过NX的话,可以采用上述方式,但是,如果有ASLR呢
  • 真的不知道libc吗?
    • libc.so大体上是公开的
      • deb, rpm之类的都包含有
    • 也就是说,你拥有很多可能的libc
    • 在ASLR下,libc以0x1000为单位被随机化加载
      • 也就是说,末尾12比特(0x00000FFF)不是随机的
      • 使用泄漏的GOT最后12位作为关键字在libcdb中搜索以缩小libc版本范围
      • 如果可以确定libc版本,就可以计算出其他地址,例如”/bin/sh”,DROP gadget等

针对libc DB的对策

  • 当然,如果libc不在DB中就无法搜索
    • CTF出题人可能会使用自定制libc
    • 这是针对libcdb的对策

绕过”针对libc DB的对策”

方式一

  • 使用Gentoo的stage3.tar.bz2中包含的libc.so

方式二

  • 从GOT等中删除地址的末尾12+α位,之后计算libc base
  • 从 libc base开始泄漏大概0x200000字节的数据
  • 根据”exit 0”之类的字符串作为参照查找system地址

方式三

  • 使用ret2dl_runtime_resolve