回连后禁用SIGALRM

在具有alarm(x)的二进制文件上,进行exploit

  • 假设已经反弹了shell
  • 能够得到远程shell,但几秒后就会断开连接
  • 如果能够快速输入’bash -i’的话当然很好
  • 没有确认细节,通过bash禁用SIGALRM(使连接继续保持)

设置EAX的值

通过ROP能够修改EAX/RAX为任意值的情况

  • 直接调用system call
  • 通过一些gadget结合EAX/RAX使用

但pop eax(rax)是很罕见的gadget

  • 可以通过函数返回值来控制
    • alarm(x) -> alarm(0)
    • write(fd, buf, x)
    • read(fd, buf, x)

某些情况下,能够将X设置为固定值

  • 如果想让rax=11,可以调用write(stdout, buf, size=11)
  • 但是,我们无法控制对应size参数的寄存器rdx
    • 在存在write(stdout, buf, size=12)这类情况下,可以使用
  • 存在使这个write的返回值为11的方法
    • 但是,仅适用于本地权限提升类型,并且不需要读取输出的题目

考虑write(stdout, buf, size)

  • 通常,stdout是阻塞模式
  • 也就是说,write()在写入size大小之前不会结束

如果stdout处于非阻塞模式呢?

  • 即便没有全部写,也可以返回,返回值小于等于size
  • 也就是说,需要将write setblocking(0)

setblocking(0)的socket stdout

  • 终端1 : 使用python的socket bind() localhost:1337
    • 作为服务器,即便有连接也不读取数据
  • 终端2: 使用python的socket s去connect() localhost:1337
    • 执行s.setblocking(0),将socket设置为非阻塞模式
  • 利用subprocess : p=Popen(“./binary”, stdin=PIPE, stdout=s)
    • 使用stdout和我们的socket s来进行ROP
    • 调用write(stdout, …),将一直写入,直到socket缓冲区被填满
    • write()因为非阻塞模式结束
    • write()的返回值将小于size(例如11)

不使用socket,使用同样buffer的pipe?

  • 这可能不行,因为返回errno: EAGAIN
  • 仅在socket指定fd,并且提前发送大量数据的情况下才有效
  • CodeGate 2014 - minibomb

__free_hook

glibc的malloc()和free()的实现,在.bss中存在函数指针

  • 如果__free_hook不为NULL,则调用它而不是free。__free_hook是bss中的函数指针

假设存在任意的Write-primitive

也就是,任意值可以写入任意内存的情况

重写__free_hook

  • 当调用free的时候,控制RIP
  • heap类问题下,重写__free_hook非常有用
    • 例如将__free_hook修改为system()
    • free(user_input)就变成了system(user_input)
  • __free_hook是glibc内部变量,所以它不受Full-RELRO影响
  • 类似的hook函数还有__malloc_hook等
  • CodeGate 2015 - weff

WildCopy

线程之间的条件竞争

充分利用两个线程

  • 线程A上执行memcpy(dst, src, 负数)
    • 参数使用负数,利用整数溢出
    • 因为memcpy将第三个参数看作unsigned,因此它会变成一个非常大的数
    • 线程A保持从src到dst的memcpy
    • 当dst到达不可访问的dst时会造成SIGSEGV
  • 线程B使用被覆盖的内存
    • 期待在线程A产生SIGSEGV之前发生上下文切换
    • 线程B通过使用被覆盖的内存,控制栈或者堆函数指针来执行execve
    • 调用execve时,所有线程都会停止,并且新进程会重置内存空间,因此不会产生SIGSEGV

栈保护页(Stack guard page)之外

Linux中创建线程的几种方式

  1. pthread系
    • pthread_create()之类
    • 标准的创建线程函数
  2. ucontext系
    • makecontext(),swapcontext(),getcontext(),setcontext()
    • 自己实现线程切换计时时使用
    • 还与sigaction()有关
  3. clone系
    • 创建线程的低层方式
    • clone(2)的flag参数,设置为CLONE_THREAD
  • 每个线程都是独立的栈
  • pthread系自动创建栈,但可以单独调整
  • ucontext系和clone系需要自己创建一个新的栈空间(例如通过mmap)

线程(pthread系)中,栈是单独创建的

  • 每个栈之间都有一个保护页(权限是—p)
  • 但是,在地址上是连续的
  • 线程B的栈
  • 线程A的栈
  • 原本的栈

如果一个线程用尽栈空间,会发生什么?

  • 在线程A中,考虑函数递归调用的情况
    • 栈可能会堆积很多
  • 最终,当尝试写入保护页时,发生SIGSEGV
    • 因为权限是---p,不能读写

默认情况下,保护页的大小只有0x1000

  • 如果线程A的递归函数越界超过0x1000呢?
  • 会跳过保护页,并且写入到线程B的栈中

线程B的栈被重写

  • 线程A的递归调用可能重写线程B栈上的返回地址

  • 之后,可以在线程B上进行ROP,getshell

  • ucontext没有保护页

  • 因为是自己准备栈空间(除非刻意生成保护页)

  • 如果各个栈的内存地址是连续的,则不需要考虑跳过保护页,只需要溢出到其他线程的栈

例题

  • Hack.lu CTF 2014 - Mario (pthread系)
  • Ghost in the shellcode 2015 - gitschat (ucontext系)

其他

  • dl_runtime_resolve(),参数(栈上push的两个值)是link_map和reloc_arg
  • 技术解释是伪造reloc_arg,可以伪造link_map产生同样的效果
  • HITCON CTF 2015 - blinkroot

针对Strict Weak Ordering的攻击

  • 针对sort()函数
  • 大多数情况下,第三个参数是作为排序规则的比较函数
  • 比较函数必须满足严格弱排序(Strict Weak Ordering)
    • 比较相同数值时,返回false,不改变顺序
  • 通常,二进制文件运行期间无法生成和指定比较函数,也不存在可以动态生成和指定(Lua或者JavaScript)比较函数的例子
  • 如果可以指定特殊比较函数(例如始终返回true),那么sort可能会产生异常行为
    • 这是一种针对未定义行为的攻击
    • 通常sort内部实现是qsort,如果最初返回N次true,之后正常返回true/false的话,那么可能能够控制特定位置的内存交换
  • 31C3 CTF 2014 - SaaS