mirror of
				https://github.com/HackTricks-wiki/hacktricks.git
				synced 2025-10-10 18:36:50 +00:00 
			
		
		
		
	Add content from: GodFather - Part 1 - A multistage dropper
- Remove searchindex.js (auto-generated file)
This commit is contained in:
		
							parent
							
								
									b26177a3fa
								
							
						
					
					
						commit
						4bc8a8ada0
					
				@ -14,11 +14,168 @@ The [Zip file format specification](https://pkware.cachefly.net/webdocs/casestud
 | 
			
		||||
 | 
			
		||||
It's crucial to note that password-protected zip files **do not encrypt filenames or file sizes** within, a security flaw not shared with RAR or 7z files which encrypt this information. Furthermore, zip files encrypted with the older ZipCrypto method are vulnerable to a **plaintext attack** if an unencrypted copy of a compressed file is available. This attack leverages the known content to crack the zip's password, a vulnerability detailed in [HackThis's article](https://www.hackthis.co.uk/articles/known-plaintext-attack-cracking-zip-files) and further explained in [this academic paper](https://www.cs.auckland.ac.nz/~mike/zipattacks.pdf). However, zip files secured with **AES-256** encryption are immune to this plaintext attack, showcasing the importance of choosing secure encryption methods for sensitive data.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## Anti-reversing tricks in APKs using manipulated ZIP headers
 | 
			
		||||
 | 
			
		||||
Modern Android malware droppers use malformed ZIP metadata to break static tools (jadx/apktool/unzip) while keeping the APK installable on-device. The most common tricks are:
 | 
			
		||||
 | 
			
		||||
- Fake encryption by setting the ZIP General Purpose Bit Flag (GPBF) bit 0
 | 
			
		||||
- Abusing large/custom Extra fields to confuse parsers
 | 
			
		||||
- File/directory name collisions to hide real artifacts (e.g., a directory named `classes.dex/` next to the real `classes.dex`)
 | 
			
		||||
 | 
			
		||||
### 1) Fake encryption (GPBF bit 0 set) without real crypto
 | 
			
		||||
 | 
			
		||||
Symptoms:
 | 
			
		||||
- `jadx-gui` fails with errors like:
 | 
			
		||||
  
 | 
			
		||||
  ```
 | 
			
		||||
  java.util.zip.ZipException: invalid CEN header (encrypted entry)
 | 
			
		||||
  ```
 | 
			
		||||
- `unzip` prompts for a password for core APK files even though a valid APK cannot have encrypted `classes*.dex`, `resources.arsc`, or `AndroidManifest.xml`:
 | 
			
		||||
  
 | 
			
		||||
  ```bash
 | 
			
		||||
  unzip sample.apk
 | 
			
		||||
  [sample.apk] classes3.dex password:
 | 
			
		||||
    skipping: classes3.dex                          incorrect password
 | 
			
		||||
    skipping: AndroidManifest.xml/res/vhpng-xhdpi/mxirm.png  incorrect password
 | 
			
		||||
    skipping: resources.arsc/res/domeo/eqmvo.xml            incorrect password
 | 
			
		||||
    skipping: classes2.dex                          incorrect password
 | 
			
		||||
  ```
 | 
			
		||||
 | 
			
		||||
Detection with zipdetails:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
zipdetails -v sample.apk | less
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Look at the General Purpose Bit Flag for local and central headers. A telltale value is bit 0 set (Encryption) even for core entries:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Extract Zip Spec      2D '4.5'
 | 
			
		||||
General Purpose Flag  0A09
 | 
			
		||||
  [Bit 0]   1 'Encryption'
 | 
			
		||||
  [Bits 1-2] 1 'Maximum Compression'
 | 
			
		||||
  [Bit 3]   1 'Streamed'
 | 
			
		||||
  [Bit 11]  1 'Language Encoding'
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Heuristic: If an APK installs and runs on-device but core entries appear "encrypted" to tools, the GPBF was tampered with.
 | 
			
		||||
 | 
			
		||||
Fix by clearing GPBF bit 0 in both Local File Headers (LFH) and Central Directory (CD) entries. Minimal byte-patcher:
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
# gpbf_clear.py – clear encryption bit (bit 0) in ZIP local+central headers
 | 
			
		||||
import struct, sys
 | 
			
		||||
 | 
			
		||||
SIG_LFH = b"\x50\x4b\x03\x04"  # Local File Header
 | 
			
		||||
SIG_CDH = b"\x50\x4b\x01\x02"  # Central Directory Header
 | 
			
		||||
 | 
			
		||||
def patch_flags(buf: bytes, sig: bytes, flag_off: int):
 | 
			
		||||
    out = bytearray(buf)
 | 
			
		||||
    i = 0
 | 
			
		||||
    patched = 0
 | 
			
		||||
    while True:
 | 
			
		||||
        i = out.find(sig, i)
 | 
			
		||||
        if i == -1:
 | 
			
		||||
            break
 | 
			
		||||
        flags, = struct.unpack_from('<H', out, i + flag_off)
 | 
			
		||||
        if flags & 1:  # encryption bit set
 | 
			
		||||
            struct.pack_into('<H', out, i + flag_off, flags & 0xFFFE)
 | 
			
		||||
            patched += 1
 | 
			
		||||
        i += 4  # move past signature to continue search
 | 
			
		||||
    return bytes(out), patched
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    inp, outp = sys.argv[1], sys.argv[2]
 | 
			
		||||
    data = open(inp, 'rb').read()
 | 
			
		||||
    data, p_lfh = patch_flags(data, SIG_LFH, 6)  # LFH flag at +6
 | 
			
		||||
    data, p_cdh = patch_flags(data, SIG_CDH, 8)  # CDH flag at +8
 | 
			
		||||
    open(outp, 'wb').write(data)
 | 
			
		||||
    print(f'Patched: LFH={p_lfh}, CDH={p_cdh}')
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Usage:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
python3 gpbf_clear.py obfuscated.apk normalized.apk
 | 
			
		||||
zipdetails -v normalized.apk | grep -A2 "General Purpose Flag"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You should now see `General Purpose Flag  0000` on core entries and tools will parse the APK again.
 | 
			
		||||
 | 
			
		||||
### 2) Large/custom Extra fields to break parsers
 | 
			
		||||
 | 
			
		||||
Attackers stuff oversized Extra fields and odd IDs into headers to trip decompilers. In the wild you may see custom markers (e.g., strings like `JADXBLOCK`) embedded there.
 | 
			
		||||
 | 
			
		||||
Inspection:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
zipdetails -v sample.apk | sed -n '/Extra ID/,+4p' | head -n 50
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Examples observed: unknown IDs like `0xCAFE` ("Java Executable") or `0x414A` ("JA:") carrying large payloads.
 | 
			
		||||
 | 
			
		||||
DFIR heuristics:
 | 
			
		||||
- Alert when Extra fields are unusually large on core entries (`classes*.dex`, `AndroidManifest.xml`, `resources.arsc`).
 | 
			
		||||
- Treat unknown Extra IDs on those entries as suspicious.
 | 
			
		||||
 | 
			
		||||
Practical mitigation: rebuilding the archive (e.g., re-zipping extracted files) strips malicious Extra fields. If tools refuse to extract due to fake encryption, first clear GPBF bit 0 as above, then repackage:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
mkdir /tmp/apk
 | 
			
		||||
unzip -qq normalized.apk -d /tmp/apk
 | 
			
		||||
(cd /tmp/apk && zip -qr ../clean.apk .)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 3) File/Directory name collisions (hiding real artifacts)
 | 
			
		||||
 | 
			
		||||
A ZIP can contain both a file `X` and a directory `X/`. Some extractors and decompilers get confused and may overlay or hide the real file with a directory entry. This has been observed with entries colliding with core APK names like `classes.dex`.
 | 
			
		||||
 | 
			
		||||
Triage and safe extraction:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# List potential collisions (names that differ only by trailing slash)
 | 
			
		||||
zipinfo -1 sample.apk | awk '{n=$0; sub(/\/$/,"",n); print n}' | sort | uniq -d
 | 
			
		||||
 | 
			
		||||
# Extract while preserving the real files by renaming on conflict
 | 
			
		||||
unzip normalized.apk -d outdir
 | 
			
		||||
# When prompted:
 | 
			
		||||
# replace outdir/classes.dex? [y]es/[n]o/[A]ll/[N]one/[r]ename: r
 | 
			
		||||
# new name: unk_classes.dex
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Programmatic detection post-fix:
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
from zipfile import ZipFile
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
 | 
			
		||||
with ZipFile('normalized.apk') as z:
 | 
			
		||||
    names = z.namelist()
 | 
			
		||||
 | 
			
		||||
collisions = defaultdict(list)
 | 
			
		||||
for n in names:
 | 
			
		||||
    base = n[:-1] if n.endswith('/') else n
 | 
			
		||||
    collisions[base].append(n)
 | 
			
		||||
 | 
			
		||||
for base, variants in collisions.items():
 | 
			
		||||
    if len(variants) > 1:
 | 
			
		||||
        print('COLLISION', base, '->', variants)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Blue-team detection ideas:
 | 
			
		||||
- Flag APKs whose local headers mark encryption (GPBF bit 0 = 1) yet install/run.
 | 
			
		||||
- Flag large/unknown Extra fields on core entries (look for markers like `JADXBLOCK`).
 | 
			
		||||
- Flag path-collisions (`X` and `X/`) specifically for `AndroidManifest.xml`, `resources.arsc`, `classes*.dex`.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## References
 | 
			
		||||
 | 
			
		||||
- [https://michael-myers.github.io/blog/categories/ctf/](https://michael-myers.github.io/blog/categories/ctf/)
 | 
			
		||||
- [GodFather – Part 1 – A multistage dropper (APK ZIP anti-reversing)](https://shindan.io/blog/godfather-part-1-a-multistage-dropper)
 | 
			
		||||
- [zipdetails (Archive::Zip script)](https://metacpan.org/pod/distribution/Archive-Zip/scripts/zipdetails)
 | 
			
		||||
- [ZIP File Format Specification (PKWARE APPNOTE.TXT)](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT)
 | 
			
		||||
 | 
			
		||||
{{#include ../../../banners/hacktricks-training.md}}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user