【恶意样本分析】当‘某书’遇到‘银狐’:一次精心设计的‘白加黑’披皮投毒
漏洞分析
某天,我一位“非常正常”的朋友在某个“极不正常”的网站上,心血来潮想要下载一份“某书”。按理说,正经人下载办公软件都是去官网,但这位朋友偏偏运气差了那么一分,硬是在大多数正常的网站夹缝中,精准命中了一个看起来就很不合规的网站。结果可想而知:某书没飞起来,电脑倒是先替他“飞”了。
1. 样本基本信息 --------- 该样本伪装了成某书的安装程序,使用火绒剑监控,可以看到双击后,立即通过内嵌的`Gogo.exe`,有了网络连接,那么可以初步怀疑了其中的运行机制应该是安装即加载的模式  审查释放文件时,没带数字签名的 **`libcef.dll`** 妥妥地暴露了嫌疑。顺藤摸瓜,发现它的‘加载驱动器’正是 **`Gogo.exe`**。既然关键道具和入场券都已锁定,接下来的重头戏,就围绕这个组合来拆解!  2、第一阶段:白加黑解密释放 -------------- 除了`DllEntryPoint`函数,剩余199个导出函数指向同一个地址,这就很可疑了,可以下一个初步怀疑,这个DLL被劫持了  如下是劫持的说明:  跟进`sub_10001BB0`函数: 先获取目录路径,找到原安装包释放出来的`10m.mp3`文件,其中`10m.mp3`是一堆混淆的二进制,且不符合`.mp3`的文件格式  拼接`目录+1no2.mp3`,通过`sub_100018A0`函数处理`10m.mp3`和`10m2.mp3`两个文件  通过RC4解密:将`10m2.mp3`作为密钥,解密`10m.mp3`,得到明文`Payload`(PE)  验证`PE`结构并复制到分配的内存中  验证`PE`类型,执行不同的操作 说明同类病毒家族有可能塞入`EXE`或者`DLL`进行执行  3、第二阶段:C2拉取payload ------------------ 入口首先负责解析配置。样本中的硬编码配置字符串为倒序保存,运行时先反转再按 `tag:value|` 结构解析。 解析出的字段包括三组C2地址/端口/模式,以及版本、流量、延迟、注入参数等附加配置。 | 字段 | 含义 / 用途 | |---|---| | **p1** | 第1组C2地址 | | **o1** | 第1组C2端口/辅助端口字段 | | **t1** | 第1组传输模式;1 表示TCP | | **p2** | 第2组C2地址 | | **o2** | 第2组C2端口/辅助端口字段 | | **t2** | 第2组传输模式;1 表示TCP | | **p3** | 第3组C2地址/故障转移地址 | | **o3** | 第3组C2端口 | | **t3** | 第3组传输模式;1 表示TCP | | **dd** | 附加开关/域名下载相关配置 | | **cl** | 工作线程重连/上线间隔 | | **fz** | 分组/标识字段,原始配置中为非ASCII宽字符 | | **bb** | 版本号/样本版本标识 | | **bz** | 日期/备注类标识 | | **jp** | 数值控制项 | | **bh** | 编号类控制项 | | **ll** | 流量/长度类控制项 | | **dl** | 下载/延迟类控制项 | | **sh** | 数值开关 | | **kl** | 传入 `CKernelManager` 的注入/载荷处理参数 | | **bd** | 布尔/数值配置项 |  样本会读取 `HKCU\CC\IpDate`。如果该注册表值存在,且内容符合配置格式,就用其中的`p1/o1/t1`、`p2/o2/t2`、`p3/o3/t3`等字段覆盖内存中的硬编码C2配置。  构造`TCP`和`UDP/ARQ`两种连接管理器,在多组C2之间轮换连接,连接成功后构造管理对象 如下是TCP连接时C2收包解码。缓存TCP流, 校验会话头, 对`payload` 解码, 再回调上层管理器处理的过程:  `UDP`与`TCP`处理的不同点: | **阶段** | **TCP** | **UDP/ARQ** | |---|---|---| | **连接方式** | `socket(AF_INET, SOCK_STREAM)` + `connect` | `socket(AF_INET, SOCK_DGRAM)` + 非阻塞 `connect` | | **接收方式** | `recv` 读取 TCP 流 | `recv` 接收单个 UDP 包 | | **粘包/半包处理** | 有 TCP 流缓存,按帧长度重组 | 按 UDP 包处理,再交给 ARQ 逻辑重组 | | **协议帧** | 4 字节长度 + 10 字节会话头 + XOR 0xCC payload | UDP 外层先经过 ARQ 包 / ACK / 重传处理 | | **解密** | `c2_recv_frame_decode_dispatch` 中 XOR 0xCC | ARQ 重组后再进入上层处理,最终 payload 处理一致 | | **心跳** | 发送 `0xC9`,60 秒超时 | 发送 `0xC9`,30 秒超时 | | **后续payload处理** | 相同 | 相同 | 如下是展示了轮询已解析出的C2配置组,按配置间隔尝试连接C2,连接成功后发送载荷请求命令,并将C2返回的数据交由后续逻辑处理,实现二阶段payload获取的实现  C2返回的明文payload会进入 `sub_405470`。该函数有两条路径: - 从注册表恢复历史payload - 保存C2下发的新payload(新payload由 `0xA44` 字节配置头和后续二阶段PE正文组成 可以表示为如下的树型结构图: ```js sub_405470 检查C2返回包类型 ├─ 首字节 == 0xC9 │ └─ 视为心跳/空包,直接返回 │ └─ 首字节 != 0xC9 ├─ cbData == 101 │ └─ 本地payload恢复流程 │ ├─ 打开注册表 HKCU\CC\0 │ ├─ 查询值 R32f351a4aeea5e608853d1a56661059 │ ├─ 若值长度 > 0xA44 │ │ ├─ 读取完整REG_BINARY数据 │ │ ├─ 前0xA44字节作为配置头 │ │ └─ 偏移2628后的数据作为二阶段PE │ ├─ 校验C2返回标识是否匹配本地保存标识 │ ├─ 匹配 │ │ └─ 启动 payload_patch_and_runpe_loop │ └─ 不匹配 │ ├─ 释放旧payload │ ├─ 构造0x05请求包 │ └─ 向C2重新请求payload │ └─ cbData != 101 └─ 新payload处理流程 ├─ 从C2包偏移1复制0xA44字节配置头 ├─ 为二阶段PE分配可执行内存 ├─ 从C2包偏移2629复制二阶段PE正文 ├─ 创建/打开注册表 HKCU\CC\0 ├─ 删除旧值 R32f351a4aeea5e608853d1a56661059 ├─ 写入新的REG_BINARY payload缓存 ```  将payload注入到系统进程中`tracerpt.exe`中执行  既然已经知道了payload的获取步骤,即从 `HKCU\\CC\\0\\R32f351a4aeea5e608853d1a56661059` 读取样本缓存的 `REG\_BINARY` 数据,前 `0xA44` 字节为`payload`配置头,`0xA44` 偏移之后为二阶段PE载荷。那么可以在虚拟机中将其PE进行运行,通过脚本的方式将其进行提取出来,结果及核心代码如下:  ```js VALUE_NAME = "R32f351a4aeea5e608853d1a56661059" REG_PATH = r"CC\0" CONFIG_HEADER_SIZE = 0xA44 with winreg.OpenKey(winreg.HKEY_CURRENT_USER, REG_PATH, 0, winreg.KEY_READ) as key: blob, reg_type = winreg.QueryValueEx(key, VALUE_NAME) config_header = blob[:CONFIG_HEADER_SIZE] stage2_pe = blob[CONFIG_HEADER_SIZE:] open("payload_config_header_0xA44.bin", "wb").write(config_header) open("stage2_payload.pe", "wb").write(stage2_pe) ``` 二阶段数据并非裸PE,而是 `shellcode包装层 + 内嵌PE`。包装层入口先跳转到初始化逻辑,通过PEB遍历模块、哈希解析API,随后定位内嵌PE,将其手动映射到新申请的内存中,完成节拷贝、重定位修复、导入表解析,最后以DllMain形式调用内嵌PE入口点。(具体详细的调试过程就不再展示)  4、第三阶段:Backdoor实现 ----------------- 分析该`payload`: 该载荷为一个DLL程序,只有一个`run`导出函数,主要的逻辑在创建一个线程中,先进行注入系统进程`svchost`,注入内容是一段看门狗 shellcode。这段 shellcode 只做一件事——用 `WaitForSingleObject(INFINITE)` 等主进程退出,主进程死了就 `WinExec` 重新加载 `DLL`。主进程这边另有线程 `sub_100089D0` 轮询 `svchost` 是否活着,死了就重新注入。  启用`SeDebugPrivilege` 调试特权,然后调用 `NtSetInformationProcess` 设置 `ProcessBreakOnTermination` 标志。这使得进程被终止时触发系统蓝屏(BSOD),防止被杀。  创建线程 `sub_10006080`,该线程首先创建互斥体防止多实例运行,然后等待注册表触发信号,最后初始化 DirectInput 键盘钩子并启动键盘记录和剪贴板监控  创建两个通信对象:`CTcpSocket`(主通信通道,大小为 0x88)和 `CUdpSocket`(备用通道,大小为 0x270)。TCP 对象初始化 Winsock 2.2 并创建同步事件;UDP 对象配备了完整的重传/确认/分片机制 其中两个对象配置如下: | **维度** | **CTcpSocket** | **CUdpSocket** | |---|---|---| | **角色** | 主通信通道 | 备用通道 | | **对象大小** | 136 字节 | 624 字节 | | **复杂度** | 极简,只有 socket + 事件 + 缓冲区 | 完整重传/确认/分片/加密/临界区 | | **加密** | 无 | 有加密算法函数表 | | **重传机制** | 无 (TCP 自带) | 应用层实现:超时重试1次,最大5次 | | **分片** | 无 (TCP 自带) | MTU=1432,最大5片 | | **关键值** | 无 | 发送间隔1、接收间隔1、重连2次/10秒、超时30s/5000ms | C2 服务器地址不硬编码在二进制文件中,而是从注册表 `HKCU\Console\IpDate` 读取。配置格式为管道分隔的键值对。解析函数 `sub_10008EE0` 按标签名搜索,提取标签后的内容直到遇到 `|` 分隔符。 **`sub_10008EE0` 解析逻辑**: ```js sub_10008EE0(数据, 标签名, 输出缓冲区, 输出DWORD): ├─ 在数据中搜索标签名(如"p1:") ├─ 从标签后开始读取, 直到遇到 '|' (ASCII 124) ├─ 如果输出缓冲区 != NULL → 将内容拷贝到缓冲区 └─ 如果输出缓冲区 == NULL → 检查最后一个字符是否为 '1', 是则设 *输出DWORD = 1 ``` **C2 配置格式**: ```js p1:主域名|o1:6666|t1:1|p2:备用域名|o2:6666|t2:1|p3:第三域名|o3:6666|t3:1| ``` 主循环会交替切换三个 C2 服务器地址。每轮迭代翻转 `byte_10034208` 标志,在主用和备用之间切换;每 200 次迭代切换到第三个 C2。连接失败则继续循环重试。(其实这一部分与第二阶段差不多)  调用 `sub_10005580` 收集主机指纹信息并发送到 C2 服务器。该函数构造一个 4690 字节的数据包,包含主机名、IP 地址、操作系统版本、硬件信息、当前活动窗口标题等 | 关键指纹 | 作用 | |---|---| | 硬件 GUID (`GetCurrentHwProfileW`) | 唯一设备标识,重装系统不变 | | IP + 主机名 | 网络定位 | | Windows 版本 + 产品名 | 系统识别 | | 显卡信息 (DXGI 枚举) | GPU 型号 + 显示器分辨率 | | 当前活动窗口标题 | 监控用户实时行为 | | 空闲时间 | 判断是否有人在使用 | | 虚拟机标记 | 反分析检测 | | 磁盘/内存 | 硬件性能评估 | | 硬件 GUID | 设备唯一 ID,追踪重装 | `sub_1000C8F0` 是这个木马的 C2 命令/配置处理器,有两路入口: 入口判断:先检查注册表 `HKCU\Console\IpDatespecial` 是否有待处理数据 - cbData > 1 → 有数据待处理 → 跳过 switch,走到函数末尾执行配置同步逻辑 - cbData ≤ 1 → 正常模式 → 进入 switch(\*Src) 命令分发 C2分发命令表: | 命令码 | 作用 | 说明 | |---|---|---| | 0 | 配置更新 | 接收 C2 下发的配置数据(2629字节),与本地列表比对版本,更新或新增后发回确认 | | **1** | 写入注册表并执行 | 将数据写入 HKCU\\Console\\0\\,启动线程 sub\_1000C2E0 执行 | | **2** | 关闭通信 | 断开当前 C2 连接 | | **4** | 下载文件 | 从 C2 接收数据 → 组包(类型=3) → sub\_10002B00 写入磁盘 | | **5** | 文件操作 | sub\_1000DD80,具体文件操作(删除/移动/重命名等) | | **6** | 启动新线程 | 以 Src+1 为参数启动线程 sub\_1000E470 | | **8** | 上传文件 | sub\_1000D4A0 读取本地文件并上传到 C2 | | **9** | 查询状态 | 返回状态码 20 | | **10** | 上传文件数据 | 从 C2 接收数据 → 组包(类型=20) → 写入磁盘 | | **11** | 清除事件日志 | 遍历 Application/Security/System 三个日志,调用 ClearEventLogW 清除——反取证 | | **12** | 执行设置 | sub\_1000E400 | | **13** | 自退出 | ExitProcess(0) | | **14** | 重启系统 | ExitWindowsEx(EWX\_REBOOT) | | **15** | 强制关机 | ExitWindowsEx(EWX\_SHUTDOWN\|EWX\_FORCE) | | **16** | 断电 | ExitWindowsEx(EWX\_POWEROFF) | | **17** | 切换模式 | 切换联网/离线通知模式,发送状态通知到 C2 | | **18** | 更新 C2 配置 | 写入 HKCU\\Console\\IpDate,解析 p1/p2/p3(域名) + o1/o2/o3(端口) + t1/t2/t3(协议类型),断开后用新配置重连 | 通过遍历窗口进行反分析/反调试功能 主要监控如下关键标题: - 网络分析工具 (Wireshark, Fiddler, Capsa, Sniff, 流量, 网络分析) - 进程监控 (TaskExplorer, Process, 任务管理器, 资源监视器) - 反病毒/安全软件 (Malwarebytes, Metascan, 火绒) - 端口/连接监控 (TCPEye, CurrPorts, Port) - 调试工具 (ApateDNS) - 命令行 (提示符)  至此,整体差不多分析完毕,不得不说,银狐这帮黑客近年来的“出勤率”堪比打卡上班,要想彻底根治这一类FakeAPP ,还是**任重道远**啊。 借助嘎子哥的一句话,下载软件你给我下好的啊!!! 5、附录 ---- ### IOC | 类型 | 内容 | |---|---| | C&C | `134[.]122[.]168[.]153` | | 类型 | 内容 | |---|---| | SHA1 | `3888b180727ea4cc21b70ae8601b48639326b2b0` | | SHA1 | `c5cfdd327b30941c39429f27b8e042920fc97e3b` | | SHA1 | `ff8d90e24547612a7ccd8241dfd30f107324b148` |
发表于 2026-08-31 09:49:47
阅读 ( 3122 )
分类:
二进制
0 推荐
收藏
0 条评论
PEkill
2 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!