54、内存逆向(三):内存保护机制(ASLR、DEP、SafeSEH、CFG)

兄弟们,咱们继续聊内存逆向。前两章我们把内存布局和堆栈机制摸了个透,今天要聊的,是操作系统为了防我们这些逆向工程师,搞出来的几道「铁栅栏」——ASLR、DEP、SafeSEH、CFG。

说实话,我刚入行那会儿,这些机制还没这么普及。那时候写个溢出 exploit,直接往栈上怼 shellcode 就行,简单粗暴。现在?你试试看。系统会把你安排得明明白白。嗯,这就是进步,也是我们必须要面对的挑战。

一、ASLR——让地址「随机化」

ASLR,全称 Address Space Layout Randomization。说白了,就是每次程序启动,系统把代码段、数据段、堆、栈、DLL 的基址都随机打乱。

为什么要这么做?

你想啊,以前写 exploit,最爽的就是知道 kernel32.dll 一定加载在 0x7C800000。我直接硬编码地址跳过去就行。ASLR 一开,每次启动地址都不一样,你硬编码的地址就废了。

核心要点:ASLR 让攻击者无法预测目标地址,从而大幅提升利用难度。

我在项目中遇到过一件事。有一次分析一个老旧的第三方组件,它内部硬编码了某个 DLL 的地址。结果在 Win10 上跑,ASLR 一开,直接崩。这就是典型的「没考虑 ASLR」的坑。

ASLR 的强度分级

级别 说明 常见场景
0 (关闭) 不随机化 调试阶段、兼容性模式
1 (部分) 仅随机化 DLL 和 EXE 旧系统、部分嵌入式
2 (完全) 堆、栈、PEB、TEB 全部随机 Win8+ 默认

我的习惯:逆向时先用 !process 0 0lm 命令查看模块基址,确认 ASLR 是否生效。如果基址每次调试都变,那就是开了。

二、DEP——不让数据段「执行」

DEP,Data Execution Prevention。名字很直白——数据段不允许执行代码。

以前我们写 exploit,最喜欢把 shellcode 塞到栈上或者堆上,然后跳过去执行。DEP 一开,你往栈上写再多机器码也没用,CPU 直接给你个异常:访问违例。

为什么会这样?

因为 DEP 利用了 CPU 的 NX(No-Execute)位。每个内存页都有一个标志位,标记它是否可执行。栈和堆默认是「可读写但不可执行」的。你强行跳过去,系统直接翻脸。

注意:DEP 不是万能的。它防的是「直接执行数据段代码」,但防不住 ROP(Return-Oriented Programming)。ROP 利用的是已有的代码片段,不是新写的 shellcode。这个我们后面会细讲。

我记得有一次逆向一个恶意软件,它为了绕过 DEP,用了 VirtualProtect 把栈改成可执行。嗯,这招很老套,但确实有效。不过现在很多杀软会监控 VirtualProtect 的调用,你一动它就报警。

三、SafeSEH——保护异常处理链

SafeSEH 是专门针对 SEH(Structured Exception Handling)攻击的防护机制。

SEH 是什么?简单说,就是程序出异常时,系统会沿着一个链表找异常处理函数。攻击者可以覆盖这个链表里的某个节点,把地址指向自己的 shellcode。程序一崩,shellcode 就执行了。

SafeSEH 的做法是:在编译时生成一个合法的异常处理函数列表。运行时,系统只允许跳转到列表里的地址。你覆盖的地址不在列表里?对不起,直接终止。

关键点:SafeSEH 只在编译时启用 /SAFESEH 选项的模块上生效。如果某个 DLL 没开 SafeSEH,它仍然是攻击目标。

我建议你在逆向时,用 !load!seh 命令查看当前进程的 SEH 链。如果发现某个模块的异常处理函数地址不在合法列表里,那基本可以断定——有人在搞事情。

四、CFG——控制流防护

CFG,Control Flow Guard,是微软在 Win8.1 和 Win10 上推的更狠的机制。

它的思路很简单:程序里所有间接调用(比如通过函数指针、虚函数表调用)都必须经过一个「校验点」。系统维护一个白名单,只有白名单里的地址才能被调用。你篡改了函数指针?系统在校验点就拦住了。

说白了,CFG 是在编译器和操作系统层面,给控制流上了一把锁。

实战技巧:逆向时如果遇到 __guard_check_icallntdll!LdrpValidateUserCallTarget,那就是 CFG 在干活。你可以尝试绕过它,但说实话,难度很大。我一般会先看看目标程序是不是全开 CFG,如果是,我会换个思路——比如找未受保护的模块。

五、四道防线的关系与实战思路

这四道防线不是孤立的。它们层层叠加,构成了现代 Windows 的内存防护体系。

我画了一张图,帮你理清它们的关系:

内存保护四道防线 ASLR - 地址随机化 让攻击者无法预测目标地址 DEP - 数据执行保护 阻止数据段代码执行 SafeSEH - 异常处理保护 限制异常处理函数跳转目标 CFG - 控制流防护 校验所有间接调用目标

你看,从 ASLR 到 CFG,一层比一层狠。ASLR 让你找不到地址,DEP 不让你执行代码,SafeSEH 堵死了异常处理这条路,CFG 连间接调用都给你管死了。

那逆向工程师怎么办?

我的经验是:不要硬刚。找薄弱环节。比如:

  • 有些旧模块没开 ASLR,基址固定,可以作为跳板。
  • 有些程序没开 CFG,间接调用随便改。
  • 有些第三方 DLL 没开 SafeSEH,SEH 攻击仍然有效。

核心思路:逆向不是要突破所有防线,而是找到那条「没锁上的门」。

六、如何检测这些保护机制

实战中,我一般用以下几个方法快速判断目标程序开了哪些保护:

  1. PE 头检查:dumpbin /headersPE-bear 查看 DLL Characteristics 字段。如果 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 置位,说明开了 ASLR;IMAGE_DLLCHARACTERISTICS_NX_COMPAT 置位,说明开了 DEP。
  2. SafeSEH 检查:看 PE 头里有没有 LOAD_CONFIG 目录,以及 SEHandlerTableSEHandlerCount 是否有效。
  3. CFG 检查:看导入表里有没有 __guard_check_icall,或者用 !process 0 0 查看 ControlFlowGuard 标志。

一个小技巧:Process Hackerx64dbg 的插件,可以直接看到每个模块的保护状态。省得自己手动翻 PE 头。

七、避坑指南

我曾经踩过一个坑。有一次逆向一个老程序,它没开 ASLR,但开了 DEP。我心想:好办,ROP 走起。结果折腾了半天,发现它连 SafeSEH 都没开,但异常处理函数里有个 call [ebp+arg0] 的指令。我直接覆盖 SEH 链,跳过去就执行了。嗯,有时候最简单的办法反而最有效。

所以我的建议是:

  • 不要一上来就搞 ROP,先看看有没有更简单的路。
  • 不要只看主模块,第三方 DLL 往往是突破口。
  • 不要迷信「全开保护」——总有没开的地方。

最后提醒一句:这些保护机制是动态的。同一个程序,在 Win7 和 Win10 上表现可能完全不同。调试前,先确认目标系统的版本和补丁级别。

好了,这一章的内容就到这儿。内存保护机制这块,说白了就是「道高一尺,魔高一丈」。系统在加固,我们也在进步。理解这些机制,不是为了绕过而绕过,而是为了更深入地理解系统的工作原理。

下次见。


公众号:蓝海资料掘金营,微信 deep3321