一个使用 C++ 实现的 dex 高性能运行时解析库,用于查找被混淆的类、方法或者属性。
目前 2.0 已经正式发布,相关改进参考 Release Notes。
基础功能:
- 多条件查找类
- 多条件查找方法
- 多条件查找属性
- 提供多种元数据API获取 field/method/class 相关数据
⭐️ 特色功能(推荐):
- 批量查找使用字符串的类
- 批量查找使用字符串的方法
备注:对于字符串搜索场景进行了优化,可以大幅度提升搜索速度,增加查询分组不会导致耗时成倍增长
- 点击此处进入文档页面查看更详细的教程。
添加 dexkit
依赖进 build.gradle
.
repositories {
mavenCentral()
}
dependencies {
// 替换 <version> 为您需要的版本,例如 `2.0.0`
implementation 'org.luckypray:dexkit:<version>'
}
Note 从 DexKit 2.0 开始,新的 ArtifactId 已从
DexKit
更改为dexkit
。
下面是一个简单的用法示例。
假设这个 Class 是我们想得到的,其中大部分名称经过混淆,且每个版本都会发生变化。
样例 APP 如下
package org.luckypray.dexkit.demo;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.h;
import java.util.Random;
import org.luckypray.dexkit.demo.annotations.Router;
@Router(path = "/play")
public class PlayActivity extends AppCompatActivity {
private static final String TAG = "PlayActivity";
private TextView a;
private Handler b;
public void d(View view) {
Handler handler;
int i;
Log.d("PlayActivity", "onClick: rollButton");
float nextFloat = new Random().nextFloat();
if (nextFloat < 0.01d) {
handler = this.b;
i = -1;
} else if (nextFloat < 0.987f) {
handler = this.b;
i = 0;
} else {
handler = this.b;
i = 114514;
}
handler.sendEmptyMessage(i);
}
public void e(boolean z) {
int i;
if (!z) {
i = RandomUtil.a();
} else {
i = 6;
}
String a = h.a("You rolled a ", i);
this.a.setText(a);
Log.d("PlayActivity", "rollDice: " + a);
}
protected void onCreate(Bundle bundle) {
super/*androidx.fragment.app.FragmentActivity*/.onCreate(bundle);
setContentView(0x7f0b001d);
Log.d("PlayActivity", "onCreate");
HandlerThread handlerThread = new HandlerThread("PlayActivity");
handlerThread.start();
this.b = new PlayActivity$1(this, handlerThread.getLooper());
this.a = (TextView) findViewById(0x7f080134);
((Button) findViewById(0x7f08013a)).setOnClickListener(new a(this));
}
}
此时我们想得到这个类可以使用如下代码:
这仅仅是个样例,实际使用中并不需要这么多条件进行匹配,按需选用即可,避免条件过多带来的匹配复杂度增长
Java Example
public class MainHook implements IXposedHookLoadPackage {
static {
System.loadLibrary("dexkit");
}
private ClassLoader hostClassLoader;
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) {
String packageName = loadPackageParam.packageName;
String apkPath = loadPackageParam.appInfo.sourceDir;
if (!packageName.equals("org.luckypray.dexkit.demo")) {
return;
}
this.hostClassLoader = loadPackageParam.classLoader;
// DexKit 创建是一项耗时操作,请不要重复创建。如果需要全局使用,
// 请自行管理生命周期,确保在不需要时调用 .close() 方法以防止内存泄漏。
// 这里使用 `try-with-resources` 语法糖自动关闭 DexKitBridge 实例。
try (DexKitBridge bridge = DexKitBridge.create(apkPath)) {
findPlayActivity(bridge);
// Other use cases
}
}
private void findPlayActivity(DexKitBridge bridge) {
ClassData classData = bridge.findClass(FindClass.create()
// 指定搜索的包名范围
.searchPackages("org.luckypray.dexkit.demo")
// 排除指定的包名范围
.excludePackages("org.luckypray.dexkit.demo.annotations")
.matcher(ClassMatcher.create()
// ClassMatcher 针对类的匹配器
.className("org.luckypray.dexkit.demo.PlayActivity")
// FieldsMatcher 针对类中包含字段的匹配器
.fields(FieldsMatcher.create()
// 添加对于字段的匹配器
.add(FieldMatcher.create()
// 指定字段的修饰符
.modifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL)
// 指定字段的类型
.type("java.lang.String")
// 指定字段的名称
.name("TAG")
)
// 添加指定字段的类型的字段匹配器
.addForType("android.widget.TextView")
.addForType("android.os.Handler")
// 指定类中字段的数量
.count(3)
)
// MethodsMatcher 针对类中包含方法的匹配器
.methods(MethodsMatcher.create()
// 添加对于方法的匹配器
.methods(List.of(
MethodMatcher.create()
// 指定方法的修饰符
.modifiers(Modifier.PROTECTED)
// 指定方法的名称
.name("onCreate")
// 指定方法的返回值类型
.returnType("void")
// 指定方法的参数类型,如果参数类型不确定,使用 null,使用此方法会隐式声明参数个数
.paramTypes("android.os.Bundle")
// 指定方法中使用的字符串
.usingStrings("onCreate"),
MethodMatcher.create()
.paramTypes("android.view.View")
// 指定方法中使用的数字,类型为 Byte, Short, Int, Long, Float, Double 之一
.usingNumbers(0.01, -1, 0.987, 0, 114514),
MethodMatcher.create()
.modifiers(Modifier.PUBLIC)
.paramTypes("boolean")
// 指定方法中调用的方法列表
.invokeMethods(MethodsMatcher.create()
.add(MethodMatcher.create()
.modifiers(Modifier.PUBLIC | Modifier.STATIC)
.returnType("int")
// 被调用方法中使用的字符串,所有字符串均使用 Equals 匹配
.usingStrings(List.of("getRandomDice: "), StringMatchType.Equals)
)
// 只需要包含上述方法的调用即可
.matchType(MatchType.Contains)
)
))
// 指定类中方法的数量,最少不少于1个,最多不超过10个
.count(1, 10)
)
// AnnotationsMatcher 针对类中包含注解的匹配器
.annotations(AnnotationsMatcher.create()
// 添加对于注解的匹配器
.add(AnnotationMatcher.create()
// 指定注解的类型
.type("org.luckypray.dexkit.demo.annotations.Router")
// 该注解需要包含指定的 element
.addElement(AnnotationElementMatcher.create()
// 指定 element 的名称
.name("path")
// 指定 element 的值
.stringValue("/play")
)
)
)
// 类中所有方法使用的字符串
.usingStrings("PlayActivity", "onClick", "onCreate")
)
).singleOrThrow(() -> new IllegalStateException("返回结果不唯一"));
// 打印找到的类:org.luckypray.dexkit.demo.PlayActivity
System.out.println(classData.getName());
// 获取对应的类实例
Class<?> clazz = classData.getInstance(loadPackageParam.classLoader);
}
}
Kotlin Example
class MainHook : IXposedHookLoadPackage {
companion object {
init {
System.loadLibrary("dexkit")
}
}
private lateinit var hostClassLoader: ClassLoader
override fun handleLoadPackage(loadPackageParam: XC_LoadPackage.LoadPackageParam) {
val packageName = loadPackageParam.packageName
val apkPath = loadPackageParam.appInfo.sourceDir
if (!packageName.equals("org.luckypray.dexkit.demo")) {
return
}
this.hostClassLoader = loadPackageParam.classLoader
// DexKit 创建是一项耗时操作,请不要重复创建。如果需要全局使用,
// 请自行管理生命周期,确保在不需要时调用 .close() 方法以防止内存泄漏。
// 这里使用 `Closable.use` 语法糖自动关闭 DexKitBridge 实例。
DexKitBridge.create(apkPath).use { bridge ->
findPlayActivity(bridge)
// Other use cases
}
}
private fun findPlayActivity(bridge: DexKitBridge) {
val classData = bridge.findClass {
// 指定搜索的包名范围
searchPackages("org.luckypray.dexkit.demo")
// 排除指定的包名范围
excludePackages("org.luckypray.dexkit.demo.annotations")
// ClassMatcher 针对类的匹配器
matcher {
// FieldsMatcher 针对类中包含字段的匹配器
fields {
// 添加对于字段的匹配器
add {
// 指定字段的修饰符
modifiers = Modifier.PRIVATE or Modifier.STATIC or Modifier.FINAL
// 指定字段的类型
type = "java.lang.String"
// 指定字段的名称
name = "TAG"
}
// 添加指定字段的类型的字段匹配器
addForType("android.widget.TextView")
addForType("android.os.Handler")
// 指定类中字段的数量
count = 3
}
// MethodsMatcher 针对类中包含方法的匹配器
methods {
// 添加对于方法的匹配器
add {
// 指定方法的修饰符
modifiers = Modifier.PROTECTED
// 指定方法的名称
name = "onCreate"
// 指定方法的返回值类型
returnType = "void"
// 指定方法的参数类型,如果参数类型不确定,使用 null,使用此方法会隐式声明参数个数
paramTypes("android.os.Bundle")
// 指定方法中使用的字符串
usingStrings("onCreate")
}
add {
paramTypes("android.view.View")
// 指定方法中使用的数字,类型为 Byte, Short, Int, Long, Float, Double 之一
usingNumbers(0.01, -1, 0.987, 0, 114514)
}
add {
paramTypes("boolean")
// 指定方法中调用的方法列表
invokeMethods {
add {
modifiers = Modifier.PUBLIC or Modifier.STATIC
returnType = "int"
// 指定方法中调用的方法中使用的字符串,所有字符串均使用 Equals 匹配
usingStrings(listOf("getRandomDice: "), StringMatchType.Equals)
}
// 只需要包含上述方法的调用即可
matchType = MatchType.Contains
}
}
// 指定类中方法的数量,最少不少于1个,最多不超过10个
count(1..10)
}
// AnnotationsMatcher 针对类中包含注解的匹配器
annotations {
// 添加对于注解的匹配器
add {
// 指定注解的类型
type = "org.luckypray.dexkit.demo.annotations.Router"
// 该注解需要包含指定的 element
addElement {
// 指定 element 的名称
name = "path"
// 指定 element 的值
stringValue("/play")
}
}
}
// 类中所有方法使用的字符串
usingStrings("PlayActivity", "onClick", "onCreate")
}
}.singleOrNull() ?: error("返回结果不唯一")
// 打印找到的类:org.luckypray.dexkit.demo.PlayActivity
println(classData.name)
// Get the corresponding class instance
val clazz = classData.getInstance(loadPackageParam.classLoader)
}
}
LGPL-3.0 © LuckyPray