最近我在Linux上利用CERT BFF研究一个PoC。我之前有大量在Windows上写PoC的经验,所以认为将这种技能扩展到不同平台上是个不错的想法。可是,当我获得指令指针的控制权后,却发现几乎可以毫无障碍地使代码执行。
除了对Metasploit字符串做一个BFF最小化之外,不需要额外的工作,就可以很容易知道哪些字节在我的控制之下,以及怎么执行到那里:
运行起来之后,就控制了EIP(0×61626364 = "abcd"),得到一个Metasploit字符串模式,以及指向那里(EAX)的寄存器。下面我们看看这个位置的内存保护情况
不会是真的吧,这部分堆内存居然有读、写和执行权限!任意一块内存既有可写权限又有可执行权限都是一件非常危险的事情。为什么呢?以上述这种情况为例,其允许攻击者将代码跳转到这里,并执行这些被系统认为是数据的字节。如果一块内存区域被标记为可执行,NX是不会起作用的。(参考OpenBSD的W^X策略。)
我们需要花点时间找出为什么这块堆内存是可执行的。我使用的平台是UbuFuzz,基于Ubuntu 12.04.01,内核是x86 3.2.0。该平台上,如果指定一块可执行栈,例如用gcc编译时指定-z execstack选项,进程的READ_IMPLIES_EXEC属性就会被设置。正如其名,如果该位被设置,那么可读的内存区域默认也同时变成可写的了,包括堆内存(heap)。
问题是在崩溃进程的ELF头部并没有指定可执行栈:
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4如果进程在编译时没有指定,默认是不会包含可执行栈的,但要是指定了READ_IMPLIES_EXEC特性会怎么样呢?而且它也没有显式调用personality()函数。这就是比较奇怪的地方。如果崩溃的进程不是从终端直接调用的,而是由另一个不同的应用程序生成的,会怎么样呢?下面我们看看其继承自父进程的栈属性:
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4罪魁祸首找到了!其同时也证明了在x86 Linux平台,READ_IMPLIES_EXEC属性位会从父进程传递给子进程。这种行为是架构决定的,因为在amd64架构的平台上,子进程就不会继承READ_IMPLIES_EXEC特性。但在所有架构的平台上,用来禁止ASLR的ADDR_NO_RANDOMIZE标记都会被子进程继承。
总结
在vanilla Linux平台,对于任意给定的进程,很难静态地确定其启用了哪些漏洞防护措施。在目前流行的X86平台的Linux上,任何正在运行的进程都可能没有启用NX保护,只是因为其父进程没有启用相应的保护。
下一篇:最新防御黑客的攻击教程