缓解措施的实现中存在很多隐含的假设,这些假设的成立是缓解措施有效工作的前提,破坏这些假设就可以绕过缓解措施。
研发过程中应当尽量减少所作的假设,并且保证必要的假设都具有不变性,从而保证缓解措施能有效工作。
缓解措施
缓解措施是微软应对软件漏洞的深度防御体系中的一个重要环节,通过破坏漏洞利用过程中的一些关键技术,来达成阻止漏洞利用的目标。
目前,Windows 10中已经引入的缓解措施有:
- SEHOP/SafeSEH
- 堆随机化和元数据保护
- 地址空间布局随机化 (ASLR)
- 数据执行保护 (DEP)
- 控制流防护 (CFG)
- 任意代码防护 (ACG)
- 代码完整性防护 (CIG)
缓解绕过
缓解绕过是指在启用了缓解措施的环境中,与缓解措施进行对抗,突破缓解措施的限制,并最终实现任意代码执行的过程。
要绕过缓解措施,首先需要了解缓解措施是怎样工作的。
袁哥曾经将安全的本质归纳为:“安全是一个条件语句。”
缓解措施,可以抽象成这样的条件语句:
if (is_allowed_by_mitigation_policy()) { do_sensitive_action(); } else { fail_fast(); }
这个条件语句中通常还隐含着许多假设,这些假设的成立是缓解措施有效工作的前提。
如果其中某个假设不具有不变性,攻击者就可能通过欺骗系统来破坏这一假设,从而绕过对应的缓解措施。
数据执行保护 (DEP)
数据执行保护对应的条件语句是:
if (PTE(address).NX == 0) { execute(address); } else { fail_fast(); }
这里的假设有:
- 代码段中的代码是可信的
- 严格遵守 W^X 原则
代码段中的代码都是可信的吗?并不一定。
由于x86架构的CPU采用复杂指令集(CISC),每条指令字长并不相等,同样的数据从不同的位置开始解码可以得到不同的指令。因此,代码段中的代码可以被重新解码为其他指令,组合这些指令就可以实现任意代码执行,这正是ROP技术的基础。
W^X 原则能够被严格遵守么?很难。
因为这不仅仅意味着避免使用可读写执行(PAGE_EXECUTE_READWRITE) 内存,还要求在内存的整个生命周期中保持W^X。具备JIT功能的应用,比如浏览器,很容易破坏这一假设,从而被用来绕过数据执行保护。
控制流防护 (CFG)
控制流防护对应的条件语句是:
if (CFG_Bitmap[address] == 1) { call(address); } else { fail_fast(); }
这里的假设有:
- CFG Bitmap 中置位的地址是可信的
- CFG 使用的指针是可信的
CFG Bitmap 中置位的地址都是可信的吗?并不一定。
实际上,由于CFG只是一个粗粒度的CFI实现,很多在CFG Bitmap 中置位的地址实际上并不应该被间接调用,滥用这些地址就可以绕过控制流防护,实现任意代码执行。
CFG 使用的指针都是可信的吗?
CFG实现中使用的2个关键指针__guard_check_icall_fptr和__guard_dispatch_icall_fptr仅仅只是通过只读内存来进行保护。
而目前Windows系统中的只读内存并不是真正的只读,通过纯数据攻击来修改只读内存并不困难。一旦这些指针被修改,控制流防护就形同虚设了。
任意代码防护 (ACG)
任意代码防护对应的条件语句是:
if (W^X(address, flNewProtect)) { change_protection(address, flNewProtect); } else { fail_fast(); }
这里的假设有:
- 本地的动态链接库是可信的
本地的动态链接库都是可信的吗?并不一定。
可以利用浏览器的缓存特性,将任意的动态链接库文件派发到本地,从而绕过任意代码防护的限制。
代码完整性防护 (CIG)
代码完整性防护对应的条件语句是:
if (is_signed_by_microsoft(file)) { create_section(file); } else { fail_fast(); }
这里的假设有:
- 微软签名的动态链接库是可信的
微软签名的动态链接库都是可信的吗?并不一定。
旧版的动态链接库在新的系统中可能会表现出不一样的行为,比如ntdll.dll中的系统调用,由于系统调用号的变化,版本6.3.9600.17936中的NtQueryDefaultUILanguage函数与版本10.0.15063.0中的NtContinue函数实质上是等同的,而前者是允许被间接调用的。因此,可以加载版本6.3.9600.17936的ntdll.dll,然后调用其中的NtQueryDefaultUILanguage函数,来实现对NtContinue函数的调用,从而实现任意代码执行。
总结
缓解措施的实现中存在很多隐含的假设,这些假设的成立是缓解措施有效工作的前提,破坏这些假设就可以绕过缓解措施。
研发过程中应当尽量减少所作的假设,并且保证必要的假设都具有不变性,从而保证缓解措施能有效工作。