回连后禁用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
线程之间的条件竞争
- http://googleprojectzero.blogspot.jp/2015/03/taming-wild-copy-parallel-thread.html
- 利用整数溢出,memcpy和上下文切换的特性
- 实现线程的二进制文件中可能有这种情况
- 对于基于线程的二进制文件,最好注意条件竞争
- 似乎也可用于沙盒问题
充分利用两个线程
- 线程A上执行memcpy(dst, src, 负数)
- 参数使用负数,利用整数溢出
- 因为memcpy将第三个参数看作unsigned,因此它会变成一个非常大的数
- 线程A保持从src到dst的memcpy
- 当dst到达不可访问的dst时会造成SIGSEGV
- 线程B使用被覆盖的内存
- 期待在线程A产生SIGSEGV之前发生上下文切换
- 线程B通过使用被覆盖的内存,控制栈或者堆函数指针来执行execve
- 调用execve时,所有线程都会停止,并且新进程会重置内存空间,因此不会产生SIGSEGV
栈保护页(Stack guard page)之外
Linux中创建线程的几种方式
- pthread系
- pthread_create()之类
- 标准的创建线程函数
- ucontext系
- makecontext(),swapcontext(),getcontext(),setcontext()
- 自己实现线程切换计时时使用
- 还与sigaction()有关
- 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系)
其他
通过return-to-dl-resolve伪造link_map
- 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