【技术分享】EternalBlue Shellcode详细分析

 http://p9.qhimg.com/t017f6d54c32efc9e9f.png

译者:Tmda_da

预估稿费:400RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

0x0 前言

说来很惭愧,EnternalBlue 已经出来很久了,最近才开始分析,漏洞原理和环境搭建网上已经烂大街了,因此这篇文章只分析Shellcode,调试平台如下:

windows 7 sp1  en    32 位

Windbg

Eternalblue

Doublepulsar

Wireshark

断点方法是将Shellcode中的31c0修改为CCCC(int 3)后发送,成功断下来后再通过ew(fdff1f1) c031即可。该Shellcode 大致分为三段: 0x1,hook nt! kiFastCallEntry篇; 0x2, 主要功能篇;0x 3, 后门通信篇,下面来逐一进行介绍。

0x1 Hook ntdll! kiFastCallEntry篇

这部分代码功能比较简单, 但还是值得学习:首先是判断系统环境,作者用法比较巧妙,利用了x86和x64系统通过对二进制指令的解析不同来判断系统环境(64位环境没有调试,直接用IDA查看, 如下图。

http://p1.qhimg.com/t01160988cbaa0ba593.png

1.1   x86环境  

http://p5.qhimg.com/t01d1728201f91c71ca.png

1.2  x64位环境

可以发现同样一段二进制代码, 在x86环境下eax=1 , x64环境下 eax = 0。 紧接着通过读取MSR的176号寄存器(放着nt! kiFastCallEntry函数地址)(这里还有简单的混淆),将其保存在Shellcode的末尾位置(Shellcode末尾空置了0x1d byte),然后再将第二段Shellcode的地址存放在MSR 176号寄存器中实现对nt! kiFastCallEntry函数的hook。

http://p6.qhimg.com/t014dae63bbc807bd18.png

1.3  简单混淆

http://p8.qhimg.com/t01e0247afd7f9426bb.png

1.4  hook nt!kiFastCallEntry

http://p0.qhimg.com/t011f52741504f8909e.png

1.5 hook前的情况

http://p5.qhimg.com/t01ebda1fed6bf2d670.png

1.6 hook后的情况

为什么要hook kiFastCallEntry?

在Ring3应用程序需要调用ring0函数的时候, 会通过ntdll!kiFastSystemCall 函数,该函数的内容如下:

     mov edx, esp
      sysenter
      ret

Ring3层最终通过 sysenter 调用内核函数 nt!KiFastCallEntry实现空间转换。因此只要存在用户空间需要调用内核函数,都会调用这个函数。

0x2 主要功能篇

首先是获取到上一段Shellcode功能中保存末尾的kiFastCallEntry实际地址,然后写入MSR的176号寄存器中,恢复kiFastCallEntry。

 http://p4.qhimg.com/t016ce976557213c172.png

2.1  unhook KiFastCallEntry

接下来获取ntoskrnl.exe 的基地址,fs:[0x38] 指向_KIDTENTRY表结构的开始地址,也指向0号中断,将0号中断的ExtendedOffset 和Offset组合成地址,就是0号中断处理函数地址。而这个函数位于ntoskrnl.exe中,因此该空间在ntoskrnl.exe进程空间中,在这个地址对齐(&0x00F000)处理后只需要每次减0x1000,然后比较前两位的PE(MZ)文件标识,最后一定能找到ntoskrnl.exe的基地址地址。

http://p1.qhimg.com/t01a23d59ce8b1eadfd.png

2.2.  寻找ntoskrnl.exe基地址

http://p1.qhimg.com/t01850bf4c41bc2442d.png

2.3  0号中断结构信息

    找到ntoskrnl.exe的基地址后,利用应用层常用的查找函数地址方式查找ntoskrnl.exe导出表中的函数地址(此代码中的所有函数名和文件名都采用了hash处理),分别是

1,  ExAllocatePool

2,  ExFreePool

3,  ZwQuerySystemInformationEx

http://p7.qhimg.com/t01802d41965e91eecb.png

2.4  根据hash 获取函数地址

函数寻找完成后,便是将后门程序hook到SrvTransaction2DispatchTable表中的函数SrvTransactionNotImplemented地址上,至于为什么要挑选这个函数我认为主要有以下三个原因:

1, 不增加无关网络协议连接,直接利用SMB协议进行数据传输(可以达到隐藏流量的目的,估计这也是为什么到公布了利用工具后才发现漏洞的原因)。

2, 通过查看SrvTransaction2DispatchTable表结构,我们有两个函数在这个表中出现了两次以上SrvSmbFsct1(3次)和SrvTransactionNotImplemented (2次),因此这两个函数更有利于hook。

3, 如果hook到SMB协议的常用处理函数, 那么这个函数的调用会比较频繁,那么处理过程就相对要复杂很多。而SrvTransactionNotImplemented的调用时在发送非正常Trans2 Request请求时才会调用,在正常情况下执行到这个函数的概率很小。

作者知道SrvTransaction2DispatchTable位于Srv.sys中的.data节中,所以作者采用了如下3个步骤实现寻找SrvTransaction2DispatchTable地址:

1,通过两次调用ZwQuerySystemInformationEx函数来获取所有内核模块的空间结构信息(_SYSTEM_MODULE_INFORMATION_ENTRY)表(第一次调用ZwQuerySystemInformationEx来获取 内核模块信息大小, 然后利用ExAllocatePool创建内存空间,在第二次调用ZwQuerySystemInformationEx来获取模块信息)。

http://p3.qhimg.com/t01f35dd7e52001db28.png

2.5  获取内核模块信息结构体

2,通过依次对比_SYSTEM_MODULE_INFORMATION_ENTRY中的ImabeName 字符串来查找 srv.sys(此模块是SMB协议的主要模块) 

http://p0.qhimg.com/t01462449e624c1e611.png

2.6 查找srv.sys地址空间

3, 找到srv.sys的基地址后通过PE文件结构信息,最终后定位到.data节。

http://p8.qhimg.com/t0130b9fcc09a25616e.png

2.7 定位.data节信息

4,找到.data节后,最后进行内存搜索,根据SrvTransaction2DispatchTable的结构特征(见图2.9):

① 根据观察发现 SrvTransaction2DispatchTable[9], SrvTransaction2DispatchTable[11], SrvTransaction2DispatchTable[12]的值都相同, 都指向srv!SrvSmbFsct1;

②SrvTransaction2DispatchTable 中的函数指针跟SrvTransaction2DispatchTable在同一个地址领空, 因此最高8位的地址应该相同;

③SrvTransaction2DispatchTable[18h] = 0;

根据这三个特征来顺序搜索.data节空间,可以定位到SrvTransaction2DispatchTable的基地址。

http://p8.qhimg.com/t0146b9d5d3852e0118.png

2.8查找SrvTransaction2DispatchTable地址

http://p7.qhimg.com/t011a95ca86c07acd85.png

2.9 SrvTransaction2DispatchTable结构信息

找到SrvTransaction2DispatchTable后开始为hook 后门程序做准备,先重新分配一块0x400大小内存空间bdBuffer,首先将前0x48字节中存放一些地址信息,因此顺序为:

http://p8.qhimg.com/t01b21138ebe12fd502.png

(此处的ebp+4我猜测是作者的一点小失误,应该是 ebp-14h对应函数地址ZwQuerySystemInformationEx,不过对后边后门程序基本没有影响也就无法验证)。

并且将上述几个地址和空间结束地址进行异或后的值存放在bdBuffer+0x24地址处,作为后门程序首次使用时的异或key的引子(后门程序所使用的异或key是通过这个值变换而来)。接下来便将后门程序拷贝到bdBuffer+0x48处,然后将 bdBuffer + 0x48赋值给SrvTransaction2DispatchTable[14]中, 实现hook后退出。

http://p8.qhimg.com/t01ac9553678696a21b.png

2.10 保存函数地址,拷贝后门程序

http://p4.qhimg.com/t015412a8e4301382cd.png

2.11 Hook SrvTransactionNotImplemented前后对比

0x3 后门通信篇

这个模块需要配合Doublepulsar程序,因此就一起分析了下,本文主要分析EternalBlue中的Shellcode,所以就对Doublepulsar的分析此处就不写了,主要配合Wireshark进行分析。

首先是获取空间起始地址获取给ebp,然后获取到已经够造好的SmbBuffer中。

http://p9.qhimg.com/t01aab14274ebd09e59.png

3.1 获取相关信息

在函数85bbd239中,获取最终返回的SMB 返回数据包地址赋值到ebp+0x38。至于这个过程如何得到的,我也没有仔细跟,应该跟SMB协议结构有关系(先将就着用吧)。

http://p7.qhimg.com/t019c5ee61814508348.png

3.2 获取SMB 返回数据包地址

接着是计算解码密钥,计算过程在85bbd1a8中,比较简单 key = (A*2)  ^ bswap(A), 然后将结果赋值到ebp+0x28中,具体过程如下:

http://p5.qhimg.com/t01fa8292360d5ced6a.png

3.3 根据解码引子计算解码key

函数85bbd1e9是获取SMB发送数据数据段的相关结构,对SMB数据结构在内存中的表现形式不太了解,我也还没太搞明白。

获取完这些后,接着判断命令类型,计算过程使根据发送的数据包的TimeOut,中的逐字节相加得到:

http://p1.qhimg.com/t010cfa98dccb479969.png

3.4 计算命令类型

http://p3.qhimg.com/t01fea411ac50b2fd7d.png

3.5 抓取的Trans2 Request数据包

判断命令类型,主要有三个命令,0x23  -> 检查后门是否存在, 0x77 –> 卸载后门, 0xC8 执行Shellcode。

http://p2.qhimg.com/t012f00e9fdbe5fb98a.png

3.6 命令类型判断


0x3.1  检查后门是否存在 0x23

当接收到的命令是0x23后,做的事情,将ebp+0x24中存放的密钥引子写入到返回数据包的Signature前4个字节中,将系统位数信息存放在Signature的第5个字节中。如下所示:

http://p0.qhimg.com/t01752f8706ec916c4e.png

3.7 后门检测处理过程

然后将返回数据包的Multiplex ID加0x10后 跳转到真正的SrvTransactionNotImplemented地址继续执行。

执行结果如下(两次结果不一样,是因为不是同一个过程里边抓到的数据包,调试过程中,分析太久SMB就自动退出链接):

http://p4.qhimg.com/t017e699bd787ee19d2.png

3.8 检测后门反馈数据包


0x3.2 卸载后门 0x77

卸载后门的过程也相对较为简单,清空并释放掉以前分配的空间,然后unhook  SrvTransactionNotImplemented函数后,设置返回数据包的Multiplex ID +=0x10恢复SrvTransactionNotImplemented的相关条件后退出,由系统在调用一次正常的SrvTransactionNotImplemented即可。

http://p0.qhimg.com/t014cb7e3a2e0757687.png

3.9 卸载后门过程


0x3.3  执行命令 0xc8

此过程主要包括组装数据(Doublepulsar的攻击数据根据大小拆分成一个或多个Trans2 Request数据包)和执行两个功能,主要流程如下:

Step 1, 解密数据包的SESSION_SETUP Parameters参数,获取当前数据的总长度total_len,当前数据包长度current_len和 当前数据包在整个数据包的位置current_pos;

http://p5.qhimg.com/t01a0e7d17fffdb0bb2.png

3.10 计算数据包信息

http://p4.qhimg.com/t01a19da634da565622.png

3.11 数据包信息存放位置

Step 2, total_len 和存放总长度存放位置(ebp+0x30)的值进行比较,如果不相等则说明这是第一个数据包或者是错误数据包都 需要重新开始转到Step 3, 否则转到Step4;

Step 3, 如果内存空间存放处 ebp + 0x2C 不为这将其空间清零后释放,然后在分配一块长度为(total_len + 4)的空间,地址为buffer_addr,如果内存分配失败则跳转到Step 10, 然后将ebp+0x30 的值设置为total_len +4, ebp+0x2c 的值设置为buffer_addr;

http://p9.qhimg.com/t01915acfb68d6548a3.png

3.12 分配存放数据包空间

Step 4, 如果current_pos + current_len >total_len,则表示数据包出错, 跳转到Step 9;

Step 5, 将接收到的数据packet_data 拷贝到  buffer_addr + current_pos处, 然后对这段拷贝的数据解码;

http://p5.qhimg.com/t0182ea73a65ac6ca56.png

3.13 拷贝数据并解码

Step 6, 如果解码完成后的位置pos  < buffer_addr+ total_len 则表示数据包没有接收完成,转到Step 8, 否则转到Step 7;

Step 7 , 直接执行(call)buffer_addr, 执行完成后, 清除并释放掉buffer_addr,并重新计算密钥引子和解密密钥;

http://p6.qhimg.com/t01ea030caa0aae5fcd.png

3.14 执行解码后的数据,重新生成解码引子和解码key

Step 8 , 将发送的SMB Reponse 中Multiplex ID + 0x10 (执行成功) ,转到Step 11;

Step 9, 将发送的SMB Reponse 中Multiplex ID + 0x20 (非后门需要的数据包),转到Step 11;

Step 10, 将发送的SMB Reponse 中Multiplex ID + 0x30 (内存分配失败),转到Step 11;

Step 11, 跳到真正的SrvTransactionNotImplemented中执行。

0x4 写在最后

这是小菜第一次内核调试,查了很多资料,学了很多内核相关知识,也学到了EternalBlue作者的一些奇淫技巧。但是还是有很多不清楚的地方,希望各位大牛不吝赐教

0x5 参考文献

1NSA Eternalblue SMB 漏洞分析 http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/

【2】NSA Eternalblue SMB 漏洞分析

http://www.myhack58.com/Article/html/3/62/2017/85358_4.htm

(完)