00 写在前面
这个文章主要是去梳理栈的相关的一些漏洞,对一些细节没有扣太细,或者说补充不多,想看的话需要一定的基础。
基础ROP
01 ret2text
ret2text即控制程序执行程序本身已有的代码(.text)。就是程序有后门函数,像system("/bin/sh")、system(“cat flag”)等等。
02 ret2shellcode
ret2shellcode即控制程序执行shellcode代码。一般来说,shellcode需要我们自己编写。简单点说就是在没有开启NX的时候因为没有“/bin/sh”然后你需要把它写进去,当然如果开了NX可以把栈迁移到bss段,然后执行shellcode。
03 ret2syscall
ret2syscalll,即控制程序执行系统调用,获取shell。就是没有后门函数了,找他的系统调用。然后把相应的参数进行修改,从而达到它是后门函数的一个效果。
典型的栈迁移加ret2syscall。
注意程序种syscall的地址可以用ROPgadget获得,system的调用号是0x3b。
04 ret2libc
一顿学到这里发现其实只是签到题。
一般来说程序中不会有system函数或者binsh字符串的,NX也是关闭。
然后又因为开启了ASLR,每次运行libc的位置都会变动,所以你就不知道函数地址,不知道字符串地址。
一般这种就是,先尝试泄露libc地址,能的话就直接用里面的函数,要开了NX,就栈劫持,要泄露不出来就劫持GOT表吧。
只有在RELRO不全开的时候才能劫持GOT表。
中级ROP
05 ret2csu
劫持GOT表的时候经常需要用到gadgets,但是其实一般程序里没有,有都是出题人故意留给你的,所以其实有时候你可以一上来直接用ROPgadgets先看看,有没有gadgets。
那要是没有gadgets的话就得用csu。
如果平常ROPgadget比较多的话就会发现,针对rdx的寄存器比较少,而很多函数都有三个参数,第三个参数就会用到rdx,那么有没有通用的可以控制rdx的gadget?
在初始化libc的时候会调用一个函数,叫__libc_csu_init,因为是初始化libc的,所以__libc_csu_init函数基本都有。
两篇很好的文章。
ret2csu
ret2csu
攻防世界welpwn就是个非常好的ret2csu的例子。
06 ret2reg
为了绕过ASLR,很快攻击者想到另一种攻击方法ret2reg,即return-to-register,返回到寄存地址执行 的攻击方法。
它的原理很简单
1) 分析和调试汇编,看溢出函返回时哪个寄存值指向溢出缓冲区空间
2)然后反编译二进制,查找call reg 或者jmp reg指令,将该指令所在的地址注入到 EIP
3)再在reg指向的空间上注入Shellcode
此攻击方法之所以能成功,是因为函数内部实现时,溢出的缓冲区地址通常会加载到某个寄存器上,在后在的运行过程中不会修改。尽管栈空间具有随机性,但该寄存器的值与缓冲区地址的关系是确定的,在随机地址之上,建立了必然的地址关系。一句话就是 在随机性上找到地址的确定性关系。
07 BROP
BROP是没有对应应用程序的源代码或者二进制文件下,对程序进行攻击,劫持程序流。
攻击条件
1、源程序必须存在栈溢出漏洞,以便于攻击者可以控制程序流程。
2、服务器端的进程在崩溃之后会重新启动,并且重新启动的进程地址与先前的地址一样(这也就是说即使程序有ASLR保护,但是其只是在程序最初启动的时候有效果)。
说白了就是出题人不讲武德。
高级ROP
08 SROP
前面的各种ROP技术其实都在想办法控制寄存器,比如控制rip,rdi,rsi,rdx等等,但是SROP是一种更厉害的控制寄存器的方法。
基本思路
伪造signal frame
寄存器要啥有啥
rip都是直接控制的
Sigreturn Oriented Programming攻击简介
非常棒的一篇SROP
据说一般SROP的题目怕你干坏事一般都会手撸一个汇编代码的程序出来,所以碰到这种的就往这块考虑。
09 ret2dl_runtime_resolve
linux中是__dl_runtime_resolve(link_map_obj,reloc_index)来对动态链接的函数进行重定位的。如果我们可以控制相应的参数以及其对应地址的内容就可以控制解析的函数。具体利用方式如下:
1、控制程序执行dl_resolve函数
2、给定Link_map以及index两个的参数。
3、控制index的大小,以便于指向自己所控制的区域,从而伪造一个指定的重定位表项。
4、伪造重定位表项,使得重定位表项所指的符号也在自己可以控制的范围内。
5、伪造符号内容,使得符号对应的名称也在自己可以控制的范围内。
此外,这个攻击成功的很必要条件:
dl_resolve函数不会检查对应的符号是否越界,它只会根据我们所定的数据来执行。
dl_resolve函数最后的解析根本上依赖于所给定的字符串。
有几篇文章实在是太好了。
第一篇
第二篇
第三篇
说个题外话,它可以绕过NX跟ASLR。
10 ret2VDSO
VDSO(Virtual Dynamically-linked Shared Object),虚拟动态链接共享对象,所以它应该是虚拟的,与虚拟内存一致,在计算机中本身并不存在。具体来说,它是内核态的调用映射到用户地址空间的库。那么它为什么会存在呢?这是因为有些系统调用经常被用户使用,这就会出现大量的用户态和内核态切换的开销。通过vdso,我们可以大量减少这样的开销,同时也可以使得我们的路径更好。这里的路径更好指的是,我们不需要使用传统的int 0x80来进行系统调用,不同的处理起实现了不同的快速系统调用指令:
intel是实现了sysenter,sysexit.
amd实现了syscall,sysret
当不同的处理器架构实现了不同的指令时,自然就会出现兼容性问题,所以linux实现了vsycall接口,在底层会根据具体的结构来进行具体操作。而vsycall就实现在vdso中。
这里,我们顺便来看一下vdso,在linux(kernel 2.6 or upper)中执行ldd /bin/sh,会发现有一个名字叫linux-vdso.so.1的动态文件,而系统中却找不到它,它就是VDSO。
其实我发现这种利用技巧有点难……暂时用不上。
有个链接可以参考一下。
11 mprotect 改权限执行shellcode
先说说什么是个mprotect函数。
在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性。
函数原型如下:
#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);
mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
1)PROT_READ:表示内存段内的内容可写;
2)PROT_WRITE:表示内存段内的内容可读;
3)PROT_EXEC:表示内存段中的内容可执行;
4)PROT_NONE:表示内存段中的内容根本没法访问。
需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。
相关的攻击手段。
小技巧
stack pivoting
栈迁移。
正如它所描述的,该技巧就是劫持栈指针指向攻击者所能控制的内存处,然后再在相应的位置进行ROP。一般来说,我们可能在一下情况需要使用栈迁移:
1、可以控制的栈溢出的字节数较少,难以构造较长的TOP链。
2、开启了PIE保护,栈地址未知,我们可以将栈劫持到已知的区域。
其他漏洞难以利用,我们需要进行转换,比如说将栈劫持到堆空间,从而在堆上写rop以及进行对漏洞利用。
stack smash
在程序加了canary 保护之后,如果我们读取的 buffer 覆盖了对应的值时,程序就会报错,而一般来说我们并不会关心报错信息。而 stack smash 技巧则就是利用打印这一信息的程序来得到我们想要的内容。这是因为在程序启动 canary 保护之后,如果发现 canary 被修改的话,程序就会执行 __stack_chk_fail 函数来打印出 argv[0] 指针所指向的字符串。
partial overwrite
partial overwrite 这种技巧在很多地方都适用, 这里先以栈上的 partial overwrite 为例来介绍这种思想。
我们知道, 在开启了随机化(ASLR,PIE)后, 无论高位的地址如何变化,低 12 位的页内偏移始终是固定的, 也就是说如果我们能更改低位的偏移, 就可以在一定程度上控制程序的执行流, 绕过 PIE 保护。
ROP技术详解
2014





