本帖最后由 图钉鱼 于 2025-7-18 04:58 编辑
之前看了下,做了一些工作,今天有空发下,我感觉这个样本包不大完整,分析有模糊地带。
主程序(hbdsl.exe)加载Language.dll:主程序负责加载DLL并传递配置参数与命令,DLL解析参数执行具体操作。
主程序(hbdsl.exe)细节
主程序通过LoadLibraryW(L"Language.dll")加载DLL,未指定绝对路径。攻击者可在主程序目录放置同文件名的恶意DLL实现DLL劫持。
主程序核心逻辑集中在WinMain函数(地址0x401100):
1. 路径处理与文件查找
• 调用GetModuleFileNameW获取自身完整路径(如C:\\hbdsl.exe)
• 通过PathRemoveFileSpecW提取目录路径(如C:\\*)
• 使用PathAppendW构建.sim文件搜索路径(如C:\\*\\*.sim)
• 通过FindFirstFileW和FindNextFileW遍历目录,未找到时使用硬编码路径"C:\\Program files\\CyberLink\\PowerDVD\\PowerDVD.sim"
2. DLL加载与参数传递
• 通过LoadLibraryW加载Language.dll
• 使用GetProcAddress获取导出函数LangDLLMain(命令处理)和SetRegPath(注册表配置)
• 传递注册表配置参数:
◦ 定义_DWORD[130]数组v28(地址esp+A7Ch)
◦ 初始化前5元素为固定值(7274579, 7602278, 6357111, 6619250, 92),其余置零
◦ 调用SetRegPath(v28)传递数组指针(数值对应注册表路径编码,如7274579(0x6F6369)解析为ASCII字符"oci")
• 通过LangDLLMain(lpCmdLine)传递命令行参数(ANSI字符串)
------------------------------------------------------------------------------------------------------------------------
DLL(Language.dll)细节
Language.dll是恶意软件的数据处理核心组件,负责解析UTF-16编码的数据,为后续C2命令执行、注册表操作等恶意行为提供可解析的结构化数据。其作用虽不直接实施恶意操作,但却是恶意软件实现复杂功能的必要环节,类似于“数据中转站”。
将主程序(hbdsl.exe)传递的ANSI命令行参数分为ANSI→UTF16编码转换和UTF16→UTF32代{过}{滤}理对处理两个阶段,并处理代{过}{滤}理对(Surrogate Pair),通过双层编码转换(ANSI→UTF16→UTF32)解析指令。
• UTF16/UTF32转换规避基于ANSI字符串的检测
• 参数多重编码降低静态分析可读性 ,强大的免杀能力。
该组件通过LangDLLMain和sub_10006EB0两个关键函数实现数据转换,所有输入均来自主程序的lpCmdLine参数传递,无需外部文件依赖。
1. 注册表配置(SetRegPath函数)
• 接收v28数组指针,解析注册表路径(如组合为Software\\Malware\\Config)
• 为后续注册表操作(如持久化)提供路径基础
2. ANSI→UTF-16编码转换(LangDLLMain函数)
主程序传递的lpCmdLine参数为ANSI字符串,需转换为UTF-16字符串:
函数原型:int (__cdecl *)(LPSTR lpCmdLine)
转换逻辑:
- int len = MultiByteToWideChar(CP_ACP, 0, lpCmdLine, -1, NULL, 0);
- wchar_t* wCmdLine = (wchar_t*)malloc(len * sizeof(wchar_t));
- MultiByteToWideChar(CP_ACP, 0, lpCmdLine, -1, wCmdLine, len);
复制代码
传递UTF-16字符串至解析函数sub_10006EB0
3. UTF-16→UTF-32代{过}{滤}理对处理(sub_10006EB0函数)
解析UTF-16字符串中的代{过}{滤}理对(0xD800-0xDFFF):
函数原型:void (__cdecl *)(__int16 *wCmdLine)
处理逻辑:- while (wCmdLine[i] != L'\0') {
- wchar_t c = wCmdLine[i];
- if (c >= 0xD800 && c <= 0xDBFF) {
- wchar_t c2 = wCmdLine[i+1];
- if (c2 >= 0xDC00 && c2 <= 0xDFFF) {
- uint32_t utf32 = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
- process_utf32(utf32);
- i += 2;
- } else i++;
- } else {
- uint32_t utf32 = c;
- process_utf32(utf32);
- i++;
- }
- }
复制代码
输出UTF-32字符供后续命令执行使用
DLL函数调用序列- _DllMain@12(入口点)
- └─ sub_10008620(参数转发)
- └─ sub_10008820(数据预处理)
- ├─ sub_10007DB0(验证a2非空)
- ├─ sub_10008020(验证a2非空)
- └─ sub_10008230(启动解析)
- └─ sub_10008060(复制数据)
- └─ sub_10006C40(复制a2到v6)
- └─ sub_10005240(转发v6)
- └─ sub_100070F0(解析v6)
- └─ sub_10006EB0(解析UTF-16)
- └─ sub_10005CA0(检查缓冲区空间)
- └─ sub_1001A550(扩展缓冲区)
- └─ sub_10005BF0(写入字符)
-
复制代码
主程序与DLL交互:
1. 主程序获取路径并查找.sim文件 (样本包中不存在该文件)
2. 加载Language.dll并获取导出函数指针
3. 调用SetRegPath(v28)传递注册表配置
4. 调用LangDLLMain(lpCmdLine)传递命令
5. DLL解析注册表路径
6. DLL转换命令行参数为UTF-16并解析为UTF-32
7. DLL执行注册表操作或网络指令
自我加解密机制
DllMain函数0x100043e6处
- memset(Dst, *v17 ^ v16, 1u); // 逐字节XOR解密
复制代码
Dst:分配的内存区域(来自v69,即v28指向的堆内存);
*v17:待解密的字节(来自sub_10003AF0函数,推测为加密数据);
v16:密钥(v70的值,动态计算为“初始值 + 127”);
1u:每次解密1字节(逐字节处理)。
算法: XOR加密
密钥: 动态计算值 + 127
实现逐字节解密到分配的内存区域
需明确lpCmdLine的来源和DLL的输出数据去向才能构成完整攻击链。这部分刚好缺失,或许就藏在Update.xml文件中?
|