-
Notifications
You must be signed in to change notification settings - Fork 9
Home
With Dexplore, you can locate any obfuscated classes and methods in apk/dex/odex files. You have the option to either find classes at runtime to hook with Xposed or perform static searches using the Dexplore CLI tool.
Dependency ⎋
Add the dependency to your build.gradle file:
repositories {
mavenCentral()
}
dependencies {
implementation 'io.github.neonorbit:dexplore:1.4.5'
}Search Sample ⎋
It's worth noting that Dexplore generates a Reference Pool for each class, which contains all the strings used within that class.
(Reference Pool actually holds other types of references as well, but we'll discuss that later.)
public class Example {
public void find(String apkPath) {
// Suppose we want to find a class that contains the string "Hello Dex".
// Create a class filter to locate the desired class.
ClassFilter classFilter = new ClassFilter.Builder()
// To narrow down the search, limit it to string references only.
.setReferenceTypes(ReferenceTypes.STRINGS_ONLY)
// Check if the class contains the string "Hello Dex" in its reference pool.
.setReferenceFilter(pool ->
pool.contains("Hello Dex")
).build();
// Load the apk into Dexplore
Dexplore dexplore = DexFactory.load(apkPath);
// Perform a dex search
MethodData result = dexplore.findClass(classFilter);
// This is our target class
if (result != null) Util.log("Found: " + result.clazz); // Print the class name
}
}ReferencePool ⎋
Think of a ReferencePool as a container that holds all the References found in a dex class. These References primarily consist of string literals and identifiers (such as the names of classes, methods, and fields) used in the source code of that particular class.
For instance, this is a Sample java class:
public class Sample {
static final String TAG = "AppInit"; // String literal
Context context;
public void init(Activiy activiy) {
context = (Context) activiy; // Type: Context
if (Build.VERSION.SDK_INT >= 30) { // Field: SDK_INT
Util.log(getMessage()); // Method: log() and getMessage()
}
}
public static String getMessage() {
return "Congratulations"; // String literal
}
}The ReferencePool object of the Sample class will contain the follwing references:
Field References: SDK_INT
Method References: log, getMessage
Type References: android.content.Context
String References: "AppInit", "Congratulations"
The ReferencePool object of the init method will contain the follwing references:
Field References: SDK_INT
Method References: log, getMessage
Type References: android.content.Context
String References: [EMPTY]
The ReferencePool object of the getMessage method will contain the follwing references:
Field References: [EMPTY]
Method References: [EMPTY]
Type References: [EMPTY]
String References: "Congratulations"
Dexplore creates a ReferencePool object for each class, making it easier to identify a class by examining its reference pool. This simplifies the process of finding classes based on the references they contain.
If you want to see the references of a class without having to decompile it, use the CLI tool:
java -jar Dexplore.jar search input.apk -cls com.app.Sample -pool a # print all the references of a class
java -jar Dexplore.jar search input.apk -cls com.app.Sample -pool s # print only the string references of a class
# Refer to the CommandLine section of this wiki for further details.Find Classes ⎋
Now, let's search for an obfuscated class.
For example, we'll try to find a class that contains a specific string within its source code.
package xy.z; // obfuscated package
public final class ObfClassAaa { // obfuscated class
public void A0X() {
Processor.process("appx_unique_token"); // Assuming no other classes have this exact string.
// ... ... ...
}
}Note: Our target class has a unique string in its ReferencePool.
Find the ObfClassAaa class:
public class Example {
public void find(String apkPath) {
// Create a ClassFilter to match the ObfClassAaa class
ClassFilter classFilter = new ClassFilter.Builder()
// Since we will check String references only
.setReferenceTypes(ReferenceTypes.STRINGS_ONLY)
// Check whether the reference pool contains the unique string
.setReferenceFilter(pool -> pool.stringsContain("appx_unique_token"))
// Search in public final classes only (our target class is 'public final')
.setModifiers(Modifier.PUBLIC | Modifier.FINAL)
// It doesn't have any interfaces (skip all classes with interfaces)
.setInterfaces(Collections.emptyList())
// It doesn't have an explicit super class, which means 'Object' is the super class.
.setSuperClass(Object.class.getName())
// To make the filter more precise, you can specify additional conditions.
.build();
// Load the apk into Dexplore
Dexplore dexplore = DexFactory.load(apkPath);
// We will talk about 'DexFilter' later
ClassData result = dexplore.findClass(DexFilter.MATCH_ALL, classFilter);
// This is our target class
if (result != null) Util.log("Found: " + result.clazz); // In this case: xy.z.ObfClassAaa
// -------------------------- Additional -------------------------- //
// If you want to find all the classes that match the filter
List<ClassData> results = dexplore.findClasses(DexFilter.MATCH_ALL, classFilter, -1); // find all
// If you want to ensure that only one class has the string "appx_unique_token".
List<ClassData> results = dexplore.findClasses(DexFilter.MATCH_ALL, classFilter, 2); // find the first 2 matches
if (results.size() > 1) Util.log("Not unique"); // not unique
}
}ClassFilter API:
- setPackages(...) Search within the specified packages only
- skipPackages(...) Skip the specified packages
- setClasses(...) Search within the specified classes only
- setClassSimpleNames(...) Match classes that match with any of the specified class names.
- setSourceNames(...) Search within the specified source files only
- setModifiers(...) Match classes with the specified class modifiers
- skipModifiers(...) Skip classes with the specified class modifiers
- setSuperClass(...) Match classes with the specified superclass
- setInterfaces(...) Match classes with the specified interfaces
- setNumbers(...) Match classes that contain all the specified numbers
- containsAnnotations(...) Match classes that contain all the specified annotations
- containsAnnotationValues(...) Match classes that contain all the specified annotation values
Note: The filter will match if and only if all the specified conditions are satisfied.
Find Methods ⎋
Consider our obfuscated class has a method that looks like BtZ(),
package xy.z;
public final class ObfClassAaa { // obfuscated class
public void A0X() {
Processor.process("appx_unique_token"); // Assuming no other classes have this exact string.
}
public static int BtZ(ActivityManager am, boolean flag) { // obfuscated method
if (am.isLowRamDevice()) { // A method reference: 'isLowRamDevice'
return Something.get();
}
return 0;
}
}Note: Our target method has a method reference 'isLowRamDevice' and the class also has a unique string.
Let's find the BtZ() method:
public class Example {
public void find(String apkPath) {
// Create a ClassFilter to match ObfClassAaa
ClassFilter classFilter = new ClassFilter.Builder()
// Check in string and method references from the reference pool
.setReferenceTypes(ReferenceTypes.builder().addString().addMethod().build())
.setReferenceFilter(pool ->
// Check whether the reference pool contains the unique string
pool.stringsContain("appx_unique_token") &&
// Check whether it contains 'isLowRamDevice' method reference too (to search more accurately)
pool.methodsContain("isLowRamDevice")
)
// Search in public final classes only (our class is 'public final')
.setModifiers(Modifier.PUBLIC | Modifier.FINAL)
// It doesn't have any interfaces (skip all classes with interface)
.setInterfaces(Collections.emptyList())
// It doesn't have an explicit super class, which means 'Object' is the super class.
.setSuperClass(Object.class.getName())
// Build
.build();
// Create a MethodFilter to match BtZ() method
MethodFilter methodFilter = new MethodFilter.Builder()
// Check in method references only
.setReferenceTypes(ReferenceTypes.builder().addMethod().build())
// Check whether the target method contains this reference
.setReferenceFilter(pool -> pool.contains("isLowRamDevice"))
// Method return type is "int"
.setReturnType(int.class.getName())
// We know the parameters of BtZ()
.setParamList(Arrays.asList("android.app.ActivityManager", "boolean"))
// OR set parameter size instead (in case parameters are also obfuscated)
.setParamSize(2)
// Search in public static methods only (our method is 'public static')
.setModifiers(Modifier.PUBLIC | Modifier.STATIC)
// Build
.build();
// If you are confused, take a look at ReferencePool section again.
// Load the apk into Dexplore
Dexplore dexplore = DexFactory.load(apkPath);
// Search method
MethodData result = dexplore.findMethod(DexFilter.MATCH_ALL, classFilter, methodFilter);
// This is our target method
if (result != null) {
Util.log("Method: " + result.method); // In this case: BtZ
Util.log("Declaring class: " + result.clazz); // xy.z.ObfClassAaa
Util.log("Parameters: " + result.params[0] + result.params[1]);
}
// Read javadocs of all filter methods
}
}MethodFilter API:
- setMethodNames(...)
- setParamList(...)
- setParamSize(...)
- setReturnType(...)
- setModifiers(...)
- skipModifiers(...)
- setNumbers(...)
- containsAnnotations(...)
- containsAnnotationValues(...)
Build DexFilter ⎋
DexFilter example:
public class Example {
public void find(String apkPath) {
// If the input apk has multiple dex files, we can provide a dex filter to speed up the search
DexFilter dexFilter = new DexFilter.Builder()
// Check string references only
.setReferenceTypes(ReferenceTypes.STRINGS_ONLY)
// Analyze only the dex file that contains this string in its reference pool.
// Checking whether a dex file contains a reference is much faster than checking each classes.
.setReferenceFilter(ReferenceFilter.contains("appx_unique_token"))
// If we know which dex file contains the class, we can specify it. (it doesn't change often)
// Setting a preferred dex file will analyze it first.
.setPreferredDexNames("classes4.dex") // Analyze classes4.dex first.
// Build
.build();
// Additionally, we can specify to load root dex files only. (classes.dex, classes2.dex, ...)
// This will avoid verifying (dex or not) thousands of non-dex file from the apk.
DexOptions options = new DexOptions();
options.rootDexOnly = true;
DexFactory.load(apkPath, options).findClass(dexFilter, classFilter);
}
}DexFilter API:
Advanced Search ⎋
What if the class we are trying to find doesn't have any unique properties?
In that case, we need to find out all the usages of that class manually.
If another unique class uses our target class, we will find that unique class first and then extract the target class.
Say we are trying to find a class named ObfClassBBB, which doesn't have any unique properties.
But another obfuscated class ObfClassAaa that uses our target class does have unique properties.
package xy.z;
public class ObfClassAaa { // a random obfuscated class
public ObfClassAaa(ObfClassBBB param) { // ObfClassBBB: our target class
// ...
}
public void A0X() {
Processor.process("appx_unique_token");
ObfClassBBB obj = new ObfClassBBB("id", 1); // ObfClassBBB() our target class constructor
}
}Let's find it:
public class Example {
public void find(String apkPath) {
// Our goal is to find ObfClassBBB, but it doesn't have any unique properties.
// So, let's find the ObfClassAaa first.
Dexplore dexplore = DexFactory.load(apkPath);
// Create a ClassFilter to find ObfClassAaa
ClassFilter classFilter = new ClassFilter.Builder()
.setReferenceTypes(ReferenceTypes.STRINGS_ONLY)
.setReferenceFilter(pool -> pool.stringsContain("appx_unique_token"))
.build();
// helperClass: ObfClassAaa
ClassData helperClass = dexplore.findClass(DexFilter.MATCH_ALL, classFilter);
if (helperClass == null) return; // failed
// First approach, the parameter of ObfClassAaa constructor is the target class (ObfClassBBB)
List<MethodData> constructors = helperClass.getConstructors() // find the constructor
.stream().filter(m ->
m.params.length == 1 // in case there are multiple constructors
).collect(Collectors.toList());
if (constructors.size() != 1) return; // further confirmation
// target: ObfClassBBB
Util.log("Result: " + constructors.get(0).params[0]); // the parameter of the constructor is the target class (ObfClassBBB)
// ------------------------------------------------------------------------------------- //
// Alternative approach: (This one is little confusing, read carefully)
// A0X() method creates an instance of ObfClassBBB class,
// which means A0X() ReferencePool contains the constructor of ObfClassBBB.
// A0X() is also obfuscated, so extract the A0X() method by checking its reference pool
MethodData A0X = helperClass.getMethods()
.stream().filter(m ->
m.getReferencePool().contains("appx_unique_token") // A0X() contains this string
).findFirst().orElse(null);
if (A0X == null) return; // failed
// Now extract the target class (ObfClassBBB) from the reference pool of A0X()
// new ObfClassBBB("id", 1);
// This is a constructor, which means A0X() ReferencePool contains it in the form of a method reference.
// Extract the method reference of the constructor first.
List<MethodRefData> mRefs = A0X.getReferencePool() // ReferencePool of A0X() method
.getConstructorSection().stream() // find in method references (constructors only)
.filter(ref ->
// match only if it has 'String' and 'int' parameters. (target class constructor has these two params)
ref.getParameterTypes().equals(Arrays.asList("java.lang.String", "int"))
).collect(Collectors.toList());
// let's hope there is only one constructor with such signature
if (mRefs.size() != 1) return; // assumption failed
// Target: ObfClassBBB
// Declaring class of the constructor 'ObfClassBBB()' is the target class 'ObfClassBBB'
Util.log("Result: " + mRefs.get(0).getDeclaringClass());
}
}This is just an example. You can find your target class in so many creative ways.
Batch Operation ⎋
public class Example {
public void find(String apkPath) {
// Create a batch of queries
QueryBatch batch = new QueryBatch.Builder()
// A class query with 'item1' id
// (id is just an arbitrary string to identify it later)
.addClassQuery("item1", dexFilter1, classFilter1)
// Another class query with 'item2' id
.addClassQuery("item2", dexFilter2, classFilter2)
// A method query with 'itemX' id
.addMethodQuery("itemX", dexFilter3, classFilter3, methodFilter3)
// To speed up the process
.setParallel(true)
// Build
.build();
Dexplore dexplore = DexFactory.load(apkPath);
// Example #1: Store the first result from each query
Map<String> results = new ConcurrentHashMap<>(); // for thread safety (since parallel flag is enabled)
dexplore.onQueryResult(batch, (key, result) -> {
results.put(key, result); // 'key' is the unique id used to create the query.
// If you only need the first result from each query, return true for each callback.
return true; // stop further search for the given key.
});
// Example #2: Examine each result manually
dexplore.onQueryResult(batch, (key, result) -> {
if (key.equals("item1")) { // a result from the 'item1' query
ClassData cls = (ClassData) result; // 'item1' was a class query
if (cls /* is your desired result? */) {
store = cls; // store it
return true; // found? stop further search for 'item1'
} else {
return false; // not the result you expected? continue searching for 'item1'
}
// Handle others ...
// ................
}
});
}
}Xposed Samples ⎋
public class XposedModule implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam param) {
if (!param.packageName.equals("com.pkg.app")) return;
// First, find all the necessary classes/methods using Dexplore.
String apk = param.appInfo.sourceDir;
Dexplore dexplore = DexFactory.load(apk);
MethodData result = dexplore.findMethod(/* query */); // for example: find an obfuscated method
// Write it to Preferences
preferences.edit()
.putString("obfMethod", result.serialize()) // the obfuscated method
.putLong("appVersion", currentVersionCode) // app version code (NOT version name).
.apply();
// Load the method
ClassLoader cl = param.classLoader;
// Either load directly (easy)
Method method = result.loadMethod(cl);
// Or load with xposed (if you want)
XposedHelpers.findMethodExact(result.clazz, cl, result.method, (Object[]) result.params);
// Hook with xposed
XposedBridge.hookMethod(method /* , hook */);
// ------------------------ . ------------------------ //
// Next time, retrieve it from Preferences
long code = preferences.getLong("appVersion", 0);
if (code == currentVersionCode) {
String raw = preferences.getString("obfMethod", null);
MethodData retrieved = MethodData.deserialize(raw); // deserialize
// Load and hook with xposed
// .........
} else {
// App version code changed, find your necessary classes/methods again
dexplore.findMethod(/* query */); // find again using the same query
// Save new data to Preferences and ...................
}
}
}Requires: JDK 8+
Available Commands:
-
s,search: Search classes and methods -
d,decode: Decompile java, smali and resource files -
m,mapver: Map classes from one version to another
Usage details:
java -jar Dexplore.jar --help
java -jar Dexplore.jar --help search
java -jar Dexplore.jar --help decode
java -jar Dexplore.jar --help mapverLet's create an alias for convenience,
alias Dexplore='java -jar Dexplore-1.4.5.jar'Static Analysis ⎋
Usage:
java -jar Dexplore.jar --help search
Find classes:
Dexplore search input.apk --mode c --sources 'AClassName.java' 'AnotherClassName.java'
Dexplore search input.apk --mode c --cls-names 'AClassName' 'AnotherClassName'
Dexplore search input.apk --mode c --classes 'com.any.AClassName' 'com.any.AnotherClassName'
Dexplore search input.apk --mode c --numbers '101' '201' '301.0f' '401.5d'
Dexplore search input.apk --mode c --ref-type s --references 'A unique string'
Dexplore search input.apk --mode c --ref-type s --references 'A unique string' 'AnotherString'
Dexplore search input.apk --mode c --ref-type sm --references 'A unique string' 'aMethodRefName'
Dexplore search input.apk --mode c --ref-type f --signatures 'java.lang.Byte.SIZE:int' # field signature
Dexplore search input.apk --mode c --ref-type m --signatures 'com.util.Time.setNow(int,java.lang.String,int):int'
# Advanced Search
# Format: 'm:public+..., s:superclass, i:interface+..., a:annotation+...'
Dexplore search input.apk --mode c --class-advanced 'm:public+final, s:java.lang.Object, i:com.any.AnInterface, a:com.any.SomeAnnotation'Find methods:
Dexplore search input.apk --mode m --ref-type s --references 'A_unique_string'
# Advanced Search
# Format: 'm:public+..., n:name+..., p:param+..., r:return, a:annot+..., z:paramSize'
Dexplore search input.apk --mode c --class-advanced 'm:public+final, n:AMethodName, p:boolean+java.lang.String, a:com.any.SomeAnnotation'Find all the matching classes from multiple versions of an app:
Dexplore search input-v1.apk input-v2.apk input-v3.apk --mode c --ref-type s --references 'A_unique_string'Print the references of classes from the search result:
Dexplore search input.apk --classes 'java.lang.String' --print-pool a # Print all references
Dexplore search input.apk --classes 'java.lang.String' --print-pool sf # Print only string and field references
Dexplore search input.apk --mode c --ref-type s --ref 'A_unique_string' --print-pool a # Print references from the search resultsPrint the references of methods from the search result:
Dexplore search input.apk --mode m --ref-type s --ref 'A_unique_string' --print-pool aGenerate java and smali source files of classes from the search result:
Dexplore search input.apk --mode c --ref-type s -ref 'A_unique_string' -gen
Dexplore search input.apk -cls 'app.pkg.ClassA' 'app.pkg.ClassB' 'app.pkg.ClassC' -genFull Decompiler ⎋
Usage:
java -jar Dexplore.jar --help decode
Decompile an app:
Dexplore decode input.apk # Default: --mode j
Dexplore decode input.apk --mode j # Decompile java sources
Dexplore decode input.apk --mode s # Decompile smali sources
Dexplore decode input.apk --mode r # Decompile manifest and resources
Dexplore decode input.apk --mode js # Decompile java and smali files
Dexplore decode input.apk --mode jr # Decompile java and resource filesDecompile partially:
Dexplore decode input.apk -cls 'ClassA' 'ClassB' # Decompile a list of classes by names
Dexplore decode input.apk -cls 'app.pkg.ClassA' 'app.pkg.ClassB' # Decompile a list of classes
Dexplore decode input.apk -pkg 'app.pkg.ds.any' 'app.pkg.d.util' # Decompile a list of packages
Dexplore decode input.apk --mode r -res 'manifest' # Decompile android manifest
Dexplore decode input.apk --mode r -res 'values' 'drawable' # Decompile specific resourcesDecompiler tweaks:
Dexplore decode input.apk -eps # Enable pause capability (ENTER key to pause/resume)
Dexplore decode input.apk -dmem # Disable In-Memory cache
Dexplore decode input.apk -dren # Disable class names renaming
Dexplore decode input.apk --jobs 4 # The number of threads to use (for better performance)Troubleshooting ⎋
Out of memory issue:
-
Solution-1: Reduce the number of threads
java -jar Dexplore.jar decode input.apk --jobs 1 # 1 Thread java -jar Dexplore.jar decode input.apk --jobs 2 # 2 Threads
-
Solution-2: Increase the maximum RAM for JVM
java -jar -Xmx6g Dexplore.jar decode input.apk # 6GB Memory java -jar -Xmx8g Dexplore.jar decode input.apk # 8GB Memory