|
近期,瞬犀安全实验室在终端安全检测中发现,多地网吧场景频发 QQ 账号被盗事件,溯源后成功定位到一款技术成熟的定向数据窃取木马程序。
该样本可在用户登录阶段静默截获账号敏感信息与业务通信数据,加密后回传至木马远端服务器,具备极强的隐蔽性与环境适配能力。中毒后的QQ会自动拉群、艾特好友并发送黑灰产消息。
下文将对该木马样本的完整攻击链路与核心技术实现进行深度拆解。
注入QQ的时机
木马样本会在持续监控进程信息,如果是QQ被拉起则会对QQ进行DLL注入。在用户还没有登录的时候获取用户的信息。
DLL注入之后的恶意行为
在注入QQ之后该DLL会直接创建一个线程执行恶意代码的操作(这个初始函数记作 StartAddress)。
之后解密在代码中已经加密的字符串去寻找对应的模块的信息,解密算法很简单,如果是宽字符通过XOR 递增 0x83. 解密关键字符串和 API 名。ASCII则是减去0x7D。
解密出来的数据如下:
StartAddress 的主要流程
1. 清空栈上缓冲区并获取当前 PID。 2. 解密 %d_txv_%d ,使用 PID * 26 和 PID * 21 生成动态 mutex 名。 3. 调用 CreateMutexW ,若发现已有实例则退出。 4. 读取自身版本资源,并从 VS_FIXEDFILEINFO 中取版本号。 5. 解密并检查固定 mutex F11CCD2AEEC5ABEC6A51D68AE34579E0 。 6. 循环等待 wrapper.node 加载,最多约 40 次,每次 Sleep(600ms) 。 7. 调用 sub_180001218 从 wrapper.node 路径解析 versions\%u.%u.%u-%u 版本。 8. 检查宿主文件名是否为 qq.exe 、 tim.exe 、 wegame.exe 或 tgp_daemon.exe 。 9. 解密 ntdll.dll 和 ZwProtectVirtualMemory 并保存函数地址到 qword_18004DB98 。 10. 初始化 hook 管理堆。 11. 调用 sub_180002978(wrapper.node) 扫描 .text 并注册 hook。 12. 调用 sub_18000D390(..., 1) 应用 hook。 13. 如果某些数据未就绪,则调用 sub_180003020 等待运行时结构并触发上报。 14. 退出前调用 sub_180007040 ,通过网络发送状态或错误信息。
wrapper.node 版本解析
主要是针对系统内不同的QQ版本信息执行不同的HOOK操作。 2. 解密 versions\ 。 3. 使用 wcsstr(Filename, L"versions\\") 定位版本目录。 4. 跳过 versions\ 后,用 swscanf 格式 %[^\\] 取目录名。 5. 再用 %u.%u.%u-%u 解析版本四元组。
6. 将四个字段打包为 64-bit 值: packed_version = (((major << 16) | minor) << 16 | patch) << 16 | build 样本中可见的版本分支:
寻找wrapper.node里面的inline hook点位
该函数首先把 `wrapper.node` 当作 PE 文件映像解析,随后判断 OptionalHeader magic。遍历 节表,寻找节名 `.text`,后续全部签名扫描都限制在 `wrapper.node` 的 `.text` 范围内。
匹配逻辑: 先计算所有模式项的总长度,确保不超过扫描范围。 从 start_offset 开始逐字节滑动。 对每个模式项: 若 type == 0x3F ,跳过 len 字节,不比较内容。 否则比较 bytes[0:len] 。 所有模式项匹配后返回当前偏移。 未找到返回 -1 。
对QQ的hook操作
sub_18000D120函数是上面的操作找到了合适的HOOK点位。并对对应的函数进行内联HOOK。
sub_18000264C 函数
函数内部会对内联HOOK里面的RCX寄存器+0x78的位置进行取值。之后通过判断数据的长度是否在命中的
长度范围内,如果命中了得进入更深一层的数据筛选。
更进一步的筛选则是为了筛选出感兴趣的编号,然后进行处理。处理方式有点类似TLV的数据结构解析。
sub_180002614 函数
如果sub_18000264C是对类似TLV数据结构的信息感觉兴趣,那么sub_180002614则是对类似 Protobuf类型的数据感兴趣。
依旧是通过rcx+0x78获得对应的数据,之后对数据的长度进行判断,然后对感兴趣的数据进行下一步解析。
疑似解析protobuf varint 的解码 sub_1800072B4 函数
这个函数则是保存了数据对象的内存地址
数据上报
在收集到数据之后,会把数据进行加密发送。同样的链接也进行了字符串加密。
在发送数据的时候为了避免被直接发现,还使用CryptoAPI和RC4进行加密,防止抓包软件直接 看出端倪。 注入后查看QQ内存
查看QQ的模块内存,已经被挂上了钩子。
跳转到钩子代码的内存地址进行查看,很典型的hook之后保存当前寄存器环境的汇编代码,并把进入自己流程的函数传递给RAX寄存器之后调用。
进入函数之后可以看到上面描述的在指定偏移的地方取内容,之后对数据长度进行对比的操作。
结论
该样本针对 wrapper.node 做了版本适配型 inline hook。它不依赖导出符号,而是通过 .text字节签名定位内部业务函数。Hook 的核心目的包括: 1. 截获 类似protobuf 业务消息,按字段路径提取嵌套数据。 2. 截获 类似TLV 业务包,提取固定 tag 数据。 3. 捕获 wrapper.node 内部上下文对象,并主动调用内部函数。 4. 将采集字段缓存后,通过压缩、RC4-like 加密和 RSA 公钥加密发送到远端。 从行为看属于面向特定宿主和特定 native 模块的窃取/监控型恶意代码,具备较强的版本适配性。
loCs: MD5 :531ade06fd0fa223dc10c7c8dcc43d5e SHA1:39ec6af2dcc79c02faa58a0de05127f357d3389c SHA256:0d5629da07f452ea1970ecd1cda24438f77ecd86cf95b71977b4394bc14c2c9b
|