`libtprt.so` 完整业务分析文档
文档状态:独立正式稿 /
libtprt完整业务唯一归口
整理日期:2026-07-14
目标进程:com.tencent.rmcn
目标架构:Android / AArch64
分析对象:libtprt.so,以及它与libil2cpp.so、libunity.so、Java 宿主层、APK 资源侧的交互
证据来源:IDA 静态逆向、MiniMem 只读快照、Frida 冷启动时序、APK manifest/dex 离线分析
本文是 libtprt 完整业务的唯一归口,集中维护以下内容:
JNI_OnLoad、JNINativeMethod[7]、AceApplication与GP6/GP7宿主流程;initialize / handleLoad* / ioctl / gp6ioctl / gp7ioctl的本地业务;- 共享注册表、
unwind_xx_*、tp_syscall_imp等对外接口; - APK/zip/ApkAssets、sidecar、环境检测与反分析业务;
libtprt在libil2cpp装载期加固链中的职责概览。
libil2cpp.init_proc 的槽位级时序、Unity/EGL 预安装、隐藏初始化数组恢复边界和失败极性不在本文重复展开,以加固专项稿为准:
- libil2cpp_init_proc_与_libtprt_完整加固业务流程分析.md
两份文档按“完整业务归本文、加固实现归专项稿”并行维护,不再把本文的完整 JNI/宿主流程回并到加固专项稿。
1. 结论先行
libtprt.so 不是一个“只在 init_proc 里被动执行几次回调”的窄保护库,而是一个同时覆盖以下业务面的运行时组件:
装载期保护核心
- 在
libil2cpp.so的DT_INIT主链里提供 53 项函数表,完成受保护段恢复、私有重定位、导入完整性处理、Hook 安装、运行时注册表写入、隐藏初始化数组调度与可选补丁恢复。
- 在
Java/JNI 宿主面
- 通过
JNI_OnLoad -> RegisterNatives(..., 7)挂到com/ace/gshell/AceApplication,暴露initialize / handleLoad / handleLoadV22 / ioctl / gp6ioctl / gp7ioctl这一组 native 入口。
- 通过
宿主服务控制面
- 借
GP6Service / GP7Service / GP7Worker三个 manifest 级 service 接受Intentextra"ioctl",把字符串命令桥接到gp6ioctl / gp7ioctl。
- 借
共享运行时状态中心
- 通过
sub_C1638 / sub_D17F0 / sub_131C70 / sub_131DA4维护一个可跨模块复用的带锁字符串注册表;slot 50、initialize、unwind_xx_*都会读写它。
- 通过
对外控制/查询面
- 除
JNI_OnLoad外,还导出tp_syscall_imp、unwind_xx_info_query、unwind_xx_ioctl三类接口,对外提供 syscall 桥接、注册表读取和两槽字符串状态写入能力。
- 除
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_OnLoad、g_tprt_pfn_array、g_tprt_ori_array、tp_syscall_imp、unwind_xx_info_query、unwind_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
其建立过程不是一次性完成,而是分三段:
init_proc前的 Unity 预处理;slot 29私有重定位;slot 30导入安装链。
5. 装载期保护业务
5.1 libunity/EGL 预安装链先于 libil2cpp.init_proc
这是整个 libtprt 业务定位里最重要的时序修正。
稳定结论:
- Unity/EGL 最终 Hook 首次落地早于
libil2cpp.so:DT_INIT; libtprt+0x358A0在init_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/1在call_constructors-enter(libil2cpp)时还未恢复,但到slot32-enter前已经变成合法 AArch64 代码; - 恢复边界已被压缩到
init_proc内的 pre-slot32 窗口。
这说明 libtprt 不只是改几个 GOT 项,而是还掌管一组隐藏构造器的恢复与调度。
6. Java/JNI 宿主与本地业务面
6.1 JNI_OnLoad:对象 VM / native 注册引导
JNI_OnLoad(libtprt.so+0x2641C)不是空壳。静态反编译表明它至少完成以下动作:
- 先触发一次
__ENABLE_OBJ_VM__相关路径; - 调用
GetEnv(vm, &env, JNI_VERSION_1_6); - 通过
sub_12E888(vm)把JavaVM *保存到全局; - 懒解码并缓存一个运行时类名字符串(
sub_C11A4,缓存到qword_1A4BE8); - 通过
JNIEnv解析该类对象; - 以固定数量
7调用RegisterNatives(env, clazz, ..., 7); - 若类解析失败、注册失败或检测到异常,则走清理路径并返回
-1;成功时返回JNI_VERSION_1_6(65540)。
6.2 运行时类名与 JNINativeMethod[7]
后续通过 root 读取 /proc/<pid>/mem,可以直接补齐 JNI_OnLoad 的两项关键运行态证据:
qword_1A4BE8在运行时被填为一条带 top-byte tag 的堆指针;去掉 tag 后可读出:
com/ace/gshell/AceApplication
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 注册结果形成交叉支撑。
继续用 androguard 对 AndroidManifest.xml 与 classes*.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")
这条链现在有三组直接证据:
AndroidManifest.xml把com.ace.gshell.AceApplication指定为宿主application,并声明了 3 个com.ace.gshellservice:service android:processexportedenabledcom.ace.gshell.GP6Service:GP6Servicetruetruecom.ace.gshell.GP7Service:GP7Servicetruetruecom.ace.gshell.GP7Worker:GP7Workertruetrue这说明 GP6/GP7 并不是普通 helper 类,而是 manifest 级、独立进程的 service 包装器。
AceApplication的 dex 字节码不只是声明 native,还显式承担启动包装逻辑:<clinit>先System.loadLibrary("tersafe"),再System.loadLibrary("tprt"),随后初始化一个静态memoryLoaders列表;attachBaseContext(context)的唯一职责就是转调attachBaseContextShell(context);attachBaseContextShell(context)会从ApplicationInfo/Context中提取:packageNamenativeLibraryDirsourceDirfilesDir.getAbsolutePath()- 以及
SDK_INT >= 21时反射取得的splitSourceDirs
- 然后以这些值直接调用 native
initialize(...);若返回值非0,它会抛出Exception("initialize failed.")。
因而
initialize不只是“存在于 native 表中的一个入口”,而是应用 attach 期必经的真实宿主启动点。GP6/GP7三个 service 的方法体和 xref 已经把gp6ioctl/gp7ioctl的 Java 包装面闭合:GP6Service.onStartCommand(Intent, int, int):读取intent.getStringExtra("ioctl"),非空则直调AceApplication.gp6ioctl(...);GP7Service.onStartCommand(...)与GP7Worker.onStartCommand(...):同样读取Intentextra"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/GP7Worker的onStartCommand(...)与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组件字符串在普通方法体中的引用。 AceApplication、GP6Service、GP7Service、GP7Worker这 4 个类的const-class与new-instancexref 都为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_path、shell_ver、lib_dir、files_dir; - 还会写入固定版本串
4.9.31.51305_cn; - 通过
sub_E8870把pf_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 对象返回。
- 先把 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),不是 JavaObject。
- 与
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中同时存在GP7Service与GP7Worker类名,高置信可把这组命令理解为围绕 GP7 服务/worker 宿主组件的控制语汇; - 因而它不是泛化字符串日志口,而是显式的服务/worker/stop 控制通道。
gp6ioctl(String) -> void- 先取一个懒初始化单例(
sub_128714); - 该单例对象带显式一次性初始化位
+0x10,首次会走sub_128824做延迟安装; - 若该单例里已经装有回调,则把 Java 字符串转成本地字符串并透传给该回调;
- 结合
classes.dex中GP6Service的存在,高置信可把它理解为 GP6 宿主服务侧的命令桥接入口; - 更像一条“把字符串命令转交给已注册 handler”的桥接入口。
- 先取一个懒初始化单例(
因此,JNI_OnLoad 下挂的 7 个 native 足以说明:libtprt 对 Java/宿主层暴露的业务,不只是初始化一次保护状态,还包括载荷/对象加载、文件型状态控制、字符串命令控制和 gp6/gp7 两条分发桥。
但从控制流上,JNI_OnLoad 作为“保存 JavaVM + 解析类 + 注册 7 个 native”的入口,以及这些 native 的总体职责分层,现在都已经有静态 + 运行态直接证据支撑。
6.4 与 TssSdk / TP2Sdk(libtersafe)宿主链的边界
在同一 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字段,再直调 nativegetsdkantidata(...);- 已直接命中的典型命令串包括:
dec_tss_info:%sEnableGameReportSetLocaleId:%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 状态标签 |
需要单独保留的边界是:2175 与 12322 当前能离线解出 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 的方法体做了两轮模式扫描:
- 以“
com/ace/gshell或GP6/GP7相关字面量”为主键; - 以“
ioctl+Intent构造/putExtra/getStringExtra/startService/setClassName/setComponent”为组合特征。
结果是:
- 命中的只有 9 个方法,全部都是已知包装面:
AceApplication.<clinit>AceApplication.attachBaseContextAceApplication.attachBaseContextShellGP6Service.onDestroy / onStartCommandGP7Service.onDestroy / onStartCommandGP7Worker.onDestroy / onStartCommand
classes2.dex / classes3.dex / classes4.dex在这组 gshell/ioctl触发模式下都没有新增命中。
这把 Java 普通静态路径的结论进一步压实为:当前 APK 已打包 dex 中,确实只看得到 AceApplication 与 GP6/GP7 这层包装器,看不到第二条“显式构造目标 service 并填入 "ioctl" 参数”的常规 Java 明文链。
但“晚加载代码”现在也不再只是抽象可能。classes2.dex 中可直接确认存在一整套腾讯 X5/TBS 的动态加载基础设施:
com.tencent.smtt.export.external.DexClassLoaderProvidercom.tencent.smtt.export.external.DexLoadercom.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。
当前已确认其职责包括:
- 建立初始化上下文;
- 写入共享注册表;
- 写入版本和路径键;
- 生成本地状态缓冲;
- 触发后续较长的内部初始化链。
已恢复的核心键和值:
| 键/字段 | 来源 |
|---|---|
apk_path |
sourceDir/base.apk 相关 |
shell_ver |
固定版本串 |
lib_dir |
nativeLibraryDir |
files_dir |
filesDir |
pf_init |
本地状态标签 |
4.9.31.51305_cn |
固定版本串 |
此外:
217512322
这两项当前更像非文本模板/二进制标签,而不是普通字符串键。
8. 共享注册表与对外导出接口
8.1 共享注册表
当前可以把:
sub_C1638sub_D17F0sub_131C70sub_131DA4
理解为 libtprt 的共享运行时状态中心。
它至少服务于:
initializeslot 50unwind_xx_info_queryunwind_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:写字符串槽 0op == 4:写字符串槽 1op == 2:把qword_1A5230格式化后桥接进注册表
所以它本质是:
- 两槽字符串状态写入
- 一个全局值写入注册表
8.5 tp_syscall_imp
tp_syscall_imp 是对外 syscall 桥接器,不是只在 init_proc 内部使用的普通 helper。
它负责:
- 系统调用转发;
errno/ 负值语义整理。
8.6 外部调用边界
当前 IDA 调用图中,tp_syscall_imp、unwind_xx_info_query、unwind_xx_ioctl 都没有 libtprt 内部 caller;libil2cpp.so 与 libunity.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
内部会动态取出:
getAssetsgetApkAssets
以及配套签名字符串。
这说明它具备经 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.PathClassLoadergetClassLoader()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 当前可以明确看出两层行为:
- 它会对输入字符串做显式子串/模式扫描;
- 命中后会把片段抽出、封装,再交给
sub_7CA84(...)做进一步判定。
已直接恢复出的关键字符串包括:
zip filebase.apk
其中一个稳定路径是:
... -> 截取双引号中的片段
-> sub_7CA84(extracted_text, mode & 1)
-> 若命中则经 sub_316D4 / sub_2C74C 把结果写入目标对象
因此,sub_7EDD0 不只是“看一眼路径里有没有 apk”:
- 它更像 zip/apk 文本记录扫描器;
- 会从上游传入的路径/描述字符串里提取片段;
- 再把命中记录转成结构化结果对象,交给上层继续消费。
sub_7CA84
sub_7CA84 现在已经能从“共享匹配器”进一步收紧为“APK 记录名匹配器”。
可直接确认的比较目标包括:
base.apkbase.odex.apklebian/data/app/
这里的 base.apk / base.odex.apk / /data/app/ 很像 Android 安装目录 / APK 主包 / odex 相关记录匹配;
lebian 则更像额外的内部标记词或特殊记录名。
从控制流上看,它会:
- 对输入字符串做逐字节比较;
- 在
dword_171400 == 1等条件下切换分支; - 同时被:
sub_7EDD0sub_7DC28sub_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+0x38、registry+0x18 和状态字节的环境落地器。
当前可直接确认:
- caller 包括:
sub_C94C4sub_32800sub_5E624sub_77340sub_A7A34sub_AD5A0sub_12D3E8
- callee 包括:
sub_CEABCsub_115B34 / sub_115BC0sub_1301DC / sub_1304A0sub_123748access
- 它一开始会把
byte_1A4BFD置1,然后以a1 + 0xB0作为一个布尔状态字节工作; - 会读取
a1 + 0x38当前字符串,也会读取a1 + 0x18这一条额外路径/标识字符串; - 会通过
sub_CEABC(a1)更新a1 + 0xB1,而sub_CEABC又是sub_CEE74(a1)与sub_D0A30(a1)之间的一次性门控包装器。
其中最关键的三类证据是:
六个硬编码包名名单
sub_C9E60会顺序取出:com.playgame.havefuncom.meta.boxcom.meta.box.shequcom.sakura.showcom.jmdk.antcom.sugar.youqu
然后把
a1 + 0x38当前字符串与这些包名逐一比较。当前最合理的解释是:- 这是一组已知宿主 / 渠道 / 容器相关包名白名单或特判名单;
- 它至少说明
registry+0x38在很多时候承载的是“包名或宿主标识”这一层语义; - 但现阶段还不能仅凭这 6 个值,把它唯一锁死为“渠道白名单”或“安装源黑白名单”。
base.odex.apk命中分支
当dword_171400 != 1且sub_CEABC(a1)未给出成功态时,sub_C9E60还会把a1 + 0x38当前字符串与base.odex.apk比较。
这说明它并不只关心纯包名,还会把 odex/apk 记录名纳入后续环境状态判断。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.Tinkercom.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 形态摘要。
当前已经补强的负证据:
- 不匹配当前已提取
dex / so / TP 资产文件体的SHA-256; - 不匹配
base.apkzip entry 解压后 payload 的SHA-256; - 不匹配
base.apkzip entry 原始压缩 payload 的SHA-256。
与此同时:
libtprt.solibtersafe.so
中都没出现:
__tpinfo.*明文文件名AAssetManager_fromJava / AAssetManager_open / AAsset_open / AAsset_getLength / AAsset_close
所以当前最稳妥的结论是:
__tpinfo.* / *.sig / *.tsa / *.sin / tpginf.dat确实属于 TP 资源侧 sidecar;libtprt已经掌握 APK/ApkAssets 访问语汇;但它的 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. 当前未闭合边界
slot 1的精确恢复算法、密钥派生和调用前后字节差异;slot 29每条 24 字节私有重定位记录的准确语义;slot 30判断“合法跳板 / 异常 Hook”的精确规则;slot 31最多 8 个保存槽与包装器的一一对应;handleLoad(...) / handleLoadV22(...) / ioctl(int, String)的真实 caller;handleLoad*在不同 Android 版本 / ABI 下最终命中的art::DexFile::{Open,OpenMemory}overload,以及qword_1A8490的上下文类型;GP6/GP7service 真正启动点来自反射、native 非明文构造还是晚加载组件,以及enable_gp7_exit_group的默认来源/写入者;unwind_xx_info_query / unwind_xx_ioctl的 app 侧调用方、两槽字符串状态最终消费方与qword_1A5230的来源语义;__tpinfo.*这组 sidecar 的精确打开点、摘要映射对象和签名覆盖范围;sub_C9E60中六个包名、1863路径模板以及 Tinker 类痕迹的最终业务语义;sub_78FB0 / sub_77340 / sub_7BB04 / sub_7E4A4 / sub_7EDD0 / sub_7CA84 / sub_74BE8 / sub_79970 / sub_78860 / sub_7DC28这组 APK/ApkAssets/zip/安装路径扫描链的完整上下游;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_342C0、sub_358A0、sub_38B74 的名称刻意保留“描述符/槽位/thunk”含义,避免把通用安装框架误限定为单一 Unity 或单一 API 专用函数。
14. 业务证据索引与分析工具
14.1 业务证据矩阵
| 结论 | 等级 | 主要证据 |
|---|---|---|
JNI_OnLoad 保存 JavaVM 并注册 7 个 native |
S | JNI_OnLoad、sub_12E888、RegisterNatives(..., 7) 反编译 |
注册类为 com/ace/gshell/AceApplication,native 表在运行期构造 |
R/S | qword_1A4BE8、qword_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.so 的 art::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的恢复与执行边界。
两份文档发生交叉时,按以下规则维护:
libtprt完整业务结论只在本文扩展;- 加固链实现细节只在专项稿扩展;
- 交叉部分保留摘要和链接,不复制完整章节。
评论
- 还没有评论,来说点什么吧。