在那个只有零和一的世界里,对零的向往,终究是一的执着。

正在下载:share-cve-2012-0158.zip CVE-2012-0158(ms12-027)漏洞分析与利用

漏洞公布后,根据安天实验室报告,此漏洞至少在一年内是apt攻击中最常被利用的格式溢出类漏洞,通常作为邮件附件进行鱼叉式攻击。在我看来,这个漏洞通用性比较强,所以才会如此流行,至少到去年(2015),都还有大量的恶意程序在利用这个漏洞。

漏洞原理

定位漏洞代码

定位漏洞触发代码的方法有很多,当然每种方法都有与其适应的条件。

分析漏洞代码的位置,首先要获得样本。在我刚入门的时候,其实要获得样本感觉是一件很难的事情,特别是好的样本,有时候即使在网上找到一两个别人放出的样本,也是很难顺利的分析下去,毕竟刚入门,参考的信息也不是很详细。不过后面才觉得,其实分析漏洞,只要有一两个样本,甚至一点漏洞的信息,想要把这个漏洞还原吃透,就不那么难了。得到样本后,一般还分两种情况,一种是这个样本包含完整的恶意攻击代码,另一种则只有验证性触发代码。这两种样本各有好坏,前者由于完整,包含这个漏洞的触发和利用,有较好的参考意义,毕竟它不止能看到怎么触发漏洞,还能看到别人怎么来利用这个漏洞,但是通常相对的分析起来也就比较难,因为可能功能代码比较多,区分起来比较不易。而后者相比则比较简单纯粹,因为它就是被研究人员制作用来演示漏洞触发的半成品,通常就只到达漏洞触发完的附近,而后面如何来合理有效的利用这个漏洞的方法不会继续透露,不过这也不绝对,实际上现在很多POC样本(验证性),都会比较完整的包含利用代码,比如其结果就是给你弹出一个计算器。

OK,不管拿到什么样的样本,有样本总归是好的,然后就可以开始分析,分析的第一步就是还原漏洞的触发,需要运行并观察它的现象。当然,这里可能会有一些关于运行环境的问题,因为尤其是对漏洞来说,通常存在于特定的一些软件的一些特定的版本环境中,所以对样本的运行环境也会有所要求,而且通用性越不好的漏洞运行环境越苛刻。所以这第一步里很重要的一个环节就是搭建环境,比如你经常分析office漏洞,你可能会在你的电脑装一个虚拟机软件,在虚拟机上装几种不同版本的操作系统,再分别在不同的操作系统装不同版本的office套件,然后做好快照方便重复进入环境。

有了环境,就可以开始运行样本。对于完整攻击样本来说,你可能看到的是偷偷的连接了网络,下载了某个木马程序,并且还欺骗性的弹出正常的软件显示界面,让你意识不到攻击正在进行。此时你可以使用一些监控工具,比如抓包工具、进程监控等,会帮助你看到这些现象,找到样本分析的线索和切入点。对于POC样本来说,你可能就看到弹个提示框或计算器,甚至就是程序崩溃这种最简单也是标志性的现象。接下来,你就可以根据这些现象,选取合适的地方进行跟踪和分析了。所谓合适的地方,当然是出现行为并且靠近漏洞发生的地方了,可以先粗略的确定发生漏洞的前后位置,然后在这个区间里再进行精确的锁定漏洞位置。跟踪、调试器、监控工具以及其他的一些辅助发生器在这里能帮助你很好的定位,有时也是必不可少,然后就是需要你耐心的跟踪分析了,跟踪程序的流程,分析代码的功能,最后才能确定流程“异常”的位置,即漏洞代码触发点。

下面给出本次漏洞的代码定位过程:

我得到的是一个完整的攻击样本,运行后会联网下载木马程序运行。在我的xp+office2007环境启动excel表格程序,打开一个Ollydebug调试器并附加到excel程序,可以事先对shellcode可能的调用下好一些api断点,好及时进行回溯跟踪,比如下WinExec、ShellExecuteA之类的断点。样本是个拓展名为“.xls”的文档,将会由excel程序解析并触发漏洞,也就是说这个文档肯定会被excel程序打开,所以我挑了一个最基础的CreateFileW下断点来不厌其烦的跟踪它,发现在我的环境下,excel通过CreateFileW打开该文档11次后触发漏洞,于是后面我每次重新调试样本,都直接跳到这里,离漏洞代码比较近。在这过程中,我也关注了目标漏洞模块MSCOMCTL.OCX,发现其前面都没有被载入进程地址空间,说明excel还没有解析到该样本文档里面包含的控件。而12次打开之后,通过Ollydebug的内存模块查看功能和日志会发现,MSCOMCTL.OCX自动进入excel进程,并且执行其中的代码,可以借助LoadLibrary断点和内存模块断点使调试器在进入刚漏洞模块代码后停下。

提前透露一下,这个漏洞是属于缓冲区溢出漏洞里的栈内存拷贝溢出漏洞,之所以说这个是因为我不知道怎么解释这里的定位好。缓冲区溢出漏洞意味着发生了内存拷贝,而内存拷贝在程序中是再正常不过的事情了,如何确定那么多的内存拷贝中哪一个才是有问题有漏洞的那一个就需要我们认真思考一下了。其实这方面也是有一定的方法和技巧,比如很多专业的挖洞者会借助一些辅助工具来触发和监控那些“异常”的内存操作,例如堆拷贝越界、DEP(数据执行保护)绕过等。这方面的经验需要一定的积累才能熟练的应付各种状况,我自问没到那个水平,哪种情况使用哪种工具可以快速探测和定位出来我不是很了解,所以通常需要思考半天才能想出对策。比如这个栈内存拷贝越界(溢出),我的想法是监控那些目标内存地址是在栈地址(即临时函数的变量地址),而拷贝的长度超过一个比较大的值判断为可疑的漏洞操作。在调试器里跟踪,会发现很多内存拷贝的代码最终是这样的形式REP MOVS DWORD PTR ES:[EDI], DWORD PTR DS:[ESI],就是以寄存器ESI为源内存指针,EDI为目标内存指针,拷贝的过程为ECX递减到0的过程,即ECX为拷贝长度。于是根据栈内存拷贝,我关注的“异常”条件是EDI的值接近EBP(栈帧基址),ECX的值相对较大这种情况,比如ECX大于0x200,因为一般函数内部的临时变量的size不会这么大(如果这么大,通常会动态分配堆内存)。基于这个条件设定,我就比较容易定位到有问题的内存拷贝代码了,而那里是更接近漏洞代码的位置,找到这个有问题的内存拷贝代码位置,再看看调用这个位置代码的属于目标漏洞模块代码部分(实际的内存拷贝代码很可能是其他模块的代码)的前后代码,分析拷贝问题发生的原因,就基本上可以确定漏洞发生的代码了。

分析漏洞成因

 从上面异常的内存拷贝可以看到,excel程序出现了栈溢出,原有的栈帧被破坏,返回值被数据覆盖。应该说,这是漏洞出现的直接现场(结果),但并不是漏洞的原因。而要分析出漏洞的成因,我们必须要往回跟踪,栈回溯是个很好的参考,其记录了函数调用的关系和地址,可以方便我们往回跟踪需要分析的函数。一般来讲,我们根据栈回溯地址定位好需要跟踪的函数,就可以一步步分析各个子函数的功能和代码,从而分析出其可能出现漏洞的地方,我们最好是耐心的往回跟踪三四个函数过程才能对代码调用有个较好的认识。这里我要介绍的是一种辅助分析的方法,也只能说是对我们的往回跟踪分析有用的手段。漏洞模块是微软的activeX控件解析模块MSCOMCTL.OCX,这个库直接看其导出函数的话只有不多的几个导出函数名可以参考,其分析价值不高。不过微软的很多库都有对应的公开符号参考,MSDN上有离线的包里就能下载到各个系统版本库以pdb/dbg为拓展名的符号文件,也可以通过其提供的符号服务器方便下载大部分的对于库的符号(本例的库在线服务器好像下载不到),这些符号包含源代码编译时的各种函数名、变量等参考信息,具有极高的分析价值。就本例模块来说,找到MSCOMCTL.OCX对应的调试符号文件MSCOMCTL.dbg,和MSCOMCTL.OCX放在同一个目录下用IDA静态代码分析工具打开,就会自动被链接解析MSCOMCTL.OCX的各个函数地址和函数名,而我们只需要根据函数的代码定位到函数位置,就可以看到其对应的函数名,从而加快我们的分析进度,根据函数名了解函数的功能。按照这个方法,得出本例的栈回溯如下:

异常拷贝处栈回溯

可以看出,Excel在解析ListView控件的时候,读取并加载了控件的数据流,我们跟踪到的异常越界拷贝便是发生在MSCOMCTL.OCX的ReadBytesFromStreamPadded函数里,该函数的功能类似于memcpy内存拷贝函数,根据参数从指定内存拷贝指定大小数据到目标内存。但仔细往上跟踪就会发现,漏洞并不是出现在这个函数里,而是出现在CObj::load这个函数,下面分析一下这个函数如何出现的漏洞,先贴上IDA关于这个函数的伪代码:


int __stdcall CObj__Load(int a1, void *lpMem)
{
  int result; // eax@1
  void *v3; // ebx@1
  int v4; // esi@4
  int v5; // [sp+Ch] [bp-14h]@1
  SIZE_T dwBytes; // [sp+14h] [bp-Ch]@3
  int v7; // [sp+18h] [bp-8h]@4
  int v8; // [sp+1Ch] [bp-4h]@8
 
  v3 = lpMem;
  result = ReadBytesFromStreamPadded(&v5, lpMem, 0xCu);
  if ( result >= 0 )
  {
    if ( v5 == 'jboC' && dwBytes >= 8 )
    {
      v4 = ReadBytesFromStreamPadded(&v7, v3, dwBytes);
      if ( v4 >= 0 )
      {
        if ( !v7 )
          goto LABEL_8;
        lpMem = 0;
        v4 = ReadBstrFromStreamPadded((UINT)&lpMem, (int)v3);
        if ( v4 >= 0 )
        {
          CObj__SetKey((BSTR)lpMem);
          SysFreeString((BSTR)lpMem);
LABEL_8:
          if ( v8 )
            v4 = ReadVariantFromStream((struct tagVARIANT *)(a1 + 20), (struct IStream *)v3);
          return v4;
        }
      }
      return v4;
    }
    result = 2147549183;
  }
  return result;
}

CObj::Load,顾名思义,是CObj对象加载的方法,需要从内存里读取对象数据,所以一开始便从数据流里读取了0x0c个字节到临时变量v5中。接着判断v5的前4个字节是否为"Cobj"来检测是否为要加载的对象类型,并且dwBytes这个变量如果大于8才进行下一步的加载。注意到,dwBytes 这个变量是读取那0x0c个字节的时候一起读取进来的,因为从IDA的变量备注中可以看出dwBytes =[bp-0x0c]落在v5=[bp-0x14]和v5+0x0c=[bp-0x08]的内存区间中,所以这里的一个关键是dwBytes的值可以通过修改数据流被控制。再看下一步,同样从原来的数据流读取dwBytes个字节到临时变量v7中,v7=[bp-0x08],而dwBytes此时却大于8,所以这个读取拷贝必然会覆盖ebp,发生越界拷贝,形成栈溢出漏洞。根据此分析可以推测,正常情况从控件数据读取出来dwBytes值不会大于8,因为如果大于8的话必然导致栈拷贝异常,那么这个漏洞早就被测试出来了。而且通过IDA里查看此函数的交叉引用会发现,这个函数似乎作用并不大,都是在加载特定几个控件的开头被调用了一下。总之,我怀疑这个漏洞不是所谓的严重的失误,把本来的小于8写成了大于8,就是微软故意留下来的后门漏洞。

构造触发漏洞的POC

 经过上面的分析,我们知道漏洞触发的原因是excel在解析ListView控件等时调用了漏洞函数CObj::Load,该函数在加载CObj对象时根据可被篡改的dwBytes读取指定大小的内存数据到8字节的临时变量,且校验大小时存在后门嫌疑,导致可被利用的缓冲区溢出漏洞。为了检验我们的分析是否正确,下面我们参考上面的栈回朔图构造可触发此漏洞的Excel文档。首先Excel文档里需要存在一个ListView控件,可以通过Excel软件里面的开发者工具添加,添加完后相当于文档里嵌入了一个空的ListView对象。接着,还需要往这个对象里面添加ListItems以及ListItem子对象,这样就能使Excel程序调用到CObj::Load函数。但是这里有个问题,ListItem对象无法直接通过Excel操作添加,Excel只能通过ListView控件的属性添加列表标题,没有直接办法添加列表内容。解决办法是通过编写Excel支持的VBA程序代码,编译生成一个ListItem对象。但是这样带来另外一个问题,就是如果文档里边存在VBA这类宏代码,Excel会默认禁止代码执行,这样依旧解析不到ListView控件里的ListItem对象,一个简单的解决办法就是先写好代码编译运行后生成了初始化好的ListView控件,再把所有的生成代码删除后保存即可,因为宏代码会被阻止执行控件对象不会被阻止解析。下一步,只要将保存好的文档通过十六进制编辑器打开,定位到CObj对象的数据,修改偏移量为8的dwBytes值为大于8的数值就能触发漏洞。事实上只修改那一个值还无法看到漏洞触发的效果,原因是拷贝函数ReadBytesFromStreamPadded还会接着校验dwBytes的值,幸运的是该校验只是从要拷贝的数据头部读取另一个dwBytes的值,检验两个值是否相等,所以我们只需要把对象数据里的那个数值也修改成相应的大小就可以通过校验从而触发漏洞。触发漏洞后,由于我们只是简单的用一些随机数据覆盖ebp和相关函数返回地址,所以Excel最终优雅的返回一个我们想要看到的程序错误提示框。

漏洞利用

现在,我们得到了一个可以触发的栈缓冲区溢出漏洞,下面要怎么利用这个漏洞来做一些事情就各显神通了,本文还是给大家弹个计算器来抛砖引玉。通过上面的构造的POC,我们可以修改两个dwBytes的值和后面的数据来控制运行栈的内存布局。为了更好的编排数据,最好通过调试样本去动态修改数据以达到目的,最后只要将内存的里编排好的数据拷贝到文档对应的部分即可。而调试过程中,我们的第一目标自然是获取程序控制器,控制eip,这里一般是通过覆盖函数返回值或SEH链指针来实现。由于MSCOMCTRL.DLL没有开启GS保护,我们采取最简单的覆盖函数返回值即可控制eip。然而,为了使程序顺利的走到返回值,我们还需要修改数据,满足一些返回条件,控制程序流程,使之不进入复杂的函数或指令操作集,避免因栈被破坏导致一些异常的发生。一旦程序顺利到达返回地址,我们便可以根据运行环境做各种事情,比如构造一个rop链绕过dep保护,或者直接跳转到栈空间执行代码,这些对于一个熟悉漏洞利用的人来说都是轻车熟路了。这里有个要求,就是需要栈内存数据需要有足够大小的空间来容纳无论是rop链还是shellcode,所以需要增加一下ListView控件的数据规模,简单的方法就是添加ListItem的时候把字符串写的足够长。当所有的必要条件都具备的时候,我们的代码就可以放进栈里执行了,这里我简单使用一个通用的跳转地址直接跳转至栈内存代码执行,由于XP+office 2007默认不开启dep保护,所以我的环境可以顺利弹出计算器。关于更多的需求比如如何编写rop链绕过office2010以上默认开启的dep保护,我将会在接下去的漏洞分享中陆续展开。

小结

通过本次详细的分析,我们了解了这个漏洞的原理和危害性,由于MSCOMCTL.DLL是基础库,影响的应用软件自然比较多,除了office全套装外,sql和其他第三方应用软件,只要存在使用该漏洞库的地方,都有可能被利用。本文通过对一个样本的详细分析后,展示了漏洞的原理和利用方法。当然,本文最大的漏洞在于,我分析过程的前提是我知道了漏洞的模块是 ActiveX控件的解析库MSCOMCTL.DLL,假如我不知道这条信息,我又该怎么来分析这个漏洞呢?