翻译:啦咔呢
预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
前言
你可能已经注意到,上一个补丁星期二并没有到来。结果导致大量0day漏洞的文章正在发表,而CVE-2017-0038无疑名列头榜。别着急,每个云里都有一个隐藏的线索。我上周正好有一些空闲时间来解决这个问题,所以,我可以给你0day漏洞的第一个0patch。
针对CVE-2017-0038的分析
CVE-2017-0038是EMF图像格式解析逻辑中的漏洞,它无法充分检查图像中指定的图像尺寸与文件提供的像素数量之间的关系。如果图像尺寸足够大,则解析器被欺骗去读取超过被解析存储器映射的EMF文件内容。攻击者可以使用此漏洞来窃取应用程序在内存中保存的敏感数据,或者辅助其他漏洞绕过ASLR的需求。
在此,我必须感谢Google Project Zero的Mateusz Jurczyk,因为他简短而准确的报告使我能够快速了解漏洞的原因并进行0patch。
以Mateusz的PoC来做为教材,通过启动IE 11的一个实例,并插入poc.emf,以下是我得到的结果:
图像必须放大到最大值,才能通过彩色像素看到惊人的堆数据泄漏。它确实泄漏:重新加载的PoC使得每个加载过的区域内出现了不同的图像。
为了看到像素值我把poc.emf放到HTML画布上,并用JavaScript打印出来。连续PoC重载后的不同结果表明,唯一没有改变的像素是左下角的像素(如果你真正仔细看上面的截图,你会发现一个红色的污点)。
换句话说就是:
FF 33 33 FF
这与报告一致(需要你仔细阅读)。因此,唯一像素是从EMF文件提供的实际数据中的一个(由4个字节表示),其他的堆数据都是从图像像素数据的边界外读取到的。
现在让我们继续修补。在我们继续之前,我们需要了解易受攻击的代码在哪里。幸运的是,报告里清楚地说到了易受攻击的函数名为MRSETDIBITSTODEVICE :: bPlay。这是很重要的,因为PoC不会产生崩溃或异常,它可以被调试器捕获进行深入分析。有了这条信息在手,我们可以附加WinDbg到iexplore.exe并设置断点bp MRSETDIBITSTODEVICE :: bPlay 。
根据报告,这正是问题所在之处。它说cbBitsSrc不检查“预期位图的字节数”,而是从cxSrc和cySrc计算出图像尺寸的数字。
到这里我们需要MSDN的一点帮助:
cxSrc
源矩形的宽度,以逻辑单位表示。
cySrc
源矩形的高度,以逻辑单位表示。
cbBitsSrc
源位图位的大小。
在向WinDbg中输入EMRSETDIBITSTODEVICE类型的符号后(解释如下),报告中的解释终于清晰了。
当处理EMF格式的PoC文件时,EMRSETDIBITSTODEVICE结构(在上面截图的右下方框中详述)作为第一个参数传递到MRSETDIBITSTODEVICE :: Play函数(rcx寄存器存储到rbx,标记为蓝色),其中包含的cbBitsSrc等于4,将与由cxSrc和cySrc定义的0x10 * 0x10图像尺寸不匹配(这些尺寸为256像素或1024字节)。
因此,为了解决缺陷,必须在任何数据被盗之前添加检查。主要需要做的是在任何图像像素被复制之前添加检查,无论cbBitsSrc是否小于cxSrc *cySrc * 4。一个合适的位置似乎是在第一系列检查之前,验证EMRSETDIBITSTODEVICE属性(请参阅上述代码截图中的cmp – jg对,代码结束于一个到函数退出的跳转,以防任何检查失败)。但是,选择确切的补丁位置不仅仅是在逻辑上适合我们要应用的修改位置。补丁的工作方式是从原始位置跳转到补丁代码,然后再返回来。在原始代码中的补丁位置处,写入jmp指令(消耗5个字节的原始指令空间),将其执行重定向到指定用于补丁指令的存储器块。由jmp指令替换的原始指令被重新定位到该补丁码块的末尾,接着是最后的jmp指令,指示在补丁位置之后立即执行回原始代码。因此,还需要满足一些额外的要求:
1、在补丁位置的指令需要可重定位(例如,没有短跳转);
2、在补丁位置,必须有一条指令或属于同一个执行流的几条指令(交叉引用不能指向它们之间的任何地方)。
考虑到这一点,我选择了确切的补丁位置。为了尽可能写少一点的补丁代码,我在第一个分支指令设置了补丁,进而跳转到函数退出(避免处理图像数据),以便我可以返回。在上面的代码截图上,补丁位置标记为红色,而重用的跳转指令标成粉红色。pvClientObjGet调用的结果是在补丁位置处检查是否为0,随后是jne以便跳转到函数退出(如果rax为0)。同样一个提供此逻辑的jne可以帮助我们的补丁代码退出函数:如果cbBitsSrc小于cxSrc * cySrc * 4 ,我们只需要将rax设置为0。
可能选择000007fe`feeae3b8作为第一个补丁位置看起来会令人满意,但是test RAX,RAX只需要3个字节,以便补丁也能包含JNE(不要重定位),因为受限于5字节的要求。然而,在当前选择的位置,修补影响两个3字节指令 – mov rsi,rax和test rax,rax – 它们都是可重定位的。
选择了补丁位置后,我们可以最终写入实际的补丁。下面是一个.0pp补丁文件的内容,可以使用包含在0patch Agent for Developers中的0patch Builder进行编译- 我们在上一篇文章中已经讨论过了。
在code_start部分的开头有一个cxSrc * cySrc * 4计算,存储在rcx中。这两个乘法之后是溢出检查,所以我们不会得到一个似乎在边界内,但是由超大因素产生的产品。如果检测到无效的图像尺寸,则调用我们0patch代理的特殊功能,以便显示“Exploit Blocked”弹出窗口,最重要的是rax寄存器设置为0,以便执行接下去的test rax,rax指令可以为标为粉色到JNE设置跳转条件(导致函数退出)。
;目标平台:Windows 7 x64
;
RUN_CMD C: Program Files Internet Explorer iexplore.exe C: 0patch Patches ZP-gdi32 poc.emf
MODULE_PATH“C: Windows System32 gdi32.dll”
PATCH_ID 259
PATCH_FORMAT_VER 2
VULN_ID 2135
PLATFORM win64
patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x0004e3b5
;注意:我们检查rcx是否可以正常使用。
; 我们不关心是否rsi被block_it损坏,因为在这种情况下它不会被任何人使用
code_start
imul ecx,dword [rbx + 28h],04h ; cxSrc * 4(每个像素由4个字节表示)
jc block_it ; 如果溢出
imul ecx,dword [rbx + 2ch] ; * cySrc
jc block_it ; 如果溢出
cmp ecx,dword [rbx + 3ch] ; cbBitsSrc <cxSrc * cySrc * 4
jbe skip
block_it:
call PIT_ExploitBlocked ; 显示“Exploit Blocked”弹出窗口
xor rax,rax ; 为补丁后的原始jne设置跳过条件
skip:
code_end
patchlet_end
顺便说一句,EMRSETDIBITSTODEVICE结构成员相对于rbx的偏移量是通过使用WinDbg的dt命令转储类型结构获得的:
0:034> dt symbollib!EMRSETDIBITSTODEVICE
+ 0x000 emr:tagEMR
+ 0x008 rclBounds:_RECTL
+ 0x018 xDest:Int4B
+ 0x01c yDest:Int4B
+ 0x020 xSrc:Int4B
+ 0x024 ySrc:Int4B
+ 0x028 cxSrc:Int4B
+ 0x02c cySrc:Int4B
+ 0x030 offBmiSrc:Uint4B
+ 0x034 cbBmiSrc:Uint4B
+ 0x038 offBitsSrc:Uint4B
+ 0x03c cbBitsSrc:Uint4B
+ 0x040 iUsageSrc:Uint4B
+ 0x044 iStartScan:Uint4B
+ 0x048 cScans:Uint4B
但是,由于WinDbg中的类型信息不可用,所以只需从Microsoft的符号服务器加载符号,需要以下解决方法。我编译了一个只有EMRSETDIBITSTODEVICE x的DLL框架项目; 实例声明为自定义源。然后就在启动iexplore.exe之前,我添加了DLL到AppInit_DLLs注册表值,所以它将被加载到进程(感谢这个技巧)。
使用0patch Builder 编译此.0pp文件后,启用该修补程序,在浏览器中显示一个空图像而不是彩虹图像,并弹出一个显示“Exploit Blocked”的窗口。
演示视频
这里是我录制的视频,总结了描述的过程。
这是我第一次修补0day漏洞。虽然不是最严重的问题,我不禁想到除了取代彩虹图像,恶意网页可能窃取我的网上银行帐户的凭据或从我的浏览器的内存中抓到我昨晚的晚会照片。
总结
如果您安装了0patch代理, 从ZP-258到ZP-264的补丁程序应该已经存在于您的机器上。如果没有,您可以下载0patch代理的免费副本,以保护自己免受在等待Microsoft的正式修复期间针对所提出问题的攻击。注意,当微软的更新修复这个问题,它将替换易受攻击的gdi32.dll并且我们的补丁将自动停止应用,因为它严格绑定到该漏洞的DLL版本。我们已经为以下平台部署了此修补程序:Windows 10 64位,Windows 8.1 64位,Windows 7 64位和Windows 7 32位。
如果你想自己写这样的补丁,请不要犹豫,下载我们的0patch代理为开发人员并尝试(我们做的.0pp文件可供下载,所以你可以自己构建它们)。我们也很乐意接受任何反馈。