Usage Examples
During the reading of this section, you may need to refer to the Structural Quick Reference Table for a better understanding.
You can get the source code and some test cases for the demo below.
- APP Demo Click to View
- APP Test Cases Click to View
Demo App
Here is a simple Demo Activity, PlayActivity, where the internal properties and methods are obfuscated, and they change in each version.
The four major components are not obfuscated by default. We assume this Activity is obfuscated.
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));
}
}
Multiple Conditions Matching Class - Example Usage
In this scenario, we want to find the PlayActivity using the following code:
This is just an example. In actual use, you don't need conditions as complex and comprehensive as this. Use as needed.
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")
}
}.single()
println(classData.name)
}
The result is as follows:
org.luckypray.dexkit.demo.PlayActivity
Parent Class Condition Nesting
if there is such a class, its only feature is that the ancestors are not obfuscated, and the middle parents are all obfuscated, we can also use DexKit
to find it.
private fun findActivity(bridge: DexKitBridge) {
bridge.findClass {
matcher { // ClassMatcher
// androidx.appcompat.app.AppCompatActivity
superClass { // ClassMatcher
// androidx.fragment.app.FragmentActivity
superClass { // ClassMatcher
// androidx.activity.ComponentActivity
superClass { // ClassMatcher
// androidx.core.app.ComponentActivity
superClass { // ClassMatcher
superClass = "android.app.Activity"
}
}
}
}
}
}.forEach {
// org.luckypray.dexkit.demo.MainActivity
// org.luckypray.dexkit.demo.PlayActivity
println(it.name)
}
}
The result is as follows:
org.luckypray.dexkit.demo.MainActivity
org.luckypray.dexkit.demo.PlayActivity
Tips
In DexKit
, any logical relationship can be used as a query condition
Fuzzy Parameter Matching
If we need to find a method with an obfuscated parameter, we can use null
to replace it, so that it can match any type of parameter.
private fun findMethodWithFuzzyParam(bridge: DexKitBridge) {
bridge.findMethod {
matcher {
modifiers = Modifier.PUBLIC or Modifier.STATIC
returnType = "void"
// Specify the parameters of the method, if the parameters are uncertain, use null
paramTypes("android.view.View", null)
// paramCount = 2 // paramTypes length is 2, which has implicitly determined the number of parameters
usingStrings("onClick")
}
}.single().let {
println(it)
}
}
Saving and Retrieving Query Results
How can you serialize and save the results obtained from DexKit queries for later use?
DexKit provides corresponding wrapper classes for Class, Method, and Field, namely DexClass
, DexMethod
, and DexField
. These wrapper classes inherit from the ISerializable
interface, allowing them to be freely converted to and from strings. For objects returned by queries, you can directly use the toDexClass()
, toDexMethod()
, and toDexField()
methods to convert them into the respective wrapper classes.
private fun saveData(bridge: DexKitBridge) {
bridge.findMethod {
matcher {
modifiers = Modifier.PUBLIC or Modifier.STATIC
returnType = "void"
paramTypes("android.view.View", null)
usingStrings("onClick")
}
}.single().let {
val dexMethod = it.toDexMethod()
val serialize = dexMethod.serialize()
val sp = getSharedPreferences("dexkit", Context.MODE_PRIVATE)
sp.edit().putString("onClickMethod", serialize).apply()
}
}
private fun readData(): Method {
val sp = getSharedPreferences("dexkit", Context.MODE_PRIVATE)
val descriptor = sp.getString("onClickMethod", null)
if (descriptor != null) {
val dexMethod = DexMethod(descriptor)
// val dexMethod = DexMethod.deserialize(serialize)
// val dexMethod = ISerializable.deserialize(serialize) as DexMethod
// val dexMethod = ISerializable.deserializeAs<DexMethod>(serialize)
val method = dexMethod.getMethodInstance(hostClassLoader)
return method
}
error("No saved")
}