WannaCry勒索软件中,利用了NSA泄露工具中的“永恒之蓝”漏洞,关于这个漏洞,之前已经有一些分析,在我看的文章中,http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/说得最详细,该文章中针对win7 32位的系统的漏洞触发过程进行了分析。

但是在整个网络上,我没有看到关于64位系统的分析,因此我在该文章的基础上,对win7 64位的触发,以及触发后的shellcode的行为进行了分析整理成此文。

其中若有疏漏,还请大家指教。最后感谢武汉科锐,感谢钱老师的悉心教学,钱老师严谨的治学作风和渊博的知识深深的影响着我,使我受益终身。

 

搭建测试环境

因为样本的原有行为,是随机产生IP地址,并对目标进行发包。这非常不方便我们测试。因此,需要先在特定的位置断下,将IP地址改为我们的测试目标,并拍快照记录。

具体步骤如下:

1. 在0x00407982处下断,该处为拼接IP字符串,在此处将该字符串修改为自己的测试目标值。

2. 单步(如果不单步可能会因为多线程的原因造成测试不成功,因此推荐单步)至下方的线程创建函数,并在该线程回调处下断点。

3. 在上一步的线程回调函数中,有两处关键点,一处为该样本的第一波发包攻击,攻击结果为:在远程电脑上安装一个后门;第二处为第二波发包攻击,攻击结果为:利用第一波攻击安装的后门,在远程电脑上的系统进程中注入一个dll。他们的调用位置如下图所示:

   

漏洞的原理

在Srv.sys中的SrvOs2FeaListToNt函数中,会有以下调用关系:


SrvOs2FeaListToNt

call SrvOs2FeaListSizeToNt

call SrvOs2FeaToNt

其中在SrvOs2FeaListSizeToNt中因为有一个DWORD*转WORD*并赋值的bug,造成在SrvOs2FeaListToNt的一个循环中,SrvOs2FeaToNt被调用的次数会多于预期,而造成SrvOs2FeaToNt中的一个memmove拷贝越界。

可以看到,运行到下图时,对大小为0x11000:

 

通过!pool指令查看:

 

而实际运行时:

不过,越界覆盖后,最终pool中的地址+0xa8处的数据,会作为一个指针被当作数据接受缓冲区,这是为什么,这其中的原理我没有完全搞明白,还请熟悉的朋友指教。

定位第一轮发包的shellcode触发点

尝试定位shellcode,要知道一个前提,这在其它报告中也已经提到,那就是

该样本的第一次发包,会安装一个后门,该后门的实现方式是在srv模块中,挂钩SrvTransaction2DispatchTable表中的一个表项,可以通过命令.

我之后就是利用这一点,来检验自己的测试机是否已经被触发漏洞,并一步步定位到触发点的(见下文分析).

kd> dps srv!SrvTransaction2DispatchTable查看:

挂钩前:

</pre>
fffff880`0301e760 &nbsp;fffff880`03088780 srv!SrvSmbOpen2

fffff880`0301e768 &nbsp;fffff880`0304fb20 srv!SrvSmbFindFirst2

fffff880`0301e770 &nbsp;fffff880`03085f40 srv!SrvSmbFindNext2

fffff880`0301e778 &nbsp;fffff880`03051650 srv!SrvSmbQueryFsInformation

fffff880`0301e780 &nbsp;fffff880`0307ad20 srv!SrvSmbSetFsInformation

fffff880`0301e788 &nbsp;fffff880`0304f670 srv!SrvSmbQueryPathInformation

fffff880`0301e790 &nbsp;fffff880`03088cb0 srv!SrvSmbSetPathInformation

fffff880`0301e798 &nbsp;fffff880`0304d420 srv!SrvSmbQueryFileInformation

fffff880`0301e7a0 &nbsp;fffff880`0304e080 srv!SrvSmbSetFileInformation

fffff880`0301e7a8 &nbsp;fffff880`0306f660 srv!SrvSmbFsctl

fffff880`0301e7b0 &nbsp;fffff880`03088ae0 srv!SrvSmbIoctl2

fffff880`0301e7b8 &nbsp;fffff880`0306f660 srv!SrvSmbFsctl

fffff880`0301e7c0 &nbsp;fffff880`0306f660 srv!SrvSmbFsctl

fffff880`0301e7c8 &nbsp;fffff880`0307b4f0 srv!SrvSmbCreateDirectory2

fffff880`0301e7d0 &nbsp;fffff880`0306f460 srv!SrvTransactionNotImplemented

fffff880`0301e7d8 &nbsp;fffff880`0306f460 srv!SrvTransactionNotImplemented
<pre>

挂钩后:

</pre>
fffff880`0301e760 &nbsp;fffff880`03088780 srv!SrvSmbOpen2

fffff880`0301e768 &nbsp;fffff880`0304fb20 srv!SrvSmbFindFirst2

fffff880`0301e770 &nbsp;fffff880`03085f40 srv!SrvSmbFindNext2

fffff880`0301e778 &nbsp;fffff880`03051650 srv!SrvSmbQueryFsInformation

fffff880`0301e780 &nbsp;fffff880`0307ad20 srv!SrvSmbSetFsInformation

fffff880`0301e788 &nbsp;fffff880`0304f670 srv!SrvSmbQueryPathInformation

fffff880`0301e790 &nbsp;fffff880`03088cb0 srv!SrvSmbSetPathInformation

fffff880`0301e798 &nbsp;fffff880`0304d420 srv!SrvSmbQueryFileInformation

fffff880`0301e7a0 &nbsp;fffff880`0304e080 srv!SrvSmbSetFileInformation

fffff880`0301e7a8 &nbsp;fffff880`0306f660 srv!SrvSmbFsctl

fffff880`0301e7b0 &nbsp;fffff880`03088ae0 srv!SrvSmbIoctl2

fffff880`0301e7b8 &nbsp;fffff880`0306f660 srv!SrvSmbFsctl

fffff880`0301e7c0 &nbsp;fffff880`0306f660 srv!SrvSmbFsctl

fffff880`0301e7c8 &nbsp;fffff880`0307b4f0 srv!SrvSmbCreateDirectory2

fffff880`0301e7d0 &nbsp;fffff880`053e2060

fffff880`0301e7d8 &nbsp;fffff880`0306f460 srv!SrvTransactionNotImplemented
<pre>

可以发现第0xe项会被hook。这个表项的hook是由一段shellcode代码所完成的,具体如何定位到shellcode的入口,见以下分析。

 

因为已知漏洞会在srvnet! SrvNetWskReceiveComplete中完成触发,只是不确认win 7 64位的shellcode写入点是否与前辈win 7 32位地址一样(通常不一样),因此一开始通过下断点并打印内存情况摸索规律:

</pre>
kd&gt; u

srvnet!SrvNetWskReceiveComplete+0x15:

fffff880`02d3cdc5 488bca &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov &nbsp;&nbsp;&nbsp;&nbsp;rcx,rdx

fffff880`02d3cdc8 498bf8 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov &nbsp;&nbsp;&nbsp;&nbsp;rdi,r8

kd&gt; ba e1 srvnet!SrvNetWskReceiveComplete+0x15 "dq r8 r8+0x48;gc"
<pre>

得到以下结果:


fffffa80`049236b0 &nbsp;00000000`00000003 fffffa80`042fd800

fffffa80`049236c0 &nbsp;fffffa80`042fd800 000059b4`918947fc

fffffa80`049236d0 &nbsp;fffffa80`049237f0 00000000`00000810

fffffa80`049236e0 &nbsp;fffffa80`04923710 fffffa80`00000000

fffffa80`049236f0 &nbsp;00000000`00000438 fffffa80`0568d730

……

fffffa80`04a9b010 &nbsp;00000000`0000ffff 00000000`0000ffff

fffffa80`04a9b020 &nbsp;00000000`00000000 00000000`00000000

fffffa80`04a9b030 &nbsp;00000000`ffdff100 ffdff020`00000000

fffffa80`04a9b040 &nbsp;ffffffff`ffdff100 00000000`10040060

fffffa80`04a9b050 &nbsp;00000000`ffdfef80&nbsp;ffffffff`ffd00010

经过比对,发现以上标红内容特殊,是x86报告所记录的溢出时机断点。因此重启环境,下条件断点:


kd&gt; ba e1 srvnet!SrvNetWskReceiveComplete+0x15 ".if(poi(@r8+0x48) ==&nbsp;0xffffffffffd00010){}.else{gc}"

结合以上所知的,SrvTransaction2DispatchTable会被shellcode挂钩,因此在该表项处下断点:

kd&gt; ba w8 srv!SrvTransaction2DispatchTable+0x8*0xe

经过验证,在以上第一个断点触发后,继续运行,会触发第二个断点:

但是,当步入ffffffff`ffd00b77后,栈已经被破坏,难以定位到该段shellcode的触发点。

 

在我所看到的其它的分析文章中,均没有提及具体是个位置转移到shellcode的,因此只有自己定位。

 

考虑到shellcode一般均需要间接call,因此,通过寻找SrvNetWskReceiveComplete中的间接call,并下断点排查。最终,定位到shellcode触发点srvnet!SrvNetCommonReceiveHandler+0xb7:


kd&gt; u srvnet!SrvNetCommonReceiveHandler+0xb3

srvnet!SrvNetCommonReceiveHandler+0xb3:

fffff880`028017e3 89742420 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov &nbsp;&nbsp;&nbsp;&nbsp;dword ptr [rsp+20h],esi

fffff880`028017e7 41ff5208 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;call &nbsp;&nbsp;&nbsp;qword ptr [r10+8]

fffff880`028017eb 8be8 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov &nbsp;&nbsp;&nbsp;&nbsp;ebp,eax

fffff880`028017ed 488b0d1cc90100 &nbsp;mov &nbsp;&nbsp;&nbsp;&nbsp;rcx,qword ptr [fffff880`0281e110]

fffff880`028017f4 493bcf &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cmp &nbsp;&nbsp;&nbsp;&nbsp;rcx,r15

fffff880`028017f7 740b &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;je &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;srvnet!SrvNetCommonReceiveHandler+0xd4 (fffff880`02801804)

fffff880`028017f9 0fba612c09 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bt &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dword ptr [rcx+2Ch],9

fffff880`028017fe 0f82be840000 &nbsp;&nbsp;&nbsp;jb &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;srvnet! ?? ::FNODOBFM::`string'+0x1341 (fffff880`02809cc2)

在该处下断点验证:


kd&gt; bp srvnet!SrvNetCommonReceiveHandler+0xb7

确实可以截取到shellcode的进入入口:

第一轮shellcode的行为分析

为了测试方便,可以下以下条件断点,这样运行win7测试机器后,断下的地点就是shellcode的入口:


ba e1 srvnet!SrvNetWskReceiveComplete+0x15 ".if(poi(@r8+0x48) ==&nbsp;0xffffffffffd00010){bp&nbsp;srvnet!SrvNetCommonReceiveHandler+0xb7;gc}.else{gc}"

进入shellcode后,该样本在进行奇怪的jne和call之后(我认为是防止静态分析以及弄乱栈),修改了第0C0000082h号MSR:

</pre>
ffffffff`ffd00201 31c0 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xor &nbsp;&nbsp;&nbsp;&nbsp;eax,eax

ffffffff`ffd00203 4090 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xchg &nbsp;&nbsp;&nbsp;eax,eax

ffffffff`ffd00205 7408 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;je &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ffffffff`ffd0020f

ffffffff`ffd0020f e8a7000000 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;call &nbsp;&nbsp;&nbsp;ffffffff`ffd002bb

ffffffff`ffd002bb b9820000c0 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov &nbsp;&nbsp;&nbsp;&nbsp;ecx,0C0000082h

ffffffff`ffd002c0 0f32 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rdmsr

ffffffff`ffd002c2 48bbf80fd0ffffffffff mov rbx,0FFFFFFFFFFD00FF8h

ffffffff`ffd002cc 895304 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov &nbsp;&nbsp;&nbsp;&nbsp;dword ptr [rbx+4],edx

ffffffff`ffd002cf 8903 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov &nbsp;&nbsp;&nbsp;&nbsp;dword ptr [rbx],eax

ffffffff`ffd002d1 488d050a000000 &nbsp;lea &nbsp;&nbsp;&nbsp;&nbsp;rax,[ffffffff`ffd002e2]

ffffffff`ffd002d8 4889c2 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov &nbsp;&nbsp;&nbsp;&nbsp;rdx,rax

ffffffff`ffd002db 48c1ea20 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;shr &nbsp;&nbsp;&nbsp;&nbsp;rdx,20h

ffffffff`ffd002df 0f30 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wrmsr

ffffffff`ffd002e1 c3 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret

ffffffff`ffd00214 c3 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret

srvnet!SrvNetCommonReceiveHandler+0xbb:

fffff880`02cec7eb 8be8 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov &nbsp;&nbsp;&nbsp;&nbsp;ebp,eax
<pre>

这样,利用系统原有的系统调用,样本有机会时系统运行到其准备好的shellcode(ffffffff`ffd002e2)处,经实测,在该处下断点会系统崩溃,但是在其后1条指令处下断点,则不会崩溃:


kd&gt; bp ffffffff`ffd002e5

此处开始的shellcode,会寻找内存中的PE格式、定位PE文件头、定位服务表项的位置,最终将服务表项进行hook,但是代码流程的细节需要亲自调试才能看清楚。在此我就不截图了。

 

病毒样本的第二轮发包攻击

先在后门处下断点:

</pre>
kd> dps srv!SrvTransaction2DispatchTable

fffff880`03302760  fffff880`0336c780 srv!SrvSmbOpen2

fffff880`03302768  fffff880`03333b20 srv!SrvSmbFindFirst2

fffff880`03302770  fffff880`03369f40 srv!SrvSmbFindNext2

fffff880`03302778  fffff880`03335650 srv!SrvSmbQueryFsInformation

fffff880`03302780  fffff880`0335ed20 srv!SrvSmbSetFsInformation

fffff880`03302788  fffff880`03333670 srv!SrvSmbQueryPathInformation

fffff880`03302790  fffff880`0336ccb0 srv!SrvSmbSetPathInformation

fffff880`03302798  fffff880`03331420 srv!SrvSmbQueryFileInformation

fffff880`033027a0  fffff880`03332080 srv!SrvSmbSetFileInformation

fffff880`033027a8  fffff880`03353660 srv!SrvSmbFsctl

fffff880`033027b0  fffff880`0336cae0 srv!SrvSmbIoctl2

fffff880`033027b8  fffff880`03353660 srv!SrvSmbFsctl

fffff880`033027c0  fffff880`03353660 srv!SrvSmbFsctl

fffff880`033027c8  fffff880`0335f4f0 srv!SrvSmbCreateDirectory2

fffff880`033027d0  fffffa80`040a3060

fffff880`033027d8  fffff880`03353460 srv!SrvTransactionNotImplemented

&nbsp;

kd> ba e1 fffffa80`040a3060
<pre>

接着,触发第二轮攻击,等待断点触发。以下是行为分析。

 

  shellcode的行为分析

第二轮发送的包,为SMB2协议的包,其timeout项,被作为控制flag。

该域经过计算后,它只有三种值,分别对应了“检查”、“卸载”和“执行程序”,计算公式如下:

域的解析判断部分如下:

在经过ping包后,会执行exec包,执行的程序也包含在(拼接后并解码后的)SMB2包中。其行为为:

1. 找ntoskrnl的基址:

2. 通过hash,找出关键的系统调用

3. 通过遍历PID,获取EPROCESS,在通过比对进程EPROCESS中进程名的HASH值,获取指定的进程PID

 

最后,利用apc注入,将自带的dll注入到指定系统进程中,apc注入看雪中已有相关文章。

Apc的回调函数地址是:

也就是刚才拷贝的apc注入的shellcode,用于内存加载dll,并执行其唯一的导出函数PlayGame,该shellcode及dll我均已经dump出,大家可以从附件下载:

PlayGame运行后,会释放并执行母体,溢出攻击到此结束,但是悲剧将在新的机器上循环上演。

留言

请输入验证码 * 请输入正确的验证码