0x00 前言
最近刚好看了下shellcode的分析方法,然后就想把之前HW遇到的shellcode拿出来分析一下,一方面检验下自己学习成果,另一方面也和大家分享一下shellcode的一些分析思路吧。这个样本是从客户的一份钓鱼邮件里发现的,伪造成正常邮件,附件为一个嵌入VBA的word文档,如果没有禁用宏的话,打开文档就会触发病毒执行。
0x01 宏代码提取
根据文件后缀其实就能看出,该word文档是带有宏代码的,m即为macro。
使用分析工具oledump.py(https://github.com/decalage2/oledump-contrib)对样本进行分析
oledump.py是一个用于分析OLE文件(复合文件二进制格式)的程序,而word、excel、ppt等文档是OLE格式文件的,可以用它来提取宏代码。
先进行文件基础分析,可以看到A3这段数据被标记为“M”,“M”即表示Macro,说明这段数据是带有VBA代码的。
python oledump.py SSL.docm
接下来我们就需要将这段VBA代码提取出来,执行以下命令,可以看到VBA代码就被提取出来了。我们把他重定向到一个文件里即可。
python oledump.py -s A3 -v SSL.docm
0x02 宏代码分析
分析宏代码,可以使用编辑器,像notepad++来分析,或者用office自带的VBA编辑器。
这里我们使用office自带的VBA编辑器,因为他还带有调试功能。
新建一个word文档,打开,然后按alt+F11即可打开VBA编辑器。
这里我们插入一个模块,然后将代码复制进去即可,需要注意的是代码首尾有几行Attribute开头的,是oledump提取出来用作解释说明当前数据段的,需要删掉,否则运行代码会报错。
我们先粗略的看下代码结构,包括声明和几个Open代码段,Auto_Open表示用户打开文档时,会自动执行该段代码,而用户一无所知。
其他两个代码段应该是应用其他情况,比如Workbook_Open是在excel打开工作簿的时候会运行的,里面调用了Auto_Open,而该段代码是存储在word中,所以不生效。
回过头我们先看下声明的内容,首先是定义了两个两个结构体,然后声明了几个dll的导出函数的引用,判断是否为VBA7版本,使用不同的语法引用。
并且可以看到它通过关键字“Alias”为每个函数取了别名,如CreateRemoteThread别名为CreateStuff。
接下来看下Auto_Open段,我们先将别名替换回去,会更直观点。
这段代码其实比较简单。
1.先判断操作系统位数设置不同路径的rundll32.exe,然后通过CreateProcessA启动rundll32.exe。
2.通过VirtualAllocEx在rundll32进程里申请一段内存空间。
3.通过WriteProcessMemory将myArray数组的内容,按字节写入到刚才申请的内存空间。
4.通过CreateRemoteThread在rundll32进程创建远程线程,入口点为刚才申请的内存空间首地址,并启动该线程。
myArray数组内容,如果想进行静态分析,需要转换成二进制存储。这里编写了一个简单的python脚本进行转换。代码如下:
将二进制文件拖入IDA就可以分析了。
同样,上面进行了静态分析,为了进一步证明我们的分析,确实进行了远程线程注入,我们动态调试下。
在VBA的CreateRemoteThread设下断点,点击运行,让VBA中断在这里,这个时候已经成功进行运行rundll32并进行shellcode注入,只差线程创建运行了。
我们把光标移倒rwxpage,即分配内存的起始地址变量,可以看到申请的内存空间地址为1703936,16进制为0x1a0000。
然后我们运行OD,将OD附加到rundll32进程上。
跳转到0x1a0000,可以看到和我们二进制文件内容一致。
其实如果需要模拟注入环境进行动态调试,再进行上述步骤后,在OD的0x1A0000处设置软件中断,然后点击运行。
然后在VBA编辑器中,也点击继续运行,那么创建的远程线程就会中断在入口点。
0x03 shellcode调试准备
一般来说,shellcode都会被编写成PIC(位置无关代码),所以静态分析会比较费时费力,建议结合OD进行动态分析。上面讲述了一种动态调试的加载方式,但可以看出来,会比较麻烦,这种方式主要用于特殊场景,和指定的被注入进程有较强关联操作时才需要。
我们这里用一种比较简单的方式,将shellcode重新编译成exe即可独立运行,方便调试。
编写一个简单的汇编,设置一个foo段,然后导入shellcode.bin的二进制文件。
然后通过yasm编译asm,在通过golink链接成exe即可,入口点设置成汇编里的“Start”。
这样就方便调试了。
0x04 PEB介绍
在开始分析反汇编之前,需要先讲解一下PEB的相关知识。因为shellcode像正常PE文件一样,会调用到一些windows API,而它注入的进程中dll函数的地址不确定,经常会涉及到PEB来获取。
1.TEB(线程环境块)存储在fs:[0]
2.PEB(进程环境块)指针存储在TEB偏移0x30的位置
3.PEB中偏移0xC是指向PEB_LDR_data结构体的指针,这个结构体包含了进程的加载模块。
4.LDR包含了三个LIST_ENTRY结构体,本质是一样的只是以不同次序将_LDR_DATA_TABLE_ENTRY项链接在一起。LDR偏移0x14为InMemoryOrderModuleList。
5.LIST_ENTRY实际上是一个双向链表,如InMemoryOrderModuleList的Flink指向下一个模块的_LDR_DATA_TABLE_ENTRY结构体的InMemoryOrderLinks成员,Blink指向上一个模块的_LDR_DATA_TABLE_ENTRY结构体的InMemoryOrderLinks成员。
6.通过_LIST_ENTRY的Flink成员获取_LDR_DATA_TABLE_ENTRY结构,比如我想获取dll的名称,InMemoryOrderLinks在_LDR_DATA_TABLE_ENTRY中的偏移为0x08,而名称字符串的偏移为0x30,那么name=[InMemoryOrderLinks+28]。
所以通过遍历InMemoryOrderLinks即可找到所有加载模块。
0x05 shellcode反汇编分析
入口先调用了一个函数sub_30108F。
进入该函数后,可以看到pop ebp,将call压栈的下一条指令地址(这里为0x301006)存入ebp。然后通过call ebp,来调用0x301006开始的函数,在call调用前,可以看到几句push,猜测传递了两个参数,一个8位16进制,一个字符串参数,这个在图中看到的是大端显示,转换为小端显示即“wininet”。
下面就会涉及到之前讲解的PEB知识,我们看下跟进去调用的函数。跟上面PEB步骤一致,PEB->LDR->InMemoryOrderModuleList->_LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer,esi存放着dllname的指针
这里使用了散列算法来计算name的散列值,通过循环,使用lodsb按字节将name从esi存储在eax里,转换成大写,将当前edi中的32位散列值循环右移13位,并将当前eax字节加到这个散列中。(shellcode中的这种算法被列入metasploit,已经成为普遍使用的算法)
edx存储着InMemoryOrderLinks,根据_LDR_DATA_TABLE_ENTRY,计算其他成员的相对偏移,可得到DllBase,然后通过它再计算PE结构中EAT的名称引用表地址,Name Point Table存储着所有dll导出函数的名称。(这边关于PE结构的知识大家可以自行查阅)
其中jz指令,用来判断当前模块是否存在导出表,如果不能存在就查找双向链表的Flink指向的下一个模块的LIST_ENTRY,从Loc_301015处重新循环。
获取了dll的Name Point Table后,通过loc_30104A遍历func name,每次循环通过loc_301054计算func name的hash值,将其和dllname的hash值相加,与call指令之前压栈的8位16进制相比。即判断hash(dllname)+hash(func) = 压栈参数。
如果遍历完无匹配,通过jecxz short loc_301088跳转到函数尾部,查找下一个模块。
如果匹配成功,就将匹配成功的Name Point Table(函数名称引用表)的索引iName,到Oridinal Table(序号表,)查找对应的索引iOridinal。然后通过iOridinal到Address Table(函数地址表)索引对应函数的RVA。获得RVA后,根据dllBase即可获取该函数在虚拟内存里的VA。
仔细观察最后几行,pop ecx,他当前是在栈底了,再出栈,就是将返回地址传入ecx了。然后我们可以看到最后两句push ecx,jmp eax。其实这个就是call语句的拆分,压栈返回地址,然后进行函数跳转(eax存放着刚刚获取到的dll导出函数VA),只是这里的返回地址是外层函数的地址而不是当前地址,并且可以看到跳转时是在栈底再往下偏移-4,即堆栈空间再往下(这里说的往下是表示往堆栈地址变大的方向)就是之前call语句调用之前压栈的参数了。
综合以上分析,从0x301006开始的函数,他实现的功能是根据传入hash值获取dll导出函数地址,并执行该导出函数。大致函数结构如下
def GetFuncByHashAndRun(hash, **args){
*hashfunction = GetFuncByHash(hash)
return hashfunction(**args)
……
}
shellcode要实现PIC,上面就是一种方式来调用windows 函数。(后续就跳过这些分析步骤直接标注对应的API)
回过头再看这个调用,在OD里,在上面的jmp eax处设置断点,查看具体调用的哪个api。
可以看到这里其实是通过LoadLibraryA来加载wininet,这个dll是用来进行网络传输的,说明该shellocde可能存在网络通信行为。
我们接着往下看,可以看到又一次的32位hash值压栈,然后调用刚才的GetFuncByHashAndRun函数。
这里实际上调用wininet.InternetOpenA进行初始化。
继续跟下来,会经过两个jmp和一个call调用,然后到达以下位置,根据OD调试,这里是调用wininet.InternetConnectA,设置和139.217.80.58需要建立一个443端口的http连接。
该IP地址实际上是明文保存在代码尾部的。
继续跟踪,会发现还调用了HttpOpenRequestA来设置URI,URI为/require-jquery.js,并且flag字段设置了INTERNET_FLAG_SECURE,表明建立的是https链接,INTERNET_FLAG_IGNORE_CERT_DATE_INVALID|
INTERNET_FLAG_IGNORE_CERT_CN_INVALID表示不检查ssl证书是否有效。(这里flag字段网上没查到数值对应,但可以通过wininet.h头文件进行搜索。)
调用InternetSetOptionA设置选项。
再调用HttpSendRequestA设置HTTP头部并发送请求。
设置内容如下,可以看到头部字符串存储在基址偏移0x110B8的地方。
通过IDA可以更直观的看到。
继续往下看,调用完HttpSendRequestA后,会判断返回是否为0(False)。
为0就跳转执行ExitProcess结束进程。
如果调用成功,会判断esi即hRequest是否为0,如果为0(Null)就调用GetLastError获取错误内容,否则不获取,然后GetDesktopWindow获取桌面窗口句柄,调用InternetErrorDlg通过合适的对话框提示错误信息。
根据InternetErrorDlg的返回值是否为ERROR_INTERNET_FORCE_RETRY判断是否需要重传,如果需要重传,就重新执行上面HttpOpenRequest等一系列操作,如下所示:
否则就跳转到loc_301157,调用VirtualAlloc分配一段虚拟内存,大小为400000h,可读写执行。
然后调用InternetReadFile读取require-jquery.js内容,保存在刚分配的虚拟内存空间里。每次读取2000h字节,循环直到全部读取完。
最后可以看到pop eax,然后retn,观察栈帧,找到相同偏移的地方,那么实际上就是返回到push ecx压栈的地址,ecx存储的地址是VirtualAlloc分配首地址+0x12B2。
上面的操作就是分配一段虚拟内存,然后将js文件内容写入,并设置偏移0x12B2为起始地址。接着就是跳到下载的新的shellcode位置继续运行。
所以实际上这个js文件是嵌入了shellcode,伪装成正常的js文件而已。
转换成exe后拖进IDA可以看到能正常反汇编,但也有反汇编不正常的地方。其实这里是做了加密。
通过OD解密,保存解密后的代码,再使用IDA查看。可以看到解析的函数多出了不少。
解密看了下开头部分的反汇编,通过hash来找API的操作也与分析的shellcode类似。
剩下的就没具体分析了。
这次先讲到这段shellcode分析,因为另一段有200多K,后面找时间分析。上传virustotal,检测该段shellcode可能为一个后门木马,用来连接C&C服务器,而目标IP端口与上面的shellcode一致。
0x06 结语
作为还在入门的小白,分析的第一个shellcode,也没啥经验,有些地方可能描述的不太正确,有问题大家指出来。
总结一下这个样本功能,这个样本通过打开word文档,触发VBA脚本,拉起rundll32.exe,并将一段shellcode注入到rundll32里,这段shellcode的功能就是一个下载器,通过访问https://139.217.80.58/require-jquery.js,下载这个伪造成js脚本的shellcode,在rundll32进程里分配一段内存,将这个shellcode写入,然后跳转到指定地址开始运行,根据粗略判断,这段shellcode可能是一个后门,用来连接C&C服务器进行远控。
分析类似shellcode,最重要的是识别出他调用了哪些API,通过这些API就可以快速判断这个shellcode具体有哪些功能,像这个样本就用了最常见方法,通过存储API的散列值,遍历模块,将API名字进行散列计算与存储散列值的相比,从而获取相应的API地址进行调用。因为要实现PIC,要获取dll模块导出函数地址就涉及到PEB和PE结构,需要去理解PEB里怎么存储模块的,dll模块的PE结构里导出函数是怎么存储,理解这个过程其实这类shellcode的分析思路就比较清晰了。
0x07 参考
宏病毒研究
https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=133
PEB相关知识
https://www.cnblogs.com/dsky/archive/2012/02/23/2364503.html
https://docs.microsoft.com/zh-cn/windows/win32/api/winternl/ns-winternl-peb_ldr_data
https://www.cnblogs.com/catchyrime/p/4222292.html
样本MD5
1A61C4FD7772D0CB173AF9FCAF80C620(docm文件,可以通过微步在线沙箱进行下载)
更多精彩内容,可加入知识星球,安全分析与研究,跟一些业界顶尖大牛一起交流学习,在知识星球跟熊猫正正一起学习安全知识,专注于安全分析与研究,分享各种安全纯干货!1.各类恶意样本分析技术,不限平台,包含:勒索病毒、挖矿病毒、蠕虫病毒,感染型病毒、僵尸网络、APT攻击、盗号木马、后门,以及各种硬件木马后门等!2.二进制常见漏洞分析与实战!3.应急响应、渗透测试、黑产追踪、威胁情报、最新热点安全事件剖析!
最后还是感谢那些支持我的朋友们,多谢你们的关注、转发、赞赏以及留言,关注微信公众号:安全分析与研究 ,可扫描下方的二维码,会不定期分享更多精彩内容!
安全的路很长,贵在坚持......
本文分享自微信公众号 - 安全分析与研究(MalwareAnalysis)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4593082/blog/4418768