首页
社区
课程
招聘
[原创][求助]某环逆向 TOP3:反作弊与反调试
发表于: 2026-9-26 17:47 325

[原创][求助]某环逆向 TOP3:反作弊与反调试

2026-9-26 17:47
325

前两篇解决了资源和原生库。这篇讲反作弊、反调试,顺带补上 SSL、登录链和资源清单。

所有结论都来自本地静态分析和模拟,真机没上。跑不通、抓不到、或者报告之间有分歧的地方,我不另作标记,直接在正文里说清楚哪块没把握。


速查:要干什么 / 怎么办 / 用什么工具

我要干什么 该怎么办 用什么工具
摸清一个 so 是干嘛的 扫导出符号 / 字符串 / 熵 rz-bin · strings · scripts/scan_so*.py
认出反作弊是谁家的 扫厂商串(tencent/netease/…) strings · scripts/scan_so*.py
看函数内部逻辑 反编译(破控制流平坦化) IDA idat + Hex-Rays
追谁调用谁 / 常量被谁引用 交叉引用 capstone · scripts/xref_*.py
抠出隐藏字符串 找取串函数 → 枚举 id unidbg Module.callFunction
看库运行时读什么 / 调什么 补环境跑 init,hook syscall Qiling(补 GOT + auto-map)
调 native 方法 / 触真分支 补 JNI,跑 callJNI_OnLoad unidbg(补 MyJni)
找内联 svc / 反调试探针 反汇编数 svc capstone · scripts/find_svc.py · themis_svc_list.py
判检测走哪层 看它读环境走 Java / libc / svc / 自带引擎 —(判据见 §五)
堵检测 堵漏斗(汇总通道),不堵点 Frida
拿网络明文 hook SSL_read / SSL_write Frida(frida_ssl_hook.js)
保稳注入 三层 hook + patch svc + spawn 改名 Frida(frida_stable.js / frida_svc_patch.js)
模拟跑 native 补环境(GOT / JNI / auto-map) Qiling / unidbg
解资源 retoc -a <key> unpack retoc
看登录 / 支付链 反编译 dex jadx
看资源里有什么 解包后分类(UE5 需 .usmap) retoc + FModel

详细做法、补环境步骤、脚本清单在 §十。

换个角度——对象 / 库 对应哪个工具:

什么 用什么
认反作弊是哪家 strings / scan_so*.py
libtprt.so(ACE) IDA idat + unidbg + Qiling
libsecsdk.so(易盾 VMP) IDA + unidbg + androguard
libthemis.so(内联 svc) capstone + Qiling + Frida
libturingmfa.so(图灵盾) Qiling
libUnreal.so(引擎/密钥) IDA + retoc
libmxcore.so(IM/SSL) Qiling + Frida
dex(登录/支付) jadx

一、反作弊是多家叠加

包里不止一家,是六家叠在一起:

厂商 库 特征
腾讯 ACE libtprt.so 控制流平坦化,检测绕 libc
腾讯图灵盾 libturingmfa.so 原生扫描(文件 / 正则 / popen / syscall)
完美 DFGA libDfga_Catch.so 反外挂
网易易盾 libsecsdk.so DEX-VMP
腾讯 libthemis.so 8 条内联 svc;JNI 名混淆
Google libclient.so Crashpad 崩溃上报

上报走腾讯 h.trace.qq.com、完美 clientlog.wanmei.com。

这六家里有两家我心里没底。libturingmfa.so 具体扫什么没验出来——Qiling 一跑它就崩在未映射页,只能从导入表看出它会开文件、popen、跑正则、发网络。libthemis.so 的归属也有分歧:报告 01 写的是"腾讯加密",报告 09/11 又写成"TapTap SDK",这个我暂时没定论。

识别这六家靠 strings 加 scripts/scan_so*.py 扫各 so 的厂商串。


二、腾讯 ACE(libtprt.so)

六家里最难的。

控制流平坦化。 libtprt.so 的核心函数 JNI_OnLoad、initialize、unwind_xx_* 全被 OLLVM 拉平成状态机。直接读汇编读不动,用 IDA 反编译:

idat.exe -A -S"scripts/ida_libtprt.py" libtprt.so

F5 后还原成可读 C。

字符串。 不明文。libtprt.so 里有个分发器:

sub_111740(int a1)  →  return  off_163750[a1 % 100]()   // 100 个取串函数

用 unidbg 的 Module.callFunction 枚举 id 0..2000,dump 出 101 条明文。

检测点。 这些路径都从 sub_111740 取出的字符串表里还原:

查什么 读哪里
调试器 /proc/%u/status 的 TracerPid、/proc/self/status
进程名 / 命令行 /proc/%d/cmdline、/proc/self/cmdline
内存映射 /proc/self/maps
打开的 fd /proc/self/fd
模拟器 /init.vbox86.rc、/dev/socket/genyd(Genymotion)
ADB / USB ADB_ENABLED、android.hardware.usb.action.USB_STATE
注入痕迹 /data/data/com.tencent.tinput、/data/app/、/data/user/0/
自身文件完整性 assets/__tpinfo.tsc / __acinfo.tsc / __tpinfo.tsb / __tp_unity.dat
开关标志 DetailSecurityCheck、__acignore.acc

自退。 命令串 initialize / start_service / start_worker / stop / enable_gp7_exit_group。stop 跟 enable_gp7_exit_group 一起走到,进程就 exit_group 退出——unidbg 里看到 libtprt.so 在 0x2603c 跑了条内联 svc #0。把这处 patch 掉就能拦住。

协议。 libtprt.so 的 ioctl(0x136d7c)没被平坦化,能直接读:按整数命令号分发,消息头魔数 0x87920101,字段 0x22080808 / 0x22080809,构建在 0x136f24、解析在 0x137018。

不走 libc。 文件检测不调 libc 的 open/stat,走 libtprt.so 自带的系统调用引擎 tp_syscall_imp(0x146be0),用内联 svc 进内核。所以 libc 上挂 open hook 抓不到它。

这一步用的工具:idat(IDA 脚本模式)反编译破平坦化,unidbg 的 Module.callFunction 枚举取串,Qiling / unidbg hook tp_syscall_imp 看行为。


三、网易易盾(libsecsdk.so):DEX-VMP

易盾不查环境,把 DEX 字节码抽出来自己解释执行,Java 层看到的方法都是桩。

入口。 Java 侧只有一个:

com.netease.nis.sdkwrapper.Utils
  static native Object rL(Object[]);

桩方法把参数打包成 Object[] 交给 rL,native 实现是 libsecsdk.so 的 rL(0xaddc),内部再转 worker(0xfcfc)。

解释器。 libsecsdk.so 的解释器在 0x30374,主循环 0x307b8:取一个字节作 opcode,查 0x48b30 的跳转表跳 handler。表是 256 个 int32,用到 212 个 handler(0x30c00–0x34458)。op 0x00 是 nop,说明 opcode 编号就是标准 DEX。

SepData 格式。 解析器是 libsecsdk.so 的 0xc1c8(out, buf, len):

[count:u32] + count × { v1:u32, header:u32(id|flags), len:u32, bytecode }

解析后填进 vector[id],元素是 methodInfo(0x50 字节),+0x40 是字节码指针。VM 上下文单例在 0x64d30,vtable 在 0x60c78,vtable[4] = lookup(id)(0xc80c)。

这份 SepData 抓不到:5 个 dex、so、还有运行时 dump 出来的解密段都搜遍了,没有,它只在运行时内存里拼。想拿到只能真机上动态截。

rL 调用约定。 androguard 查 xref:106 个方法调 rL,全是 MSA OAID SDK(com.bun.miitmdid.*)——设备标识获取链被整个虚拟化了。

rL([ receiver, *args, Integer(methodId), Long(salt) ])

salt 约 1606976968xxx,是易盾 2020-12-03 的构建戳。

ClassLoader 链。 VM 解释前要先拿 DEX,走标准链:

getClassLoader → BaseDexClassLoader → pathList → DexPathList → dexElements[0]
  → Element.dexFile → DexFile.mFileName("/data/app/.../base.apk")
  → String.getBytes("GB2312")

unidbg 里补这条链修了 4 处(MyJni.java):getClassLoader 要返回 BaseDexClassLoader 对象、字段名按签名解析、补 mFileName、补 String.getBytes。修完 rL 能进 VM 入口,但再往下要真实的 DEX 内容,我手上是空 stub,所以停在解析器门口——VM 里到底怎么执行,没走通。

字符串加密。 解密器是 libsecsdk.so 的 ctor 0xa868(init_array[0]):

明文 = ((K − 密文) ^ K) & 0xff     // 每串一个 7-bit K

这一步用的工具:androguard 查 rL 的 xref,IDA 逆解释器和 opcode 表,unidbg 补 MyJni 走 ClassLoader 链。


四、其余的

  • libthemis.so:.text 里 8 条内联 svc。4 条 getuid、2 条 close 无害,2 条 openat(0x33d38 / 0x356a8)绕 libc 读文件,patch 成 movn x0,#0。反调试 prctl(PR_SET_DUMPABLE=21) 在 0x71f0c,走 libc。
  • libturingmfa.so:图灵盾原生,导入 open/access/stat/readlink/opendir、popen(跑 shell)、regcomp/regexec(正则扫)、syscall、传感器接口、socket 上报。它具体扫什么没验出来。
  • libDfga_Catch.so:init 和 JNI_OnLoad 里没检测,其实是个崩溃/信号捕获库。
  • libclient.so:ptrace 来自 Crashpad,不是反调试。

这一步用的工具:scripts/themis_svc_list.py 数 svc,Qiling 跑 libDfga_Catch.so 的 init / JNI_OnLoad。


五、怎么堵:看路,不看点

一条经验:看它读环境走哪条路,就往那条路堵。

它走哪 在哪堵 本样本
Java API 改返回值 图灵盾 / DFGA
libc / syscall() hook libc libsecsdk.so、libturingmfa.so
内联 svc patch svc libthemis.so(2 条 openat)
自带 syscall 引擎 hook 该引擎 libtprt.so 的 tp_syscall_imp

另一条是堵漏斗,不堵点。ACE 的汇总通道:

libtprt 检测 ──(ioctl/gp*ioctl)──> Java AceManager ──> h.trace.qq.com

堵 libtprt.so 的 ioctl,或在 Java 层把 AceApplication.initialize 改返回 0。


六、Frida

分三层写:

  • Java 层:改检测方法返回,堵漏斗(AceApplication.initialize→0,图灵盾 / DFGA / Alipay / Bugly / Debug)。
  • libc 层:/proc/self/{maps,status} 洗白(TracerPid→0)、prctl/ptrace→0、dl_iterate_phdr 过滤注入模块、syscall() 拦截。
  • raw svc 层:hook libtprt.so 的 tp_syscall_imp,patch 内联 svc(libtprt.so 的 0x2603c、libthemis.so 的 0x33d38 / 0x356a8)。

注入用 spawn(-f) + --no-pause,frida-server / gadget 改名。自检看 /proc/self/maps 有没有 frida、TracerPid 是不是 0。

这套方案只在模拟里过过——frida_stable.js 里只有 libthemis.so 那条(prctl)验过会被绕过,Java 层和 libturingmfa.so 都没验,真机也没上。

这一步用的工具:frida-tools(frida -U -f ... -l frida_stable.js -l frida_svc_patch.js),配 node 校验 .js。


七、SSL:没有 pin

5 个 dex、所有带 SSL 的库、libUnreal.so 全流式搜 sha256/<base64> 形式的 pin,一个都没有。dex 里的 sha256/ 是 okhttp 格式串,libmxcore.so 的 verify 回调只打日志。所以抓包不用清 pin,直接 hook SSL_write/SSL_read。

SSL 栈分布:libmxcore.so、libprimekit.so、libtapsdkcore.so、libliteavsdk.so(BoringSSL)、libUnreal.so(UE 自带 OpenSSL)。

这一步用的工具:strings + 全库流式搜(scripts/scan_all_ssl.py / find_realpins.py)。


八、登录 / 支付链

账号和支付全在虎鱼 laohu SDK(com.laohu.sdk.LaohuPlatform),纯 Java,不加载 so:

LaohuPlatform(Facade) → c.a / c.b / c.c
   → loginForGame → ActivityLogin / 三方(QQ/微信/微博/TapTap) → pay

完美 oneSdk(oneAppId=1289)域名加密,只有 dfga_domain.json 明文(clientlog.wanmei.com)。UE 侧只有标准桥 com.epicgames.unreal.NativeCalls(13 个 native,无业务),业务桥是 OneGlobalJNI。虎鱼包扫 kycgm / loadLibrary / RegisterNatives 全是 0,说明国密 libkycgm.so 的 GmCipher 调用方在 UE 层。

这一步用的工具:jadx 反编译 dex(scripts/find_login_classes.py 定位),unzip 出 dex。


九、资源内容

retoc 解包后 31,880 个文件:

  • 类型:.uasset 27300 / .ubulk 4500 / .umap 71 / .ushaderbytecode 9。
  • 目录(HT/Content/):Characters 13843、Blueprints 5595、FX 3087、UI 2237、sequence_pack 2195、DataTable 867、WwiseAudio 276、Text 137。

DataTable(数值)、Text(文案)、Blueprints(逻辑)最有用。UE5 资产要配 .usmap 才能读属性名。

这一步用的工具:unzip 取 OBB 里的 .utoc/.ucas,retoc -a <key> unpack 解包。


十、工具使用与补环境

三家工具各管一段。环境和 JNI 都得自己补,现成的跑不起来。

环境。 Python 要 capstone / pyelftools / unicorn / pycryptodome / loguru(见 requirements.txt)。Qiling 得源码装——pip 上那个 qiling 是空壳,用 pip install D:\qiling\qiling-master;rootfs 取 arm64_android,路径不能含中文(异环 会触发 latin-1 报错)。unidbg 用 JDK + Maven。IDA 是 Pro 9.4 的 idat.exe。Frida 配 frida-tools + Node。

IDA(静态破混淆)。 反编译 libtprt.so 的平坦化函数,出可读 C:

idat.exe -A -S"scripts/ida_libtprt.py" libtprt.so

脚本模式跑,不开 GUI。判据:F5 后状态机(mov wState,#imm + cmp/b.ge)还原成可读 C。产物 reports/ida_libtprt.txt。

Qiling(纯原生拟真)。 想把 libtprt.so 的 JNI_OnLoad 跑起来,先补三步:

  1. GOT 不自动重定位。 Qiling 加载后 PLT 读 GOT 拿到的是文件里的原值(未加基址),一 br 就跳进未映射页崩掉。得解析 .rela.plt(libmxcore.so 有 512 条)给每个导入打桩、回填 GOT、hook 桩做分发(scripts/qiling_harness.py)。
  2. auto-map 未映射页,否则 JNI_OnLoad 跑一半就崩。
  3. 补完才能 hook 它对内联的 tp_syscall_imp(0x146be0),看它读了哪些文件。

跑通后能看到它读了 /proc/<pid>/cmdline(查可疑进程/改名)之类。边界:Qiling 是纯原生,JNIEnv 全被 stub,只到"外壳"。

unidbg(真 JNIEnv)。 纯原生跑不出 JNI 语义,得靠 unidbg 的 DalvikVM。但它自带的 JNI 实现不全,要在 MyJni.java 里补 4 处:

  • getClassLoader 返回 BaseDexClassLoader 对象(不是 ClassLoader);
  • getObjectField 收到的字段名是完整签名,要解析成裸名再匹配;
  • 补 DexFile.mFileName;
  • 补 String.getBytes("GB2312")。

补完,rL 和 ClassLoader→DEX 链才走得通(VM base = 0x12000000);也正是在 unidbg 里看到 libtprt.so 在 0x2603c 自退。

字符串枚举(黑盒转白盒)。 IDA 反编译里找到取串分发器 sub_111740(id) = off_163750[id % 100]();再用 unidbg m.callFunction(base + 0x111740, id) 枚举 id 0..2000,dump 出 101 条明文(检测点 + 命令通道)。

Frida(三层保稳)。

frida -U -f com.hottagames.yh.laohu --no-pause -l frida_stable.js -l frida_svc_patch.js

自检看 /proc/self/maps 有没有 frida、TracerPid 是不是 0。

脚本清单。 ida_libtprt.py(IDA 反编译)、tprt_deflat.py(找平坦化分发器)、tprt_strxref.py / tprt_syscall_xref.py(取串 / svc 引用)、qiling_harness.py / qiling_tprt_sim.py(Qiling 补环境)、unidbg/TprtProbe.java / SecsdkProbe.java / MyJni.java(unidbg 探针)、secsdk_*.py / search_sepdata*.py(易盾 VMP)、themis_svc_list.py / frida_themis_svc.js(themis svc)、frida_stable.js / frida_svc_patch.js(Frida)。

分工与边界。 IDA 破混淆 → Qiling 看纯原生行为(读文件 / 命令)→ unidbg 走真 JNI(协议 / VM)。三家都只到"外壳和监督行为"层,平坦化核心没打开。


十一、这些组件能干嘛(含推测)

  • libtprt.so(ACE):读 /proc/{cmdline,status,maps,fd}、查调试器 / 模拟器 / ADB / 注入痕迹、通过 ioctl 命令通道收发、必要时让进程自退。检测全走自带 tp_syscall_imp,绕 libc。推测:那条 ioctl 通道能远程下发策略(魔数 0x87920101 看着像固定协议头)。
  • libsecsdk.so(易盾):把业务方法编译成自研 VM 字节码、运行时解释(DEX-VMP)。推测:它保护的正是 OAID 设备标识获取链(106 个 rL 调用全是 miitmdid)。
  • libthemis.so:读文件(2 处内联 openat)、prctl 反调试。除此之外很干净。
  • libturingmfa.so(图灵盾):按导入表推断是套完整环境扫描——开文件、popen 跑 shell、正则扫、读传感器指纹、socket 上报。具体扫什么没验。
  • libUnreal.so:装载并解资源(Oodle + AES)、跑游戏逻辑。
  • libmxcore.so:IM 消息收发(自带 OpenSSL + curl)。
  • libkycgm.so:国密 SM2 + SM4-CBC。
  • 服务端(h.trace.qq.com / clientlog.wanmei.com):收客户端上报做风控判定。这块完全没碰,纯属常识推测。

带"推测 / 推断"的都不是实证,只是从字符串表、导入表、调用点推出来的能力范围。


附:精确溯源(文件 · 偏移 · 函数)

结论 文件 偏移 / 符号 函数
ACE 取串混淆 libtprt.so — sub_111740(id) → off_163750[id % 100]
ACE raw svc 引擎 libtprt.so 0x146be0 tp_syscall_imp
ACE 命令分发 libtprt.so 0x136d7c ioctl;构建 0x136f24 / 解析 0x137018
ACE native 方法 libtprt.so init 0x25ae0 / ioctl 0x136d7c / gp6 0x26058 / gp7 0x25ea4 initialize/ioctl/gp6ioctl/gp7ioctl
ACE 自退 libtprt.so 0x2603c 内联 svc #0(exit_group)
易盾 rL 桥 libsecsdk.so 0xaddc rL;worker 0xfcfc
易盾解释器 libsecsdk.so 0x30374 / 主循环 0x307b8 InterpretInternal
易盾 opcode 表 libsecsdk.so 0x48b30(256×int32) handler 0x30c00–0x34458
易盾 SepData 解析器 libsecsdk.so 0xc1c8(out,buf,len) 读器 0x12968、写字节码 0xc2fc
易盾 VM 上下文 libsecsdk.so 单例 0x64d30 / vtable 0x60c78 / lookup 0xc80c vtable[4]=lookup(id)
易盾串解密 libsecsdk.so 0xa868(init_array[0]) ((K-c)^K)
themis 反调试 libthemis.so 0x71f0c / 0xc8a60 prctl(PR_SET_DUMPABLE=21)
themis 内联 openat libthemis.so 0x33d38 / 0x356a8 内联 svc #0(x8=56)
Unreal AES(OpenSSL) libUnreal.so 0x267EC98 / 0x267EF30 AES_encrypt / AES_decrypt
Unreal AES(类) libUnreal.so 0xABFA760 / 0xABFA9A8 / 0xABFA8E0 SetKey(16B) / Encrypt / Decrypt(ECB)
Unreal Oodle libUnreal.so 0xB587284 / 0xB588538 OodleLZ_Decompress / Compress 包装
Unreal IoStore 密钥 libUnreal.so 常量 0xcdbc40 key-provider 0x9a93d08(注册 0x26a26e8 / 触发 0x9aae9e0)
Unreal IoStore 解密 libUnreal.so 包装 0x3AC7CA8 / 取key 0x3AC7E14 / AES 0x264DA0C —
mxcore SSL_write 包装 libmxcore.so — sub_46AA74 → 写 sub_489DBC
国密 GmCipher libkycgm.so 0x4a80 / 0x4ca8 / 0x4f6c sm2Encrypt / sm4CbcEncrypt / sm4CbcDecrypt
IM JNI 面 libmxcore_javasupport.so JNI_OnLoad 0x11e58 140 个 Java_..._pwim_*

顺带交代两个我自己踩过的坑。一是 libturingmfa.so 我最初把它标成了"JNI 名混淆",其实那是 libthemis.so 的特征,后来改了。二是早期找 libtprt.so 解密器时,被 disasm_range.py 里写死的 SO=libsecsdk.so 带偏过,报告 77 才更正——那之后我不再用那个脚本的旧结论。

没搞定的还有两处:libsecsdk.so 的 SepData 只在内存里,得真机动态抓;服务端风控(h.trace.qq.com / clientlog.wanmei.com)完全是另一摊,我没碰。


工程文件(GitHub)

脚本、.so、unidbg 探针、依赖清单都在这儿:

9b2K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6y4j5h3y4Z5j5h3)9I4y4o6M7J5y4e0S2Q4x3X3c8E0j5i4S2Q4x3V1k6&6K9q4)9J5k6s2c8G2M7o6x3`.

so/        反作弊相关库(libtprt / libsecsdk / libthemis / libturingmfa / libDfga_Catch / libclient / libmxcore)
scripts/   对抗脚本(.py / .js)
unidbg/    unidbg 探针(.java)
requirements.txt / README.md

本仓库不含 AES 密钥(已在脚本里抹掉)。


传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

最后于 2026-9-26 20:39 被哄鼠杀虫腐编辑 ,原因:
收藏
点赞 ・1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回