A high-performance runtime parsing library for dex implemented in C++, used for lookup of obfuscated classes, methods, or properties.
Currently 2.0 has been officially released, please refer to Release Notes for related improvements.
Basic Features:
- Multi-condition class search
- Multi-condition method search
- Multi-condition field search
- Provides multiple metadata APIs to obtain field/method/class related data
⭐️ Distinctive Features (Recommended):
- Batch search of classes using strings
- Batch search of methods using strings
Note: Optimizations have been implemented for string search scenarios, significantly enhancing search speed. Increasing query groups will not lead to a linear increase in time consumption.
- Click here to go to the documentation page to view more detailed tutorials.
Add dexkit
dependency in build.gradle
.
repositories {
mavenCentral()
}
dependencies {
// replace <version> with your desired version, e.g. `2.0.0`
implementation 'org.luckypray:dexkit:<version>'
}
Note Starting with DexKit 2.0, the new ArtifactId has been changed from
DexKit
todexkit
.
Here's a simple usage example.
Suppose this class is what we want to obtain, with most of its names obfuscated and changing in each version.
Sample 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));
}
}
At this point, to obtain this class, you can use the following code:
This is just an example, in actual usage, there's no need for such an extensive set of matching conditions. Choose and use as needed to avoid unnecessary complexity in matching due to an excessive number of conditions.
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 creation is a time-consuming operation, please do not create the object repeatedly.
// If you need to use it globally, please manage the life cycle yourself and ensure
// that the .close() method is called when not needed to prevent memory leaks.
// Here we use `try-with-resources` to automatically close the DexKitBridge instance.
try (DexKitBridge bridge = DexKitBridge.create(apkPath)) {
findPlayActivity(bridge);
// Other use cases
}
}
private void findPlayActivity(DexKitBridge bridge) {
ClassData classData = bridge.findClass(FindClass.create()
// Search within the specified package name range
.searchPackages("org.luckypray.dexkit.demo")
// Exclude the specified package name range
.excludePackages("org.luckypray.dexkit.demo.annotations")
.matcher(ClassMatcher.create()
// ClassMatcher Matcher for classes
.className("org.luckypray.dexkit.demo.PlayActivity")
// FieldsMatcher Matcher for fields in a class
.fields(FieldsMatcher.create()
// Add a matcher for the field
.add(FieldMatcher.create()
// Specify the modifiers of the field
.modifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL)
// Specify the type of the field
.type("java.lang.String")
// Specify the name of the field
.name("TAG")
)
// Add a matcher for the field of the specified type
.addForType("android.widget.TextView")
.addForType("android.os.Handler")
// Specify the number of fields in the class
.count(3)
)
// MethodsMatcher Matcher for methods in a class
.methods(MethodsMatcher.create()
// Add a matcher for the method
.methods(List.of(
MethodMatcher.create()
// Specify the modifiers of the method
.modifiers(Modifier.PROTECTED)
// Specify the name of the method
.name("onCreate")
// Specify the return type of the method
.returnType("void")
// Specify the parameter type of the method
.paramTypes("android.os.Bundle")
// Specify the strings used by the method
.usingStrings("onCreate"),
MethodMatcher.create()
.paramTypes("android.view.View")
// Specify the numbers used in the method, the type is Byte, Short, Int, Long, Float, Double
.usingNumbers(0.01, -1, 0.987, 0, 114514),
MethodMatcher.create()
.modifiers(Modifier.PUBLIC)
.paramTypes("boolean")
// Specify the methods called in the method list
.invokeMethods(MethodsMatcher.create()
.add(MethodMatcher.create()
.modifiers(Modifier.PUBLIC | Modifier.STATIC)
.returnType("int")
// Specify the strings used in the method called in the method,
.usingStrings(List.of("getRandomDice: "), StringMatchType.Equals)
)
// Only need to contain the call to the above method
.matchType(MatchType.Contains)
)
))
// Specify the number of methods in the class, a minimum of 1, and a maximum of 10
.count(1, 10)
)
// AnnotationsMatcher Matcher for annotations in a class
.annotations(AnnotationsMatcher.create()
// Add a matcher for the annotation
.add(AnnotationMatcher.create()
// Specify the type of the annotation
.type("org.luckypray.dexkit.demo.annotations.Router")
// The annotation needs to contain the specified element
.addElement(AnnotationElementMatcher.create()
// Specify the name of the element
.name("path")
// Specify the value of the element
.stringValue("/play")
)
)
)
// Strings used by all methods in the class
.usingStrings("PlayActivity", "onClick", "onCreate")
)
).singleOrThrow(() -> new IllegalStateException("The returned result is not unique"));
// Print the found class: org.luckypray.dexkit.demo.PlayActivity
System.out.println(classData.getName());
// Get the corresponding class instance
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 creation is a time-consuming operation, please do not create the object repeatedly.
// If you need to use it globally, please manage the life cycle yourself and ensure
// that the .close() method is called when not needed to prevent memory leaks.
// Here we use `Closable.use` to automatically close the DexKitBridge instance.
DexKitBridge.create(apkPath).use { bridge ->
findPlayActivity(bridge)
// Other use cases
}
}
private fun findPlayActivity(bridge: DexKitBridge) {
val classData = bridge.findClass {
// Search within the specified package name range
searchPackages("org.luckypray.dexkit.demo")
// Exclude the specified package name range
excludePackages("org.luckypray.dexkit.demo.annotations")
// ClassMatcher Matcher for classes
matcher {
// FieldsMatcher Matcher for fields in a class
fields {
// Add a matcher for the field
add {
// Specify the modifiers of the field
modifiers = Modifier.PRIVATE or Modifier.STATIC or Modifier.FINAL
// Specify the type of the field
type = "java.lang.String"
// Specify the name of the field
name = "TAG"
}
// Add a matcher for the field of the specified type
addForType("android.widget.TextView")
addForType("android.os.Handler")
// Specify the number of fields in the class
count = 3
}
// MethodsMatcher Matcher for methods in a class
methods {
// Add a matcher for the method
add {
// Specify the modifiers of the method
modifiers = Modifier.PROTECTED
// Specify the name of the method
name = "onCreate"
// Specify the return type of the method
returnType = "void"
// Specify the parameter types of the method, if the parameter types are uncertain,
// use null, and this method will implicitly declare the number of parameters
paramTypes("android.os.Bundle")
// Specify the strings used in the method
usingStrings("onCreate")
}
add {
paramTypes("android.view.View")
// Specify the numbers used in the method, the type is Byte, Short, Int, Long, Float, Double
usingNumbers(0.01, -1, 0.987, 0, 114514)
}
add {
paramTypes("boolean")
// Specify the methods called in the method list
invokeMethods {
add {
modifiers = Modifier.PUBLIC or Modifier.STATIC
returnType = "int"
// Specify the strings used in the method called in the method,
usingStrings(listOf("getRandomDice: "), StringMatchType.Equals)
}
// Only need to contain the call to the above method
matchType = MatchType.Contains
}
}
count(1..10)
}
// AnnotationsMatcher Matcher for annotations in a class
annotations {
// Add a matcher for the annotation
add {
// Specify the type of the annotation
type = "org.luckypray.dexkit.demo.annotations.Router"
// The annotation needs to contain the specified element
addElement {
// Specify the name of the element
name = "path"
// Specify the value of the element
stringValue("/play")
}
}
}
// Strings used by all methods in the class
usingStrings("PlayActivity", "onClick", "onCreate")
}
}.singleOrNull() ?: error("The returned result is not unique")
// Print the found class: org.luckypray.dexkit.demo.PlayActivity
println(classData.name)
// Get the corresponding class instance
val clazz = classData.getInstance(loadPackageParam.classLoader)
}
}
LGPL-3.0 © LuckyPray