为什么 Windows 不继续随机化我的可执行文件的基地址?
Why doesn't Windows keep randomizing the base address of my executable?
我编写了一个简单的 C 程序,它在执行时简单地打印 main()
的地址:
printf("%08X\n", &main);
我使用带有参数 /DYNAMICBASE
的 Visual C++ 2015 为 x86 编译它(为 x64 编译时会发生同样的事情)。
前两次我运行它,返回的地址是不同的,正如预期的那样。
但是,两次之后,程序地址 returns 保持不变:
00C31050
00221050
00221050
00221050
重新编译或重命名可执行文件会再次随机化地址。
这里发生了什么? Windows 是否以某种方式缓存可执行文件?
首先,你不需要获取函数的地址,指针最好用%p说明符打印,这有助于编译器检查类型。更准确的代码是:
printf ("%p\n", main);
关于主题,ASLR technology 负责重新设置可执行映像的基础是一项 OS 功能,旨在通过降低地址的可预测性来抵御缓冲区溢出 运行 攻击。 不保证对于两个连续的运行图像将被放置在不同的位置,但是OS 会根据许多因素不时尝试改变基数。对于我的测试,我在 Windows-7 32 位构建上获得了(例如)编译后 10 个连续 运行s 的以下结果:
00BE1260
00BE1260
00221260
00F71260
01391260
01391260
01391260
01391260
01391260
003A1260
如您所见,即使连续 运行 被放置在相同的位置,一段时间后基数也会发生变化。可以保证的是,不支持动态基址的可执行映像将始终放置在基址上,由链接器在 exe-headers 中设置。可执行映像的默认基址是 400000h,因此打印的值将是这样的:
00401260
00401260
00401260
00401260
00401260
...
至于你的情况,我认为 OS 变基算法更具有预测性,因为 OS 算法处理攻击威胁的可能性较小或由于缺乏熵或资源。变基需要额外的时间和资源来重新映射内存页面和调整重定位,因此 OS 可能会决定在您的情况下不需要频繁的变基。
当然,Windows 缓存加载的可执行文件以加速它们的启动。这就是为什么基数在下一个 运行 时不会改变的概率足够高的原因。如果您有足够的 RAM,则用于缓存的 RAM 越多,该图像就越有可能不会重新定位。如果图像完全重新加载,则没有理由保持相同的基础。此外,OS 版本之间的变基政策可能会有所不同。
关于缓存。它不仅仅是功能上的缓存。如果图像被加载到内存中,它可能同时被多个进程(实例)使用。这些实例可以安全地共享代码页,因为代码是只读的。这是进程终止后 Windows 不立即 "unload" 图像的主要原因之一。但是只有将代码调整到相同的基数,两个进程才可以共享代码,因为重定位会修补内存中的代码。如果我们强制不同的进程进行变基,我们不可避免地需要放弃代码共享,这将导致 RAM 消耗增加。
编辑:
顺便说一句,我发现 VS2015 忽略了 /DYNAMICBASE 选项,并且链接器始终生成具有 ASLR 支持的可执行映像,即使我明确设置了 /DYNAMICBASE:NO。位对位比较还表明,编译后的文件除了一个时间戳和(作为结果)校验和外是相同的。为了在没有 ASLR 支持的情况下获得由 VS2015 构建的可执行文件,我必须手动删除 exe-header 中的 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 位。不知道是故意的还是微软的bug。
我编写了一个简单的 C 程序,它在执行时简单地打印 main()
的地址:
printf("%08X\n", &main);
我使用带有参数 /DYNAMICBASE
的 Visual C++ 2015 为 x86 编译它(为 x64 编译时会发生同样的事情)。
前两次我运行它,返回的地址是不同的,正如预期的那样。 但是,两次之后,程序地址 returns 保持不变:
00C31050
00221050
00221050
00221050
重新编译或重命名可执行文件会再次随机化地址。
这里发生了什么? Windows 是否以某种方式缓存可执行文件?
首先,你不需要获取函数的地址,指针最好用%p说明符打印,这有助于编译器检查类型。更准确的代码是:
printf ("%p\n", main);
关于主题,ASLR technology 负责重新设置可执行映像的基础是一项 OS 功能,旨在通过降低地址的可预测性来抵御缓冲区溢出 运行 攻击。 不保证对于两个连续的运行图像将被放置在不同的位置,但是OS 会根据许多因素不时尝试改变基数。对于我的测试,我在 Windows-7 32 位构建上获得了(例如)编译后 10 个连续 运行s 的以下结果:
00BE1260
00BE1260
00221260
00F71260
01391260
01391260
01391260
01391260
01391260
003A1260
如您所见,即使连续 运行 被放置在相同的位置,一段时间后基数也会发生变化。可以保证的是,不支持动态基址的可执行映像将始终放置在基址上,由链接器在 exe-headers 中设置。可执行映像的默认基址是 400000h,因此打印的值将是这样的:
00401260
00401260
00401260
00401260
00401260
...
至于你的情况,我认为 OS 变基算法更具有预测性,因为 OS 算法处理攻击威胁的可能性较小或由于缺乏熵或资源。变基需要额外的时间和资源来重新映射内存页面和调整重定位,因此 OS 可能会决定在您的情况下不需要频繁的变基。
当然,Windows 缓存加载的可执行文件以加速它们的启动。这就是为什么基数在下一个 运行 时不会改变的概率足够高的原因。如果您有足够的 RAM,则用于缓存的 RAM 越多,该图像就越有可能不会重新定位。如果图像完全重新加载,则没有理由保持相同的基础。此外,OS 版本之间的变基政策可能会有所不同。
关于缓存。它不仅仅是功能上的缓存。如果图像被加载到内存中,它可能同时被多个进程(实例)使用。这些实例可以安全地共享代码页,因为代码是只读的。这是进程终止后 Windows 不立即 "unload" 图像的主要原因之一。但是只有将代码调整到相同的基数,两个进程才可以共享代码,因为重定位会修补内存中的代码。如果我们强制不同的进程进行变基,我们不可避免地需要放弃代码共享,这将导致 RAM 消耗增加。
编辑:
顺便说一句,我发现 VS2015 忽略了 /DYNAMICBASE 选项,并且链接器始终生成具有 ASLR 支持的可执行映像,即使我明确设置了 /DYNAMICBASE:NO。位对位比较还表明,编译后的文件除了一个时间戳和(作为结果)校验和外是相同的。为了在没有 ASLR 支持的情况下获得由 VS2015 构建的可执行文件,我必须手动删除 exe-header 中的 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 位。不知道是故意的还是微软的bug。