首页
社区
课程
招聘
[原创]Frida 脚本运行时机与Java.perform的原理
发表于: 2026-9-7 11:40 23517

[原创]Frida 脚本运行时机与Java.perform的原理

2026-9-7 11:40
23517

《凡人修仙传之 - Android 逆向开发》· 03 动态插桩功法 · 技能 F02

前置:F01 Frida 整体启动、进程注入与 Zygote 门控

后续:F03 server IPC;F06 Stalker;01 炼气境 · 第 10 课《Android Java Hook 原理》

源码锚点:frida-core / frida-gum 17.9.1;frida-tools 14.8.0;frida-java-bridge 固定提交 f72e61ed18fa6b72f0559df223b22a899685b22c;AOSP frameworks/base 固定提交 6e47c7075b91983ae501114425ea25e6df7690c8;AOSP ART / libnativehelper 固定 tag android-14.0.0_r1

进度:已打通脚本 top-level、Android LoadedApk、JavaVM/JNIEnv、Java.perform()、ART ClassLoader 枚举与指定 ClassFactory Hook 的完整源码链;待补插件双 ClassLoader 真机记录

词库按“脚本执行、JNI 环境、类加载命名空间”三段排列。带 ★ 的对象决定本课主时间线。

“Frida 第一个脚本运行时机”指的是用户脚本的第一行 JavaScript,不是最早进入进程的 Frida 原生代码。后者是 F01 已讲过的 zymbiote、loader 和 agent 初始化;用户 JS 要等 Script.load() 才执行。

对 Android 普通、无 instrumentation 的早期 spawn,主线顺序是:

这条时间线只有三个需要分别判断的边界:

后文只沿这三个边界展开。

源码直达:client Session.create_script() · client Script.load() · agent create/load 入口 · ScriptEngine.create_script() · ScriptInstance.load() · QuickJS backend create · QuickJS load / JS_EvalFunction() · JS scheduler

脚本生命周期由 client 发起,由目标进程内的 agent 执行。frida-server 负责会话路由,不负责运行 JavaScript [1][2]。

client 侧先创建,再加载 [1]:

active_session 是远端 AgentSession 的代理。第一段调用返回 script_id,client 据此建立一个可控制的 Script 对象;第二段才要求 agent 加载这个实例。

agent 侧的 create 路径把源码交给 ScriptEngine,选择 backend,创建 Gum.Script 并保存实例 [2][3]:

QuickJS 的 backend create 会建立 runtime/context,并解析或编译源码 [4]:

所以语法错误可能在 create 阶段出现,但这不等于用户顶层代码已经执行。create 完成时,脚本状态仍是 CREATED。

load 路径从实例表取回脚本并推进状态 [3]:

QuickJS 不在控制请求线程上直接执行用户代码,而是把 load 任务交给 JS scheduler [5]:

JS 线程中的 load 最终求值 entrypoint:

V8 对应路径使用 module Evaluate() 或 script Run();实现函数不同,但 create 与 load 的边界相同 [6]。上游 17.9.1 的 scheduler 创建 gum-js-loop 后台线程并运行 GLib main loop [7]:

因此这部分只需记住一条源码链:

LOADED 只说明顶层初始化完成。Interceptor.attach() 可以已经安装,但它的回调仍要等目标控制流经过 Hook 点;Java.perform() 也可能仍在等待 App ClassLoader。

源码直达:frida-tools application.py · repl.py · F01 · resume 放行

frida -U -f TARGET_PACKAGE -l probe.js 在工具内部不是一个不可分割的动作,而是下面的固定次序 [8]:

F01 的 zymbiote 此时只阻塞 App 主线程。agent 已经拥有自己的控制线程和 JS scheduler,所以它能在 App 主线程尚未进入 ActivityThread.main() 时执行 top-level。

这也是 spawn 模式能提前安装 Hook 的原因:

--pause 只让 frida-tools 跳过最后的自动 resume()。它不暂停 agent,也不阻止 Script.load():

已有进程的 attach 不经过这段 spawn 门控。目标进程已经运行到哪里,取决于 attach 时的真实状态;不能把“top-level 早于 Application”从 spawn 直接推广到 attach。

send() 和 console.log() 还要经过 agent 消息队列、控制通道与主机回调。终端显示时间晚于代码执行时间,适合证明阶段是否到达,不适合推断进程内的精细时间差 [9]。

理解 Java.perform() 之前,必须先建立 Android 自身的装载基线:

下一节再解释 Frida 在哪个返回点接入这条链。

源码直达:AOSP ActivityThread.java · LoadedApk.java · ApplicationLoaders.java · ContextImpl.java · AppComponentFactory.java

源码直达:AOSP ActivityThread.main() · ActivityThread.attach() · ApplicationThread.bindApplication() · H.handleMessage() · ActivityManagerService.attachApplicationLocked()

App 主线程进入 ActivityThread.main() 后准备主 Looper,并把本进程的 ApplicationThread Binder 对象交给 system_server [13][18]:

attach(false, startSeq) 的非 system 分支调用:

system_server 的 attachApplicationLocked() 随后跨 Binder 调用 thread.bindApplication(...)。App 进程的 ApplicationThread.bindApplication() 保存关键参数并向主线程发消息 [13][18]:

主线程的 H.handleMessage() 收到消息后调用:

handleBindApplication() 才是建立目标包运行状态、创建 Application 和安装 Provider 的主线程入口。

源码直达:AOSP ActivityThread.handleBindApplication() · ActivityThread.getPackageInfo() · LoadedApk 构造函数 · LoadedApk.setApplicationInfo()

handleBindApplication() 先把绑定数据保存到 mBoundApplication,再请求一份包含代码的包状态 [13]:

AppBindData.appInfo 是 PackageManager 解析出的 ApplicationInfo。getPackageInfo() 先按包名查询 mPackages 缓存;没有可复用对象时创建 [13]:

LoadedApk.setApplicationInfo() 把包路径分开保存 [14]:

此时得到的是“当前进程中目标包的运行时档案”。它已经知道 base APK、split APK、资源、数据和 native 库路径,但不表示所有 dex 已打开,也不表示业务类已经逐个加载。

源码直达:AOSP LoadedApk.createOrUpdateClassLoaderLocked() · LoadedApk.getClassLoader() · ApplicationLoaders · AppComponentFactory.instantiateClassLoader()

LoadedApk.getClassLoader() 检查最终字段 mClassLoader [14]:

createOrUpdateClassLoaderLocked() 先由 makePaths() 汇总:

这些路径交给 ApplicationLoaders,产生默认应用 loader [14][15]:

ApplicationLoaders 在未传入父 loader 时,以 system class loader 的 parent 作为基础父节点,再通过 ClassLoaderFactory.createClassLoader() 创建包含 APK 路径的 loader [15]:

这解释了为什么应用 loader 的父链中能看到 bootstrap loader:它是父节点,不是 Frida 最终保存的对象。

最后,AppComponentFactory 有机会保留或替换默认 loader [14][16]:

默认 AppComponentFactory.instantiateClassLoader() 返回传入的 mDefaultClassLoader;应用也可通过 manifest 配置自定义 factory。真正返回给 bridge 的是最后的 mClassLoader:

源码直达:AOSP ActivityThread.currentApplication() · ActivityThread.handleBindApplication() · LoadedApk.makeApplicationInner() · Instrumentation.newApplication()

ActivityThread.getPackageInfo() 返回 LoadedApk 后,Android 才继续创建 App Context 与 Application。这个返回点因此位于最终 loader 可创建、Application 尚未创建的中间位置;下一节的 Frida early hook 正是接在这里。

ActivityThread.currentApplication() 读取的正是 mInitialApplication [13]:

Android 从 getPackageInfo() 返回后,尚未走到 mInitialApplication = app。接下来的源码顺序如下。

先创建 App Context [13]:

再创建 Application [13]:

随后保存 mInitialApplication,安装 Provider,最后调用 Application.onCreate() [13]:

makeApplicationInner() 自己也使用同一个最终 loader [14]:

因此时序不是“先创建 Application,再产生 loader”,而是:

Android 的自然主线最迟会在 makeApplicationInner() 中取得最终 loader。getPackageInfo() 返回点更早,并且返回的 LoadedApk 已经具备按需创建 loader 所需的全部路径;下一节会看到 Frida 正是在这个间隙主动调用 apk.getClassLoader()。

源码直达:AOSP ActivityThread.performLaunchActivity() · ContextImpl.createActivityContext() · AppComponentFactory.instantiateActivity()

普通同包 Activity 启动时,performLaunchActivity() 取得或复用同一包的 LoadedApk,再从 Activity Context 取 loader [13]:

所以主包默认路径是:

隔离 split 会通过 getSplitClassLoader() 使用专用 loader;插件、加固和热更新也能在默认链之外创建 DexClassLoader、PathClassLoader 或自定义 loader。主包 loader 成功不代表这些独立命名空间已经可见。

top-level 开始执行,只证明 GumJS 已经运行。bridge 初始化与 Java.perform() 调用按下面两层推进:

源码直达:AOSP libnativehelper jni.h:JNIEnv 定义 · JNIInvokeInterface / JavaVM · Android Developers · JNI Tips

三个对象解决的问题不同:

AOSP jni.h 中,JNIEnv 是 JNI 函数表包装 [22]:

JavaVM 则持有 invocation table,其中包含线程相关入口 [22]:

所以“拿到 JavaVM”“当前线程拿到 JNIEnv”“选对业务 ClassLoader”是三道独立条件。JavaVM 已存在时,Frida 的 JS 线程仍可能尚未附加;JNIEnv 已取得时,默认 App loader 仍可能为空。

必须讲清 JNIEnv,是因为 frida-agent 的 GumJS scheduler 本质上从 native 线程执行脚本。它不能只拿一个进程级 JavaVM 指针就直接调用 Java API:每次进入 JNI 都要先确认当前 tid 对应的 JNIEnv。必须讲清 ClassLoader,则是因为 JNIEnv 只提供调用接口,不会替 Frida 决定目标类位于主 APK、隔离 split 还是插件 dex。

源码直达:frida-java-bridge lib/api.js · lib/android.js:getApi() 与 libart/libdvm 识别 · JNI_GetCreatedJavaVMs 取 VM · index.js::_tryInitialize()

lib/api.js 先按运行环境选择 Android 或标准 JVM backend [20]:

Android backend 枚举模块,只接受真实的 libart.so 或 libdvm.so [20]:

解析 JNI_GetCreatedJavaVMs 后,bridge 请求当前进程已经创建的 VM,并保存第一个 JavaVM* [20]:

Runtime._tryInitialize() 再用这个指针创建 VM 包装,并初始化 Android bridge 与 ClassFactory [10]:

到这里仅完成了“发现进程 JavaVM”。classFactory.loader 仍是 null,当前 Frida 线程也要通过下一步确认自己是否已有 JNIEnv。

源码直达:frida-java-bridge lib/vm.js:JavaVM vtable · VM.perform() / attach / GetEnv · env 嵌套缓存与清理 · AOSP jni.h invocation table

VM 构造函数从 JavaVM* 的 invocation table 取出三个函数。索引 4、5、6 正好对应 AOSP jni.h 中的 AttachCurrentThread、DetachCurrentThread、GetEnv [11][22]:

VM.perform() 的完整执行主线如下 [11]:

它调用的查询与附加函数也是 bridge 自己从 vtable 绑定的 [11]:

嵌套调用通过 activeEnvs 按 tid 复用同一份 wrapper [11]:

准确顺序是:

这条路径没有调用 ThreadList::SuspendAll。固定 index.js 中 withAllArtThreadsSuspended() 的调用点位于 _enumerateClassLoadersArt(),不在 perform() 或 VM.perform() 中 [21]。取得 JNIEnv 是当前线程与 JavaVM 的关系;全线程暂停发生在 §4.10 的 ART ClassLoader 枚举中。

源码直达:frida-java-bridge index.js::perform() · Runtime._isAppProcess()

Java.perform(fn) 在 JNIEnv 之外还处理 Android App 默认 ClassLoader 的时机 [10]:

只有队列从 0 变成 1 时才安装等待 Hook;后续 Java.perform() 只继续入队。_isAppProcess() 则通过 /proc/self/exe 是否匹配 /system/bin/app_process 判断并缓存结果 [10]。

因此 Java.perform() 是两层组合:

源码直达:frida-java-bridge index.js::_performPendingVmOpsWhenReady() · _performPendingVmOps() · AOSP ActivityThread.handleBindApplication()

下面是固定 bridge 提交 index.js:400-454 的完整实现 [10]:

这段等待逻辑没有定时器,也没有 sleep。它先检查当前 Application,若仍为空,就通过 Frida Java 方法替换安装三个可能的 Hook 点:

此时 ClassFactory.loader 虽然为空,factory.use('android.app.ActivityThread') 仍可工作,因为它是 framework 类;空 loader 分支会走 JNI FindClass。业务 APK 类此时仍不具备同样的可见性,具体分支见 §4.11。

安装完 Hook 后,最初那次 _performPendingVmOpsWhenReady() 就返回。用户的 fn 仍在 _pendingVmOps 中,top-level 可以继续执行;只有 Android 后续真正调用这些方法,队列才会被清空。

固定源码没有在 initialized = true 后恢复这几个 .implementation。这些 wrapper 仍保留,但 initialized 阻止重复初始化和重复清空队列,wrapper 随后继续调用原方法。

源码直达:frida-java-bridge 晚注入分支与 initFactoryFromApplication() · initFactoryFromApplication() · AOSP Application.attach() · ContextWrapper.getClassLoader() · ContextImpl.getClassLoader()

如果进入 _performPendingVmOpsWhenReady() 时:

已经返回非空对象,bridge 不安装上述 Hook,而是立即执行 [10]:

对应初始化函数是:

Application.getClassLoader() 经 ContextWrapper → ContextImpl → LoadedApk.getClassLoader() 返回目标包的最终 loader [14][17]。赋值完成后,pending 队列立即执行。

这条委托链在 AOSP 中是直接代码,不是概念推导 [17]:

源码直达:frida-java-bridge early getPackageInfo hook · initFactoryFromLoadedApk() · AOSP ActivityThread.getPackageInfo()

spawn 早期 currentApplication() 为空,bridge 已经安装前面的 Hook。客户端 resume() 后,Android 按 §3.1 的顺序在主线程进入 handleBindApplication(data),并按 §3.2 调用参数最多的 getPackageInfo(...)。Frida 此时已经在等待这个调用。

bridge 把所有 getPackageInfo overload 按参数数量降序排列:

固定 Android 14 中,排在第一位的正是 handleBindApplication() 使用的最长 overload。它的 replacement 先调用原方法:

原方法查询 mPackages 缓存;未命中时创建目标包的 LoadedApk 并返回。返回值首先回到 Frida replacement,所以此刻 Android 已经有 LoadedApk,但 handleBindApplication() 还没有继续创建 Application。

随后 replacement 执行:

initFactoryFromLoadedApk() 的完整实现是 [10]:

关键语句不是枚举 ClassLoader,也不是读取线程 context loader,而是直接调用:

如果 LoadedApk.mClassLoader 仍为空,Android 会在这次调用内部执行 createOrUpdateClassLoaderLocked();如果此前已创建,则直接返回同一个最终 loader。§3 已经展开这个 Android 内部过程。

loader 和缓存目录都设置后,bridge 立即调用 _performPendingVmOps() [10]:

shift() 表明回调按入队顺序逐个取出。每个回调再次经过 vm.perform(fn),确保当前触发 Hook 的线程具有合法 JNI 环境;异常被转交到下一轮 JS 调度。

普通早期路径的精确执行点因此是:

在这条固定实现路径上,pending 回调由 App 主线程调用 getPackageInfo() 时同步触发,所以本次回调会落在 App 主线程上;这是当前 Hook 位置产生的结果,不是 Java.perform() 的 API 线程承诺。

源码直达:frida-java-bridge instrumentation late hook · AOSP Android 14 handleBindApplication() 调用 makeApplicationInner()

handleBindApplication replacement 最先检查:

一旦 hookpoint 变成 late,前面的 getPackageInfo replacement 仍会执行原方法并返回 LoadedApk,但不会初始化 factory,也不会清空队列:

固定 bridge 选择等到 LoadedApk.makeApplication() 被调用,再以该 LoadedApk 的 this 执行:

原因是 instrumentation 的 APK、split 与 native 路径会参与 loader 组合,普通路径的早期时点可能尚未形成最终环境。

这里存在明确版本边界:固定 bridge 提交 Hook 的是 LoadedApk.makeApplication(),本课固定 Android 14 的 handleBindApplication() 调用的是 makeApplicationInner() [10][13]。因此 instrumentation 分支要按目标 ROM 验证真实调用点;不能用普通启动路径成功替代。

源码直达:frida-java-bridge index.js::performNow() · scheduleOnMainThread()

performNow() 的完整实现是 [10]:

它只检查当前 Application:有就取 loader,没有也立即执行 fn。它不写 _pendingVmOps,也不 Hook handleBindApplication()、getPackageInfo() 或 makeApplication()。

所以早期 spawn 中:

会先执行 A、将 B 入队、继续执行 C;resume 后 Android 进入前述 Hook 点,才执行 B。

四种 API 的职责至此完全分开:

源码直达:frida-java-bridge enumerateClassLoaders() · ART 枚举实现 · withRunnableArtThread() / withAllArtThreadsSuspended() · makeArtClassLoaderVisitor() · AOSP ART ClassLinker::VisitClassLoaders() · ThreadList::SuspendAll() / ResumeAll()

Java.perform() 只为默认 Java.classFactory 选择主包 loader。插件、壳、热更新框架或隔离 split 创建的 loader,需要在 Java 环境可用后另行枚举。

公共 API 先按 VM 类型分派 [21]:

标准 JVM 路径通过 choose('java.lang.ClassLoader') 枚举堆实例;Android ART 使用 ClassLinker::VisitClassLoaders。固定 bridge 中的 ART 实现是 [21]:

这段代码分成四个阶段。

第一,vm.getEnv() 要求调用线程已经拥有 JNIEnv。因此实际脚本通常把枚举放在 Java.perform() 回调中;这里没有再次自动 attach [11][21]。

第二,withRunnableArtThread() 把当前 JNI 线程带入 bridge 需要的 ART 线程状态,并把底层 art::Thread* 交给回调 [20]:

makeArtClassLoaderVisitor() 则按 ART 的 C++ visitor ABI 在内存中构造对象和虚表,把 Visit() 转回 JavaScript 回调 [20]:

第三,真正访问 ART 的 ClassLinker 列表前,bridge 才暂停所有 ART mutator 线程 [20]:

finally 保证 visitor 抛出异常时仍执行 ResumeAll()。AOSP Android 14 的关键代码是 [23]:

真实函数还包含超时、追踪和调试检查;这里摘出改变线程状态与 mutator lock 的主干。SuspendAllInternal() 的源码注释明确说明:先请求所有正在运行 Java 的线程暂停,并等待它们完成暂停;新线程也不能绕过 suspend-request 直接开始执行 Java [23]。

第四,暂停窗口内只做 ClassLoader 指针遍历和 global reference 收集。AOSP Android 14 的 VisitClassLoaders() 直接遍历 ClassLinker::class_loaders_,解码仍存活的 weak global root,再调用 visitor [23]:

Frida visitor 为每个 native loader 指针调用 JavaVMExt::AddGlobalRef。这样退出暂停窗口、恢复其他线程后,这些对象仍不会被 GC 回收;随后 bridge 才把 handle 转成 java.lang.ClassLoader wrapper,调用用户的 onMatch(),最后删除 global ref [21]。

因此这里的“全部 ClassLoader”有明确边界:

所以应把两个动作严格分开:

源码直达:ClassFactory get(loader) · loader / use() · FindClass / loader.loadClass() · AOSP ART LookupClassesVisitor · Android JNI Tips · FindClass

factory.loader = apk.getClassLoader() 会进入 ClassFactory.loader setter [12]:

真正决定后续类查找路径的是 _loader 字段。

ClassFactory.use() 根据 _loader 选择两条类解析路径 [12]:

loader == null 时,基础分支最终调用 JNI FindClass:

附加的原生线程没有 App Java 调用栈,Android 的 FindClass 会从 system class loader 语境查找;framework 类可见,不能证明 APK loader 已建立 [19]。

loader != null 后,bridge 改为显式调用该对象的 loadClass() [12]:

Java 类身份不只由二进制类名决定,还取决于 defining ClassLoader。AOSP ART 查找指定 loader 定义的类时,也明确检查 [23]:

因此以下对象可以同时存在:

它们有相同类名,却是两个不同的 java.lang.Class,各自拥有独立的静态字段、方法入口和实例类型关系。

主包默认 loader 通常只能看到:

插件 loader 可能把另一个 dex 加在自己的搜索路径中。父加载器通常不能反向看到子加载器新增的类,两个并列插件 loader 也不共享命名空间。因此:

如果默认 factory 看不到目标类,Java.use(TARGET_CLASS) 在得到方法 wrapper 之前就会抛出 ClassNotFoundException,自然没有可替换的目标方法。

如果多个 loader 都能解析同名类,默认 factory 还可能拿到另一份 Class。此时 Hook 安装成功但业务调用走的是另一 loader 定义的类,回调仍不会命中。

固定 bridge 会按 loader 缓存独立 factory [12]:

指定 factory 的 use() 会通过该 loader 的 loadClass() 取得目标 Class,然后围绕这份 class handle 构造方法 wrapper。Hook 因而落在该命名空间中的真实方法上。

推荐保持默认 factory 不变,为候选 loader 建立独立 factory:

直接替换全局默认值也会改变后续 Java.use() 的查找入口:

但 ClassFactory 会缓存已成功创建的 class wrapper,而 loader setter 本身只更新 _loader,不会清空 _classes。在同一默认 factory 上反复切换 loader,容易把先前缓存的同名 wrapper 与新 loader 混在一起。独立 Java.ClassFactory.get(loader) 的边界更清楚。

下面的脚本先要求候选 loader 成功执行 loadClass(),再记录返回 Class 的 defining loader;只有验证通过后才创建 factory 并安装 Hook:

TARGET_OVERLOAD 必须替换为目标方法的真实参数类型。脚本只在 candidate.equals(definingLoader) 时安装 Hook,因此通过父委派拿到目标 Class 的其他候选 loader只负责留下记录,不会重复改写同一方法。

sleep 只改变采样时刻。可靠做法是观察 loader 创建事件,或在确定的业务阶段重新枚举并用 loadClass() 验证。

实验分两部分:主机端主动停在 create、load、resume 三个点;目标脚本记录 top-level、performNow()、perform() 和主线程回调。

测试 App 的 Application.onCreate() 需输出一条可识别日志:

另开终端执行 adb logcat -s F02Target。这样 resume 前后是否进入 Application 不依赖界面现象判断。

将下面内容保存为 f02_probe.js,把 TARGET_CLASS 替换为测试 App base APK 中已知的类:

将下面内容保存为 f02_host.py:

执行:

验收看阶段边界,不要求不同机器输出完全相同:

消息跨线程、队列和控制通道返回,HOST load returned 与相邻 SCRIPT 打印的视觉顺序可能受主机事件循环影响。硬证据是 create 阶段没有任何 top-level 消息,以及 resume 前后 App 主线程行为发生变化。

仓库快照保存了 frida-core、frida-gum 以及 Java bridge 的 index.js、vm.js、class-factory.js,这些文件可直接离线定位。lib/api.js 与 lib/android.js 未收进快照,下面用固定 GitHub 提交在线复核,不把外部源码写成本地已有文件:

不要把所有“没输出”都归到注入失败。按最后一个已确认阶段定位:

还要保留四条边界:

--pause 跳过的是 load 之后的自动 resume()。agent 的 JS scheduler 已经执行脚本,App 主线程仍停在 zymbiote 的门控点。

早期路径在 getPackageInfo() 返回 LoadedApk 后就取得最终 loader,并清空 pending 回调。此时 ClassLoader 可以使用,而 makeApplicationInner() 还没有创建 Application。


冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
点赞 ・28
打赏
分享
最新回复 (9)
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2 楼
666
2026-9-7 11:45
0
雪    币: 51
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3 楼
tql
2026-9-7 11:55
0
雪    币: 195
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4 楼
太强了
2026-9-7 12:01
0
雪    币: 547
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5 楼
小佳我的神
2026-9-7 12:04
0
雪    币: 1219
活跃值: (285)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6 楼
3a0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2T1K9h3I4A6j5X3W2D9K9g2)9J5k6h3y4G2L8g2)9J5c8Y4k6A6k6r3g2G2i4K6u0r3b7W2j5I4y4W2c8T1N6K6j5%4c8i4S2i4i4K6u0r3i4K6y4r3N6X3c8Q4y4h3k6K6L8%4g2J5j5$3g2Q4x3@1c8V1j5K6M7^5z5e0x3I4y4$3g2X3z5o6m8W2x3r3f1I4x3r3p5J5y4X3p5K6y4o6q4U0j5$3p5J5j5e0W2U0x3b7`.`.
视频教程
2026-9-7 12:16
0
雪    币: 110
活跃值: (9626)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7 楼
太强了也
2026-9-8 16:18
0
雪    币: 5436
活跃值: (3540)
能力值: (RANK:180 )
在线值:
发帖
回帖
粉丝
8 楼
核弹级原理分析,太强了
2026-9-15 10:03
0
雪    币: 6805
活跃值: (12037)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9 楼
可以的
2026-9-16 09:08
0
雪    币: 731
活跃值: (7234)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
10 楼
分析的很仔细。
2026-9-17 15:59
0
游客
登录 | 注册 方可回帖
返回