【学习】shellcode分析

    逆向 lz520520 3年前 (2019-10-06) 507次浏览

    0x00 前言

    最近刚好看了下shellcode的分析方法,然后就想把之前HW遇到的shellcode拿出来分析一下,一方面检验下自己学习成果,另一方面也和大家分享一下shellcode的一些分析思路吧。这个样本是从客户的一份钓鱼邮件里发现的,伪造成正常邮件,附件为一个嵌入VBA的word文档,如果没有禁用宏的话,打开文档就会触发病毒执行。

     

    0x01 宏代码提取

    根据文件后缀其实就能看出,该word文档是带有宏代码的,m即为macro。

    【学习】shellcode分析

     

    为了分析脚本内容,打开文件就会触发脚本运行,但如果禁用宏的话,打开文档是看不到脚本内容的。为了以静态方式提取样本,这里会使用到一个分析程序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

    【学习】shellcode分析

     

    接下来我们就需要将这段VBA代码提取出来,执行以下命令,可以看到VBA代码就被提取出来了。我们把他重定向到一个文件里即可。

    python oledump.py -s A3 -v SSL.docm

    【学习】shellcode分析

     

     

     

    0x02 宏代码分析

    分析宏代码,可以使用编辑器,像notepad++来分析,或者用office自带的VBA编辑器。

    这里我们使用office自带的VBA编辑器,因为他还带有调试功能。

    新建一个word文档,打开,然后按alt+F11即可打开VBA编辑器。

    这里我们插入一个模块,然后将代码复制进去即可,需要注意的是代码首尾有几行Attribute开头的,是oledump提取出来用作解释说明当前数据段的,需要删掉,否则运行代码会报错。

    【学习】shellcode分析

     

     

    我们先粗略的看下代码结构,包括声明和几个Open代码段,Auto_Open表示用户打开文档时,会自动执行该段代码,而用户一无所知。

    【学习】shellcode分析

     

    其他两个代码段应该是应用其他情况,比如Workbook_Open是在excel打开工作簿的时候会运行的,里面调用了Auto_Open,而该段代码是存储在word中,所以不生效。

    【学习】shellcode分析

     

     

    回过头我们先看下声明的内容,首先是定义了两个两个结构体,然后声明了几个dll的导出函数的引用,判断是否为VBA7版本,使用不同的语法引用。

    【学习】shellcode分析

     

    并且可以看到它通过关键字“Alias”为每个函数取了别名,如CreateRemoteThread别名为CreateStuff。

    【学习】shellcode分析

     

    接下来看下Auto_Open段,我们先将别名替换回去,会更直观点。

    这段代码其实比较简单。

    1.先判断操作系统位数设置不同路径的rundll32.exe,然后通过CreateProcessA启动rundll32.exe。

    2.通过VirtualAllocEx在rundll32进程里申请一段内存空间。

    3.通过WriteProcessMemory将myArray数组的内容,按字节写入到刚才申请的内存空间。

    4.通过CreateRemoteThread在rundll32进程创建远程线程,入口点为刚才申请的内存空间首地址,并启动该线程。

    【学习】shellcode分析

     

     

    myArray数组内容,如果想进行静态分析,需要转换成二进制存储。这里编写了一个简单的python脚本进行转换。代码如下:

    【学习】shellcode分析

     

    将二进制文件拖入IDA就可以分析了。

    【学习】shellcode分析

     

     

    同样,上面进行了静态分析,为了进一步证明我们的分析,确实进行了远程线程注入,我们动态调试下。

     

    在VBA的CreateRemoteThread设下断点,点击运行,让VBA中断在这里,这个时候已经成功进行运行rundll32并进行shellcode注入,只差线程创建运行了。

    【学习】shellcode分析

     

     

    我们把光标移倒rwxpage,即分配内存的起始地址变量,可以看到申请的内存空间地址为1703936,16进制为0x1a0000。

    【学习】shellcode分析

     

     

    然后我们运行OD,将OD附加到rundll32进程上。

    【学习】shellcode分析

     

     

    跳转到0x1a0000,可以看到和我们二进制文件内容一致。

    【学习】shellcode分析

     

     

    其实如果需要模拟注入环境进行动态调试,再进行上述步骤后,在OD的0x1A0000处设置软件中断,然后点击运行。

    【学习】shellcode分析

     

    然后在VBA编辑器中,也点击继续运行,那么创建的远程线程就会中断在入口点。

    【学习】shellcode分析

     

     

    0x03 shellcode调试准备

    一般来说,shellcode都会被编写成PIC(位置无关代码),所以静态分析会比较费时费力,建议结合OD进行动态分析。上面讲述了一种动态调试的加载方式,但可以看出来,会比较麻烦,这种方式主要用于特殊场景,和指定的被注入进程有较强关联操作时才需要。

    我们这里用一种比较简单的方式,将shellcode重新编译成exe即可独立运行,方便调试。

    编写一个简单的汇编,设置一个foo段,然后导入shellcode.bin的二进制文件。

    【学习】shellcode分析

     

    然后通过yasm编译asm,在通过golink链接成exe即可,入口点设置成汇编里的“Start”。

    【学习】shellcode分析

     

    这样就方便调试了。

    【学习】shellcode分析

     

     

    0x04 PEB介绍

    在开始分析反汇编之前,需要先讲解一下PEB的相关知识。因为shellcode像正常PE文件一样,会调用到一些windows API,而它注入的进程中dll函数的地址不确定,经常会涉及到PEB来获取。

    【学习】shellcode分析

     

    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。

    【学习】shellcode分析

    5.LIST_ENTRY实际上是一个双向链表,如InMemoryOrderModuleList的Flink指向下一个模块的_LDR_DATA_TABLE_ENTRY结构体的InMemoryOrderLinks成员,Blink指向上一个模块的_LDR_DATA_TABLE_ENTRY结构体的InMemoryOrderLinks成员。

    【学习】shellcode分析

     

    6.通过_LIST_ENTRY的Flink成员获取_LDR_DATA_TABLE_ENTRY结构,比如我想获取dll的名称,InMemoryOrderLinks在_LDR_DATA_TABLE_ENTRY中的偏移为0x08,而名称字符串的偏移为0x30,那么name=[InMemoryOrderLinks+28]。

    【学习】shellcode分析

     

    所以通过遍历InMemoryOrderLinks即可找到所有加载模块。

     

     

    0x05 shellcode反汇编分析

    入口先调用了一个函数sub_30108F。

     

    【学习】shellcode分析

     

    进入该函数后,可以看到pop ebp,将call压栈的下一条指令地址(这里为0x301006)存入ebp。然后通过call ebp,来调用0x301006开始的函数,在call调用前,可以看到几句push,猜测传递了两个参数,一个8位16进制,一个字符串参数,这个在图中看到的是大端显示,转换为小端显示即“wininet”。

    【学习】shellcode分析

     

    下面就会涉及到之前讲解的PEB知识,我们看下跟进去调用的函数。跟上面PEB步骤一致,PEB->LDR->InMemoryOrderModuleList->_LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer,esi存放着dllname的指针

    【学习】shellcode分析

     

    这里使用了散列算法来计算name的散列值,通过循环,使用lodsb按字节将name从esi存储在eax里,转换成大写,将当前edi中的32位散列值循环右移13位,并将当前eax字节加到这个散列中。(shellcode中的这种算法被列入metasploit,已经成为普遍使用的算法)

    【学习】shellcode分析

     

     

    edx存储着InMemoryOrderLinks,根据_LDR_DATA_TABLE_ENTRY,计算其他成员的相对偏移,可得到DllBase,然后通过它再计算PE结构中EAT的名称引用表地址,Name Point Table存储着所有dll导出函数的名称。(这边关于PE结构的知识大家可以自行查阅)

    【学习】shellcode分析

     

    其中jz指令,用来判断当前模块是否存在导出表,如果不能存在就查找双向链表的Flink指向的下一个模块的LIST_ENTRY,从Loc_301015处重新循环。

    【学习】shellcode分析

     

     

    获取了dll的Name Point Table后,通过loc_30104A遍历func name,每次循环通过loc_301054计算func name的hash值,将其和dllname的hash值相加,与call指令之前压栈的8位16进制相比。即判断hash(dllname)+hash(func) = 压栈参数。

    【学习】shellcode分析

     

    如果遍历完无匹配,通过jecxz short loc_301088跳转到函数尾部,查找下一个模块。

    【学习】shellcode分析

     

     

    如果匹配成功,就将匹配成功的Name Point Table(函数名称引用表)的索引iName,到Oridinal Table(序号表,)查找对应的索引iOridinal。然后通过iOridinal到Address Table(函数地址表)索引对应函数的RVA。获得RVA后,根据dllBase即可获取该函数在虚拟内存里的VA。

    【学习】shellcode分析

     

     

    仔细观察最后几行,pop ecx,他当前是在栈底了,再出栈,就是将返回地址传入ecx了。然后我们可以看到最后两句push ecx,jmp eax。其实这个就是call语句的拆分,压栈返回地址,然后进行函数跳转(eax存放着刚刚获取到的dll导出函数VA),只是这里的返回地址是外层函数的地址而不是当前地址,并且可以看到跳转时是在栈底再往下偏移-4,即堆栈空间再往下(这里说的往下是表示往堆栈地址变大的方向)就是之前call语句调用之前压栈的参数了。

    【学习】shellcode分析

     

     

    综合以上分析,从0x301006开始的函数,他实现的功能是根据传入hash值获取dll导出函数地址,并执行该导出函数。大致函数结构如下

    def GetFuncByHashAndRun(hash, **args){

    *hashfunction = GetFuncByHash(hash)

    return hashfunction(**args)

    ……

    }

    shellcode要实现PIC,上面就是一种方式来调用windows 函数。(后续就跳过这些分析步骤直接标注对应的API)

     

    回过头再看这个调用,在OD里,在上面的jmp eax处设置断点,查看具体调用的哪个api。

    【学习】shellcode分析

     

    可以看到这里其实是通过LoadLibraryA来加载wininet,这个dll是用来进行网络传输的,说明该shellocde可能存在网络通信行为。

    【学习】shellcode分析

     

     

    我们接着往下看,可以看到又一次的32位hash值压栈,然后调用刚才的GetFuncByHashAndRun函数。

    【学习】shellcode分析

     

    这里实际上调用wininet.InternetOpenA进行初始化。

    【学习】shellcode分析

     

    继续跟下来,会经过两个jmp和一个call调用,然后到达以下位置,根据OD调试,这里是调用wininet.InternetConnectA,设置和139.217.80.58需要建立一个443端口的http连接。

    【学习】shellcode分析

     

     

    该IP地址实际上是明文保存在代码尾部的。

    【学习】shellcode分析

     

    【学习】shellcode分析

     

    继续跟踪,会发现还调用了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头文件进行搜索。)

    【学习】shellcode分析

     

    调用InternetSetOptionA设置选项。

    【学习】shellcode分析

     

     

     

    再调用HttpSendRequestA设置HTTP头部并发送请求。

    【学习】shellcode分析

     

    设置内容如下,可以看到头部字符串存储在基址偏移0x110B8的地方。

    【学习】shellcode分析

     

     

    通过IDA可以更直观的看到。

    【学习】shellcode分析

     

     

     

    继续往下看,调用完HttpSendRequestA后,会判断返回是否为0(False)。

    【学习】shellcode分析

     

    为0就跳转执行ExitProcess结束进程。

    【学习】shellcode分析

     

     

    如果调用成功,会判断esi即hRequest是否为0,如果为0(Null)就调用GetLastError获取错误内容,否则不获取,然后GetDesktopWindow获取桌面窗口句柄,调用InternetErrorDlg通过合适的对话框提示错误信息。

    【学习】shellcode分析

     

     

    根据InternetErrorDlg的返回值是否为ERROR_INTERNET_FORCE_RETRY判断是否需要重传,如果需要重传,就重新执行上面HttpOpenRequest等一系列操作

    【学习】shellcode分析

     

     

    否则就跳转到loc_301157,调用VirtualAlloc分配一段虚拟内存,大小为400000h,可读写执行。

    【学习】shellcode分析

     

     

    然后调用InternetReadFile读取require-jquery.js内容,保存在刚分配的虚拟内存空间里。每次读取2000h字节,循环直到全部读取完。

    【学习】shellcode分析

     

     

    最后可以看到pop eax,然后retn,观察栈帧,找到相同偏移的地方,那么实际上就是返回到push ecx压栈的地址,ecx存储的地址是VirtualAlloc分配首地址+0x12B2。

    【学习】shellcode分析

     

    上面的操作就是分配一段虚拟内存,然后将js文件内容写入,并设置偏移0x12B2为起始地址。接着就是跳到下载的新的shellcode位置继续运行。

     

    所以实际上这个js文件是嵌入了shellcode,伪装成正常的js文件而已。

    【学习】shellcode分析

     

    转换成exe后拖进IDA可以看到能正常反汇编,但也有反汇编不正常的地方。其实这里是做了加密。

    【学习】shellcode分析

     

    通过OD解密,保存解密后的代码,再使用IDA查看。可以看到解析的函数多出了不少。

    【学习】shellcode分析

     

     

    解密看了下开头部分的反汇编,通过hash来找API的操作也与分析的shellcode类似。

    【学习】shellcode分析

     

     

    剩下的就没具体分析了。

    【学习】shellcode分析

     

     

    这次先讲到这段shellcode分析,因为另一段有200多K,后面找时间分析。上传virustotal,检测该段shellcode可能为一个后门木马,用来连接C&C服务器,而目标IP端口与上面的shellcode一致。

    【学习】shellcode分析

     

    【学习】shellcode分析

     

     

    0x06 结语

    emmm,作为还在入门的小白,分析的第一个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文件,可以通过微步在线沙箱进行下载)​

     

     

     


    Security , 版权所有丨如未注明 , 均为原创丨
    转载请注明原文链接:【学习】shellcode分析
    喜欢 (0)