在内存中躲猫猫:ASLR
[toc]
内存随机化保护机制的原理
前面的所有漏洞利用都有一个共同特征:都需要确定一个明确的跳转地址。而 ASLR(Address Space Layout Randomization)技术就是通过使用随机的基址加载程序,从而干扰 shellcode 定位的一种保护机制。
ASLR 的实现需要程序和操作系统的双重支持,其中程序的支持不是必需的。
支持 ASLR 的程序在它的 PE 头中会设置 IMAGE_DLL_CHARACTERISTICS_ DYNAMIC_BASE 标识来说明其支持 ASLR。在Visual Studio 2008 (VS 9.0)中,可以在通过菜单中的 Project→project Properties→ Configuration Pr operties→ Linker→ Advanced→ Randomized Base Address 选项对 /dynmicbase 链接选项(启用则支持 ASLR)进行设置。
映像随机化
映像随机化是在 PE 文件映射到内存时,对其加载的虚拟地址进行随机化处理,这个地址是在系统启动时确定的,系统重启后这个地址会变化。
微软在系统中设置了映像随机化的开关,通过设置注册表中 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\MoveImages 的键值来设定映像随机化的工作模式。
- 设置为 0 时映像随机化将禁用。
- 设置为 -1 时强制对可随机化的映像进行处 理,无论是否设置 IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE 标识。
- 设置为其他值时为正常工作模式,只对具有随机化处理标识的映像进行处理。
如果注册表中不存在 MoveImages,大家可以手工建立名称为 MoveImages,类型为 DWORD的值,并根据需要设置它的值,如下图所示。
堆栈随机化
堆栈随机化是在程序运行时随机的选择堆栈的基址,在程序打开时确定。也就是说同一个程序任意两次运行时的堆栈基址都是不同的,进而各变量在内存中的位置也就是不确定的。
测试一下堆栈随机化对变量在内存位置的影响,我们分别在堆和栈上各申请 100 个字节的空间,然后在 Windows XP 和 Windows Vista 下面各运行两次。
Windows vista:两次申请空间的起始地址不同
Windows XP:两次申请空间的起始地址完全相同
PEB 与 TEB 随机化
微软在 XP SP2 之后不再使用固定的 PEB 基址 0x7FFDF000 和 TEB 基址 0x7FFDE000,而是使用具有一定随机性的基址,这就增加了攻击 PEB 中的函数指针的难度。
获取当前进程的 TEB 和 PEB ,TEB 存放在 FS:0 和 FS:[0x18]处, PEB 存放在 TEB 偏移 0x30 的位置。
可以从下面的结果看出,PEB 和 TEB 的随机效果不是很好。
ASLR 的弱点
(1)在映像随机化中,虽然模块的加载基址变化了,但是各模块的入口点( Entry 那列)地址的低位 2 个字节是不变的,也就是说映像随机化只是对加载基址的前 2 个字节做了随机处理。地址的前 2 个字节是随机的,而后 2 个字节是固定的。
(2)在堆栈随机化中,将每个线程的堆栈基址都做了随机化处理,使得程序每次运行时变量的地址都不相同。好处是可以防止精准攻击。例如我们需要根据 shellcode 的起始地址直接跳转到 shellcode 执行,但是自从 JMP ESP 跳板指令开始使用后溢出时很少直接跳到 shellcode 中执行了;另外在浏览器攻击方面很流行的 heap spray 等技术,这些技术也是不需要精准跳转的,只需要跳转到一个大概的位置即可。所以这项措施对于目前的溢出手段影响有限。
(3)在PEB 和 TEB 的随机化中,它们的随机化程度很低。
攻击未启用 ASLR 的模块
ASLR 仅仅是项安全机制,不是什么行业标准, 不支持 ASLR 的软件有很多。不支持 ASLR 意味着加载基址固定,如果我们能够在当前进程空间中找到一个这样的模块,就可以利用它里边的指令来做跳板了,直接无视 ASLR。
本次实验需要用到 IE 和 Flash,由于这两项技术目前已经淘汰,并且 Flash无法找到实验版本(Flash Player ActiveX 9.0.262),所以我们先跳过这个实验。
利用部分覆盖进行定位内存地址
之所以能利用部分覆盖进行定位内存地址,有两个原因,一是**映像随机化只是对映像加载基址的前2个字节做随机化处理。**如果我们借鉴 “off by one” 的思想,只覆盖这个地址的最后一个字节(或者两个字节),那么我们就能在一定范围内控制程序。二是因为 ASLR 只是随机化了映像的加载基址,而没有对指令序列进行随机化,指令序列相对于基址的位置还是不变的。
实验代码:
|
|
实验思路:
( 1)为了更直观地反映绕过 ASLR 的过程,本次实验编译的程序不启用 GS。 ( 2)编译程序时禁用 DEP。 ( 3) test 函数中通过复制超长字符串可以溢出并覆盖函数返回地址。 ( 4)复制结束后, test 函数返回 tt 字符数组的首地址。 ( 5)在相对程序加载基址 0x0000~0xFFFF 的范围内,找到一条跳板指令,并用它地址的后 2 个字节覆盖返回地址的后两个字节。 ( 6)采用这种类似 “相对寻址” 的方法来动态确定跳板指令的地址,以实现跳板指令的通用性。
注意:test 函数返回的 tt 字符数组的首地址是没有实际意义的,因为 tt 的空间是在栈上的,程序从 test 函数返回后 tt 字符数组所在的空间就会被释放。
推荐使用的环境 | |
---|---|
操作系统 | Windows Vista SP2 |
DEP 状态 | Optin (Vista 默认状态) |
编译器 | Visual Studio 2008 |
优化选项 | 禁用优化选项 |
GS 选项 | GS 关闭 |
DEP 选项 | /NXCOMPAT:NO |
build 版本 | release 版本 |
实验步骤:
计算能覆盖到返回地址的填充长度
调试得到,test() 的返回地址位于 001FF940处,tt 的起始地址位于 0x001FF83C处,相距 260 个字节,所以我们可以使用 261~262 这两个字节来覆盖返回地址的后两位。
寻找跳板指令
|
|
如上图所示,函数返回地址的后两个字节已经被覆盖为 0x90 了。接下来就是寻找一条适合的跳板地址,由于是部分覆盖,所以 shellcode 只能放在返回地址前面,这样 JMP ESP 指令就不能再使用了。我们需要让函数跳转执行 shellcode,所以要向低地址跳转,观察寄存器,只有EAX 符合(EAX 指向 tt 的起始地址)。将函数返回地址覆盖为 CALL/JMP EAX 的指令地址(0x00b7141c)。
选择 ASLR_Offbyone.exe 中的指令,因为只有它里面的指令才有可能控制,我们选择 0x0008141C,重启系统后再查找一次。
布置 shellcode
Shellcode 最开始部分为弹出对话框的机器码,然后是 0x90 填充,最后为用来覆盖返回地址后 2 个字节的 0x141C。
|
|
运行成功,即使是重启系统,shellcode 依然能成功执行。
利用 Heap spray 技术定位内存
Heap spray 原理:通过申请大量内存,占领内存中的 0x0C0C0C0C 的位置,并在这些内存中放置 0x90 和 shellcode,最后控制程序转入 0x0C0C0C0C 执行。