手把手之提取Emotet的C2配置

邮件分析

使用到的邮件(hash:6ce9963985aae402b209012902213aeb5ce43c247f75842db3b8d2eae56f87d7):
在这里插入图片描述
样本为eml类型,属于文本文件,分析时可以直接用文本编辑器打开,如下是使用vscode打开:
在这里插入图片描述
eml文件中有一些在分析时值得注意,如下:
(1)“Received"字段在邮件中用于追踪邮件的传输路径。每当邮件通过一个邮件服务器或中继服务器时,这个服务器都会在邮件的头部添加一个新的"Received"字段。最近的服务器添加的"Received"字段显示在头部的顶部,而最早的服务器添加的"Received"字段显示在头部的底部。
(2)“Content-Type"字段是EML邮件中的一个标头字段,用于指示邮件正文的数据类型和编码方式。在包含附件的邮件中,通常会使用"multipart"主媒体类型的"Content-Type"字段来指示邮件的主体部分由多个部分组成。每个部分都可以有不同的媒体类型,其中包括邮件正文和附件。
(3)发送邮件的时间为2022-03-2 10:02:14,但是是东8时区,将其转化为0时区可得:2022-03-02 02:02:14,梳理样本中的时间线可以对整个攻击事件有更清晰的认识。
该eml文件中Received中一共有两处,可以得到邮件是先从14.11.7.162发出再到www684.sakura.ne.jp(59.106.19.134)。
从Content-Type字段的boundary可知该eml邮件使用”----=_NextPart_00157_32440_1414601071.1109381944"作为内容分隔符,如下所示:
在这里插入图片描述
该eml文件中有两个部分,如下所示:
在这里插入图片描述
在这里插入图片描述
第一部分为邮件正文展示部分,第二部分为邮件附件内容,附件使用的编码方式由"Content-Transfer-Encoding"给出为:base64,附件的文件格式为XLSM(Content-Type可以使用的类型可以在此查看:https://www.contractsfinder.service.gov.uk/apidocumentation/ResourceModel?modelName=FileType),附件名为"PO 03022022.xlsm”。
使用CyberChef(https://gchq.github.io/CyberChef/)可以提取附件xlsm,CyberChef是一个功能强大的在线工具(https://github.com/mattnotmax/cyberchef-recipes有一些使用示例),用于数据转换、加密解密、编码解码,转化如下所示:
在这里插入图片描述
至此我们成功提取出邮件附件(hash:3ab889ece1ded185d2b07c27d20a62fce9b3bb6af3d9aa28f5bc35ec4d03f6f7)。

文档分析

xlsm文档分析

Office文档主要基于三种格式:
(1)doc、xls、ppt三种扩展名文档属于97-2003版Office,基于OLE格式的文件类型,可使用"OffVis"解析出ole格式文件进行分析。
(2)docm、xlsm、pptm是启用宏的Office文档,存储Visual Basic Applications(VBA)宏代码,可解析出xml文件。
(3)docx、xlsx、pptx均基于XML的压缩文件格式,使其占用空间更小,运算速度也会快一点,后缀中最后一个"x"的意义就在于此。
上述得到的样本为xlsm是启用宏的Office文档,首先将后缀改为zip,然后解压可得到如下所示:
在这里插入图片描述
在"docProps"文件夹中,"core.xml"包含了文档的核心属性,如标题、作者、创建日期、最后修改日期等,"app.xml"包含了文档的应用程序属性,如应用程序名称、版本号、公司名称等。修改文件时间为:2022-03-01 20:57:45是在邮件发送时间之前,可以作为文件修改时间参考,如下所示:
在这里插入图片描述
在"xl"文件夹中,"sharedStrings.xml"文件是存储Excel单元格文本值的XML文件。在Excel中,单元格可以包含不同类型的数据,例如数字、日期、布尔值或文本。当单元格包含文本值时,Excel将这些文本值存储在"sharedStrings.xml"文件中,而不是直接存储在每个单元格中。所以可以通过该文件检测是否存在可疑字符串,
在这里插入图片描述
从上述文件中发现敏感程序名regsvr32.exe以及几个URL,如下所示:
http://nataliapereira.com/wp-admin/pE8xYY3x6p/
http://annewelshsalon.com/wp-admin/2c9l2o1/cWWAzTVQ/
http://hellocloudgurusgerald.com/wp-content/iXYx/
https://ramijabali.com/licenses/0/
https://africa-roadworks.com/lilo-bard/vk3GSY7/
通过VT关联可发现该上述链接均属于恶意载荷的下载链接以及下载的发起文件都是xlsm,如下所示:
在这里插入图片描述
在"xl"文件夹中,“workbook.xml"文件是Excel工作簿的主要定义文件之一。它包含了工作簿的整体结构、工作表的定义以及与工作簿相关的其他元数据,内容如下所示:
在这里插入图片描述
其中”_xlnm.Auto_Open"是Excel的宏自动运行事件的标识符,用于指定在打开Excel文件时自动执行的宏,上述可知在"EFWFSFG"中有自动运行的宏。还有一些sheet设置状态为state=“hidden”,使用Excel打开(务必在虚拟机环境中进行实验),得到如下所示:
在这里插入图片描述
取消隐藏的sheet,得到如下所示:
在这里插入图片描述
启用宏之后,宏自动将自动填充成如下:
在这里插入图片描述
由上可看出该样本从前述的5个URL中下载到"xlsm"文件的父目录中的"dw1.ocx"中,并用32位的regsvr32.exe以不显示消息框的方式运行。使用WireShark进行抓包,验证确实有请求的动作,请求包如下所示:
在这里插入图片描述
至此该xlsm文档的宏部分析完毕,由于下方载荷的服务器失效故无法进一步获取载荷,通过VT关联获取到下一步载荷。

使用工具提取信息

提取宏代码

提取宏代码工具有很多,这里介绍一款oletools该工具基于python内置多种分析文档的工具,这里使用的是olevba可以从MSOffice文档(OLE和OpenXML)中提取和分析VBA宏源代码。该工具集使用pip安装,命令为:pip install -U oletools[full],安装好后使用命令:"olevba {office文件路径}"提取xlsm文件中的宏代码,结果显示提取出的代码同手工提取,如下所示:
在这里插入图片描述

提取文档信息

文档的元数据对于我们分析一次攻击事件与溯源有很大的帮助,可以使用ExifTool工具来对文档元数据的提取。ExifTool是一个免费的软件,用于读取、写入和操作图像、音频、视频和PDF元数据,下载地址为:https://exiftool.org/exiftool-12.69.zip,
运行后如下所示,有文档创建时间,修改时间以及作者信息等:
在这里插入图片描述

样本分析

初始分析

静态基本信息

前述分析得到载荷最终通过32位的regsvr32.exe加载,由于下载链接均失效,故无法通过宏进行加载载荷。这里使用的载荷HASH为:39cabb86c07e0daa87dd43dcdaa36a4bea002616169929e25fd0aa689603a5c9。
首先使用DIE进行样本信息的简要收集,结果如下所示:
在这里插入图片描述
DIE显示该样本生成的时间戳为2022-03-02 03:36:23,由于DIE转化的时间戳根据本地的时区进行转化所以该时间是处于东8时区,转化为0时区为2022-03-01 19:36:23,也符合攻击者生成的时间顺序。
DIE还显示该样本为32位DLL,使用MFC编写,继续观察样本各个区段中的熵值,一般都会将核心的代码加密,加密后的熵值都会变得很高,如下所示:
在这里插入图片描述
"rsrc"区段是进程存放资源的地方,说明编写该程序的人可能将后续载荷加密保存到资源段中,这里利用ResourceHacker查看资源,下载地址为:https://angusj.com/resourcehacker,发现可疑的俄文资源名,资源ID为6363,右下角显示24000/4B2D0分别代表该资源的大小为24000B以及在文件中的偏移为4B2D0,如下所示:
在这里插入图片描述

动态信息

接下来使用如下命令,将程序运行起来:
C:\Windows\SysWOW64\regsvr32.exe /s {样本路径}
使用ProcessHacker(下载地址为:https://processhacker.sourceforge.io/downloads.php)查看regsvr32.exe的内存发现其中有RWX标识,表示当前内存可读可写可执行,此标识一般被用来运行ShellCode,大小为十进制的144KB转化为16进制为24000B刚好和前述中资源相符合,可能是资源解密后的结果,如下所示:
在这里插入图片描述
右键选取读写内存查看其内存,发现魔术字4d5a、5045以及一系列PE文件特征,均表明该内存可能是后续载荷,如下所示:
在这里插入图片描述
单击Save计算其HASH:E62B20BBA48004CED338F64329AF0319,VT关联一下,是emote的载荷:
在这里插入图片描述
查看其启动命令,发现其命令行已经改变,通过比对hash发现suyjcnv.jpd是该样本的复制,如下所示:
在这里插入图片描述
查看其各个线程的堆栈发现其中一个有网络请求,请求函数为HttpSendRequestW处返回地址为:0x10012587,如下所示:
在这里插入图片描述
对应内存如下所示:
在这里插入图片描述
观察到该上个内存块是4KB,而当前内存地址刚好是0x1000的偏移,容易联想到0x400展开到0x1000,查看其内存验证了确实是PE文件。
在这里插入图片描述
将上述内存dump到本地,再与之前的0x24000大小的代码进行对比,发现0x10000000处为0x24000处的展开,对比结果如下所示(这里使用010Editor进行内存部分的合并与对比):
在这里插入图片描述
使用ProcMon进行监测发现,样本会将自身移动到C:\Users{用户名}\AppData\Local的随机名目录下,而进行这一操作的仍是上述在内存中展开的进程,如下所示:
在这里插入图片描述
生成的文件如下所示:
在这里插入图片描述

第一阶段

使用IDA分析该文件,加载之后定位到DllMain处,第一个if为判断当前是否是Dll的进程加载,若是则继续若不是就直接返回。第二个if由一个函数的返回值控制,该函数申请Size大小的空间并进行赋值,但紧接着就释放掉,这样做的原因猜测是为了保证一定可以申请到Size那么大的空间,如下所示:
在这里插入图片描述
随后为一连串的栈上数据的构造:
在这里插入图片描述
使用IDA快捷键R进行数字向字符的转化,发现其是栈上的字符串分别是kernel32.dll、ntdll.dll以及msvcrt.dll,再利用快捷键n修改对应变量名称,结果如下所示:
在这里插入图片描述
接着是将刚加载好的字符串传入sub_10001AB0函数中,如下所示:
在这里插入图片描述
查看该函数如下所示,都是一些全局变量的计算,最后有一个函数为sub_71521A10,但该函数也是一些计算,如下所示:
在这里插入图片描述
由于有些混淆,而且没有其他的函数调用,通过动态调试基本可以确定该函数的作用,这里使用x32dbg进行调试(下载地址为:https://x64dbg.com),由于该样本是由C:\Windows\SysWOW64\regsvr32.exe启动的所以需要将该文件拖入x32dbg中,结果如下所示:
在这里插入图片描述
随后修改命令行进行样本的加载,如下所示:
在这里插入图片描述
将如下命令写入:
“C:\Windows\SysWOW64\regsvr32.exe” {样本路径}
在启动调试前还需要设置一下Dll入口处断点,如下所示:
在这里插入图片描述
经过两次F9到达了样本的模块,x32dbg会在标题栏中写出当前代码所处的模块位置,如下所示:
在这里插入图片描述
使用快捷键Alt+E查看所有模块的基址,找出样本模块的基址为0x707D0000,如下所示:
在这里插入图片描述
由于IDA的加载基址为样本中的默认基址0x10000000,所以导致当前IDA与x32dbg中的函数地址对应不上,需要转换基址,但我们可以通过调整IDA的基址来使之与x32dbg中的对应,操作步骤如下所示:
在这里插入图片描述
将Value中的0x10000000改为0x707D0000后可找到函数地址,如下所示:
在这里插入图片描述
使用IDA快捷键Tab,转化到反汇编窗口,得到调用该函数的地址为0x707D5E3A,如下所示:
在这里插入图片描述
x32dbg中使用快捷键Alt+C跳转回调试区,继续使用快捷键Ctrl+G打开跳转窗口,输入0x707D5E3A地址,跳转后使用快捷键F2下软件断点,如下所示:
在这里插入图片描述
F9将调试进程继续运行,会断到上述设置断点的位置,可以看到PUSH到栈中的参数确实为kernel32.dll:
在这里插入图片描述
F8步过该函数,观察返回值eax,确实为kernel32的基址,其余三个也同样为传入参数模块的基址,如下所示:
在这里插入图片描述
继续回到IDA使用Tab或F5回到反编译窗口,发现获得三个基址后会将其和一个数字传入sub_707D1BF0,这里符合获取模块基址后进一步获取函数地址的特征,如下所示:
在这里插入图片描述
通过查看sub_707D1BF0内的代码,发现其未调用其他函数,同上述获取模块基址一样,内部有多处全局变量组成的混淆计算,使用x32dbg调试看下函数结果,如下所示:
在这里插入图片描述
验证了之前的想法,通过这些动态调试的结果,使用IDA快捷键n修改变量名称,如下所示:
在这里插入图片描述
之后进一步分析伪代码,可以看出该样本寻找并加载资源ID为6363的资源,刚好为前述使用ResourceHacker发现的可疑资源,如下所示:
在这里插入图片描述
随后申请空间,复制资源中的数据到其中,如下所示:
在这里插入图片描述
之后有几个关键函数sub_707D1F20、sub_707D2210、sub_707D4FC0,以及一个保存着函数地址的全局变量dword_7081BCE8,如下所示:
在这里插入图片描述
sub_707D1F20参数有malloc申请的0x5C40,sub_707D2210参数有malloc出的0x5C40、提取的资源和资源大小,而且该函数其中并无其他函数嵌套,可以通过动态调试根据结果判断以确定其功能。sub_707D1F20传入的3个参数分别为malloc申请的0x5C40地址、变量aTe8aIfi9IrN3gk地址以及疑似变量aTe8aIfi9IrN3gk的大小0x50,如下所示:
在这里插入图片描述
sub_707D1F20运行后的结果猜测是展开的密钥,如下所示:
在这里插入图片描述
sub_707D2210运行后,前述申请加载资源的空间中解密出PE文件,如下所示:
在这里插入图片描述
解密后该PE随后会被展开到0x10000000上(由前述ProcessHacker分析得出),使用x32dbg快捷键ALT+M,转到内存界面,当前还未展开,结果如下所示:
在这里插入图片描述
运行sub_707D4FC0后发现,PE文件被展开到0x10000000,也就大致确定了该函数的作用,且返回值为0x004E5120被放到0x7081BCEC中,如下所示:
在这里插入图片描述
查看全局变量dword_7081BCE8,其保存的为PE展开的地址,0x1001223F,如下所示:
在这里插入图片描述
调用dword_7081BCE8的地址后,这里是第二阶段样本的入口点,后续会证明,没有什么特别的操作,如下所示:
在这里插入图片描述
之后进行堆栈检查,直接返回结束将控制权交还给系统,由于样本是由regsvr32.exe调用,所以还会调用第一阶段样本的导出函数DllRegisterServer,IDA观察此处代码,jnz跳转后有个call eax,该处和上述跳转到第二阶段入口点相似,猜测也是利用sub_707D4450(该函数同样有大量全局变量混淆)解密出一个地址,随后跳转过去,如下所示:
在这里插入图片描述
X32dbg中在DllRegisterServer地址(0x707D4CE0)处下断点并F9运行,如下所示:
在这里插入图片描述
随后跳转到0x707D4CF6处,查看传入的参数,第一个是指针指向展开内存的IMAGE_NT_HEADERS处,第二个是字符串"DllRegisterServer"的地址,所以大概率是找出导出函数"DllRegisterServer"的地址,如下所示:
在这里插入图片描述
果然eax为0x1000CA29,是第二阶段样本的导出函数"DllRegisterServer"的地址,如下所示:
在这里插入图片描述
导出函数"DllRegisterServer"地址为0x1000CA29,如下所示:
在这里插入图片描述

所以综上目前第一阶段是个加载器,目的是加载第二阶段的PE文件,并调用其导出函数"DllRegisterServer"。

第二阶段

静态基本信息

这小结分析上述内存加载的PE文件,使用0x24000未展开的PE文件进行分析,首先使用DIE查看其及基本信息,如下所示:
在这里插入图片描述
可以看出上小结中全局变量dword_7081BCE8中保存着就是该PE文件的入口点,而且时间为东8区的2022-02-25 21:30:45转化为0时区为2022-02-25 13:30:45也在第一阶段样本的时间戳之前。
另一个值得注意的地方是样本的.text区段体积很大,熵值较高可能被加密,如下所示:
在这里插入图片描述
模块名为Y.dll,如下所示:
在这里插入图片描述

关键点分析1

由于emotet样本有大量混淆这里仅对如何提取其C2做介绍,从动态分析定位到的线程0x10012587开始分析,该线程不断地向外发送网络请求,IDA定位到该地址如下所示:
在这里插入图片描述
F5查看其伪代码,发现其通过函数sub_1000DBF3获取到HttpSendRequestW的地址,如下所示:
在这里插入图片描述
查看sub_1000DBF3函数被引用的次数,共109次,使用快捷键x查看,结果如下所示:
在这里插入图片描述

api hash解密

该函数引用次数很多加之其参数都为数字,猜测其为通过api hash获取函数地址,查看其函数内部,其dword_10026218是个函数列表用来保存函数地址,如下所示:
在这里插入图片描述
由于获取函数地址一般有两个步骤,第一获取函数所在模块地址,所以一般可能需要进行模块的加载或模块基址的获取,第二通过函数基址获取。上述可以合理猜测,sub_100047BD获取模块基址或加载模块其参数为模块名hash,sub_1001B123获取函数地址,通过sub_100047BD返回的基址加传入的函数名hash作为参数,查看sub_100047BD,函数如下所示:
在这里插入图片描述
PEB、0XC、0XC这些都是找寻加载模块步骤中的地址和偏移,接下来进入sub_1000E293函数,可以看到这里有将大写转化为小写的操作(0x20是ascii中大小写相距的偏移),以及计算hash的操作(遍历传入的地址为0时退出,前一次计算结果会影响本次结果,以及位移操作都是计算字符串hash的特点),结合sub_100047BD的情况可知计算完hash后会与0x2A464ABF进行异或,如下所示:
在这里插入图片描述
接下来看疑似获取函数地址的sub_1001B123,该函数有0x3C、0x78这些常用的寻找导出表的偏移,如下所示:
在这里插入图片描述
查看sub_10012254函数,与前述疑似计算函数模块名hash的算法相同,只是没有变化大小写的操作以及异或参数变为了0x32A076D6,如下所示:
在这里插入图片描述
接下来在关键函数sub_1000DBF3中的两处关键调用地址处下断点,动态调试,验证下猜测结果,因为这里跳过了分析的细节,如下所示:
在这里插入图片描述
结果显示sub_100047BD返回值为kernel32的函数基址,如下所示:
在这里插入图片描述
继续调试sub_100047BD返回值为GetProcessHeap地址,sub_1000DBF3函数的地址为0x100124A1(确定传入的参数),如下所示:
在这里插入图片描述
使用DIA快捷键G跳转到0x100124A1地址处,第一个参数0x76959973会传给sub_1001B123作为函数hash,第四个参数为0xA538ACCD会传给sub_100047BD作为模块名hash,如下所示:
在这里插入图片描述
编写python脚本进行验证hash,如下所示:

def emotet_api_hash(api_name:str):
    hash = 0
    for cur_char in list(api_name):
        cur_char = ord(cur_char)
        hash = (hash << 16) + (hash << 6) + cur_char - hash
        hash &= 0xFFFFFFFF
    return hash

print(hex(emotet_api_hash("Kernel32.dll".lower()) ^ 0x2A464ABF).upper())
print(hex(emotet_api_hash("GetProcessHeap") ^ 0x32A076D6).upper())

运行结果如下所示:
在这里插入图片描述
至此确定了sub_1000DBF3的作用,通过函数模块名和函数名hash获取函数地址。

hashdb解密api hash

也可使用IDA插件进行快速验证,这里使用hashdb插件,该插件可解决函数名hash转化为函数名的操作,下载地址为:https://github.com/OALabs/hashdb-ida,只需复制最新版本的hashdb.py进入您的IDA插件目录,即可使用右击后如下所示:
在这里插入图片描述
一般先使用Hunt Algorithm确定hash算法,再用Lookup导入枚举,该样本这里在计算完hash后使用了异或,所以还需要用到set XOR key这一选项,首先定位到函数hash的异或值处,右击set XOR key,如下所示:
在这里插入图片描述
随后定位到函数hash处,右击Hunt Alogorithm,如下所示:
在这里插入图片描述
之后就可以搜索函数hash了,点击Lookup,Import导入模块内函数hash的枚举,如下所示:
在这里插入图片描述
使用IDA快捷键m,将常量转为枚举值,如下所示:
在这里插入图片描述
转化后如下所示:
在这里插入图片描述

关键点分析2

这里发现使用的API为HttpSendRequestW,那么一定会使用HttpOpenRequest,如下是MSDN的解释:
在这里插入图片描述
也会调用InternetConnect,如下所示:
在这里插入图片描述
而InternetConnect的ipszServerName保存着域名或IP字符串,如下所示:
在这里插入图片描述
接下来使用x64dbg在InternetConnectA和InternetConnectW函数上下断点,这里保存下虚拟机快照,后续会经常使用之前的状态,这里运行后发现断不下来,是因为前述分析到了联网线程所对应的命令行参数已经变化,不是同一个进程。由于该线程是一直进行连接C2操作,所以这里可以使用附加进程来调试,如下所示:
在这里插入图片描述
InternetConnectW下断,如下所示:
在这里插入图片描述
使用x32dbg快捷键Ctrl+F9走到函数return的地方,其中InternetConnectW会调用InternetConnectA所以要Ctr+F9两次才能返回到调用处,返回值为0x100185F3,如下所示:
在这里插入图片描述
IDA定位到0x100185F3处,也是通过函数hash调用API,这里需要关注sub_10018501包含IP地址的字符串形式的a6参数和包含端口的a7,如下所示:
在这里插入图片描述
使用IDA快捷键x交叉引用得到sub_10018501的引用处,a6、a7对应的这里是IP地址的字符串形式的a11和端口的a9,如下所示:
在这里插入图片描述
使用IDA快捷键x交叉引用得到sub_10005EF5的引用处,该函数为调用sub_10018501的函数,且跟踪a11、a9均未有被写入的情况,如下所示:
在这里插入图片描述
这里看到一个关键的全局变量dword_1002620C,该全局变量的0x14处保存着IP与端口保存地址,偏移分别是端口的0x44与IP的0x20,对该变量进行交叉引用得到如下所示:
在这里插入图片描述
一共四个调用处sub_1001194D、sub_10011B85、sub_10014320、sub_1001DD39,其中sub_10011B85与sub_10014320有多处调用需要重点关注,首先看sub_10014320处,该函数有个大的switch,每个case中都会有改变switch判断值v8的操作,这种形式属于控制流平坦化的混淆。

控制流平坦化混淆

控制流平坦化的基本思想是将原始的控制流结构转换为一个巨大的状态机。它通过插入大量的条件分支和跳转语句来随机改变程序的控制流路径。这些条件分支和跳转语句可能是无用的、重复的或者是随机生成的,使得分析者难以理解程序的实际控制流。
他的核心是通过一个状态码来进行代码流程的变化,这里是v8(ecx)当作状态码,如下所示:
在这里插入图片描述
将光标移动到v8 <= 0xCB4E4A9处使用IDA快捷键Tab移动到反汇编窗口,这里是比较状态码的开始位置,地址为0x1001491E,如下所示:
在这里插入图片描述
在0x1001491E下断点,不断F9,捕获ecx的值,可以发现如下的变化:
0F7B9084,0F017183,0C4B0A0F,05AABD0B,0F789E9F(InternetConnectW所在的流程),097F0FB6,0FA901A9,0F7B9084、0F017183,0C4B0A0F。
可以发现这里是一个循环,每次改变都会连接不同的IP和端口,值得注意的是0FA901A9,这个状态码下有对dword_1002620C的改变,如下所示:
在这里插入图片描述
结合动态调试的结果如下所示,可以得出*dword_1002620C的0x14偏移处记录的是下一次的IP与端口,dword_1002620C的0x1C处为当前尝试连接IP和端口的个数,通过每次加一看出,因为此数最后会与dword_1002620C的0x8处相比较,作为退出循环的条件,所以可以判断0x8处偏移记录着IP端口的总数,这里总数为0x2F,如下所示:
在这里插入图片描述
接下来观察sub_10011B85处的调用,该函数也是使用了控制流平坦化,如下所示:
在这里插入图片描述
这里看起来就像初始化的地方,可以看到0x1C这个计数处被赋值为0,0x14处也被赋值,0x8处一直在增加,这里应该有一个循环,我们还是可以通过找状态码的变化来确定关键点,由于这里可能是初始化的地方,所以我们需要重新调试,由于该函数没有传入的参数,所以可能与其他变量无关,尝试直接将EIP设置到该函数地址处,使用x32dbg快捷键Ctrl+G跳转到sub_10011B85地址处,右键在此设置新的EIP,如下所示:
在这里插入图片描述
然后找到比较状态码的开始位置,地址为0x10011E51,如下所示:
在这里插入图片描述
断到的状态码除了前两个后续均为循环,这里可以猜测前两个状态码中可能会解密出关键的数据,如下所示:
07A209C7、06A752FC、0A9A8008、0B1B78E2、0AECE900、02C65E3F、0A9A8008、0B1B78E2、0AECE900、02C65E3F、0A9A8008。
调试期间看到ESI指向宽字符的%u.%u.%u.%u,它应该是于格式化IP字符串,如下所示:
在这里插入图片描述
07A209C7的代码块中没有什么特别的操作仅是保存了地址,如下所示:
在这里插入图片描述
06A752FC的代码块中调用了一个函数,如下所示:
在这里插入图片描述
进入sub_1001501B函数,该函数看起来像解密的函数,有异或左移操作,这里很可能是解密IP端口处,函数sub_1000E10B中调用了HeapAlloc返回值为v8,而异或后也保存到v8中(39行),v4偏移v12,而v12为v8-v4(35行),故一叠加就变为v8地址,所以解密后的数据应该是保存到v8中,如下所示:
在这里插入图片描述
重新将EIP设置到sub_10011B85函数处,调试一下该函数在HeapAlloc处下断点,断下后看esp+C处(HeapAlloc的第三个参数)查看申请空间,为0x178,如下所示:
在这里插入图片描述
如下是MSDN中HeapAlloc的参数解释:
在这里插入图片描述
返回值为0x00B152D0,如下所示:
在这里插入图片描述
接下来运行到函数处,使用x32dbg快捷键Ctrl+F9,在内存窗口使用Ctrl+G输入0x00B152D0查看内存,0x178大小,可以发现其中每组的大小均为8个字节,故这里共有0x2F组和前述分析的IP总个数对应,如下所示:
在这里插入图片描述
接下来是状态码为0A9A8008的代码,该处仅仅是申请空间保存到v2中,如下所示:
在这里插入图片描述
X32dgb调试,得到申请空间为0x00B28658,如下所示:
在这里插入图片描述
然后是状态码为0B1B78E2的代码,通过下一个为状态码为0AECE900判断在此,下方中有两个关键函数sub_100182CE与sub_10017CA3分别是snwprintf与HeapFree,注意他们传入的参数都有v8,v8是之前申请的空间,并可能存放着关键的信息。再看这里的v1是06A752FC中解密函数sub_1001501B的返回值v5的赋值,且取出前四个字节分别赋值给v6、v7、v15、v16,这些都会传给snwprintf,和可能是IP的解析,传入的还有v2 + 0x20偏移(与前述存放IP地址的偏移相同),v2是上个状态码时申请的空间,如下所示:
在这里插入图片描述
观察sub_1000A1F2函数,发现该函数和sub_1001501B相似,如下所示:
在这里插入图片描述
继续调试观察sub_1000A1F2的返回值,为%u.%u.%u.%u之前动态跑状态码时也出现过,应该就是这里之后使用的,这个格式化字符串很可能用来拼接IP字符串,如下所示:
在这里插入图片描述
继续调试观察sub_100182CE返回后v2的情况,得到IP,如下所示:
在这里插入图片描述
由上可以得出,sub_1001501B解密出的即为IP地址,IP字符串地址偏移0x20是与前述对应,再次观察,可以发现0x44存放端口处也在sub_1001501B解密的结果v1中有对应的数据,如果后续没有v1的变化那么v1 + 8就代表着一个数据块大小为8与前述观察相符,如下所示:
在这里插入图片描述
调试运行完上述代码,得到如下所示:
在这里插入图片描述
之后是0AECE900状态码的代码,包括dword_1002620C结构体的构造,如下所示:
在这里插入图片描述
最后是02C65E3F状态码,负责判断是可以退出循环,状态码0A9A8008会开始新的申请空间,状态码084736D9会直接退出循环,如下所示:
在这里插入图片描述

使用python解密

至此初始化IP和端口部分大致分析完毕,其中还有一些细节的结构体需要进一步分析对照得出结论。将sub_1001501B的解密过程翻译成python代码,得到如下所示:

def emoete_str_decode(encode_data:bytes):
    key = struct.unpack('<I', encode_data[:4])[0]
    size = struct.unpack('<I', encode_data[4:8])[0] ^ key
    data = list(encode_data[8:])
    key = list(encode_data[:4])
    decode_data = []
    for i in range(size):
        decode_data.append(data[i] ^ key[i % 4])
    return bytes(decode_data)

编写解析格式代码,提取dword_10026000处IP加密数据,如下所示:

import struct

def emoete_str_decode(encode_data:bytes):
    key = struct.unpack('<I', encode_data[:4])[0]
    size = struct.unpack('<I', encode_data[4:8])[0] ^ key
    data = list(encode_data[8:])
    key = list(encode_data[:4])
    decode_data = []
    for i in range(size):
        decode_data.append(data[i] ^ key[i % 4])
    return bytes(decode_data)

def emotet_ip_parse(ip_list_raw:bytes):
    ip_list = []
    for i in range(0, len(ip_list_raw), 8):
        ip_list.append("%u.%u.%u.%u:%u" % (ip_list_raw[i+0],ip_list_raw[i+1],ip_list_raw[i+2],ip_list_raw[i+3],struct.unpack('>H', ip_list_raw[i+4: i+6])[0]))
    return ip_list

ip_list_encode = b"\x8B\xCF\xAC\x16\xF3\xCE\xAC\x16\x5A\xC0\x40\x31\x94\x5F\xAC\x17\x29\x3B\xFC\x52\x8A\x74\xAC\x17\x48\x55\x51\x2A\x94\x5F\xAC\x17\x94\xD7\x32\x2E\x94\x5F\xAC\x17\x5A\xB1\xCE\xD8\x94\x5F\xAC\x17\xA6\x41\xDE\xF1\x94\x5F\xAC\x17\x14\xC7\x97\x44\x94\x5F\xAC\x17\x14\x8E\xF4\x1C\x94\x5F\xAC\x17\xD9\x6A\x34\x69\x94\x5F\xAC\x17\x8A\x25\xAE\xFE\x94\x5F\xAC\x17\x39\x80\x3F\x54\x94\x5F\xAC\x17\xEC\x84\x65\x12\x8A\x74\xAC\x17\x08\xAB\xB4\xF1\x8B\x9F\xAC\x17\x0A\x27\x10\x4B\x8A\x74\xAC\x17\x26\x1B\x6D\xEF\x94\x5F\xAC\x17\xE0\x79\x4D\x98\x94\x5F\xAC\x17\xEC\x49\xF9\x43\x8B\x9F\xAC\x17\x3B\xA7\xC6\x76\x94\x5F\xAC\x17\x40\xBD\xC1\x6A\x8A\x74\xAC\x17\x53\x51\x4E\xD8\x8A\x74\xAC\x17\xFC\x24\x53\xDF\x94\x5F\xAC\x17\xEC\x84\x65\x14\x8A\x74\xAC\x17\x3B\xF7\x2C\x60\x8A\x74\xAC\x17\x48\x55\x29\x02\x8A\x74\xAC\x17\xB8\x31\x20\xF8\x90\x67\xAC\x17\xA6\xB9\xDF\x75\x94\x5F\xAC\x17\x5F\x22\x94\x62\x90\x67\xAC\x17\x01\x76\xE4\x0C\x94\x5F\xAC\x17\x15\x8A\x72\x73\x8A\x74\xAC\x17\xA5\xF8\x72\x1D\x8A\x74\xAC\x17\xC4\x63\x78\xCE\x94\x5F\xAC\x17\xDA\xCF\x40\x4C\x8A\x74\xAC\x17\xE5\x27\xD9\xAC\x94\x5F\xAC\x17\xB9\xD1\x84\xD2\x94\x5F\xAC\x17\x32\x52\xFE\xC5\x94\x5F\xAC\x17\x29\x3C\x03\x29\x8A\x74\xAC\x17\x39\x4F\xFF\xB3\x8B\x9F\xAC\x17\x12\xB1\x67\xF3\x94\x5F\xAC\x17\xB9\xBB\x9A\xC1\x8A\x74\xAC\x17\xA6\x7F\x44\x6A\x8A\x74\xAC\x17\x2F\x8B\xCF\x15\x94\x5F\xAC\x17\x44\xE9\xF8\xD5\x94\x5F\xAC\x17\x52\x79\x23\xD9\x8A\x74\xAC\x17\x5F\xD7\xCE\x75\x94\x5F\xAC\x17\xA6\xB9\x2B\xDD\x90\x67\xAC\x17\xB1\x2C\x86\xFA\x8B\x9F\xAC\x17\x5F\x22\xBD\x75\x94\x5F\xAC\x17"
ip_list_raw = emoete_str_decode(ip_list_encode)
ip_list = emotet_ip_parse(ip_list_raw)
for i in ip_list:
    print(i)

输出的结果如下所示:
在这里插入图片描述

使用python提取C2配置

接下来可以使用mkyara插件提取特征码,下载地址为https://github.com/fox-it/mkYARA,右击后操作如下所示:
在这里插入图片描述
生成的yara规则如下所示:
在这里插入图片描述
利用yara可以轻松地转换为正则表达式,转化如下所示:
“\x8B\x54\x24.\x8D\x44\x24.\x8B\x4C\x24.\x68…\x50\xE8…\x8B\x9C\x24…\x8B\xF8\x59\x59\x03\xD8\x89\x44\x24.\x89\x5C\x24.\xB9”
通过上述正则表达式可以定位到关键加密IP数据的偏移,在13到16个字节处,即0x68之后的四个字节,通过特征码的正则可以定位到关键的加密IP数据,进而提取出配置信息,编写python代码如下所示:

import struct
import re
import pefile

def emoete_str_decode(encode_data:bytes):
    key = struct.unpack('<I', encode_data[:4])[0]
    size = struct.unpack('<I', encode_data[4:8])[0] ^ key
    data = list(encode_data[8:])
    key = list(encode_data[:4])
    decode_data = []
    for i in range(size):
        decode_data.append(data[i] ^ key[i % 4])
    return bytes(decode_data)

def emotet_ip_parse(ip_list_raw:bytes):
    ip_list = []
    for i in range(0, len(ip_list_raw), 8):
        ip_list.append("%u.%u.%u.%u:%u" % (ip_list_raw[i+0],ip_list_raw[i+1],ip_list_raw[i+2],ip_list_raw[i+3],struct.unpack('>H', ip_list_raw[i+4: i+6])[0]))
    return ip_list

def extract_ip_list_encode(file_path):
    pe = pefile.PE(file_path)
    #正则表达式
    pattern = rb"\x8B\x54\x24.\x8D\x44\x24.\x8B\x4C\x24.\x68....\x50\xE8....\x8B\x9C\x24....\x8B\xF8\x59\x59\x03\xD8\x89\x44\x24.\x89\x5C\x24.\xB9"
    #获取text段代码
    code = pe.sections[0].get_data()
    #获取匹配
    match = re.search(pattern, code)
    #获取加密IP数据的va 
    addr_va = struct.unpack('<I',match.group()[13:17])[0]
    #获取加密IP数据的rva
    addr_rva = addr_va - pe.NT_HEADERS.OPTIONAL_HEADER.ImageBase
    #获取加密IP数据段长度
    data_key_size = pe.get_data(addr_rva, 8)
    key = struct.unpack('<I', data_key_size[:4])[0]
    size = struct.unpack('<I', data_key_size[4:])[0] ^ key
    #获取整个加密内容
    return pe.get_data(addr_rva, size + 8)

path = r'regsvr32.exe_0x2f30000-0x24000'
ip_list_encode = extract_ip_list_encode(path)
ip_list_raw = emoete_str_decode(ip_list_encode)
ip_list = emotet_ip_parse(ip_list_raw)
for i in ip_list:
    print(i)

至此我们实现了使用python提取emotet载荷的C2配置,关于提取的特征码是否准确的问题,还需要经过对比相似样本后才可得出结论。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值