Library / SDK
LuckyPray/DexKit avatar
LuckyPray/DexKit

DexKit: finding obfuscated classes by their structure, not their names

An easy-to-use, high-performance dex deobfuscation library.

1,070 stars122 forksKotlinApache-2.0

At a glance

What is it?
DexKit is a native runtime parsing library for dex that lets you locate a class, method, or field by a set of structural conditions rather than by name, which is what you need when the names change with every release. It ships a Kotlin API over a C++ core, and batch string search is the feature it recommends.
Who is it for?
Adopt DexKit if you write Xposed modules or instrumentation that has to keep working across app releases that rename their classes. Do not adopt it for code you control, where a direct reference is clearer and faster.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly Kotlin, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is names that move every release

DexKit is described as an easy-to-use, high-performance dex deobfuscation library, and the more precise description in the README is a high-performance runtime parsing library for dex implemented in C++, used for lookup of obfuscated classes, methods, or properties.

The problem it solves is specific and very common. When an app is obfuscated, its class names are meaningless strings that change with every release. A hook or a patch written against a name from last month's build breaks today. The example in the README makes this concrete: a class whose names are mostly obfuscated and change in each version, with fields called a and b, a method called d that rolls a random number, and a method called e that sets text.

Note what is stable in that sample. The class has three fields, one of type java.lang.String that is private, static and final and named TAG, one of type android.widget.TextView, and one of type android.os.Handler. The field count is stable, the field types are stable, and the modifiers are stable. Those are the things DexKit matches on, and the whole design follows from the observation that structure survives obfuscation while names do not.

The project is Apache-2.0 licensed, its home page is luckypray.org/DexKit, the last push to the repository was on 2026-10-10, and the recent releases are 2.3.0 from 2026-09-22, 2.2.0 from 2026-04-11, and 2.1.0 from 2026-04-02.

The matching API, and why more conditions is not better

The Kotlin API is a builder of matchers, one per object kind, and the nesting mirrors the structure of the dex.

The top level is FindClass.create(), which holds the search scope, including searchPackages to restrict to a package range and excludePackages to remove one. Inside it is ClassMatcher.create(), which holds the conditions on the class itself, and inside that are FieldsMatcher and MethodsMatcher, which hold the conditions on the members.

The individual conditions are narrow. A FieldMatcher can specify the modifiers of the field, the type, and the name. In the example the field matcher asks for private, static and final, of type java.lang.String, named TAG. The fields matcher then adds a second matcher for the field of a specified type with addForType, twice, once for android.widget.TextView and once for android.os.Handler, and finally constrains the number of fields in the class with count(3).

That last constraint is the one that makes the search fast, and it is the one people leave out. Specifying that the class has exactly three fields turns an open-ended search into a very selective one, and a class with a distinctive member count is a much better anchor than a distinctive name.

The README is direct about the discipline required. It says the example is just an example, that in actual usage there is no need for such an extensive set of matching conditions, and that you should choose and use as needed to avoid unnecessary complexity in matching due to an excessive number of conditions. The tension is real: more conditions narrow the result set but also make the rule more likely to break when the app changes, and a rule that breaks is worse than a slow one.

The basic features are multi-condition class search, multi-condition method search, multi-condition field search, and multiple metadata APIs to obtain field, method, and class related data.

Batch string search, and the version that renamed the artifact

The distinctive features, and the ones the README marks as recommended, are batch search of classes using strings and batch search of methods using strings.

The optimisation note explains why they are different from the rest. Optimizations have been implemented for string search scenarios, significantly enhancing search speed, and increasing query groups will not lead to a linear increase in time consumption. That last property is the point. A batch search of a hundred string groups takes roughly the same time as a batch search of ten, because the implementation does not run a hundred independent scans.

This matters because string search is how you find the entry point of an obfuscated app in practice. A URL, a log tag, a resource name, a method name fragment: strings survive obfuscation because they have to, and finding the class that contains a given string is often the fastest route to the class you actually want.

The dependency setup is a single Gradle block, and there is a change worth knowing about:

gradle
repositories {
    mavenCentral()
}
dependencies {
    // replace <version> with your desired version, e.g. `2.0.0`
    implementation 'org.luckypray:dexkit:<version>'
}

Starting with DexKit 2.0, the new ArtifactId has been changed from DexKit to dexkit. The artifact is on Maven Central under the org.luckypray namespace, and the version is dynamic, so you substitute your own.

The bridge lifecycle, which is where the cost is

The usage example is an Xposed hook, and the first thing it does is load the native library, and the second thing it does is the part worth internalising.

The comment in the example says it plainly: 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. The example uses try-with-resources to close the DexKitBridge instance automatically.

The shape of the code makes the cost obvious. The bridge is created from the apk path, the source directory of the loaded package, and it is a single object for the whole app process. Creating it means parsing the dex, and the parse is proportional to the size of the application. For a large app that is not free, and the advice to do it once and hold it is not a style preference.

The scope of the work is also visible in the example. The hook checks the package name, and returns early if it does not match the package it cares about, so the bridge is only created for the target application rather than for every process the hook sees. The class loader is captured from the load package parameter, which is the object the framework hands you, and the search runs against the apk path from the app info.

The repository layout shows a multi-module Gradle project. There is dexkit/ for the main library, dexkit-android/ for the Android facing part, dexkit-dev/ for the development tooling, main/ and Core/ for the native layers, demo/ for the sample application, build-support/ for the native build plumbing, lint-rules/ for custom lint rules, schema/ for the persisted data model, and doc-source/ for the documentation.

Against a hardcoded name, and against a manual dex parser

The obvious alternative is to hardcode the obfuscated name and update it whenever the app releases. That is what most hooks start as, and it works until it does not. The maintenance cost is one lookup per app release per hook, and the failure is silent in the sense that the hook simply stops doing anything, which is often harder to notice than a crash.

The difference in approach is that DexKit turns that maintenance into a rule. You write the structural conditions once, and they keep matching as long as the structure holds. The cost is that the rule can also break, and when it does, the search either finds nothing or finds the wrong class, which is worse than a name that stopped matching because a wrong match can corrupt state.

The second alternative is to parse the dex yourself. Libraries exist for that, and they work, but the matching layer is what you would have to write, and matching classes by a set of structural predicates is the tedious part. DexKit is essentially that matching layer, with the performance work already done.

The third option is to hook at a higher level and never identify the class at all. If the behaviour you want to change is reachable through a public API, a framework callback, or a resource, hooking that is more robust than resolving an obfuscated class, and it survives a redesign rather than just a rename. DexKit is for the cases where the class itself is the thing you need.

The trade-off that comes with all of this is a native dependency in your build. The library is a JNI wrapper, so it adds a .so to your APK and requires ABI support, and the minimum supported API level is something to check for your own project rather than assume.

Editorial conclusion

Adopt DexKit if you write Xposed modules or instrumentation that has to keep working across app releases that rename their classes. Do not adopt it for code you control, where a direct reference is clearer and faster. Verify first how long the bridge takes to create, because the documentation calls it a time-consuming operation and that cost is worth measuring for your own app before you rely on it in a hot path.

Frequently asked questions

What problem does DexKit solve?

It is a runtime dex parsing library in C++ for locating obfuscated classes, methods, or properties by structural conditions rather than by name, so a lookup keeps working when the app's obfuscated identifiers change with each release.

How do I add DexKit to my project?

Add the dependency in build.gradle from mavenCentral as implementation 'org.luckypray:dexkit:<version>'. Note that starting with DexKit 2.0 the ArtifactId changed from DexKit to dexkit, so an older coordinate will not resolve.

How should I create the DexKitBridge instance?

Create it once, not repeatedly, because DexKit creation is described as a time-consuming operation. The example uses try-with-resources so the bridge is closed automatically, and the documentation advises managing the life cycle yourself and calling close when it is not needed to prevent memory leaks.

What are the distinctive features of DexKit?

Batch search of classes using strings and batch search of methods using strings, which the README marks as recommended. Optimisations have been implemented for string search scenarios, so increasing the number of query groups does not lead to a linear increase in time consumption.

Should I use many matching conditions in DexKit?

The README says the example is only an example, that in actual usage there is no need for such an extensive set of matching conditions, and that you should choose conditions as needed to avoid unnecessary complexity from an excessive number of them.

Official sources

  1. License: Apache-2.0
  2. LuckyPray/DexKit on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/luckypray-dexkit.svg)](https://hysenlabs.com/projects/luckypray-dexkit)

Was this useful?