This article is currently an experimental machine translation and may contain errors. If anything is unclear, please refer to the original Chinese version. I am continuously working to improve the translation.
Nowadays, most apps use obfuscation to prevent reverse engineering. However, a few apps still use code protection (app hardening) techniques to increase the difficulty of analysis.
Deobfuscation (or “unpacking”) isn’t actually that hard — you can achieve it using Xposed or even a self-compiled Android ROM.
However, when using Xposed to hook such hardened apps, there’s one extra step required: obtaining the real ClassLoader.
I came across an excellent article online that clearly explains this issue. I’m saving it here for future reference.
The following content is reposted from: https://curz0n.github.io/2018/07/10/xposed_find_classloader/
Using Xposed to Obtain the Real ClassLoader: Common Use Cases
When using Xposed to instrument code and analyze app behavior, under normal circumstances you can simply use the system’s default classloader to meet your needs. However, in special cases — such as when an app is hardened or uses dynamic dex loading — directly using the default classloader for hooking will often result in a ClassNotFoundException. Below is a summary of several common scenarios for obtaining the actual classloader associated with a class, for future reference.
0x01 Using 360 Protection
After being protected by 360, an app’s classloader is replaced with 360’s custom loader. Therefore, during hooking, you must replace the classloader with 360’s version.
Code Characteristics
When you decompile a 360-protected app, the dex file contains only a few classes. The most important one is usually named StubAppxxxx (where “xxxx” is a sequence of digits). Inside this class, you can hook the getNewAppInstance method to obtain the Context parameter. From this context, you can retrieve 360’s classloader. After that, simply use this classloader to perform further hooking operations successfully.
img
Example Code
1 | // Hook 360 wrapper |
0x02 Using Baidu Protection
The principle is the same as with 360 — the classloader is replaced, so you need to substitute Xposed’s default loader with Baidu’s.
Code Characteristics
For Baidu-protected apps, decompilation only reveals the outer shell code. The package name typically starts with com.baidu.xxx. You can hook the StubApplication.onCreate method to obtain the real classloader, then use it to hook into the actual business logic.
img
Example Code
1 | // Hook Baidu wrapper |
0x03 Using Other Protection Schemes
The idea remains the same: before hooking the target code, you must first obtain the real classloader responsible for loading it. This is typically done by hooking the app’s entry point — either attachBaseContext or onCreate — then retrieving the Context parameter (in before) or the result object (in after), and casting it to obtain the classloader.
Code Characteristics
After decompiling a hardened app, you usually only see a few shell classes. The core logic is hidden. Typical signs of hardening include:
img
Example Code
1 | // Retrieve classloader from protected app; usually start analysis at attachBaseContext or onCreate |
0x04 Using DexClassLoader for Dynamic Loading of JAR/APK
During app reverse engineering, you may encounter apps that use dynamic plugin loading. Typically, the app downloads the latest plugin package (usually a JAR file) from a server upon startup. Then, when reaching specific functionality, it uses DexClassLoader to load the dex file inside the JAR/APK and execute its bytecode.
Code Characteristics
When DexClassLoader is used to dynamically load dex, the three arguments passed to the loadParser method in the image below are:
- arg5: Context
- arg6: Absolute path to the plugin JAR file
- arg7: Full class name to be loaded from the JAR
img
The DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) constructor has four parameters:
- dexPath: Path to the JAR/APK containing the target classes
- optimizedDirectory: Directory where the extracted dex files are stored
- libraryPath: Directory for native libraries (C/C++) used by the target classes
- parent: Parent classloader
Example Code
When dynamically loading plugins via a classloader, the process eventually calls java.lang.ClassLoader.loadClass to load the actual bytecode. Therefore, you can hook the system’s loadClass method to obtain the real classloader. (In theory, this approach also works for hardened apps to retrieve the correct classloader.)
1 | @Override |
0x05 Other Cases of Dynamic Loading
In the previous example, after loadClass is called, the app proceeds to newInstance to instantiate an object, calls a method on it, and returns the result (e.g., a UrlParser object). Since the returned object cannot be cast to ClassLoader, we need to hook loadClass directly.
However, in some apps using plugin architectures, the custom classloader might be created and used within a specific method. In such cases, simply hook that method and retrieve the classloader in the afterHookedMethod block. (This is similar to retrieving the classloader from the outer shell in hardened apps.)
References:
Android Dynamic Loading — DexClassLoader Analysis
Android Plugin System Exploration (1): Class Loader DexClassLoader
ClassLoader Source Code
Using Xposed to Hook Apps Protected by 360
This article is licensed under the CC BY-NC-SA 4.0 license.
Author: lyc8503, Article link: https://blog.lyc8503.net/en/post/xposed-hook-packed/
If this article was helpful or interesting to you, consider buy me a coffee¬_¬
Feel free to comment in English below o/