daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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)