manual-deobfuscation.md (18364B)
1 --- 2 title: "Manual De-obfuscation Techniques" 3 section: "Mobile" 4 sectionSlug: "mobile-pentesting" 5 sourcePath: "src/mobile-pentesting/android-app-pentesting/manual-deobfuscation.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/manual-deobfuscation.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Manual De-obfuscation Techniques 14 15 ## Manual **De-obfuscation Techniques** 16 17 In the realm of **software security**, the process of making obscured code understandable, known as **de-obfuscation**, is crucial. This guide delves into various strategies for de-obfuscation, focusing on static analysis techniques and recognizing obfuscation patterns. Additionally, it introduces an exercise for practical application and suggests further resources for those interested in exploring more advanced topics. 18 19 ### **Strategies for Static De-obfuscation** 20 21 When dealing with **obfuscated code**, several strategies can be employed depending on the nature of the obfuscation:<sup>[[2]](#references)</sup> 22 23 - **DEX bytecode (Java)**: One effective approach involves identifying the application's de-obfuscation methods, then replicating these methods in a Java file. This file is executed to reverse the obfuscation on the targeted elements. 24 - **Java and Native Code**: Another method is to translate the de-obfuscation algorithm into a scripting language like Python. This strategy highlights that the primary goal is not to fully understand the algorithm but to execute it effectively. 25 26 ### **Identifying Obfuscation** 27 28 Recognizing obfuscated code is the first step in the de-obfuscation process. Key indicators include:<sup>[[2]](#references)</sup> 29 30 - The **absence or scrambling of strings** in Java and Android, which may suggest string obfuscation. 31 - The **presence of binary files** in the assets directory or calls to `DexClassLoader`, hinting at code unpacking and dynamic loading. 32 - The use of **native libraries alongside unidentifiable JNI functions**, indicating potential obfuscation of native methods. 33 34 ## **Dynamic Analysis in De-obfuscation** 35 36 By executing the code in a controlled environment, dynamic analysis **allows for the observation of how the obfuscated code behaves in real time**. This method is particularly effective in uncovering the inner workings of complex obfuscation patterns that are designed to hide the true intent of the code.<sup>[[3]](#references)</sup><sup>[[4]](#references)</sup> 37 38 ### **Applications of Dynamic Analysis** 39 40 - **Runtime Decryption**: Many obfuscation techniques involve encrypting strings or code segments that only get decrypted at runtime. Through dynamic analysis, these encrypted elements can be captured at the moment of decryption, revealing their true form. 41 - **Identifying Obfuscation Techniques**: By monitoring the application's behavior, dynamic analysis can help identify specific obfuscation techniques being used, such as code virtualization, packers, or dynamic code generation. 42 - **Uncovering Hidden Functionality**: Obfuscated code may contain hidden functionalities that are not apparent through static analysis alone. Dynamic analysis allows for the observation of all code paths, including those conditionally executed, to uncover such hidden functionalities. 43 44 ### Automated De-obfuscation with LLMs (Androidmeda) 45 46 While the previous sections focus on fully manual strategies, in 2025 a new class of *Large-Language-Model (LLM) powered* tooling emerged that can automate most of the tedious renaming and control-flow recovery work. 47 One representative project is **[Androidmeda](https://github.com/In3tinct/Androidmeda)** – a Python utility that takes *decompiled* Java sources (e.g. produced by `jadx`) and returns a greatly cleaned-up, commented and security-annotated version of the code.<sup>[[5]](#references)</sup><sup>[[6]](#references)</sup> 48 49 #### Key capabilities 50 * Renames meaningless identifiers generated by ProGuard / DexGuard / DashO / Allatori / … to *semantic* names. 51 * Detects and restructures **control-flow flattening**, replacing opaque switch-case state machines with normal loops / if-else constructs. 52 * Decrypts common **string encryption** patterns when possible. 53 * Injects **inline comments** that explain the purpose of complex blocks. 54 * Performs a *lightweight static security scan* and writes the findings to `vuln_report.json` with severity levels (informational → critical). 55 56 #### Installation 57 ```bash 58 git clone https://github.com/In3tinct/Androidmeda 59 cd Androidmeda 60 pip3 install -r requirements.txt 61 ``` 62 63 #### Preparing the inputs 64 1. Decompile the target APK with `jadx` (or any other decompiler) and keep only the *source* directory that contains the `.java` files: 65 ```bash 66 jadx -d input_dir/ target.apk 67 ``` 68 2. (Optional) Trim `input_dir/` so that it only contains the application packages you want to analyse – this massively speeds-up processing and LLM costs. 69 70 #### Usage examples 71 72 Remote provider (Gemini-1.5-flash): 73 ```bash 74 export OPENAI_API_KEY=<your_key> 75 python3 androidmeda.py \ 76 --llm_provider google \ 77 --llm_model gemini-1.5-flash \ 78 --source_dir input_dir/ \ 79 --output_dir out/ \ 80 --save_code true 81 ``` 82 83 Offline (local `ollama` backend with llama3.2): 84 ```bash 85 python3 androidmeda.py \ 86 --llm_provider ollama \ 87 --llm_model llama3.2 \ 88 --source_dir input_dir/ \ 89 --output_dir out/ \ 90 --save_code true 91 ``` 92 93 #### Output 94 * `out/vuln_report.json` – JSON array with `file`, `line`, `issue`, `severity`. 95 * A mirrored package tree with **de-obfuscated `.java` files** (only if `--save_code true`). 96 97 #### Tips & troubleshooting 98 * **Skipped class** ⇒ usually caused by an unparsable method; isolate the package or update the parser regex. 99 * **Slow run-time / high token usage** ⇒ point `--source_dir` to *specific* app packages instead of the entire decompile. 100 * Always *manually review* the vulnerability report – LLM hallucinations can lead to false positives / negatives. 101 102 #### Practical value – Crocodilus malware case study 103 Feeding a heavily obfuscated sample from the 2025 *Crocodilus* banking trojan through Androidmeda reduced analysis time from *hours* to *minutes*: the tool recovered call-graph semantics, revealed calls to accessibility APIs and hard-coded C2 URLs, and produced a concise report that could be imported into analysts’ dashboards.<sup>[[5]](#references)</sup> 104 105 --- 106 107 ### Targeted Dalvik string decryption with DaliVM 108 109 **DaliVM** is a Python Dalvik bytecode emulator aimed at statically recovering runtime-only values (especially decrypted strings) without spinning up Android. It executes a *specific* method inside an APK by emulating Dalvik opcodes and mocking Android/Java APIs.<sup>[[1]](#references)</sup> 110 111 **Workflow** 112 1. **Select target method** by Dalvik signature (`Lpkg/Class;->method(Args)Ret`). Examples: `Lutil/Crypto;->decrypt(Ljava/lang/String;)Ljava/lang/String;`, `LMyClass;->compute(II)I`. 113 2. **Enumerate call sites** across **multi-DEX** (`classes*.dex`) and **reconstruct arguments** via backward data-flow tracing, forward lookup, and partial execution when needed. 114 3. **Emulate the method** inside the Dalvik VM (covers 120+ opcodes across const/array/control/field/invoke, handles class init via `<clinit>`) and **collect return values** (e.g., decrypted strings). 115 4. **Bypass runtime dependencies** using built-in mocks for common Android APIs (Context, PackageManager, Signature, reflection, system services) and hooks for Java stdlib (String/StringBuilder/Integer/Math/Arrays/List/Iterator). 116 5. If execution stalls, **enable opcode-level tracing** to see PC/register changes and extend opcode handlers. 117 118 **CLI usage** 119 ```bash 120 # Emulate a decryptor and dump all returns 121 python emulate.py app.apk "Lcom/example/Decryptor;->decrypt" 122 123 # Verbose, debug trace, and limit outputs 124 python emulate.py app.apk "Lcom/example/Decryptor;->decrypt" -v --debug --limit 10 125 ``` 126 127 Outputs are the collected return values per invocation; useful for bulk string/config extraction during malware triage or heavily obfuscated apps. 128 129 ### Offline recovery of staged Android malware payloads 130 131 A recurring Android malware pattern is a **small Java stub + stripped JNI loader + high-entropy asset**. If the APK contains a native library with one abnormally large JNI export, encrypted strings, and an `assets/` blob that doesn't match its file extension, you can usually recover the next stage **without executing the sample**.<sup>[[7]](#references)</sup> 132 133 #### Repair hostile APK and DEX metadata before decompiling 134 135 Treat an APK as an **adversarial ZIP**, not as a trustworthy filesystem tree. List members before extraction and reject absolute paths, normalized paths that escape the output directory, and file/directory collisions. A sample can remain installable while crafted names make extractors omit entries, crash, or write outside the analysis directory.<sup>[[8]](#references)[[11]](#references)</sup> 136 137 A DEX parser error also does **not** prove that the bytecode is encrypted. Compare the header and `map_list` against the actual layout: section sizes must fit inside `file_size`, fixed-width tables must be aligned, indexes must stay within their target tables, and `string_data`, `class_data`, and `code_item` references must decode consistently. A packer can swap map type labels or point table entries at valid-but-wrong data so a disassembler follows the attacker's metadata instead of the real structures.<sup>[[9]](#references)[[11]](#references)</sup> 138 139 Useful repairs on a disposable copy are:<sup>[[9]](#references)[[11]](#references)</sup> 140 141 - Rebuild incorrect map entries from structurally valid candidate sections instead of trusting the declared type/offset pair. 142 - Replace a junk `code_item.debug_info_off` with `0` when debugging data is nonessential; `0` explicitly means that no debug information exists. 143 - Parse only through the DEX header's declared `file_size`, but carve any trailing overlay for separate analysis; triage invalid references in unreachable methods separately from reachable code. 144 - After patching offsets or instructions, update `file_size`/map values as needed and recompute the DEX SHA-1 signature and Adler-32 checksum before reopening it in strict tools. 145 146 #### Recover indexed native string oracles and encrypted assets 147 148 When most Java strings are calls such as `nativeGetStr(int)`, enumerate the integer call sites and reverse the single JNI routine as a **string oracle**.<sup>[[8]](#references)[[11]](#references)</sup> In a stripped native library, the fixed AES S-box and Rcon tables identify AES; a nonce/counter block plus an incrementing counter distinguishes CTR-like use from ECB/CBC.<sup>[[11]](#references)</sup> Preserve the exact counter layout and endianness when reimplementing it, then iterate all valid indexes to recover configuration, asset names, permission strings, and payload parameters in bulk.<sup>[[11]](#references)</sup> 149 150 Apply recovered cipher parameters to high-entropy, extensionless assets offline and validate the plaintext independently rather than trusting a successful decrypt. For an embedded APK, check ZIP integrity and its signing metadata:<sup>[[8]](#references)[[11]](#references)</sup> 151 152 ```bash 153 file stage2.bin 154 unzip -t stage2.bin 155 apksigner verify --verbose --print-certs stage2.bin 156 ``` 157 158 #### Reconstruct DPT-Shell method bodies 159 160 DPT-Shell hollows DEX method implementations and reconstructs them at runtime.<sup>[[10]](#references)</sup> Strong fingerprints are a small `ProxyApplication`/`JniBridge` stub, `assets/OoooooOooo`, and a native loader below `assets/vwwwwwvwww/` for each ABI.<sup>[[10]](#references)[[11]](#references)</sup> Repair deceptive DEX table offsets **before** resolving method indexes; otherwise valid code-store records will be mapped to the wrong methods.<sup>[[11]](#references)</sup> 161 162 The upstream writer and runtime parser define the code store as little-endian records:<sup>[[10]](#references)</sup> 163 164 ```text 165 u16 version 166 u16 dex_count 167 u32 dex_section_offset[dex_count] 168 for each DEX section: 169 u16 method_count 170 repeat method_count times: 171 u32 method_idx 172 u32 instruction_size_bytes 173 u8 instructions[instruction_size_bytes] 174 ``` 175 176 For each DEX section, use `method_idx` as an index into that file's `method_ids`, locate the method's `code_item`, and restore its `insns[]` bytes.<sup>[[10]](#references)[[11]](#references)</sup> Keep DEX instruction units in mind: `code_item.insns_size` counts 16-bit code units whereas the external store records a byte length. Validate every offset/length against the store boundary, then regenerate the DEX signature/checksum after patching.<sup>[[9]](#references)[[10]](#references)</sup> 177 178 Also inspect nominal image resources instead of assuming they are decoration. For PNGs, concatenate `IDAT` chunks in file order, decompress the zlib stream, and compare its expected scanline length with the actual output; unexplained trailing data or additional streams can be another code/payload carrier.<sup>[[11]](#references)</sup> 179 180 #### OLLVM-style native XOR string recovery 181 182 A common native pattern is a one-time init block that decrypts strings **in place** byte-by-byte: 183 184 ```c 185 if (init_done == 0) { 186 DAT_00142f50 ^= 0xd7; 187 DAT_00142f51 ^= 0xb4; 188 DAT_00142f52 ^= 0xa6; 189 init_done = 1; 190 } 191 ``` 192 193 Practical workflow: 194 - In **Ghidra**, increase the decompiler timeout if one JNI function is tens of kilobytes long and initially fails to decompile. 195 - Parse assignments like `DAT_xxxx ^= 0xNN` from the decompiler output to build an **address → XOR key** map. 196 - Apply that map to the corresponding bytes from the ELF `.data` / `.rodata` section using Python, LIEF, or raw `readelf` offsets. 197 - Recovered strings often expose **asset names, class names, JNI signatures, crypto primitives, and backend URLs** needed to unpack the next stage. 198 199 This is especially useful when the native loader decrypts its strings only on the **first invocation**, leaving plaintext only in process memory but never on disk. 200 201 #### Rebuilding filename-derived AES asset decryptors 202 203 Once native strings reveal constants such as an asset name, `SHA-1`, `SHA-256`, and `AES/CBC/PKCS5Padding`, recreate the decryptor offline and validate the result with file magic: 204 205 ```python 206 from Crypto.Cipher import AES 207 import hashlib 208 ct = open('asset.bin','rb').read() 209 seed = b'asset_name2' 210 key = hashlib.sha1(seed).digest()[:16] 211 iv = hashlib.sha256(seed).digest()[:16] 212 pt = AES.new(key, AES.MODE_CBC, iv).decrypt(ct) 213 pt = pt[:-pt[-1]] 214 open('stage2.bin','wb').write(pt) 215 ``` 216 217 Triage hints: 218 - Test seeds derived from **asset names, parent folder names, or adjacent literals**. 219 - If plaintext starts with `PK\x03\x04`, treat it as a **ZIP container** and inspect every entry before focusing on a single `classes.dex`. 220 - Reuse the same derivation pattern against **nested assets**; packers frequently keep the same KDF across stages. 221 222 #### StringFog and similar DEX string obfuscators 223 224 If JADX shows many Base64-looking constants and a helper like `StringFogImpl.decrypt(String)`, extract the key and replay the transform over all candidate strings. One common variant is **Base64 decode + repeating-key XOR**: 225 226 ```python 227 import base64 228 def sf(s): 229 key = b'UTF-8' 230 ct = base64.b64decode(s) 231 return bytes(c ^ key[i % len(key)] for i, c in enumerate(ct)).decode() 232 ``` 233 234 Recovered plaintext commonly reveals **Firebase paths, Telegram bot logic, phishing URLs, WebView resources, and operator config** that do not appear anywhere else in the APK. 235 236 #### Treat split APKs, loaders, and web assets as one payload chain 237 238 After decrypting a staged container: 239 - Inspect **`bootstrap.dex` / `installer.dex` / `payload_split*.apk` / HTML assets** together instead of analyzing each file in isolation. 240 - Expect **`DexClassLoader`** or equivalent runtime assembly logic even if each split APK looks incomplete on its own. 241 - Check both **native code** and **phishing WebView assets** for separate backend URLs; the payment-theft infrastructure may be distinct from the RAT C2. 242 - Search config files for **subscription timestamps, HMAC/signature fields, wallet addresses, or miner toggles** because MaaS builders often hide monetization logic in JSON rather than code. 243 244 ## References 245 246 - [1] [DaliVM: Python Dalvik emulator for static string decryption](https://github.com/fatalSec/DaliVM) 247 - [2] [6. Reverse Engineering Android Apps - Obfuscation](https://maddiestone.github.io/AndroidAppRE/obfuscation.html) 248 - [3] [BlackHat USA 2018: "Unpacking the Packed Unpacker: Reverse Engineering an Android Anti-Analysis Library" (video)](https://www.youtube.com/watch?v=s0Tqi7fuOSU) 249 - This talk goes over reverse engineering one of the most complex anti-analysis native libraries I’ve seen used by an Android application. It covers mostly obfuscation techniques in native code. 250 - [4] [REcon 2019: "The Path to the Payload: Android Edition" (video)](https://recon.cx/media-archive/2019/Session.005.Maddie_Stone.The_path_to_the_payload_Android_Edition-J3ZnNl2GYjEfa.mp4) 251 - This talk discusses a series of obfuscation techniques, solely in Java code, that an Android botnet was using to hide its behavior. 252 - [5] [Deobfuscating Android Apps with Androidmeda: A Smarter Way to Read Obfuscated Code](https://www.mobile-hacker.com/2025/07/22/deobfuscating-android-apps-with-androidmeda-a-smarter-way-to-read-obfuscated-code/) 253 - [6] [Androidmeda source code](https://github.com/In3tinct/Androidmeda) 254 - [7] [Fake RTO Challan Checker Part 2: Cracking the Payload, Mapping the Operator, and Why This Is Worse Than I Thought](https://medium.com/@singhbkn07/fake-rto-challan-checker-part-2-cracking-the-payload-mapping-the-operator-and-why-this-is-3eb78e512d7f) 255 - [8] [Fake mParivahan APK — original malware analysis and sample research](https://github.com/0x6773/mparivahan-apk-scam) 256 - [9] [Android Open Source Project — Dalvik executable format](https://source.android.com/docs/core/runtime/dex-format) 257 - [10] [dpt-shell — Android DEX protection shell source](https://github.com/luoyesiqiu/dpt-shell) 258 - [11] [78 Victims, One Lazy Key, and a Firebase Named After India’s Ruling Party](https://medium.com/@singhbkn07/78-victims-one-lazy-key-and-a-firebase-named-after-indias-ruling-party-62cf0ad0380e)