LuckyPray / DexKit

An easy-to-use, high-performance dex deobfuscation library.
https://luckypray.org/DexKit/
GNU Lesser General Public License v3.0
434 stars 54 forks source link
deobfuscation dex high-performance library native-module xposed

DexKit

[![license](https://img.shields.io/github/license/LuckyPray/DexKit.svg)](https://www.gnu.org/licenses/lgpl-3.0.html) [![Maven Central](https://img.shields.io/maven-central/v/org.luckypray/dexkit.svg?label=Maven%20Central)](https://central.sonatype.com/search?q=dexkit&namespace=org.luckypray) [![Telegram](https://img.shields.io/badge/discussion%20dev-Telegram-blue.svg?logo=telegram)](https://t.me/LuckyPray_DexKit) English | [简体中文](https://github.com/LuckyPray/DexKit/blob/master/README_zh.md)

A high-performance runtime parsing library for dex implemented in C++, used for lookup of obfuscated classes, methods, or properties.


DexKit 2.0

Currently 2.0 has been officially released, please refer to Release Notes for related improvements.

Supported APIs

Basic Features:

⭐️ Distinctive Features (Recommended):

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.

Documentation

Dependencies

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 to dexkit.

DexKit current version: Maven Central

Usage Example

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

```java 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 method invoke the methods list .invokeMethods(MethodsMatcher.create() .add(MethodMatcher.create() .modifiers(Modifier.PUBLIC | Modifier.STATIC) .returnType("int") // be invoke method using strings .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

```kotlin 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) } } ```

Third-Party Open Source References

Star History

Star History Chart

License

LGPL-3.0 © LuckyPray