APT 恶意 DLL 分析及 C2 配置提取(子 DLL 篇)

参照 VMware Security 博客 学习 emotet 家族的 C2 配置提取,并对 DLL 进行完整分析。

0x00 前言

这次 DLL 的分析主要是参照 VMware Security 博客 学习 emotet 家族的 C2 配置提取,所以只有一个 DLL 样本,没有对应的宏文档和窃密程序。

由于 C2 配置在解密后的子 DLL 中,所以我首先分析的是内层的子 DLL,这一篇文章都是子 DLL 转储出来后的分析过程。分析完子 DLL 后我想看下母 DLL 是怎么与子 DLL 关联的,所以又把母 DLL 的行为分析了,但是由于篇幅的原因和手法的不太一样,所以我把母 DLL 的分析放在另一篇了。

建议先看这篇子 DLL 的分析过程,因为这是我先分析的,样本的发展阶段也在这里提及~

0x01 内层 DLL 分析

样本 IOC

HASH
MD54e22717b48f2f75fcfd47531c780b218
SHA160b637e95b1f2d14faaa71085b7e26321bfeeb6d
SHA2567f94107c9becbcc6ca42070fca7e1e63f29cdd85cbbd8953bbca32a1b4f91219

总体行为预览

动态获取函数手法

在 Emotet C2 Configuration Extraction and Analysis 文章中我得知该样本每个动态获取的 API 函数都通过包装器包装起来供外层核心代码调用,所以我们分析时需要进入每个包装器中识别出动态获取的函数。更高级的是在 get_dll_and_funbase 上下断点,因为所有 API 函数都通过此函数动态获取,这样我们就可以提取出所有 API 包装器并命名它们。

举例文章中分析的 ExitProcess 包装器:

image.png

image.png

image.png

image.png

image.png

在下面的分析中,所有标注了 API 函数别名的,都是基于手动进入包装器内提取动态获取的函数后再回到包装器外标注出来。

字符串解密手法

举例,在我定义的包装器 data_decrypt_to_string 中,申请空间,解密字符:

(这一部分待解密的字符串都在 .text 段中,在 IDA 的 string window 窗口中看不到)

image.png

image.png

image.png

混淆手法

通过控制流平坦化和大数混淆使相同代码编译得到的二进制特征各不相同,即干扰杀软特征匹配检查,也让逆向跟踪分析增大困难,特别是静态分析。

控制流平坦化:

image.png

大数混淆:

执行多次数学运算,运算结果有的传入函数中,但是从不使用。有的作为控制流跳转的一部分,不断混淆代码流。

image.png

image.png

image.png

image.png

所属阶段

该转储出来的内部 DLL 属于内层恶意代码,从火绒实验室的 层层对抗安全软件 火绒对Emotet僵尸网络深度分析 中我们可以对比出此次分析的内层恶意代码属于Emotet内层恶意代码中内层PE混淆的第三阶段。

3.控制流平坦化和大数运算混淆

在原有API动态获取和加密字符串的基础上,病毒使用控制流平坦化和大数运算进行混淆。这使得相同的代码编译得到的二进制特征各不相同,从而增大安全软件的检出难度,阻碍分析人员对病毒功能逻辑的分析。

控制流平坦化使逻辑难以分析

大数运算混淆改变二进制特征

image.png

image.png

C2 配置提取与解密

VMware Security 博客 中的重点就在这个 emotet 家族的 C2 配置提取,但是过程并没有很明晰,只是直接放上 截图说这就是 C2 的解密函数,现在我从头到尾分析过一遍后发现还是有迹可循的,比如 F8 单步执行时会有明显的 ECK1、ECS1 字样,在后面调用大量加密解密和网络通信 API 中也会发现有对密钥和 IP 的导入,凭借这些迹象足够我们定位到这个 C2 配置解密函数了。

迹象定位

image.png

image.png

解密流程分析

image.png

image.png

image.png

image.png

加密数据格式及手动解密

根据 VMware Security 博客 的研究可以知道公钥在加密中的数据格式,第一个 Dword 是解密的 key,第二个 Dword 是公钥的长度,剩下的就是加密的数据了,在上面解密流程分析的伪代码中也可以分析得出来这点。
image.png

手动解密中可以通过将加密数据区的第一个 DWORD 与第二个 DWORD 进行异或来获得加密数据的长度。从第 3 个 Dword 开始,在长度范围内,用第一个 DWORD 对每个块进行 XOR,即可得到解密后的公钥。

以上面 ECK1 为例解密:
image.png

image.png

提取公钥

通过以下方法得到如下公钥:

ECK1(base64 编码):RUNLMSAAAADzozW1Di4r9DVWzQpMKT588RDdy7BPILP6AiDOTLYMHkSWvrQO5slbmr1OvZ2Pz+AQWzRMggQmAtO6rPH7nyx2

ECS1(base64 编码):RUNTMSAAAABAX3S2xNjcDD0fBno33Ln5t71eii+mofIPoXkNFOX1MeiwCh48iz97kB0mJjGGZXwardnDXKxI8GCHGNl0PFj5

image.png

image.png

同理解密 IP 配置

image.png

image.png

image.png

image.png

image.png

image.png

行为分析

DLL 调试及入口设置

我们通过微软的 rundll32.exe 来调试子 DLL,按照母 DLL 样本给出的命令行参数设置同样的即可,这里我们跟进的是 DllRegisterServer 的导出函数。

image.png

"C:\Windows\SysWOW64\rundll32.exe" C:\Users***\AppData\Local\Kfsdwbgbdwjo\tlcdjloq.dfv,DllRegisterServer

image.png

先执行的入口点,检查命令行参数

image.png

image.png

image.png

image.png

image.png

生成随机数:(用途不详)

image.png

image.png

image.png

获取路径信息,尝试连接服务控制管理器:(猜测是想将自身作为服务来开机自启动)

image.png

image.png

image.png

获取命令行参数:(应该是冗余操作)

image.png

image.png

image.png

获取当前文件信息

image.png

image.png

image.png

image.png

获取计算机相关信息

image.png

image.png

image.png

image.png

使用事件对象来通知等待线程发生事件

image.png

image.png

线程函数

创建一个线程,线程的作用是检查当前目录下文件改动的情况。
image.png

image.png

image.png

解密公钥和 IP 等 C2 配置

image.png

image.png

image.png

导入ECK1公钥

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

动态生成 AES 密钥:(猜测是用 ECK1 公钥加密动态生成的 AES 加密密钥再来加密信息)

image.png

image.png

image.png

image.png

image.png

导入ECS1公钥

image.png

image.png

image.png

检索文件目录来确认当前目录中只有一个文件

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

收集当前系统相关信息

image.png

image.png

检索关联的远程桌面服务会话

image.png

对相关系统信息进行加密

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

BASE64 格式化输出加密信息

image.png

image.png

image.png

网络通信操作,利用COOKIE发送加密数据,读取远程文件

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

解密读取的远程文件数据

image.png

image.png

对文件流进行 HASH 加密后验证签名

image.png

注册表操作:(空操作)

image.png

image.png

image.png

创建临时文件并复制当前文件过去来躲避查杀

image.png

image.png

image.png

image.png

image.png

退出程序

image.png

行为总结

首先子 DLL 先获取当前程序命令行参数,然后截取出对应参数来看是否在运行导出函数 DllRegisterServer 。然后获取就尝试连接并打开本地服务控制管理器同时获取自身路径信息,猜测是想将自身作为服务来开机自启动。

紧接着检索程序自身所在问价的创建,访问,写入时间等信息,并对比系统时间来查看文件状态是否正常或已被他人操作。(检查是否改动的操作是在开启的线程中进行的)

然后就是获取本地计算机名、磁盘序列号、系统版本信息、关联的远程桌面服务会话来加密传输,其中加密的方式应该是使用解密出的 ECK1 公钥加密动态生成 AES 密钥后再对上面信息进行加密,并对加密后信息以 base64 格式附在 cookie 中发送给解密出的 C2 服务器列表。

再然后就是在发送完相关信息后从 C2 服务器处读取远程文件并进行 HASH 验证,猜测是进行另一种恶意操作,但是这里并没有跟踪到。

最后就是在系统开机自启动的 RUN 目录下进行操作,由于程序最后尝试在系统临时目录下根据系统时间创建唯一的临时文件名并复制自身过去,可以猜测程序想写入的是复制成功后的临时文件路径到 RUN 目录中来维持权限和延长存活时间。

0x02 函数链顺序划分

获取程序命令行参数切割导出函数并匹配验证:(对比不上就退出程序)

GetCommandLineA---->L"DllRegisterServer"---->lstrcmpiw_data(---->SHGetFolderPathA)

申请内存空间:processheap---->RtlAllocateHeap

生成随机数:"RNG"---->BCryptOpenAlgorithmProvider---->BCryptGenRandom---->BCryptCloseAlgorithmProvider

连接控制管理器:OpenSCManagerW

获取计算机信息:SHGetFolderPathA、GetModuleFileName

获取时间信息:GetTickCount

获取当前程序命令行参数:GetCommandLineW---->CommandLineToArgvW---->LocalFree

打开文件获取信息:GetModuleFileName---->CreateFileW---->GetFileInformationByHandleEx---->GetSystemTimeAsFileTime---->closehandle

获取计算机系统信息并格式化输出:GetComputerNameA---->GetWindowsDirectoryW---->GetVolumeInformationW---->sprintfW

使用事件对象创建线程(可参考:CreateEvent函数用法):CreateEventW---->CreateThread

线程函数:GetModuleFileName---->PathFindFileNameW---->CreateFileW---->ReadDirectoryChangesW---->

循环格式化输出解密 IP 配置:循环---->snwprintf

使用 Windows 的 API 导入 ECK1 密钥附加生成的私钥创建协议值:

“ECDH_P256”+L"Microsoft Primitive Provider"---->BCryptOpenAlgorithmProvider(256位素数椭圆曲线 Diffie-Hellman 密钥交换算法)---->BCryptGenerateKeyPair---->BCryptFinalizeKeyPair(啥变化也没,可能就是标志用的)---->L"ECCPUBLICBLOB"---->BCryptExportKey---->memcpy---->BCryptImportKeyPair---->BCryptDeriveKey---->BCryptDestroySecret---->BCryptCloseAlgorithmProvider

使用 Windows 的 API 进行 AES 加密:

“AES”+L"Microsoft Primitive Provider"---->BCryptOpenAlgorithmProvider(基于高级加密标准 (AES) 密码的消息认证码 (CMAC) 对称加密算法。)L"ObjectLength"---->BCryptGetProperty---->BCryptImportKey---->BCryptCloseAlgorithmProvider

使用 Windows 的 API 导入解密的 ECS1 密钥:

L"ECDSA_P256"+L"Microsoft Primitive Provider"---->BCryptOpenAlgorithmProvider---->L"ECCPUBLICBLOB"---->BCryptImportKeyPair---->BCryptCloseAlgorithmProvider

单个线程等待函数:WaitForSingleObject

检索文件目录并用通配符比较文件名,确保当前目录文件夹中只有一个文件:"%s%s"---->sprintfw---->PathFindFileNameW---->L"%s\"---->L"C:\Users\xxx\AppData\Local\Kfsdwbgbdwjo\\"---->FindFirstFileW---->FindNextFileW(2次)---->PathFindFileNameW---->lstrcmpiw---->FindClose

收集当前系统相关信息:RtlGetVersion---->GetNativeSystemInfo

检索与指定进程关联的远程桌面服务会话:ProcessIdToSessionId---->GetCurrentProcessId

加密信息:L“SHA256”+L"Microsoft Primitive Provider"---->BCryptOpenAlgorithmProvider---->L"ObjectLength"---->BCryptGetProperty---->BCryptGetProperty---->BCryptCreateHash---->BCryptHashData---->BCryptDestroyHash---->BCryptCloseAlgorithmProvider---->BCryptEncrypt(这个句柄不知道是谁的,加密了两次)

BASE64 格式化输出:CryptBinaryToStringW

BASE64加密数据合并到cookie中传输:L"Cookie: %s=%s\r\n"---->sprintfW

网络通信操作:InternetOpenW---->InternetConnectW---->HttpOpenRequestW---->InternetSetOptionW(选项要16进制转10进制)---->InternetQueryOptionW---->InternetQueryOptionW---->HttpSendRequestW---->HttpQueryInfoW---->InternetReadFile---->InternetCloseHandle(3次)

又加密读取的文件数据流:L“SHA256”+L"Microsoft Primitive Provider"---->BCryptOpenAlgorithmProvider---->L"ObjectLength"---->BCryptGetProperty---->BCryptCreateHash---->BCryptHashData---->BCryptFinishHash---->BCryptDestroyHash---->BCryptCloseAlgorithmProvider---->BCryptVerifySignature

注册表操作:0x800001(HKEY_CURRENT_USER)+ L"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"---->RegCreateKeyExW---->RegDeleteValueW---->RegCloseKey

复制文件到临时文件:GetTempPathW---->GetTempFileNameW---->L"C:\Users\xxx\AppData\Local\Kfsdwbgbdwjo\tlcdjloq.dfv" + L"C:\Users\xxx\AppData\Local\Temp\9000.tmp"---->SHFILEOPSTRUCTA(移动文件)---->PathFindFileNameW---->RemoveDirectoryA

  • 发表于 2022-08-12 09:35:58
  • 阅读 ( 7511 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
沐一·林
沐一·林

20 篇文章

站长统计