DexKit 调试器
DexKit 调试器
DexKit 调试器 是 JSXHook 里一个很适合新手入门的“界面化 DexKit 条件摸索器”。
它不是给你直接写 Hook 代码的,也不是给你浏览 smali/ 目录的。它更像一个中间层工具:
- 先对当前缓存 APK 做条件搜索。
- 再把候选范围一点点缩小。
- 最后把条件导出成 DexKit 模板,回到脚本里固化。
如果你还没熟到能手写 FindClass / FindMethod / FindField 查询,这个调试器就是最适合你的过渡工具。
配合阅读:
先记住这 6 句话
第一次用之前,先把这 6 句记住,后面就不容易绕晕:
- 你必须先缓存一个 APK,不然调试器只能看见“未检测到缓存 APK”,五个搜索按钮也点不动。
- 当前版本每次重新打开
DexKit 调试器启动页时,都会把筛选链重置回Full APK。 - 结果页当前只接了 3 个动作:
复制、生成模板、继续筛选。 - 底层已经支持“类 -> 方法”“字段 -> 方法”这种跨模式继续缩小,但当前这个界面版还没有把那两个跳转按钮接出来。
- 表单里很多输入框都只按“换行”拆分,不按逗号拆分;看见“一行一个”就照着做。
- 这篇文档按当前实现来写,哪里界面提示和实际解析不完全一致,我会明确标出来,以“实际行为”为准。
它到底适合谁
这个功能特别适合下面几类人:
- 你只知道一两个字符串、一个方法名片段,暂时还不会写 DexKit 查询。
- 你是第一次接触混淆 App,不知道从类、方法、字段哪个入口开始。
- 你想先靠界面把条件跑通,再把模板复制回脚本。
- 你知道 DexKit 很强,但每次一写
matcher.xxx(...)就容易手抖。
如果你已经非常熟悉 DexKit 原生 API,这个调试器的价值更多在于:
- 快速试条件
- 对照结果
- 生成模板骨架
使用前准备
先缓存 APK
DexKit 调试器不是直接对“当前进程”乱扫,它会先打开 JSXHook 当前缓存好的那份 APK。
所以在打开调试器前,你至少要先完成这件事:
- 去 JSXHook 的设置 / APK 分析相关页面。
- 选择一个 APK 并完成缓存。
- 让当前状态里能看到
cachedApkPath。
如果没有缓存 APK,启动页会出现这类提示:
未检测到缓存 APK / No cached APK请先去设置页缓存一个 APK,然后再打开 DexKit 调试
这时候不是你条件写错了,而是“搜索目标”都还没准备好。
启动页从上到下怎么看
打开 DexKit 调试器 后,启动页主要有这几块:
| 区域 | 你会看到什么 | 它代表什么 |
|---|---|---|
| 顶部说明 | 选择一种搜索模式,对当前缓存 APK 逐步筛选 | 告诉你这个工具的核心思路是“逐步筛选” |
APK 名称 / APK Name | APK 名或文件名 | 当前缓存的目标是谁 |
缓存路径 / Cached Path | 一个本地路径 | DexKit 实际打开的是这份 APK |
当前筛选链 / Scope | Full APK 或 Class 12 items 之类 | 当前搜索范围 |
| 五个搜索按钮 | 类 / 方法 / 字段 / 批量类 / 批量方法 | 你要从哪个入口开始搜 |
关闭 | 退出对话框 | 只关当前窗口,不做搜索 |
有两个新手特别容易忽略的点:
- 如果没有缓存 APK,这五个搜索按钮会被禁用。
- 当前实现里,启动页一打开就会把已有 scope 清掉,所以你看到的默认 scope 几乎总是
Full APK。
也就是说,想连续缩小范围,就别靠“关掉再重开”来接力。
应该在结果页里点 继续筛选,让当前结果变成新的搜索范围。
先学会 5 种常见填法
很多人第一次卡住,不是卡在“不会点按钮”,而是卡在“不知道某个输入框到底应该填什么格式”。
你先把下面这 5 种格式看懂,后面会轻松很多。
1. 类描述符 / Class Descriptor
这是类搜索里最精确的写法,常见格式是:
Lcom/example/login/LoginManager;
Lcom/tencent/mobileqq/activity/LoginActivity;
特点:
- 以
L开头 - 包路径用
/ - 以
;结尾
适合场景:
- 你已经几乎确定目标类是谁
- 只想做“精确命中”
不适合场景:
- 你只知道一个模糊关键词
2. 方法描述符 / Method Descriptor
这个是“方法签名里参数和返回值那一段”,不是“完整方法全名”。
常见格式:
()V
(Ljava/lang/String;)V
(I)Z
(Ljava/lang/String;I)Ljava/lang/String;
它适合填在“方法搜索”的 descriptor 输入框里。
你可以把它理解成:
()里面是参数列表- 右边是返回值
V表示voidI表示intZ表示boolean- 对象类型一般写成
L包名/类名;
3. 完整方法签名 / Full Method Signature
像下面这种,就是“完整方法签名”:
Lcom/example/login/LoginManager;->saveToken(Ljava/lang/String;)V
Lcom/example/account/UserStore;->getUid()Ljava/lang/String;
这类格式通常适合填在这些位置:
callerMethodsinvokeMethodsreadMethodswriteMethods
因为这些字段不是拿来模糊搜名字的,而是拿来描述“谁调用谁”“谁读谁写”的关系。
4. 完整字段签名 / Full Field Signature
字段常见格式:
Lcom/example/account/UserStore;->token:Ljava/lang/String;
Lcom/example/account/UserStore;->uid:J
这类格式最适合填在:
- 字段搜索的
descriptor - 方法搜索的
usingFields
为什么这里推荐写完整?
因为这两个位置都不是偏“关键词模糊匹配”的入口,写完整字段签名最稳,不容易撞到别的同名字段。
5. Java 类型名 / Type Name
有些输入框不是要你填描述符,而是要你填“类型名”。
比如:
void
int
boolean
java.lang.String
android.content.Context
android.view.View
这类格式常用在:
returnTypetypeparamTypes
新手最简单的记法是:
- 看着像 Java 代码里写的类型名,就大概率对
- 看着像
Lxxx/yyy;的,多半是描述符,不是类型名
Scope / 筛选链,一定要彻底搞懂
如果你只记一个核心概念,那就是 scope。
你可以把它想成一个“当前候选集合”。
第一次打开调试器时,scope 是:
Full APK
意思是:整个 APK 都可以搜。
当你搜出一批结果,然后在结果页点 继续筛选,当前这批结果就会被“提升”为新的 scope。
后面的查询,不再对整个 APK 扫,而是先在这批候选里继续缩。
这就是它最有价值的地方。
scope 可能出现哪些值
当前实现里你会看到这几种摘要:
| 显示值 | 含义 |
|---|---|
Full APK | 当前没有额外筛选链,搜索范围就是整个缓存 APK |
Class N items | 当前候选是一批类 |
Method N items | 当前候选是一批方法 |
Field N items | 当前候选是一批字段 |
继续筛选 到底做了什么
结果页里的 继续筛选 不是“再搜一次”的意思。
它实际做了两步:
- 把当前结果保存成新的 scope。
- 重新打开当前这个搜索表单,并保留你刚刚填写过的条件。
所以它更像:
把这批结果装进篮子里,然后拿着这个篮子继续筛
当前版本的一个重要限制
底层 DexKitDebugSession 其实已经支持这些路线:
- 类结果 -> 再查方法
- 类结果 -> 再查字段
- 方法结果 -> 再回推类
- 方法结果 -> 再找它用到的字段
- 字段结果 -> 再找读写它的方法
但是当前这个界面控制器只实现了“同模式继续筛选”。
也就是说:
- 类结果页点
继续筛选,只会重新打开“类搜索” - 方法结果页点
继续筛选,只会重新打开“方法搜索” - 字段结果页点
继续筛选,只会重新打开“字段搜索”
它不会直接把你送到另一个模式。
这也是为什么你会看到资源字符串里有:
继续筛到方法继续筛到字段
但当前界面上并没有真的出现这两个按钮。
重新打开启动页会发生什么
这个点一定要单独提醒:
当前实现里,一旦你重新打开 DexKit 调试器 的启动页,控制器会先执行一次:
clearScope()
所以之前那条筛选链会被清掉,重新回到:
Full APK
这意味着:
- 想在同一批结果上继续缩,就直接在结果页点
继续筛选 - 不要想着“先关掉结果页,回去再点另一个模式”
不然 scope 会丢
不同 scope 下,搜索范围会怎样变化
虽然当前 UI 还没有把跨模式流程接出来,但底层搜索范围的规则很值得你知道。这样你至少能理解“未来为什么能这么设计”,也能看懂模板思路。
| 当前 scope | 类搜索会基于什么搜 | 方法搜索会基于什么搜 | 字段搜索会基于什么搜 |
|---|---|---|---|
Full APK | 整个 APK | 整个 APK | 整个 APK |
Class N items | 这 N 个类 | 这 N 个类里的方法 | 这 N 个类里的字段 |
Method N items | 这些方法所属的类 | 这 N 个方法本身 | 这些方法里用到的字段 |
Field N items | 这些字段所属的类 | 这些字段的读者 / 写者方法 | 这 N 个字段本身 |
再翻成更直白的话:
- 你有一批类,就能在这些类里继续找方法和字段。
- 你有一批方法,就能往回看“它们属于哪些类”,也能看“它们用到了哪些字段”。
- 你有一批字段,就能往回追“谁读了它、谁写了它”。
这套设计本身是很合理的,只是当前 UI 还没把跨模式跳转做完。
通用选项一口气讲清
不管你进类搜索、方法搜索、字段搜索还是批量搜索,有几项“通用控件”会反复出现。
这几项必须吃透。
String Match 有哪些值
当前调试器里,字符串匹配方式一共 5 个值:
| 值 | 含义 | 例子 |
|---|---|---|
Contains | 只要包含这个片段就行 | 搜 login,可以命中 LoginActivity、prelogin_check |
StartsWith | 必须以前缀开头 | 搜 com.tencent,适合按包名前半段收窄 |
EndsWith | 必须以后缀结束 | 搜 Activity,适合筛 Activity 类名 |
Regex | 按正则规则匹配 | 比如 ^get[A-Z].* 这类模式 |
Equals | 完全相等 | 已经知道完整值时最稳 |
怎么选最实用?
- 不确定时先用
Contains - 已经知道完整值时用
Equals - 想按前缀筛包名或类名前半段,用
StartsWith - 只有在你真的会写正则时,再碰
Regex
String Match 影响哪些字段
它主要影响这些“文本型条件”:
- 类搜索:
className、source、superClass、usingStrings - 方法搜索:
name、declaredClass、returnType、usingStrings - 字段搜索:
name、declaredClass、type - 批量搜索:每组关键词
注意:
String Match不控制descriptor的匹配方式,描述符入口更偏精确条件。String Match也不控制usingFields、callerMethods、invokeMethods、readMethods、writeMethods这种关系型字段。
Op Match 有哪些值
方法搜索里还有一个专门给 opcode 用的匹配方式,一共 4 个值:
| 值 | 含义 |
|---|---|
Contains | 只要操作码序列里包含你填的片段 |
StartsWith | 操作码序列必须以它开头 |
EndsWith | 操作码序列必须以它结尾 |
Equals | 操作码序列需要整体一致 |
这个选项只影响:
opNamesopCodes
它和普通 String Match 不是同一个东西。
Ignore Case 到底影响什么
Ignore Case 的作用是“字符串比较忽略大小写”。
它主要影响:
- 名称类条件
usingStrings- 包前缀过滤
它不适合拿来理解成“所有条件都忽略大小写”。
比如:
opCodes本来就是数字,谈不上大小写usingFields/callerMethods/invokeMethods这类更偏精确签名匹配
所以你可以把它当成:
让文本关键词搜索更宽一点
Find First 有什么用
Find First 开启后,会让查询只要求“找到第一个命中”。
优点:
- 更快
- 适合你只想验证“这条思路能不能命中”
缺点:
- 你看不到完整候选集
- 不利于对比多个相似结果
最适合它的场景:
- 你已经非常确定目标
- 或者只是想快速探路
第一次摸条件时,我更建议关掉它,先看看全貌。
Max Results 和 Max Results Per Group
这两个值都必须是正整数,不能填 0,也不能填负数。
默认值:
| 模式 | 默认值 |
|---|---|
| 类搜索 / 方法搜索 / 字段搜索 | 50 |
| 批量类搜索 / 批量方法搜索 | 20 |
它们的作用不是“限制 DexKit 真正搜到多少个”,而是“限制结果页最多展示多少个”。
所以你经常会看到这种情况:
- 结果页显示了 50 条
- 但顶部
total其实是 213
这说明不是只找到 50 条,而是“还有很多,只是没全展开”。
Clear 会清掉什么
表单上的 清空 / Clear 不只是清文本框,它会一起恢复一些通用选项:
String Match回到ContainsOp Match回到ContainsUsing Field Type回到AnyFind First关掉Ignore Case关掉Max Results回到默认值
有些通用选项会被记住
当前实现会按“搜索模式”分别记住这些值:
String MatchOp MatchUsing Field TypeFind FirstIgnore CaseMax Results
也就是说:
- 你在方法搜索里把
Op Match改成了Contains - 下次再打开方法搜索时,它大概率还是这个值
这个行为是正常的,不是它自己乱改。
类搜索 / Class
类搜索最适合用来回答这类问题:
- 哪个类里出现了某个字符串?
- 哪个类的名字像
Login、UserStore、VipCenter? - 哪个类继承了某个父类?
- 哪个类的方法数、字段数比较像你要找的目标?
descriptor
你应该填什么:
- 完整类描述符
例子:
Lcom/example/login/LoginManager;
空着代表什么:
- 不按类描述符限制
什么时候适合填:
- 你已经从 Smali、日志、结果页里拿到了完整类描述符
什么时候别急着填:
- 你还只是模糊知道“像登录相关的类”
最常见误区:
- 把
com.example.LoginManager当成类描述符直接填
这更像类名 / 全名文本,不是标准描述符
name
你应该填什么:
- 类名关键词
- 全限定类名的一部分
例子:
Login
LoginManager
com.example.login
它受谁影响:
String MatchIgnore Case
使用建议:
- 完全没把握时,先用
Contains - 你只想看某个包名前缀下的类,可以配合
StartsWith
直观类比:
descriptor像身份证号name像名字关键字
source
你应该填什么:
- 源文件名
例子:
LoginActivity.kt
UserStore.java
a.java
它适合什么时候用:
- 结果页里你已经看到可疑类的
sourceFile - 混淆后的类名看不懂,但源文件名还留着一点线索
非常实用的一个思路是:
- 先宽搜出几十个类
- 看结果里的
sourceFile - 再回来把
source补上,继续筛
superClass
你应该填什么:
- 父类名称或明显特征
例子:
android.app.Activity
androidx.fragment.app.Fragment
BasePresenter
它受谁影响:
String MatchIgnore Case
什么时候好用:
- 你知道目标一定是某类组件的子类
- 比如一定是
Activity、Fragment、RecyclerView.Adapter
usingStrings
你应该填什么:
- 类内部使用过的字符串
- 一行一个
例子:
login_success
token expired
/user/profile
拆分规则:
- 只按换行拆
- 不按逗号拆
什么时候最值钱:
- 你只知道界面文案、报错文本、接口路径、埋点 key
怎么理解这个条件:
它不是去搜“类名里有没有这个字”,而是去搜:
这个类的字节码 / 常量池 / 相关逻辑里有没有用到这些字符串
对混淆 App 特别有用,因为就算类名叫 a.b.c,字符串常量往往还是很有辨识度。
Search Packages
你应该填什么:
- 包名前缀
- 一行一个
例子:
com.example
com.example.login
com.tencent.mobileqq
空着代表什么:
- 不限制包范围
什么时候建议填:
- 你已经知道目标大概在哪个包树下面
什么时候别填太死:
- 你对目标包名没把握
- 或者应用里存在插件 / 动态模块 / 迁移包名
Exclude Packages
作用是反过来排除噪音包前缀。
很适合拿来排掉:
androidandroidxkotlinokhttpretrofitcom.google
例子:
android
androidx
kotlin
这样做的目的不是“这些包一定没用”,而是先把明显的系统和三方噪音收掉。
countSpec
这是类搜索里最容易被一笔带过、但其实很有用的一个条件。
它用来按“类里有多少方法 / 多少字段 / 实现了多少接口”来收窄范围。
允许的 key 只有这几种:
| 写法 | 含义 |
|---|---|
method / methods | 方法数量 |
field / fields | 字段数量 |
interface / interfaces / iface | 接口数量 |
允许的分隔符:
=:
允许的范围格式:
| 写法 | 含义 |
|---|---|
3 | 恰好等于 3 |
1..10 | 1 到 10 |
..5 | 小于等于 5 |
5.. | 大于等于 5 |
你可以逗号分隔,也可以换行分隔。
安全例子:
method=1..10
field=0..5
interface=1
或者:
method=1..10, field=0..5, interface=1
适合什么时候用:
- 你知道目标类“比较小”“比较大”“只有少数几个字段”
- 你已经搜出很多候选,想用结构特征继续排
比如你想找一个“只有 1 到 3 个字段、方法也不多的配置类”,就可以这么写:
field=1..3
method=1..8
一个类搜索的完整思路示例
假设你只知道这几个线索:
- 可能和登录有关
- 里面用到了
login_success - 类名里大概率会出现
Login
那么第一轮就可以这样填:
| 字段 | 值 |
|---|---|
name | Login |
usingStrings | login_success |
String Match | Contains |
Max Results | 20 |
如果结果还太多,再加:
| 字段 | 值 |
|---|---|
Exclude Packages | android / androidx / kotlin |
再多的话,再继续加:
| 字段 | 值 |
|---|---|
source | 结果里看见的可疑源文件名 |
方法搜索 / Method
方法搜索是最常用、也最容易“条件写太多把自己锁死”的一类。
它适合的问题包括:
- 某个方法名像
setToken - 返回值是
void - 参数 1 个,而且是
String - 方法里用到了
login_success - 方法里调用了另一个已知方法
- 方法里出现了一些典型 opcode
descriptor
这里填的是“方法描述符”,不是完整方法签名。
安全例子:
()V
(Ljava/lang/String;)V
(I)Z
(Ljava/lang/String;I)Ljava/lang/String;
什么时候它特别有用:
- 你想找无参方法
- 你想找“一个字符串参数、返回 void”的方法
一个非常关键的现实点:
当前界面的 paramCount 不允许填 0,所以如果你要找“无参方法”,最稳的办法反而是直接写:
()V
或者:
()Ljava/lang/String;
name
这里填方法名关键词。
例子:
login
setToken
refreshProfile
它受:
String MatchIgnore Case
影响。
什么时候最实用:
- 你知道方法可能叫
getUid、setToken、refresh
什么时候别太依赖:
- 目标已严重混淆,方法名全是
a、b、c
declaredClass
这里填“声明这个方法的类”的名称特征。
例子:
LoginManager
com.example.account
android.app.Activity
它受:
String MatchIgnore Case
影响。
你可以把它理解成:
我不是直接按方法体特征查,我先要求这个方法得属于某类类
returnType
这里填的是返回类型名。
常见值:
void
int
boolean
java.lang.String
android.view.View
它也受:
String MatchIgnore Case
影响。
新手最好怎么填:
- 已确定时直接填完整类型名
- 例如
void、java.lang.String
paramCount
这里要求的是正整数。
当前版本的实际校验规则是:
- 只能留空
- 或者填
1、2、3这类大于等于 1 的整数
不能填:
0
-1
1.5
这意味着:
- 你没法直接用
paramCount=0搜无参方法
解决办法:
- 改用
descriptor = ()V - 或者
descriptor = ()Ljava/lang/String;
paramTypes
这里填参数类型列表,一行一个,按顺序写。
例子:
java.lang.String
int
android.content.Context
直观例子:
如果目标方法长这样:
saveToken(String token, int from)
那么就可以填:
java.lang.String
int
注意点:
- 最稳妥的理解是“按参数顺序匹配”
- 这里只按换行拆,不按逗号拆
- 如果你对完整参数列表没把握,宁愿先不填,只用
name + returnType + usingStrings
usingStrings
这里填“方法体里用到的字符串”,一行一个。
例子:
token expired
login_success
/user/profile
这个条件在方法搜索里非常强,因为它经常能直接把候选压到很少。
适合的线索:
- Toast 文案
- 接口路径
- 日志 tag
- 埋点 key
- JSON 字段名
usingFields
这里推荐填完整字段签名,一行一个。
最稳写法:
Lcom/example/account/UserStore;->token:Ljava/lang/String;
为什么强调“完整字段签名”:
- 这个入口不是模糊字符串搜索
- 没有单独的
String Match参与这里 - 你只写
token的话,可读性很高,但稳定性没法和完整签名比
如果你现在还拿不到完整字段签名,这个条件可以先不填,别为了显得“条件多”硬加。
Using Field Type
这个下拉框只有 3 个值:
| 值 | 含义 | 适合什么时候用 |
|---|---|---|
Any | 只要这个方法和字段有关系就算 | 你先宽搜,看看都有哪些候选 |
Read | 这个方法读取了字段 | 你在找“取值”“判断”“显示”类逻辑 |
Write | 这个方法写入了字段 | 你在找“保存”“赋值”“更新状态”类逻辑 |
举个非常直观的例子:
字段是:
Lcom/example/account/UserStore;->token:Ljava/lang/String;
如果一个方法里是:
return token;
那它更像 Read。
如果一个方法里是:
this.token = newToken;
那它更像 Write。
如果你现在都不确定,先用 Any。
callerMethods
这里适合填“谁会调用目标方法”,一行一个,推荐完整方法签名。
例子:
Lcom/example/ui/LoginActivity;->onClick(Landroid/view/View;)V
适合场景:
- 你已经知道某个入口方法
- 想反推“它下面会调用哪些候选方法”
invokeMethods
这里填“目标方法会去调用谁”,一行一个,推荐完整方法签名。
例子:
Lcom/example/account/UserStore;->saveToken(Ljava/lang/String;)V
适合场景:
- 你知道目标方法最终一定会调用某个保存、加密、网络、跳转方法
它和 callerMethods 的区别可以这样记:
callerMethods看上游invokeMethods看下游
opNames
这里填操作码名称,一行一个。
常见风格像这样:
CONST_STRING
INVOKE_VIRTUAL
SGET_OBJECT
IPUT_OBJECT
RETURN_OBJECT
它适合谁:
- 已经会看一点 Smali
- 知道某类方法通常会出现什么指令风格
如果你还是小白,可以先把它理解成:
按“方法内部指令特征”筛
但第一次上手时,优先级通常不如:
namereturnTypeparamTypesusingStrings
opCodes
这里填操作码数值,可以:
- 一行一个
- 用逗号分隔
- 用
|分隔 - 用
;分隔
比如:
26
110
或者:
26,110
它更适合什么人:
- 你已经从外部资料、逆向工具或底层资料里确认了 opcode 数值
如果你还在入门阶段,优先用 opNames,会更直观。
一个方法搜索的完整思路示例
假设你只知道:
- 方法名可能像
setToken - 返回
void - 参数只有 1 个
- 大概率用到了字符串
token expired
第一轮可以这样填:
| 字段 | 值 |
|---|---|
name | setToken |
returnType | void |
paramCount | 1 |
usingStrings | token expired |
String Match | Contains |
如果命中太多,再补:
| 字段 | 值 |
|---|---|
paramTypes | java.lang.String |
如果结果只剩几条,你就已经很接近可用模板了。
字段搜索 / Field
字段搜索适合的问题是:
- 我想找存 token、uid、手机号、状态位的字段
- 我知道谁会读这个字段
- 我知道谁会写这个字段
- 我知道字段类型是
String、long、boolean
descriptor
这里推荐直接填完整字段签名。
例子:
Lcom/example/account/UserStore;->token:Ljava/lang/String;
什么时候适合用它:
- 你已经从别处拿到了完整字段签名
- 想做精确确认
name
这里填字段名关键词。
例子:
token
uid
phone
isVip
它受:
String MatchIgnore Case
影响。
如果目标已混淆,字段名可能没什么参考价值;但很多未深混的业务字段还是非常好用。
declaredClass
这里填“字段属于哪个类”的名称特征。
例子:
UserStore
AccountManager
LoginState
当你知道“这个字段大概率在用户存储类里”时,这个条件会很好用。
type
这里填字段类型名。
常见例子:
java.lang.String
int
boolean
long
你可以这样理解它:
name解决“这个字段叫啥”type解决“这个字段装的是什么”
比如你搜 token 时,如果再加上:
java.lang.String
结果通常会更干净。
readMethods
这里填“谁会读取这个字段”,推荐完整方法签名,一行一个。
例子:
Lcom/example/account/UserStore;->getToken()Ljava/lang/String;
Lcom/example/network/AuthInterceptor;->intercept(Ljava/lang/Object;)Ljava/lang/Object;
适合什么时候:
- 你已经知道某个 getter 或拦截器会读取它
- 想从“读者”反推字段
writeMethods
这里填“谁会写入这个字段”,推荐完整方法签名,一行一个。
例子:
Lcom/example/account/UserStore;->saveToken(Ljava/lang/String;)V
Lcom/example/login/LoginPresenter;->onLoginSuccess(Ljava/lang/String;)V
这个入口非常适合找:
- 登录成功后保存 token 的字段
- 切换状态时写入的布尔字段
- 更新用户资料时写入的对象字段
一个字段搜索的完整思路示例
假设你想找“保存登录 token 的字段”,你只知道:
- 字段名大概率带
token - 类型大概率是
String - 写入它的方法可能叫
saveToken
那么可以这样填:
| 字段 | 值 |
|---|---|
name | token |
type | java.lang.String |
writeMethods | Lcom/example/account/UserStore;->saveToken(Ljava/lang/String;)V |
String Match | Contains |
如果你还不知道完整写入方法签名,那就先只填:
| 字段 | 值 |
|---|---|
name | token |
type | java.lang.String |
先看候选,再回来补强。
批量类搜索 / Batch Class
批量类搜索的思路不是“一次搜一个词”,而是:
我给你多组关键词,请你按组分别告诉我,每组对应命中了哪些类
很适合做这类事情:
- 一次对比多个业务模块
- 一次测试多个文案 / 埋点 key / 接口路径
- 快速建立“这堆字符串分别落在哪些类里”的初步地图
groups 输入框的实际规则
这是当前版本里最需要讲清楚的一个点。
界面提示看起来像:
groupName=keyword1, keyword2
但按当前实现,解析规则其实是:
- 一次读一行。
- 以
=或:左边当组名。 - 右边的内容当前不会再按逗号拆成多个关键词。
- 空行会忽略。
- 以
#开头的行会忽略。 - 如果你没写组名,会自动生成
group1、group2之类的名字。
所以当前版本最稳的写法是:一行一个分组,一行一个关键词。
安全写法:
login=login_success
token=access_token
vip=member_center
也可以不写组名:
login_success
access_token
member_center
这时程序会自动把它们当成:
group1
group2
group3
一个非常重要的现实提醒
如果你现在这样写:
auth=login_success, access_token
当前版本更可能把右边当成一个完整字符串:
login_success, access_token
而不是两个独立关键词。
所以现阶段如果你真的需要“一个 group 里放多个字符串”:
- 可以先在调试器里一组一组地试单字符串。
- 然后点击
生成模板。 - 再去模板代码里手动把数组扩成多个字符串。
Max Results Per Group
它和普通 Max Results 的区别是:
- 普通模式:限制整个结果列表展示多少条
- 批量模式:限制每个组各自最多展示多少条
例如你填 10,那意思是:
login组最多显示 10 条token组最多显示 10 条vip组最多显示 10 条
一个批量类搜索的完整示例
假设你想同时看看这几个线索都落在哪些类里:
login_successaccess_tokenmember_center
建议这样填:
login=login_success
token=access_token
vip=member_center
配套建议:
| 字段 | 值 |
|---|---|
Search Packages | com.example |
Exclude Packages | android / androidx / kotlin |
String Match | Contains |
Max Results Per Group | 10 |
这样一页结果就能同时看三组线索的分布。
批量方法搜索 / Batch Method
批量方法搜索和批量类搜索的思路一样,只是结果单位从“类”变成了“方法”。
它适合:
- 同时看多个字符串分别落在哪些方法里
- 快速判断哪一组字符串最能收窄到核心逻辑
groups
规则和“批量类搜索”完全一样:
- 一行一个 group
- 最稳是一行一个关键词
- 当前版本不要指望逗号帮你拆成多个关键词
结果怎么看
每个组下面会展示:
groupsearchStringstotal- 命中的方法列表
每条方法至少会显示:
methodSigndescriptordeclaredClasskind
其中 kind 只会出现 3 个值:
| 值 | 表示什么 | 新手可以怎么理解 |
|---|---|---|
constructor | 构造函数 | 这条结果是对象初始化入口,通常对应 new Xxx(...) 之后会走到的地方 |
static-initializer | 静态初始化块 | 这条结果是类首次加载时执行的初始化逻辑,通常对应静态字段赋值、static {} 代码块 |
method | 普通方法 | 其余业务方法、工具方法、生命周期方法基本都归到这一类 |
如果你第一轮只是想先判断“哪组字符串更像真正入口”,kind 很有用:
- 想找对象初始化时机,优先看
constructor - 想找类加载就执行的逻辑,重点看
static-initializer - 想找登录、加密、网络、存储这类业务逻辑,通常先看
method
如果你是在“摸线索最有效的是哪一个字符串”,批量方法搜索比一条条手搜省很多时间。
结果页怎么读
不管你搜的是类、方法、字段还是批量组,结果页本质上都是一段文本摘要。
这段文本不是让你背的,是让你拿来:
- 观察候选
- 复制给自己
- 生成模板
- 继续筛选
顶部三块信息
结果页顶部通常会先出现:
| 项 | 说明 |
|---|---|
Query Summary | 当前这次查询的条件摘要 |
apkPath | 这次搜的是哪份 APK |
Scope | 这次查询是在什么范围里做的 |
这里的 Query Summary 是程序自动生成的,不是你要手写的代码。
类结果会显示什么
每个类候选通常会显示:
- 类描述符
- 简单类名
sourceFilemethodCountfieldCountinterfaceCountsuperClass
这意味着你可以在类结果页顺手得到二次筛选线索,比如:
- 看见
sourceFile后,回去补source - 看见
superClass后,回去补superClass - 看见方法数 / 字段数后,回去补
countSpec
方法结果会显示什么
每个方法候选通常会显示:
methodSigndescriptordeclaredClassnamekindreturnTypeparamTypesopNamesopCodesusingStringsusingFieldscallerMethodsinvokeMethods
这页是信息量最大的,因为它天然带有很多“下一轮能怎么继续缩”的线索。
kind 这一项建议你一定要看,因为它会先告诉你“这条方法到底属于哪一类”。
它在结果页里只会显示下面 3 种值:
kind 值 | 代表什么 | 这类结果常见长什么样 |
|---|---|---|
constructor | 构造函数 | name 通常是 <init>,methodSign 往往像 Xxx.<init>(...) |
static-initializer | 静态初始化块 | name 通常是 <clinit>,常见于静态常量、静态缓存、类加载初始化 |
method | 普通方法 | name 是正常业务名,比如 login、saveToken、onClick、encrypt |
当前调试器对 kind 的判断规则也很直接,就是按方法名分类:
name = <init>时,显示为constructorname = <clinit>时,显示为static-initializer- 其他任何方法名,都显示为
method
你可以直接把它当成“方法类别提示”,先判断这条结果值不值得继续深挖。
比如下面这三条,kind 就会分别不同:
Lcom/example/account/UserStore;-><init>(Landroid/content/Context;)V
kind: constructor
Lcom/example/account/UserStore;-><clinit>()V
kind: static-initializer
Lcom/example/account/UserStore;->saveToken(Ljava/lang/String;)V
kind: method
这个区分特别适合解决三个常见判断:
- 你怀疑某个字段是在对象创建时写进去的,那就多看
constructor - 你怀疑某个常量、URL、密钥缓存是在类加载时准备好的,那就多看
static-initializer - 你想继续追登录、签名、解密、请求发送这类链路,那通常还是从
method结果往下追最自然
字段结果会显示什么
每个字段候选通常会显示:
- 完整字段描述信息
declaredClasstypereadMethodswriteMethods
所以字段结果页特别适合回答:
- 这个字段是干嘛的
- 谁在读它
- 谁在写它
批量结果会显示什么
每个 group 都会显示:
- 组名
- 当前组用的搜索字符串
- 当前组总命中数
- 当前组具体命中的类或方法
如果超出显示上限,还会显示:
... N more
意思是:
- 这组不是只有当前看到的几条
- 后面还有更多,只是被截断了
结果页的 3 个按钮分别做什么
复制 / Copy
复制的是“当前这段结果文本”。
适合什么时候用:
- 你想先把结果存起来
- 想丢到记事页、聊天、脚本注释里
生成模板 / Generate template
这个按钮不会直接帮你写完 Hook。
它生成的是“当前条件对应的 DexKit 查询模板”。
比如你在类搜索里填了:
classNameusingStringspackageIncludes
那生成的模板里,就会自动带上:
FindClass.create()ClassMatcher.create()- 对应的
matcher.xxx(...) - 对应的
searchPackages(...) bridge.close()
这个模板最大的价值是:
- 让你不用从空白开始写 DexKit 代码
- 能把当前调试器里试出来的条件原样搬回脚本
继续筛选 / Continue filtering
它会:
- 把这次结果提升为新的 scope。
- 回到当前这个搜索表单。
- 把你刚才填的条件保留下来。
它不会:
- 自动跳去其他模式
- 自动帮你新增条件
所以你点击之后,下一步通常应该是:
- 删除一个太宽的条件
- 增加一个更精确的条件
- 调小
Max Results
生成模板 之后应该怎么用
建议你把它当成“脚本起草器”,不要把它当成“最终成品”。
模板里一般会有什么
当前实现生成的模板通常会自动带上:
imports(...)- 对应查询类和 matcher 类
- 当前条件
openDexKit(...)bridge.close()
如果当前缓存 APK 路径可用,模板会倾向于生成这种打开方式:
const bridge = openDexKit("缓存 APK 路径");
模板不会替你做什么
它不会自动帮你:
- 选择最终唯一目标
- 写 Hook 逻辑
- 写后处理逻辑
- 处理多个候选之间的取舍
所以正确姿势一般是:
- 先在调试器里把条件跑顺。
- 点击
生成模板。 - 把模板放到你的脚本里。
- 再手工加上日志、结果筛选和 Hook。
5 个最常用的教学例子
下面这几组例子,我都按“小白真的照着填”的方式来写。
例子 1:我只知道字符串 login_success,怎么找类
假设你现在唯一线索是:
- 某个关键类里用到了字符串
login_success
第 1 步:打开类搜索
在启动页点:
类搜索
第 2 步:先只填最强线索
填法:
| 字段 | 值 |
|---|---|
usingStrings | login_success |
String Match | Contains |
Max Results | 20 |
如果你知道业务包前缀,再补:
| 字段 | 值 |
|---|---|
Search Packages | com.example |
如果你不确定,就先别填。
第 3 步:看结果页
重点看:
- 哪些类的
sourceFile像业务代码 - 哪些类的
superClass比较像页面或管理器 - 方法数 / 字段数是否合理
第 4 步:结果太多怎么办
回到类搜索,再加一条:
| 字段 | 值 |
|---|---|
Exclude Packages | android / androidx / kotlin |
还多,再加:
| 字段 | 值 |
|---|---|
name | Login |
第 5 步:结果缩小后怎么继续
如果结果已经从几十个变成几条,就点:
继续筛选
然后在重新打开的类搜索表单里继续补强,比如:
| 字段 | 值 |
|---|---|
source | 刚才结果里看到的 LoginManager.kt |
这样你就不是“从全 APK 重搜”,而是在刚才那批候选类里继续缩。
例子 2:我想找 setToken 这种方法
已知线索:
- 名字可能像
setToken - 返回
void - 参数 1 个
- 大概率收一个
String
建议填法
| 字段 | 值 |
|---|---|
name | setToken |
returnType | void |
paramCount | 1 |
paramTypes | java.lang.String |
String Match | Contains |
Max Results | 20 |
这样填的思路是什么
name:从语义上卡住“像设置 token 的方法”returnType=void:排掉 getterparamCount=1:排掉重载噪音paramTypes=java.lang.String:进一步逼近“保存 token 字符串”的写法
如果一个结果都没有
不要立刻怀疑 App 没有这个方法。先按这个顺序松条件:
- 去掉
paramTypes - 只保留
name + returnType + paramCount - 如果还是没有,把
name从setToken改成token
这是新手最重要的节奏:
先宽一点,让结果出来;再慢慢收
例子 3:我要找“无参方法”,但 paramCount 不能填 0
这是当前界面里一个很典型的坑。
假设你要找:
- 名字像
getUid - 无参数
- 返回
String
不要这样填
paramCount = 0
当前版本会把它判成“数字格式不正确”。
正确思路
改用方法描述符。
填法:
| 字段 | 值 |
|---|---|
name | getUid |
descriptor | ()Ljava/lang/String; |
String Match | Contains |
如果你只知道返回 String,也可以:
| 字段 | 值 |
|---|---|
name | getUid |
returnType | java.lang.String |
但从“无参”这个条件本身来说,descriptor 是更直接的办法。
例子 4:我要找保存 token 的字段
已知线索:
- 字段名可能带
token - 类型大概率是
String - 写它的方法可能像
saveToken
第 1 步:先做字段搜索
填法:
| 字段 | 值 |
|---|---|
name | token |
type | java.lang.String |
String Match | Contains |
Max Results | 20 |
第 2 步:观察结果
重点看:
declaredClasswriteMethods
如果你很快发现某条结果的 writeMethods 里有明显业务方法,那基本就很有戏。
第 3 步:进一步补强
如果你已经知道完整写入方法签名,比如:
Lcom/example/account/UserStore;->saveToken(Ljava/lang/String;)V
就把它填进:
| 字段 | 值 |
|---|---|
writeMethods | Lcom/example/account/UserStore;->saveToken(Ljava/lang/String;)V |
这样字段搜索会立刻干净很多。
例子 5:我想一次试 3 个关键词,看它们分别落在哪些方法里
目标:
- 同时看
login_success access_tokenmember_center
进入批量方法搜索
在启动页点:
批量方法搜索
推荐填法
login=login_success
token=access_token
vip=member_center
配套条件:
| 字段 | 值 |
|---|---|
Search Packages | com.example |
String Match | Contains |
Max Results Per Group | 10 |
看结果时关注什么
不是只看“有没有命中”,更要看:
- 哪一组最容易收窄到 1 到 3 个方法
- 哪些方法的
declaredClass一看就很像业务核心类
如果其中某一组非常准,你后面就可以回到普通“方法搜索”,把那组的关键词拿去继续深挖。
当前版本最容易踩的 7 个坑
1. 没缓存 APK 就直接打开调试器
表现:
- 搜索按钮灰掉
- 提示你先缓存 APK
处理:
- 先去 APK 分析 / 设置页准备缓存 APK
2. 想跨模式继续筛,但一回启动页 scope 就没了
表现:
- 你刚筛出来的候选集不见了
- 启动页又回到
Full APK
原因:
- 当前实现一打开启动页就会
clearScope()
处理:
- 继续缩小时别回启动页
- 先在当前模式里点
继续筛选
3. paramCount 填了 0
表现:
- 报数字格式不正确
处理:
- 用
descriptor = ()V - 或
descriptor = ()Ljava/lang/String;
4. 以为很多输入框能按逗号拆
表现:
- 明明填了多个值,结果不符合预期
处理:
- 看到“多值输入框”,默认按“一行一个”来填
- 尤其是
usingStrings、paramTypes、usingFields、callerMethods、invokeMethods、readMethods、writeMethods
5. 以为批量分组可以一行塞多个逗号关键词
表现:
auth=login_success, access_token没按你想的拆成两个关键词
处理:
- 当前版本最稳是一行一个 group,一行一个关键词
- 真要多关键词 group,先生成模板,再手动改模板数组
6. 条件一口气加太满,结果直接 0 条
表现:
- 你确信目标存在,但就是查不到
处理顺序:
- 先去掉最不确定的那条
- 再把
Equals放宽成Contains - 再把完整类型 / 完整签名改成更宽的线索
7. 只盯着名字,不看结果页给你的二次线索
表现:
- 一直在重复搜名字
- 忽略了
sourceFile、superClass、writeMethods、usingStrings
更好的思路:
- 第一轮先把候选搜出来
- 第二轮开始吃结果页里的附加信息
最后给新手的一条总策略
如果你现在还不熟练,不要想着一上来就写出“完美条件”。
DexKit 调试器最正确的打开方式,永远是这四步:
- 先拿最强线索开一枪,让结果出来。
- 观察结果页,捡新的高价值线索。
- 点
继续筛选,在同一模式里继续收窄。 - 条件成熟后点
生成模板,再把它搬回脚本。
把它当成“摸条件工具”,而不是“最终 Hook 工具”,你会用得特别顺。
