溺的文档
libtprt · 第 1 篇 / 共 1 篇

`libtprt.so` 完整业务分析文档

2026-07-14 · 阅读 3

文档状态:独立正式稿 / libtprt 完整业务唯一归口
整理日期:2026-07-14
目标进程:com.tencent.rmcn
目标架构:Android / AArch64
分析对象:libtprt.so,以及它与 libil2cpp.solibunity.so、Java 宿主层、APK 资源侧的交互
证据来源:IDA 静态逆向、MiniMem 只读快照、Frida 冷启动时序、APK manifest/dex 离线分析

本文是 libtprt 完整业务的唯一归口,集中维护以下内容:

  • JNI_OnLoadJNINativeMethod[7]AceApplicationGP6/GP7 宿主流程;
  • initialize / handleLoad* / ioctl / gp6ioctl / gp7ioctl 的本地业务;
  • 共享注册表、unwind_xx_*tp_syscall_imp 等对外接口;
  • APK/zip/ApkAssets、sidecar、环境检测与反分析业务;
  • libtprtlibil2cpp 装载期加固链中的职责概览。

libil2cpp.init_proc 的槽位级时序、Unity/EGL 预安装、隐藏初始化数组恢复边界和失败极性不在本文重复展开,以加固专项稿为准:

  • libil2cpp_init_proc_与_libtprt_完整加固业务流程分析.md

两份文档按“完整业务归本文、加固实现归专项稿”并行维护,不再把本文的完整 JNI/宿主流程回并到加固专项稿。


1. 结论先行

libtprt.so 不是一个“只在 init_proc 里被动执行几次回调”的窄保护库,而是一个同时覆盖以下业务面的运行时组件:

  1. 装载期保护核心

    • libil2cpp.soDT_INIT 主链里提供 53 项函数表,完成受保护段恢复、私有重定位、导入完整性处理、Hook 安装、运行时注册表写入、隐藏初始化数组调度与可选补丁恢复。
  2. Java/JNI 宿主面

    • 通过 JNI_OnLoad -> RegisterNatives(..., 7) 挂到 com/ace/gshell/AceApplication,暴露 initialize / handleLoad / handleLoadV22 / ioctl / gp6ioctl / gp7ioctl 这一组 native 入口。
  3. 宿主服务控制面

    • GP6Service / GP7Service / GP7Worker 三个 manifest 级 service 接受 Intent extra "ioctl",把字符串命令桥接到 gp6ioctl / gp7ioctl
  4. 共享运行时状态中心

    • 通过 sub_C1638 / sub_D17F0 / sub_131C70 / sub_131DA4 维护一个可跨模块复用的带锁字符串注册表;slot 50initializeunwind_xx_* 都会读写它。
  5. 对外控制/查询面

    • JNI_OnLoad 外,还导出 tp_syscall_impunwind_xx_info_queryunwind_xx_ioctl 三类接口,对外提供 syscall 桥接、注册表读取和两槽字符串状态写入能力。
  6. APK/资源侧完整性链

    • 现在已经能确认它掌握 apk_path / base.apk / splitSourceDirs / zip file / getAssets / getApkAssets 这一组语汇,并且 APK 内还带有 __tpinfo.* / *.sig / *.tsa / *.sin / tpginf.dat 一组 TP sidecar 元数据;只是 sidecar 的精确消费点仍待进一步闭合。

换句话说,libtprt 既是加固主控,也是宿主桥、状态中心和 APK 侧完整性/控制框架的一部分。


2. 样本基线与证据口径

2.1 样本摘要

文件 大小 SHA-256
libtprt.so 1,692,296 0E7DF19BCD1ED6F1AE5EC38AF22FBA630C581F3B537DD9BF416A457CF16E9883
base.apk 见 APK 样本目录 当前分析对象

2.2 证据分级

等级 含义
S 静态直接证据:ELF、反编译、交叉引用、结构体、字符串、manifest/dex
R 运行期只读快照:实际指针、映射、表项、内存数据
T 动态时序证据:Frida 冷启动 trace
H 高置信合并推断:静态能力与运行状态吻合,但尚未直接命中单点入口

2.3 地址约定

  • 本文中的 libtprt.so+0xXXXXXX 一律表示 RVA;
  • 运行期绝对地址按 runtime_base + RVA 换算;
  • libtprt 本身,当前未观察到 libil2cpp 那种“最低映射不等于有效工作映像”的歧义。

3. libtprt 的模块定位

从当前样本可直接确认,libtprt 至少处在以下四条链路的交汇点:

Java / Android 宿主层
  -> AceApplication / GP6Service / GP7Service / GP7Worker
  -> JNI_OnLoad + 7 个 native

libil2cpp 装载期保护链
  -> g_tprt_pfn_array[53]
  -> slot 1/29/30/31/50/32/51

共享状态 / 对外接口链
  -> sub_C1638 / sub_D17F0 / sub_131C70 / sub_131DA4
  -> unwind_xx_info_query / unwind_xx_ioctl / tp_syscall_imp

APK / 资源 / sidecar 链
  -> apk_path / base.apk / splitSourceDirs
  -> getAssets / getApkAssets / zip file
  -> __tpinfo.* / *.sig / *.tsa / *.sin / tpginf.dat

所以对 libtprt 的分析,不应再只盯 slot 30/31 这类装载期钩子,而要按“保护主链 + 宿主面 + 注册表 + APK 资源面”四块来理解。


4. ELF、全局表和基础构造链

4.1 关键 ELF 属性

项目
ELF 类型 ET_DYN
架构 AArch64
GNU Build ID 22679299bec8ba3baf066d837c30ff0afce44b29
DT_INIT_ARRAY 0x165328
导出核心 JNI_OnLoadg_tprt_pfn_arrayg_tprt_ori_arraytp_syscall_impunwind_xx_info_queryunwind_xx_ioctl

4.2 四个 .init_array 构造器

动态 trace 已直接命中:

libtprt.so+0x269E0
libtprt.so+0x3BED8
libtprt.so+0xE3FD8
libtprt.so+0x136A78

它们属于真实构造链,但命中时:

  • tprtSavedHooks[0/1] == 0
  • Unity/EGL 最终 Hook 仍未完成

所以它们是“准备期构造器”,不是最终 Hook 的直接落地点。

4.3 两张核心全局表

g_tprt_pfn_array

  • RVA:0x1A3210
  • 大小:424 bytes
  • 总项数:53

这是 libtprt 提供给主链的业务函数表。

当前已闭合的关键槽位:

槽位 RVA 语义
1 0x32800 受保护段处理
29 0x323E8 私有重定位
30 0x342C0 导入描述符处理/通用安装链
31 0x38B74 保存原指针并安装 thunk
32 0x39CD8 隐藏初始化数组调度
50 0x1361BC 运行时命令解析/模块基址注册
51 0x337E8 可选补丁应用

g_tprt_ori_array

  • RVA:0x1A4B48
  • 大小:160 bytes
  • 20 项

静态初值全零,但运行后会逐步填入原函数/回调/运行时状态。当前稳定非零项为:

4, 6, 7, 8, 9, 10, 14, 15, 16, 17, 19

其建立过程不是一次性完成,而是分三段:

  1. init_proc 前的 Unity 预处理;
  2. slot 29 私有重定位;
  3. slot 30 导入安装链。

5. 装载期保护业务

5.1 libunity/EGL 预安装链先于 libil2cpp.init_proc

这是整个 libtprt 业务定位里最重要的时序修正。

稳定结论:

  • Unity/EGL 最终 Hook 首次落地早于 libil2cpp.so:DT_INIT
  • libtprt+0x358A0init_proc 前已对 libunity.so 的目标槽位做过一次过渡改写;
  • modules-ready 时已经观察到最终 Hook 状态;
  • slot 30 / slot 31 是同一安装框架在 libil2cpp 侧的再次使用,不是 Unity Hook 首装点。

5.2 libil2cpp DT_INIT 主链里 libtprt 的固定职责

当前稳定时序:

slot 1
  -> slot 29
  -> slot 30
  -> slot 31
  -> slot 50
  -> slot 32

其中:

  • slot 1:最强候选的受保护段恢复入口;
  • slot 29:私有重定位;
  • slot 30:导入描述符处理、导入完整性/支撑安装;
  • slot 31:保存原函数并写入包装 thunk;
  • slot 50:写运行时注册表;
  • slot 32:顺序调度 32 项隐藏初始化函数;
  • slot 51:条件路径,不是每次稳定启动都命中。

5.3 隐藏初始化数组

BBMG311ENC12IAI 描述的隐藏数组共有 32 项。

当前已确认:

  • 数组 32/32 有效;
  • sub_39CD8 的循环从索引 0 顺序遍历到 31;
  • 全量主 trace + focused probe 已合并覆盖 32/32;
  • hidden 0/1call_constructors-enter(libil2cpp) 时还未恢复,但到 slot32-enter 前已经变成合法 AArch64 代码;
  • 恢复边界已被压缩到 init_proc 内的 pre-slot32 窗口。

这说明 libtprt 不只是改几个 GOT 项,而是还掌管一组隐藏构造器的恢复与调度。


6. Java/JNI 宿主与本地业务面

6.1 JNI_OnLoad:对象 VM / native 注册引导

JNI_OnLoadlibtprt.so+0x2641C)不是空壳。静态反编译表明它至少完成以下动作:

  1. 先触发一次 __ENABLE_OBJ_VM__ 相关路径;
  2. 调用 GetEnv(vm, &env, JNI_VERSION_1_6)
  3. 通过 sub_12E888(vm)JavaVM * 保存到全局;
  4. 懒解码并缓存一个运行时类名字符串(sub_C11A4,缓存到 qword_1A4BE8);
  5. 通过 JNIEnv 解析该类对象;
  6. 以固定数量 7 调用 RegisterNatives(env, clazz, ..., 7)
  7. 若类解析失败、注册失败或检测到异常,则走清理路径并返回 -1;成功时返回 JNI_VERSION_1_665540)。

6.2 运行时类名与 JNINativeMethod[7]

后续通过 root 读取 /proc/<pid>/mem,可以直接补齐 JNI_OnLoad 的两项关键运行态证据:

  1. qword_1A4BE8 在运行时被填为一条带 top-byte tag 的堆指针;去掉 tag 后可读出:
com/ace/gshell/AceApplication
  1. qword_1A4948 位于 .bss 起点,不是静态只读 native 表;运行时它被填成 7 项 JNINativeMethod,且方法名/签名字符串也落在同一片 .bss 附近。

恢复出的表如下:

索引 name signature 本地实现 RVA
0 initialize (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)I 0x25AE0
1 handleLoad ([BII)Ljava/lang/Object; 0x136544
2 handleLoad (Ljava/lang/Object;I)Ljava/lang/Object; 0x1362F8
3 handleLoadV22 ([BII)J 0x136818
4 ioctl (ILjava/lang/String;)I 0x136D7C
5 gp7ioctl (Ljava/lang/String;)V 0x25EA4
6 gp6ioctl (Ljava/lang/String;)V 0x26058

这意味着 JNI_OnLoad 并不是给某个通用 helper 类注册零散 native,而是把一整组 TPRT/gshell 业务入口绑定到 com/ace/gshell/AceApplication

补充一条宿主层证据:直接从设备拉取 base.apk 后,对 classes.dex 做原始字符串池检索,可以同时看到:

  • Lcom/ace/gshell/AceApplication;
  • Lcom/ace/gshell/GP6Service;
  • Lcom/ace/gshell/GP7Service;
  • Lcom/ace/gshell/GP7Worker;
  • gp6ioctl / gp7ioctl / handleLoad / handleLoadV22

这说明 AceApplication 并不是孤立占位类,而是和 GP6/GP7 宿主服务族一起实际打进 APK;这与前述 JNI 注册结果形成交叉支撑。

继续用 androguardAndroidManifest.xmlclasses*.dex 做类、方法体和交叉引用解析后,Java 宿主包装链可以进一步闭合为:

Zygote / Application attach
  -> AceApplication.<clinit>
     -> System.loadLibrary("tersafe")
     -> System.loadLibrary("tprt")
  -> AceApplication.attachBaseContext(context)
     -> attachBaseContextShell(context)
        -> 收集 packageName / nativeLibraryDir / sourceDir / filesDir / splitSourceDirs
        -> initialize(...)
  -> 后续按需进入显式 service 包装器
     -> GP6Service.onStartCommand : Intent extra "ioctl" -> gp6ioctl(...)
     -> GP7Service.onStartCommand : Intent extra "ioctl" -> gp7ioctl(...)
     -> GP7Worker.onStartCommand : Intent extra "ioctl" -> gp7ioctl(...)
     -> GP7Service / GP7Worker.onDestroy : gp7ioctl("stop")

这条链现在有三组直接证据:

  1. AndroidManifest.xmlcom.ace.gshell.AceApplication 指定为宿主 application,并声明了 3 个 com.ace.gshell service:

    service android:process exported enabled
    com.ace.gshell.GP6Service :GP6Service true true
    com.ace.gshell.GP7Service :GP7Service true true
    com.ace.gshell.GP7Worker :GP7Worker true true

    这说明 GP6/GP7 并不是普通 helper 类,而是 manifest 级、独立进程的 service 包装器。

  2. AceApplication 的 dex 字节码不只是声明 native,还显式承担启动包装逻辑:

    • <clinit>System.loadLibrary("tersafe"),再 System.loadLibrary("tprt"),随后初始化一个静态 memoryLoaders 列表;
    • attachBaseContext(context) 的唯一职责就是转调 attachBaseContextShell(context)
    • attachBaseContextShell(context) 会从 ApplicationInfo/Context 中提取:
      • packageName
      • nativeLibraryDir
      • sourceDir
      • filesDir.getAbsolutePath()
      • 以及 SDK_INT >= 21 时反射取得的 splitSourceDirs
    • 然后以这些值直接调用 native initialize(...);若返回值非 0,它会抛出 Exception("initialize failed.")

    因而 initialize 不只是“存在于 native 表中的一个入口”,而是应用 attach 期必经的真实宿主启动点。

  3. GP6/GP7 三个 service 的方法体和 xref 已经把 gp6ioctl/gp7ioctl 的 Java 包装面闭合:

    • GP6Service.onStartCommand(Intent, int, int):读取 intent.getStringExtra("ioctl"),非空则直调 AceApplication.gp6ioctl(...)
    • GP7Service.onStartCommand(...)GP7Worker.onStartCommand(...):同样读取 Intent extra "ioctl",再直调 AceApplication.gp7ioctl(...)
    • GP7Service.onDestroy()GP7Worker.onDestroy():在 stopSelf()super.onDestroy() 之后,还会额外发送字面量 gp7ioctl("stop")

base.apk 当前 4 个 dex(classes.dex .. classes4.dex)做统一 xref 后,可把调用面进一步收敛为:

  • initialize(...) 只有 1 个 Java caller:AceApplication.attachBaseContextShell(...)
  • gp6ioctl(...) 只有 1 个 Java caller:GP6Service.onStartCommand(...)
  • gp7ioctl(...) 有 4 个 Java caller:GP7Service/GP7WorkeronStartCommand(...)onDestroy()
  • handleLoad(...)handleLoadV22(...)ioctl(int, String) 在当前 4 个 dex 中都没有静态 Java caller。

这里有两个收紧边界的附加事实:

  • base.apk 实际包含第 4 份 dex:classes4.dex;把它纳入统一扫描后,上述 caller 关系没有变化;
  • AceApplication.memoryLoaders 在当前 4 个 dex 中表现为“<clinit> 唯一写入、零读取”,说明它至少不是现有 Java 明文路径下已投入使用的 loader 注册表。

最后这点很重要:它说明 handleLoad*ioctl(int, String) 至少不是由当前 APK 已打包 4 个 dex 中的普通宿主代码直接调用,更可能来自反射路径、后续加载的 dex/so,或更深的 native 桥接逻辑。

再往 Java 产品层前追一步,当前 classes.dex .. classes4.dex 的统一静态扫描还没有检出:

  • 显式 putExtra("ioctl", ...)startService/startForegroundService 组合;
  • 显式 com.ace.gshell.GP6Service / GP7Service / GP7Worker 组件字符串在普通方法体中的引用。
  • AceApplicationGP6ServiceGP7ServiceGP7Worker 这 4 个类的 const-classnew-instance xref 都为 0

因此,就当前 APK 已打包 4 个 dex 的静态明文路径而言,GP6/GP7 service 的真正触发点仍然不在可直接命中的 Java 常规代码里;剩余更可能的是反射、native 侧构造 Intent,或更晚加载的代码路径。

6.3 7 个 native 的本地业务边界

结合签名和反编译,当前可以把这 7 个 native 归纳为:

  • initialize(...) -> int

    • 入口为 sub_25AE0 -> sub_C3764
    • 会复用 sub_C1638 共享注册表;
    • 已确认会把多项路径/版本键写入内部上下文,至少包括 apk_pathshell_verlib_dirfiles_dir
    • 还会写入固定版本串 4.9.31.51305_cn
    • 通过 sub_E8870pf_init 写入一块 64 字节本地状态缓冲;另有一个同类 64 字节 payload 来自 12322,但当前更像非文本模板,而不是普通 ASCII 键;
    • 收尾时触发一条较长的内部初始化/上报链;
    • 最终返回 0-1
  • handleLoad([B, int, int) -> Object

    • 先把 Java byte[] 拷到对齐后的 native buffer(sub_13674C);
    • 再根据第三个整型参数选择不同的 loader 分支;
    • n23 == 24/25 时,会在 libart.so 中运行时解析 art::DexFile::Open 的两种重载候选:
      • _ZN3art7DexFile4OpenEPKhj...OatDexFile...
      • _ZN3art7DexFile4OpenEPKhm...OatDexFile...
    • n23 == 23 时,会改走 art::DexFile::OpenMemory 的两种 OatDexFile 候选:
      • _ZN3art7DexFile10OpenMemoryEPKhj...OatDexFile...
      • _ZN3art7DexFile10OpenMemoryEPKhm...OatDexFile...
    • 这些候选名通过 sub_1368F4 在运行时从 libart.so 扫图并解析;
    • 调用时会传入一块以 "Location" 为首字段的上下文对象;
    • 成功时把结果再包装成 Java 对象返回。
  • handleLoad(Object, int) -> Object

    • 从 Java 对象/缓冲区取出底层内容;
    • 复制到新分配的 native 堆块;
    • 再通过 sub_136410 封装成 Java 返回对象。
  • handleLoadV22([B, int, int) -> long

    • handleLoad([BII)Object 同属一组加载路径;
    • 同样依赖 sub_13674C + sub_1368F4
    • 但它对应的候选回调已进一步收敛为 libart.so 中两种 art::DexFile::OpenMemory(..., OatFile, ...) 重载:
      • _ZN3art7DexFile10OpenMemoryEPKhj...OatFile...
      • _ZN3art7DexFile10OpenMemoryEPKhm...OatFile...
    • 但最终返回的是 native 侧包装对象指针(J / long),不是 Java Object
  • ioctl(int, String) -> int

    • 把 Java 字符串按分隔符拆成至少两段;
    • 第一段作为名字/路径语义,第二段转整数;
    • op == 0 时会先通过 sub_136CD4 结合当前进程特征生成目标路径,再读取该文件并校验一个固定 5 字段包头/包尾与编号,不匹配时会 unlink
    • op == 1/2 时会通过 sub_136F24 写入结构化状态包,核心特征是:
      • 固定起止哨兵 0x87888781(有符号视角即 -2020474623);
      • 中间包含操作码 0x22080808 / 0x22080809
      • 以及本次解析出的整数参数;
    • op == 2 写入前还会先复验现有状态文件是否已匹配同编号记录;
    • 更像“文件型状态信道 / 控制信道”,不是 Linux ioctl 的直接透传。
  • gp7ioctl(String) -> void

    • 是一条字符串命令控制通道;
    • 现在已经恢复出三组关键字:
      • 精确匹配 start_service:走一条独立的服务启动路径;
      • 前缀匹配 start_worker,再按格式串 start_worker_%d 解析 worker 编号并分派;
      • 精确匹配 stop:再查询布尔开关键 enable_gp7_exit_group,满足时调用 exit_group
    • 结合 classes.dex 中同时存在 GP7ServiceGP7Worker 类名,高置信可把这组命令理解为围绕 GP7 服务/worker 宿主组件的控制语汇;
    • 因而它不是泛化字符串日志口,而是显式的服务/worker/stop 控制通道。
  • gp6ioctl(String) -> void

    • 先取一个懒初始化单例(sub_128714);
    • 该单例对象带显式一次性初始化位 +0x10,首次会走 sub_128824 做延迟安装;
    • 若该单例里已经装有回调,则把 Java 字符串转成本地字符串并透传给该回调;
    • 结合 classes.dexGP6Service 的存在,高置信可把它理解为 GP6 宿主服务侧的命令桥接入口;
    • 更像一条“把字符串命令转交给已注册 handler”的桥接入口。

因此,JNI_OnLoad 下挂的 7 个 native 足以说明:libtprt 对 Java/宿主层暴露的业务,不只是初始化一次保护状态,还包括载荷/对象加载、文件型状态控制、字符串命令控制和 gp6/gp7 两条分发桥。

但从控制流上,JNI_OnLoad 作为“保存 JavaVM + 解析类 + 注册 7 个 native”的入口,以及这些 native 的总体职责分层,现在都已经有静态 + 运行态直接证据支撑。

6.4 与 TssSdk / TP2Sdklibtersafe)宿主链的边界

在同一 APK 的 classes2.dex 中,还存在另一条需要与 AceApplication -> libtprt 明确区分的宿主链:

  • Lcom/tencent/tp/TssSdk;
  • Lcom/tencent/tersafe2/TP2Sdk;

当前 4-dex 静态结果表明:

  • TssSdk.<clinit> 只加载 tersafe,没有加载 tprt
  • TssSdk.ioctl(String) 是一个 Java 包装器:构造 TssIOCtlResult,把命令串塞进 cmd 字段,再直调 native getsdkantidata(...)
  • 已直接命中的典型命令串包括:
    • dec_tss_info:%s
    • EnableGameReport
    • SetLocaleId:%d
  • TP2Sdk 则主要是把 decTssInfo / init / ioctl / setLocaleId 等调用转发给 TssSdk

因此,当前样本里至少并行存在两条 Java 宿主面:

AceApplication / GP6Service / GP7Service / GP7Worker
  -> libtprt.so

TssSdk / TP2Sdk
  -> libtersafe.so

它们都属于腾讯 TP/Tersafe 家族,但不能混成一条链解释。前者是本文当前锁定的 JNI_OnLoad -> RegisterNatives(7) / gp6/gp7 / handleLoad* 宿主链;后者更接近产品层 Tersafe SDK API 面。这个边界也说明:即使 app 内同时出现 ioctl 一词,TssSdk/TP2Sdk 的字符串命令面也不能直接拿来当作 AceApplication.initialize / handleLoad / gp6ioctl / gp7ioctl 的 caller 证据。

6.5 sub_111740 字符串调度器:已恢复的高价值明文

sub_111740(id) 通过 id % 100 选择 bucket,再从 libtprt.so:.rodata 的私有编码表中按需解出字符串。当前已经可以离线恢复一批与业务边界直接相关的明文:

id 明文 当前用途
2141 apk_path initialize 上下文字段
2152 shell_ver initialize 版本字段
292 lib_dir initialize 上下文字段
302 files_dir initialize 上下文字段
9027 sdcard/sdk 路径/环境相关字符串
9040 /sdcard/Android/data/%s/files 路径模板
9615 %s/ano_tmp/shell_foo.dat 临时文件路径模板
9642 libart.so handleLoad* 目标模块
9654 art::DexFile::Open(..., OatDexFile, ...) 候选 1 handleLoad
9772 art::DexFile::Open(..., OatDexFile, ...) 候选 2 handleLoad
9890 art::DexFile::OpenMemory(..., OatDexFile, ...) 候选 1 handleLoad
10026 art::DexFile::OpenMemory(..., OatDexFile, ...) 候选 2 handleLoad
10162 art::DexFile::OpenMemory(..., OatFile, ...) 候选 1 handleLoadV22
10294 art::DexFile::OpenMemory(..., OatFile, ...) 候选 2 handleLoadV22
11920 start_worker gp7ioctl 前缀命令
11935 start_service gp7ioctl 精确命令
11951 start_worker_%d gp7ioctl worker 格式串
11969 stop gp7ioctl 精确命令
12015 enable_gp7_exit_group gp7ioctl stop 分支开关键
12367 pf_init initialize 状态标签

需要单独保留的边界是:217512322 当前能离线解出 payload,但结果不呈现为稳定 ASCII 文本,更像模板/二进制片段,而不是普通字符串:

  • 2175 目前只在 sub_C3764 中出现一次,调用形式是“单独触发 sub_111740(2175),随后立刻进入 12367 -> pf_init 分支”,其返回值在当前反编译里不直接参与普通字符串 API;
  • 12322 同样只在 sub_C3764 中出现,且会像 12367 一样经 sub_E8870 写入同一类 64 字节本地状态缓冲;
  • 因而它们当前更应被视为 initialize 私有状态模板 / 二进制标签,而不是尚未解出的普通文本键名。

进一步把支持 bucket 范围内的 sub_111740 记录扫到 0..15999 后,又得到一组对“宿主触发面 / APK 资源面”很关键的补充字符串:

id 明文 当前意义
327 .apk APK 路径/文件类型相关
1505 assets/bin/Data/Managed/Assembly-CSharp.dll APK 资源/Managed 组件路径
1567 assets APK 资源根目录
1622 assets/__ac_unity.dat APK 内置资源名
2027 splitSourceDirs ApplicationInfo 分包字段
2451 zip file APK/zip 处理相关
2462 base.apk APK 主包名
4892 getAssets Java AssetManager 入口名
4943 getApkAssets Java ApkAssets 入口名
6926 assets/__vcignore.acc APK 内置资源名
9354 assets/supplierconfig.json APK 内置资源名
9552 assets/yj_exchange APK 内置资源名
11492 assets/__acsob.dat APK 内置资源名

这说明 libtprt 的字符串调度表里,除了 initialize / handleLoad* / gp7ioctl 这类已知命令和键名,本身还包含一组与 APK 路径、分包信息、AssetManager/ApkAssets 和特定资源文件名直接相关的明文。

6.6 宿主触发面进一步收紧:普通静态 Java 路径 vs 晚加载候选

在 4-dex 统一 xref 之外,又对当前 classes.dex .. classes4.dex 的方法体做了两轮模式扫描:

  1. 以“com/ace/gshellGP6/GP7 相关字面量”为主键;
  2. 以“ioctl + Intent 构造/putExtra/getStringExtra/startService/setClassName/setComponent”为组合特征。

结果是:

  • 命中的只有 9 个方法,全部都是已知包装面:
    • AceApplication.<clinit>
    • AceApplication.attachBaseContext
    • AceApplication.attachBaseContextShell
    • GP6Service.onDestroy / onStartCommand
    • GP7Service.onDestroy / onStartCommand
    • GP7Worker.onDestroy / onStartCommand
  • classes2.dex / classes3.dex / classes4.dex 在这组 gshell/ioctl 触发模式下都没有新增命中。

这把 Java 普通静态路径的结论进一步压实为:当前 APK 已打包 dex 中,确实只看得到 AceApplicationGP6/GP7 这层包装器,看不到第二条“显式构造目标 service 并填入 "ioctl" 参数”的常规 Java 明文链。

但“晚加载代码”现在也不再只是抽象可能。classes2.dex 中可直接确认存在一整套腾讯 X5/TBS 的动态加载基础设施:

  • com.tencent.smtt.export.external.DexClassLoaderProvider
  • com.tencent.smtt.export.external.DexLoader
  • com.tencent.smtt.export.external.DexClassLoaderProviderService

其方法体里大量直接构造 DexClassLoader、调用 loadClass(...),并支持 provider/service 侧异步 dex 装载。因此,对 handleLoad(...) / handleLoadV22(...) / ioctl(int, String)GP6/GP7 真正触发点继续保留“后续加载代码路径”的候选,不再只是泛化保守说法,而是当前 APK 宿主面里确有现成动态加载框架存在。


7. initialize 的业务语义

initialize 的主体是 sub_25AE0 -> sub_C3764

当前已确认其职责包括:

  1. 建立初始化上下文;
  2. 写入共享注册表;
  3. 写入版本和路径键;
  4. 生成本地状态缓冲;
  5. 触发后续较长的内部初始化链。

已恢复的核心键和值:

键/字段 来源
apk_path sourceDir/base.apk 相关
shell_ver 固定版本串
lib_dir nativeLibraryDir
files_dir filesDir
pf_init 本地状态标签
4.9.31.51305_cn 固定版本串

此外:

  • 2175
  • 12322

这两项当前更像非文本模板/二进制标签,而不是普通字符串键。


8. 共享注册表与对外导出接口

8.1 共享注册表

当前可以把:

  • sub_C1638
  • sub_D17F0
  • sub_131C70
  • sub_131DA4

理解为 libtprt 的共享运行时状态中心。

它至少服务于:

  • initialize
  • slot 50
  • unwind_xx_info_query
  • unwind_xx_ioctl

8.2 slot 50

slot 50 = sub_1361BC 的核心语义不是简单写一个全局变量,而是解析运行时命令后,把值写入共享注册表。

当前已确认的命令形态包括:

  • mb::模块基址注册
  • ui::配置/状态写入

以及:

  • SOBASE_<module>

这类注册表键。

8.3 unwind_xx_info_query

虽然名字看起来像“崩溃回溯查询”,但当前反编译已足够确认:

unwind_xx_info_query
  -> sub_1319D4
  -> sub_131C70

本质是共享注册表读接口。

8.4 unwind_xx_ioctl

当前已确认的行为:

  • op == 1:写字符串槽 0
  • op == 4:写字符串槽 1
  • op == 2:把 qword_1A5230 格式化后桥接进注册表

所以它本质是:

  • 两槽字符串状态写入
  • 一个全局值写入注册表

8.5 tp_syscall_imp

tp_syscall_imp 是对外 syscall 桥接器,不是只在 init_proc 内部使用的普通 helper。

它负责:

  • 系统调用转发;
  • errno / 负值语义整理。

8.6 外部调用边界

当前 IDA 调用图中,tp_syscall_impunwind_xx_info_queryunwind_xx_ioctl 都没有 libtprt 内部 caller;libil2cpp.solibunity.so 的动态导入表也没有这三个符号。因此它们不是本轮 init_proc 主链上的 ELF 直链调用,而是面向外层组件的桥接接口。

对 APK 当前 58 个自带原生库继续做依赖表与符号字符串扫描后,只有 libtprt.so 自身出现这三个导出名,未发现第二条明确的原生库直导入链。剩余调用方候选收敛为:

  • Java/JNI 宿主桥;
  • 运行期 dlsym
  • 外部或延迟加载组件。

当前仍未闭合的是两槽字符串状态、qword_1A5230 的最终消费方,以及这些导出在 app 侧的真实 caller。


9. APK / 资源 / sidecar 业务

9.1 已确认的 APK/asset 访问语汇

sub_111740 的支持 bucket 字符串全量离线扫描,当前已能直接恢复:

id 明文
327 .apk
1505 assets/bin/Data/Managed/Assembly-CSharp.dll
1567 assets
1622 assets/__ac_unity.dat
2027 splitSourceDirs
2451 zip file
2462 base.apk
4892 getAssets
4943 getApkAssets
6926 assets/__vcignore.acc
9354 assets/supplierconfig.json
9552 assets/yj_exchange
11492 assets/__acsob.dat

这说明 libtprt 内部明确知道:

  • APK 路径
  • 分包信息
  • Java AssetManager/ApkAssets
  • 以及多条 APK 资源名

9.2 当前已挂回的函数链

sub_78FB0

内部会动态取出:

  • getAssets
  • getApkAssets

以及配套签名字符串。

这说明它具备经 Java 反射或 JNI 方法查找进入 AssetManager / ApkAssets 的能力。

sub_7BB04

会显式比较:

  • base.apk

它的 caller 是 sub_77340

sub_77340

这不是单纯的路径比较 wrapper,而是更大的 APK/环境检查状态机:

  • 会调用 sub_7BB04
  • 会调用 sub_11D264(环境/兼容层检测)
  • 还会进入多条与路径、内容、返回值相关的判断分支

当前更合理的归类是:

  • APK/环境检查控制器

而不是单一文件名比较函数。

sub_7E4A4

这条链现在已经不只是“调用了 sub_7EDD0”这么宽泛。

从反编译可直接确认,它会:

  • 通过 sub_111740(2419) / sub_111740(2363) / sub_111740(2380) 动态取出:
    • dalvik.system.PathClassLoader
    • getClassLoader
    • ()Ljava/lang/ClassLoader;
  • 通过 sub_111740(2408) / sub_111740(381) 动态取出:
    • toString
    • ()Ljava/lang/String;
  • 基于这组 Java 反射/桥接调用,取得某个类加载器或路径对象的字符串表示;
  • 再把结果拷入本地 0x400 缓冲,交给 sub_7EDD0((const char *)buf, ctx, mode & 1)

因此,sub_7E4A4 的业务语义已经可以收紧为:

  • 不是普通字符串 wrapper;
  • 而是“从 Java 类加载器/路径对象侧抽取 APK 相关路径字符串,再驱动下层 zip/apk 扫描”的桥接驱动器。

sub_7EDD0

sub_7EDD0 当前可以明确看出两层行为:

  1. 它会对输入字符串做显式子串/模式扫描;
  2. 命中后会把片段抽出、封装,再交给 sub_7CA84(...) 做进一步判定。

已直接恢复出的关键字符串包括:

  • zip file
  • base.apk

其中一个稳定路径是:

... -> 截取双引号中的片段
    -> sub_7CA84(extracted_text, mode & 1)
    -> 若命中则经 sub_316D4 / sub_2C74C 把结果写入目标对象

因此,sub_7EDD0 不只是“看一眼路径里有没有 apk”:

  • 它更像 zip/apk 文本记录扫描器;
  • 会从上游传入的路径/描述字符串里提取片段;
  • 再把命中记录转成结构化结果对象,交给上层继续消费。

sub_7CA84

sub_7CA84 现在已经能从“共享匹配器”进一步收紧为“APK 记录名匹配器”。

可直接确认的比较目标包括:

  • base.apk
  • base.odex.apk
  • lebian
  • /data/app/

这里的 base.apk / base.odex.apk / /data/app/ 很像 Android 安装目录 / APK 主包 / odex 相关记录匹配; lebian 则更像额外的内部标记词或特殊记录名。

从控制流上看,它会:

  • 对输入字符串做逐字节比较;
  • dword_171400 == 1 等条件下切换分支;
  • 同时被:
    • sub_7EDD0
    • sub_7DC28
    • sub_78860

调用。

因此当前最稳妥的归类是:

  • APK/odex/安装目录相关的共享记录匹配器;
  • 而不是泛化的任意字符串比较函数。

sub_7DC28

sub_7DC28 是另一条与 sub_7CA84 共享匹配逻辑的扫描入口。

它会:

  • 通过对象方法取出字符串;
  • 过程中动态取 sub_111740(4995) / sub_111740(875),这一组现在已能恢复为:
    • getAssetPath
    • ()Ljava/lang/String;
  • 调用 sub_7CA84(..., 1) 做命中判定;
  • 若命中,则通过 sub_316D4 -> sub_2C74C 把命中字符串写入目标对象;
  • 失败或结束时会走对象清理/释放路径。

当前更合理的归类是:

  • 另一条对象侧字符串记录扫描器;
  • 而且它不是单纯对对象 toString() 做扫描,而是显式尝试经 getAssetPath() 提取 APK asset path 后再参与记录匹配;
  • sub_7E4A4 -> sub_7EDD0 共享 sub_7CA84 这一匹配核心。

sub_78860

sub_78860 不是 APK 文件名扫描,而是 /proc/self/maps 侧的补充扫描入口。

当前可直接确认:

  • 它会用 sub_111740(1785) 匹配 /data/app/
  • 通过 sub_124528 / sub_12458C / sub_1247B0 遍历文本记录;
  • 对每条记录调用 sub_7CA84((__int64 *)line, 1)
  • 命中后同样走 sub_316D4 -> sub_2C74C 把结果封装写入对象。

因此它更像:

  • 安装路径 / APK 记录的 maps 侧枚举器;
  • sub_7E4A4 / sub_7DC28 / sub_7EDD0 一起,构成“Java 路径对象 + zip 文本记录 + /proc/self/maps”三路并行的 APK 记录发现链。

sub_74BE8

sub_74BE8 的语义已经可以从“上层包装器”进一步收紧。

它会:

  • 先构造一个本地结果对象 v15[0..1] = 0
  • 调用 sub_7E4A4(v15, 1)
  • 再取 sub_C1638() 返回的共享状态单例,并通过 sub_C4074(registry) 取出 registry + 0x38 处的当前字符串状态;
  • 使用 sub_81654(current_state, scanned_value) 做极轻量的有效性门槛判断;
  • 失败时会:
    • *(a1 + 48) 写成 11
    • sub_75774()
    • 再调 sub_C94C4(registry, scanned_value)

其中:

  • sub_C4074 本质是读 registry+0x38
  • sub_C94C4 会释放旧的 registry+0x38,把输入字符串经 sub_122ADC 变换后的结果写回,并无条件触发 sub_C9E60(registry)
  • sub_81654 只体现出空指针 / 首字节有效性的门槛,不应把它描述成真正的字符串内容比较器。

所以 sub_74BE8 当前最合理的归类是:

  • “扫描结果 -> 与当前共享状态做最低限度有效性门槛判断 -> 必要时刷新共享状态并设置错误/事件码”的状态发布器。

sub_C9E60

sub_C9E60 现在已经可以单列出来看。它不是简单的“写回后的尾收函数”,而是围绕 registry+0x38registry+0x18 和状态字节的环境落地器。

当前可直接确认:

  • caller 包括:
    • sub_C94C4
    • sub_32800
    • sub_5E624
    • sub_77340
    • sub_A7A34
    • sub_AD5A0
    • sub_12D3E8
  • callee 包括:
    • sub_CEABC
    • sub_115B34 / sub_115BC0
    • sub_1301DC / sub_1304A0
    • sub_123748
    • access
  • 它一开始会把 byte_1A4BFD1,然后以 a1 + 0xB0 作为一个布尔状态字节工作;
  • 会读取 a1 + 0x38 当前字符串,也会读取 a1 + 0x18 这一条额外路径/标识字符串;
  • 会通过 sub_CEABC(a1) 更新 a1 + 0xB1,而 sub_CEABC 又是 sub_CEE74(a1)sub_D0A30(a1) 之间的一次性门控包装器。

其中最关键的三类证据是:

  1. 六个硬编码包名名单
    sub_C9E60 会顺序取出:

    • com.playgame.havefun
    • com.meta.box
    • com.meta.box.shequ
    • com.sakura.show
    • com.jmdk.ant
    • com.sugar.youqu

    然后把 a1 + 0x38 当前字符串与这些包名逐一比较。当前最合理的解释是:

    • 这是一组已知宿主 / 渠道 / 容器相关包名白名单或特判名单;
    • 它至少说明 registry+0x38 在很多时候承载的是“包名或宿主标识”这一层语义;
    • 但现阶段还不能仅凭这 6 个值,把它唯一锁死为“渠道白名单”或“安装源黑白名单”。
  2. base.odex.apk 命中分支
    dword_171400 != 1sub_CEABC(a1) 未给出成功态时,sub_C9E60 还会把 a1 + 0x38 当前字符串与 base.odex.apk 比较。
    这说明它并不只关心纯包名,还会把 odex/apk 记录名纳入后续环境状态判断。

  3. a1 + 0x18 路径探针分支
    a1 + 0x18 非空,sub_C9E60 会用 sub_123748(name, sub_111740(1863), *(a1 + 0x18)) 组装一个路径,然后直接 access(name, 0)
    当前 1863 还没唯一解出,所以只能高置信地说:

    • 这里是“模板路径 + a1+0x18”的文件存在性探针;
    • 它确实不是抽象字符串处理,而是落到了真实文件路径探测;
    • 探测结果会影响 a1 + 0xB0 状态字节和最终返回值。

sub_CEABC 下游还补出了一条更细的候选链:

  • sub_D0A30 会对 sub_C7F50(a1) 返回的包名做多条路径模板拼接和 access(...)
  • 已能从相关字符串恢复出:
    • loadClass
    • (Ljava/lang/String;)Ljava/lang/Class;
    • com.tencent.tinker.lib.tinker.Tinker
    • com.tencent.tinker.loader.TinkerTestDexLoad

这说明 C9E60 一侧不只是“包名白名单”,还夹带了对特定宿主/补丁框架环境的可达性探针;当前最像的是:

  • 宿主包名 / APK 记录名 / 路径存在性 / 特定框架类痕迹
  • 四类信号汇总后的环境状态落地器。

sub_79970

sub_79970 的语义也已经能收紧:

  • 先调用 sub_7E4A4(a1, 1)
  • 读取 a1[1]
  • 若结果为空,则补一次 sub_7E4A4(a1, 0)
  • 最终不直接写注册表,只负责尝试两种模式把扫描结果填回同一个结果对象。

因此它更像:

  • 同一扫描框架的双模式重试入口;
  • mode=1 优先,失败时回退 mode=0

把这组函数连在一起后,当前已经能落成一条比“APK/sidecar 访问面”更细的业务链:

Java PathClassLoader / 路径对象
  -> sub_7E4A4
     -> getClassLoader / toString / String path
     -> sub_7EDD0
        -> 提取 zip/apk 记录片段
        -> sub_7CA84
           -> 匹配 base.apk / base.odex.apk / /data/app/ / lebian
        -> sub_316D4 + sub_2C74C
           -> 组装命中结果对象
  -> sub_7DC28
     -> getAssetPath
     -> sub_7CA84
        -> 另一条 asset path 记录匹配入口
  -> sub_74BE8
     -> 读取共享状态 registry+0x38
     -> sub_81654 有效性门槛
     -> 必要时 sub_C94C4 写回共享状态
        -> sub_C9E60
           -> 包名名单 / odex 记录 / 路径存在性探针

这条链虽然仍没有把 __tpinfo.* 明文文件名直接闭合到显式打开点,但已经能确认:

  • libtprt 不只是“知道 APK 字符串”;
  • 它确实在做 APK 安装路径、zip 文本记录、odex 相关记录以及共享状态刷新后的环境状态落地。

9.3 sidecar 元数据:__tpinfo.*

APK 当前带有:

文件 大小
assets/__tpinfo.tsd 3945
assets/__tpinfo.tsc 33708
assets/__tpinfo.tsd.sig 256
assets/__tpinfo.tsc.sig 256
assets/__tpcfinfo.tsa 272
assets/__tpsinfo.t.p.sin 67
assets/__tpinfo.tp 12
assets/tpginf.dat 8

其中 __tpinfo.tsd 内含 57 个 SHA-256 形态摘要。

当前已经补强的负证据:

  1. 不匹配当前已提取 dex / so / TP 资产 文件体的 SHA-256
  2. 不匹配 base.apk zip entry 解压后 payload 的 SHA-256
  3. 不匹配 base.apk zip entry 原始压缩 payload 的 SHA-256

与此同时:

  • libtprt.so
  • libtersafe.so

中都没出现:

  • __tpinfo.* 明文文件名
  • AAssetManager_fromJava / AAssetManager_open / AAsset_open / AAsset_getLength / AAsset_close

所以当前最稳妥的结论是:

  1. __tpinfo.* / *.sig / *.tsa / *.sin / tpginf.dat 确实属于 TP 资源侧 sidecar;

  2. libtprt 已经掌握 APK/ApkAssets 访问语汇;

  3. 但它的 sidecar 消费方式更像:

    • apk_path/base.apk/splitSourceDirs
    • zip 处理
    • getAssets/getApkAssets

    这条桥接链,

    而不是直接用 NDK AAsset* API 明文打开固定文件名。

从最新函数链看,这条桥接链现在可以进一步细化为:

  • PathClassLoader / 路径对象 -> toString -> zip 文本记录扫描
  • /proc/self/maps -> /data/app/... 记录扫描
  • 命中结果 -> 写入 registry+0x38 这类共享状态字段

因此,当前最稳妥的解释已经不只是“可能经由 zip / ApkAssets”:

  • 而是 libtprt 确实有一组围绕 APK 安装路径、主包名、odex 相关记录和共享状态的扫描/发布框架;
  • sidecar 更像挂在这套 APK 记录发现与状态同步框架之后的完整性消费面。

10. 环境检测与反分析面

当前已确认的环境/反分析能力包括:

  • /proc/self/maps 读取与扫描;
  • 映射地址、权限、偏移、设备号、inode 和路径采集;
  • 模拟器/兼容层路径检测;
  • sub_11D264 这一类环境检测分支;
  • sub_C9E60 / sub_CEABC / sub_D0A30 一侧的包名名单、Tinker 类痕迹与路径存在性探针;
  • ptrace / prctl / syscall / waitpid / sigaction / kill 等反调试或进程控制能力;
  • 失败即退的早退/退出逻辑;
  • gp7ioctl("stop") 在特定条件下触发 exit_group
  • 对早期敏感入口强插桩时,目标出现显著 detach/早退概率上升。

已解密的环境路径包括:

/init.vbox86.rc
/dev/socket/genyd
/data/data/com.tencent.tinput
/vendor/lib/libdrm.so.exagear
/system/lib/ld-android.so.exagear
/system/priv-app/TGPAServer/TGPAServer.apk.exagear

从动态导入面看,libtprt 还具备文件/目录操作、socket 建链与收发、fork/execv、进程调度和时间读写能力。这些导入证明库的能力边界,但不能单凭导入存在就断言每条路径都在当前启动流程中执行。

因此,当前可确认的是 libtprt 具备完整的运行环境判定和失败控制面;单项检测在本样本中的实际可达性仍需分别验证。


11. 当前最稳的完整业务图

libtprt 装载
  -> 执行 4 个 .init_array 构造器
  -> 进入 Unity/EGL 预安装链
  -> 为 libil2cpp DT_INIT 提供 53 项函数表

JNI / Java 宿主面
  -> JNI_OnLoad
     -> RegisterNatives(AceApplication, 7)
  -> AceApplication.attachBaseContextShell
     -> initialize(package/sourceDir/libDir/filesDir/splitSourceDirs)
  -> GP6Service / GP7Service / GP7Worker
     -> getStringExtra("ioctl")
     -> gp6ioctl / gp7ioctl

保护主链
  -> slot 1 受保护段恢复
  -> slot 29 私有重定位
  -> slot 30 导入描述符处理
  -> slot 31 保存原函数并写包装 thunk
  -> slot 50 写运行时注册表
  -> slot 32 调度隐藏初始化数组

共享状态 / 对外接口
  -> sub_C1638 / sub_D17F0 / sub_131C70 / sub_131DA4
  -> unwind_xx_info_query / unwind_xx_ioctl / tp_syscall_imp

APK / 资源 / 完整性侧
  -> apk_path / base.apk / splitSourceDirs
  -> zip file / getAssets / getApkAssets
  -> PathClassLoader / getClassLoader / toString / getAssetPath
  -> /data/app/ / base.apk / base.odex.apk / lebian
  -> 扫描结果写入 registry+0x38
  -> 包名名单 / 路径存在性探针 / 特定框架类痕迹
  -> __tpinfo.* / *.sig / *.tsa / *.sin / tpginf.dat

12. 当前未闭合边界

  1. slot 1 的精确恢复算法、密钥派生和调用前后字节差异;
  2. slot 29 每条 24 字节私有重定位记录的准确语义;
  3. slot 30 判断“合法跳板 / 异常 Hook”的精确规则;
  4. slot 31 最多 8 个保存槽与包装器的一一对应;
  5. handleLoad(...) / handleLoadV22(...) / ioctl(int, String) 的真实 caller;
  6. handleLoad* 在不同 Android 版本 / ABI 下最终命中的 art::DexFile::{Open,OpenMemory} overload,以及 qword_1A8490 的上下文类型;
  7. GP6/GP7 service 真正启动点来自反射、native 非明文构造还是晚加载组件,以及 enable_gp7_exit_group 的默认来源/写入者;
  8. unwind_xx_info_query / unwind_xx_ioctl 的 app 侧调用方、两槽字符串状态最终消费方与 qword_1A5230 的来源语义;
  9. __tpinfo.* 这组 sidecar 的精确打开点、摘要映射对象和签名覆盖范围;
  10. sub_C9E60 中六个包名、1863 路径模板以及 Tinker 类痕迹的最终业务语义;
  11. sub_78FB0 / sub_77340 / sub_7BB04 / sub_7E4A4 / sub_7EDD0 / sub_7CA84 / sub_74BE8 / sub_79970 / sub_78860 / sub_7DC28 这组 APK/ApkAssets/zip/安装路径扫描链的完整上下游;
  12. 2175 / 12322 两类非文本 payload 的真实结构。

13. 建议的 IDA 重命名

以下名称覆盖 libtprt 的加固、JNI、注册表、环境和 APK 业务。名称表达当前分析语义,不代表厂商原始符号。

原名 建议名称
sub_32800 tprt_process_protected_sections
sub_323E8 tprt_apply_encoded_relocations
sub_342C0 tprt_process_import_descriptor
sub_347C0 tprt_resolve_import_records
sub_358A0 tprt_write_resolved_import_slots
sub_38B74 tprt_install_saved_hook_thunks
sub_39CD8 tprt_call_hidden_init_array
sub_1361BC tprt_parse_runtime_command
sub_337E8 tprt_apply_embedded_patch
sub_120278 tprt_decode_relocation_records
sub_12021C tprt_process_encrypted_region
sub_15E618 flush_arm64_code_cache
sub_25AE0 jni_initialize
sub_136544 jni_handle_load_bytes_to_object
sub_1362F8 jni_handle_load_object_to_object
sub_136818 jni_handle_load_v22_bytes_to_long
sub_136D7C jni_ioctl_string_command
sub_25EA4 jni_gp7_ioctl
sub_26058 jni_gp6_ioctl
sub_C1638 get_tprt_shared_registry
sub_D17F0 tprt_registry_set
sub_1319D4 get_tprt_shared_registry_for_query
sub_131C70 tprt_registry_query
sub_131DA4 tprt_registry_set_exported_path
sub_128B74 get_unwind_ioctl_registry_bridge
sub_128BF0 write_unwind_ioctl_registry_value
sub_148F78 get_unwind_ioctl_string_slots
sub_148FFC set_unwind_ioctl_slot0
sub_149040 set_unwind_ioctl_slot1
sub_11D258 elf_flags_to_mprotect
sub_11D264 detect_emulator_or_compat_paths
sub_124528 open_proc_maps_reader
sub_12458C read_next_proc_maps_entry
sub_78FB0 tprt_get_java_asset_bridge
sub_77340 tprt_check_apk_and_environment
sub_7BB04 tprt_match_base_apk_name
sub_7E4A4 tprt_drive_pathclassloader_zip_scan
sub_7EDD0 tprt_scan_zip_or_apk_content
sub_7CA84 tprt_match_apk_install_record
sub_7DC28 tprt_scan_asset_path_record
sub_78860 tprt_scan_proc_maps_for_apk_record
sub_74BE8 tprt_publish_scanned_apk_state
sub_79970 tprt_retry_scanned_apk_state_with_mode_switch
sub_C9E60 tprt_finalize_apk_env_state

sub_342C0sub_358A0sub_38B74 的名称刻意保留“描述符/槽位/thunk”含义,避免把通用安装框架误限定为单一 Unity 或单一 API 专用函数。


14. 业务证据索引与分析工具

14.1 业务证据矩阵

结论 等级 主要证据
JNI_OnLoad 保存 JavaVM 并注册 7 个 native S JNI_OnLoadsub_12E888RegisterNatives(..., 7) 反编译
注册类为 com/ace/gshell/AceApplication,native 表在运行期构造 R/S qword_1A4BE8qword_1A4948 运行态内存与 .bss 布局
7 个 native 的名称、签名与 RVA 已恢复 R 运行态 JNINativeMethod[7] 与字符串
AceApplication 是 manifest application,initialize 是 attach 期必经入口 S manifest、dex 方法体与 xref
GP6Service / GP7Service / GP7Worker 通过 Intent extra "ioctl" 桥接命令 S manifest、dex 方法体与 xref
handleLoad* / ioctl(int, String) 在当前 4 个 dex 中无静态 Java caller S 4-dex 统一 xref 与指令级模式扫描
handleLoad* 解析 libart.soart::DexFile::{Open,OpenMemory} 多版本符号族 S 本地实现反编译与 sub_111740 解码
gp7ioctl 已恢复 start_service / start_worker / stop 命令 S sub_25EA4 反编译与解码字符串
TssSdk / TP2Sdk -> libtersafe 是独立宿主 API 面 S classes2.dex 字节码与 caller 分析
sub_C1638 / sub_D17F0 构成带锁共享字符串注册表 S 单例初始化、树查找/插入与对称查询逻辑
unwind_xx_info_query / unwind_xx_ioctl 分别提供注册表读取和状态写入 S 导出符号与分派路径反编译
当前 APK 自带 so 中未见 unwind_xx_* / tp_syscall_imp 的第二条直导入链 S 58 个原生库依赖表与符号扫描
libtprt 掌握 APK/ApkAssets/zip 与多条资源路径语汇 S sub_111740 支持 bucket 全量离线扫描
APK/zip/asset-path 扫描链已挂回共享状态写入和环境落地 S/H caller/callee、局部伪代码与恢复字符串
APK 打包 __tpinfo.* / *.sig / *.tsa / *.sin / tpginf.dat TP sidecar S APK 资源清单与二进制摘要
__tpinfo.tsd 的 57 个摘要不匹配已提取文件体及 zip entry 两种 payload S 离线双形态哈希比对

14.2 当前分析工具

文件 作用
frida-il2cpp-bridge/example/tprt-string-dump.js dump 运行期解码字符串、JNI 类名和 JNINativeMethod[7]
frida-il2cpp-bridge/example/tprt-offline-string-decode.py 离线解码 sub_111740 字符串记录
frida-il2cpp-bridge/example/tprt-apk-host-analysis.py 分析 manifest、4-dex caller、GShell 触发面、动态加载候选与 libtersafe 边界
frida-il2cpp-bridge/example/tprt-init-trace.js 采集装载期槽位、状态与隐藏数组时序
frida-il2cpp-bridge/example/trace-tprt-init.py 冷启动、附加并将 trace 落盘
frida-il2cpp-bridge/example/summarize-tprt-trace.py 汇总 JSONL、slot 50 命令和状态差分

15. 本文与加固专项稿的边界

本文负责 libtprt 的完整业务事实和跨层关系,包括 JNI/Java 宿主、本地 native、共享状态、对外接口、APK/sidecar 与环境检测。

加固专项稿只负责:

  • libunity 提前 Hook 的精确时序;
  • libil2cpp.init_proc 的槽位调用、返回极性与失败路径;
  • 受保护段、私有重定位、导入安装和隐藏初始化数组;
  • hidden 0/1/2 的恢复与执行边界。

两份文档发生交叉时,按以下规则维护:

  1. libtprt 完整业务结论只在本文扩展;
  2. 加固链实现细节只在专项稿扩展;
  3. 交叉部分保留摘要和链接,不复制完整章节。

评论

  • 还没有评论,来说点什么吧。

无需注册或登录,填个昵称即可评论。