首页
社区
课程
招聘
[原创]从主控调度到登录态窃取:多平台游戏账号攻击链分析
发表于: 3天前 491

[原创]从主控调度到登录态窃取:多平台游戏账号攻击链分析

3天前
491


近日,瞬犀安全技术团队发现了一组针对游戏账号与登录态的窃取样本。报告记录的运行链从 cli2update2.exe 启动开始:程序下载并加载 pz2.bin,随后持续检查 Steam、QQ、WeGame/TGP 等客户端进程;发现目标后,进入对应采集分支,并将获取的数据发送到外部服务器。


样本在后台按周期调度多个业务分支,采集路径因平台而异:WeGame/TGP 分支涉及目标进程内存读取及 DLL 注入相关行为;QQ 分支读取账号与 clientkey,借助正常登录跳转链路获取 Cookie;Steam 分支解析 JWT 载荷并读取账号相关数据。报告记录了采集数据向外部地址发送的行为。


•  账号接管风险:账号标识和登录凭据一旦外泄,攻击者可能尝试未授权登录,造成账号控制权丢失。


•  登录态滥用风险:Cookie、令牌等会话材料可能绕过单纯的密码验证,在有效期内被用于冒用会话。


•  游戏资产与关联账户风险:账号失控可能进一步影响虚拟资产、绑定邮箱及其他关联服务。实际是否发生损失,需要结合账户日志和设备取证确认。


•  隐蔽采集风险:分支按目标进程和状态条件触发,且通过内存加载、进程访问等方式运行,单看磁盘文件或单条告警可能无法还原完整链路。




cli2update2.exe 是这条链路的前置程序。本节以 cli2update2_dump_SCY.exe 的脱壳程序代码为依据,地址均对应当前脱壳项目。



解码字符串和获取外网地址


下载并动态加载pz2.bin


网络请求1:


网络请求2: 


网络请求3:


网络请求4: 


网络请求5:




程序从易语言模块入口 fn_EYuyanMain0(0x1002922D)进入初始化流程,创建运行库和全局对象后,通过 fn_StartMonitorThread(0x1000103B)启动后台线程。


线程经 fn_MonitorThreadEntry 进入 fn_MonitorLoop(0x10001079),循环执行 fn_CheckTargetsAndDispatchPayloads(0x100010AA),每轮约等待 2 秒。


主调度围绕是否已处理过对应业务决定后续操作:micc 尚未尝试下载时,下载并启动;Steam 采集状态允许时,检查目标并采集;Trxa 尚未尝试执行且发现 WeGame/TGP 时,依次下载执行两份载荷;QQ 尚未尝试启动采集时,检查进程并创建采集线程。下文按各项业务分别展开,公共字符串解码集中放在后面说明。


这种编排用于阅读和后续补充分析;主函数的真实调用顺序仍以以下逻辑树为准,Steam 检查先于 micc、QQ 和 Trxa 处理。


fn_CheckTargetsAndDispatchPayloads的函数逻辑如下:


标志位名 | 地址 | 置位条件及含义

g_micc_download_attempted | 0x10045A4D | 为 0 时先置 999,再尝试下载、落地启动 micc.bin;不依赖目标进程是否存在

g_qq_collector_start_attempted | 0x10045A51 | 为 0 且找到 QQ.exe(PID > 1)时,先置 999,再尝试创建 QQ 采集线程

dword_10045A55 | 0x10045A55 | 为 0 时置 999

g_trxa_dispatch_attempted | 0x10045A59 | 为 0 且找到 WeGame/TGP 目标(最终 PID > 1)时,先置 999,再依次下载、内存加载两份 Trxa 载荷


这些标志表示对应业务是否已经尝试处理。


这里有两类不同的重试行为:QQ 或 WeGame/TGP 进程未找到时,对应的 g_qq_collector_start_attempted、g_trxa_dispatch_attempted 保持 0,下轮还会检查;一旦满足进程条件并置位,即使线程创建、下载或 PE 加载失败,这个调度函数也不会再尝试该分支。WeGame 查找仅在 PID < 1 时回退到 tgp_daemon.exe,最终是否进入载荷分支则判断 PID > 1。


Steam 不由上述三个 attempted 标志控制,每轮仍会调用其检查函数。Steam 子流程另有状态重置逻辑,用于重新判断进程和窗口是否满足采集条件。



主调度检查 g_micc_download_attempted。为 0 时,先将其设为 999,再调用 fn_DownloadDropAndRun(0x10008142)。该函数接收主流程传入的加密地址,解码后请求 d1dK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8W2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6g2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6g2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6e0p5I4y4W2)9K6b7e0V1#2x3e0N6Q4x3V1k6E0K9h3y4U0i4K6u0W2j5X3W2F1i4@1f1K6i4K6R3H3i4K6R3J5


响应长度大于 5000 字节时,程序生成六个随机小写字母组成的名称,将下载内容写入宿主 EXE 所在目录,设置隐藏属性,再调用 CreateProcessA 启动。


▌落地启动中的关键操作


目录由 fn_RuntimeGetHostDirectory(0x1002B5F0)取得,其内部使用 GetModuleFileNameA(NULL, ...) 获取宿主 EXE 路径,再去掉文件名。写文件、设置属性和创建进程分别由对应的运行库包装函数完成。


后续经分析micc.bin:与盗号业务无关,故在此不做进一步展示.



fn_CheckSteamAndCollect 分析: 


fn_CollectSteamTokensAndSend 分析:


循环获取 steam.exe / steamchina.exe 的pid 


调用fn_ReadRemoteText获取到JwtPayload:


传入 fn_DecodeJwtPayload(0x10005A35)。该函数把文本按点号分成三段,Base64 解码中间载荷段并返回 JSON 文本。


随后,从 JSON 中取得 iss 字段,与解码得到的 steam 字符串比较


获取steam的steamui模块的基址 


请求6c1K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8W2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6g2)9J5b7g2)9J5b7g2)9J5k6g2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6e0t1K6z5q4)9K6b7e0f1J5x3e0u0Q4x3V1k6H3P5W2)9J5k6i4c8^5N6q4!0q4z5q4)9^5c8g2!0n7y4#2!0q4y4g2)9^5c8W2)9&6y4W2!0q4y4g2)9^5x3g2)9^5c8W2!0q4y4#2!0m8y4#2!0n7b7W2!0q4y4#2)9&6b7g2)9^5y4q4!0q4y4g2!0m8c8W2)9^5y4W2!0q4y4W2)9&6y4W2)9^5y4H3`.`.


解码偏移密文:1194E85


获取steam账号信息

 

向服务器发送获取到的steam数据


网络请求1:

 

网络请求2: 



这一分支从 g_trxa_dispatch_attempted 开始判断。为 0 表示尚未尝试分发执行 Trxa;随后检查 wegame.exe,仅在查找结果小于 1 时改查 tgp_daemon.exe。最终 PID 大于 1,才把标志设为 999,进入两份载荷的下载执行流程。


1. 判断是否需要下载执行 


2. 两份载荷的下载顺序


主流程先查找 wegame.exe,未找到时再检查 tgp_daemon.exe;目标存在后,请求以下两份文件:


下载地址 | 后续处理

565K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8W2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6g2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6g2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6e0p5I4y4W2)9K6b7e0V1#2x3e0N6Q4x3V1k6f1M7Y4S2S2i4K6g2X3j5h3c8V1M7X3g2K6M7K6y4Q4x3X3g2T1K9h3^5`. | 将响应交给内存 PE 加载器

6fbK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8W2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6g2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6g2)9J5b7g2)9J5b7g2)9J5b7g2)9J5k6e0p5I4y4W2)9K6b7e0V1#2x3e0N6Q4x3V1k6f1M7Y4S2S2i4K6u0W2j5X3W2F1 | 将响应交给内存 PE 加载器


3. 下载后在当前进程内加载执行


两份下载响应经 fn_LoadDownloadedPe(0x100086A2)传入 fn_MemLoadPE0(0x100086C0)。继续进入加载器,可以看到完整的手工映射过程。


程序先检查 MZ 和 PE 标记,按映像大小分配内存,再复制头部和各节。当实际加载地址与首选基址不同,加载器遍历重定位表修正地址;随后解析导入模块和函数,设置节属性,最后调用被加载 DLL 的入口函数。


3. 下载后在当前进程内加载执行相关逆向分析截图


4. 动态加载Trxa_address3.bin 分析


分析其脱壳版本 Trxa_address3_dump_SCY.dll,当前 IDA 加载基址为 0x701F0000。模块在 DLL 初始化时创建工作线程,随后进入 WeGame/TGP 的凭据采集流程。


入口调用树如下:


[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)

收藏
点赞 ・1
打赏
分享
最新回复 (1)
雪    币: 9196
活跃值: (13544)
能力值: (RANK:385 )
在线值:
发帖
回帖
粉丝
2 楼
感谢楼主分享多平台游戏账号窃取链的逆向分析思路

帖子要点:
1. 入口 cli2update2.exe 解码字符串、下载并动态加载 pz2.bin,由其承担后续调度
2. pz2 主线程每约 2 秒轮询,按 micc、Steam、Trxa、QQ 标志位分发各业务分支
3. micc 分支下载落地为随机名隐藏文件并 CreateProcessA 启动,与盗号业务无关
4. Steam 分支读取 JWT 载荷、Base64 解出 iss 校验 steam,再取 steamui 基址与账号信息上报
5. WeGame/TGP 分支下载两份 Trxa 载荷,经手工 PE 映射在当前进程内加载执行
6. Trxa 载荷全权限打开 TGP 进程,ReadProcessMemory 遍历内存,并用 hx 库钩子注入 DLL 采集上报
7. QQ 分支定位 wrapper.node,用 NtWow64ReadVirtualMemory64 读账号与 clientkey,走官方跳转取 Cookie 后外传
8. 公共字符串经 Base64 加固定密钥 RC4 解码,字节集密文需按长度读取
9. 文末给出各环节样本文件名与 MD5 便于比对

资源链接:
楼主在帖内附有下载/仓库链接(加密显示),请到原帖查看。

谢谢楼主的完整链路梳理,读完也有一些想法:
· 优点:把调度标志位、各平台采集分支和公共解码串成一条链,读起来能对上每一步的地址与调用树
· 优点:保留了实际网络请求与上报载荷,方便对照判断哪条是官方链路、哪条是外传
· 局限:micc 分支因与盗号无关未展开,整条链在这一环上留了空白
· 对比:相比只盯单条告警或磁盘文件,这种按进程触发条件还原的方式更能看清分支间的重试差异
大家对这条链路里的调度逻辑或某个平台分支有什么看法,欢迎一起聊聊

小结由 AI 辅助整理,方便大家先看个大概,完整内容还是看楼主原帖更准确。
3天前
0
游客
登录 | 注册 方可回帖
返回