mirror of
https://github.com/khanhduytran0/coruna.git
synced 2026-04-29 15:16:05 +02:00
191 lines
8.8 KiB
Markdown
191 lines
8.8 KiB
Markdown
# Coruna Exploit Toolkit — Payload Decryption Analysis
|
||
|
||
## Overview
|
||
|
||
This document describes the full decryption pipeline used to extract the encrypted binary payloads from the Coruna iOS exploit toolkit. Starting from the obfuscated JavaScript files, we reverse-engineered the encryption scheme, recovered the keys from static data in the repository, and successfully decrypted and decompressed all 19 payload bundles — yielding 65+ Mach-O arm64/arm64e dylibs targeting iOS 13–17.
|
||
|
||
## Exploit Chain Architecture
|
||
|
||
The exploit chain operates in stages, orchestrated from `group.html`:
|
||
|
||
1. **Stage 1** — WebKit/WASM memory corruption (builds read/write primitives)
|
||
2. **Stage 2** — PAC (Pointer Authentication Code) bypass (for A12+ devices)
|
||
3. **Stage 3** — Sandbox escape + payload delivery (`Stage3_VariantB.js`)
|
||
|
||
Stage 3 builds a Mach-O dylib in-memory (`MachOPayloadBuilder`), loads it into the exploited process, and then feeds encrypted payloads to it via the `window.qbrdr()` bridge function.
|
||
|
||
## Encryption Pipeline
|
||
|
||
Payloads go through the following layers (outermost first):
|
||
|
||
```
|
||
[Server] raw encrypted blob
|
||
↓ (or base64-encoded in JS files for local hosting)
|
||
[ChaCha20] DJB variant, 64-bit counter, 64-bit nonce (all zeros)
|
||
↓
|
||
[LZMA/XZ] Apple compression_decode_buffer (algorithm 0x306)
|
||
↓
|
||
[F00DBEEF] Custom container format with multiple Mach-O entries
|
||
↓
|
||
[Mach-O] arm64 / arm64e dylibs
|
||
```
|
||
|
||
### ChaCha20 Details
|
||
|
||
- **Variant**: Original DJB ChaCha20 (NOT IETF RFC 8439)
|
||
- 64-bit block counter (starting at 0)
|
||
- 64-bit nonce (all zeros)
|
||
- 20 rounds (10 double-rounds)
|
||
- **Implementation**: Found at offset `0xad8c` in `bootstrap.dylib`
|
||
- **Sigma constant**: Standard `"expand 32-byte k"` at offset `0xbb80`
|
||
|
||
### LZMA Compression
|
||
|
||
- Uses Apple's `compression_decode_buffer` with algorithm constant `0x306` (LZMA)
|
||
- 8-byte header before compressed data:
|
||
- Bytes 0–3: Magic `0x0BEDF00D` (little-endian)
|
||
- Bytes 4–7: Expected decompressed size (`uint32`)
|
||
- Bytes 8+: XZ/LZMA compressed stream (starts with `fd 37 7a 58 5a 00`)
|
||
|
||
## Key Recovery
|
||
|
||
### Master Key (for the manifest file)
|
||
|
||
The master ChaCha20 key is derived from `fixedMachOVal2`, which is the 3rd argument to `platformModule.init()` in `group.html` line 293:
|
||
|
||
```javascript
|
||
platformModule.init("", fqMaGkNg(), fqMaGkN4([3436285875, 2332907478, 2884495420, 233193687,
|
||
1144711575, 1605576699, 1942246444, 1994816675]), ...)
|
||
```
|
||
|
||
The `fqMaGkN4()` function decodes an array of `uint32` values into a 16-character JavaScript string (2 chars per word, using byte-pair encoding). When stored in the Mach-O as UTF-16LE, this yields the 32-byte key:
|
||
|
||
```
|
||
b38fd1ccd6570d8b3ce8edabd740e60d97e93a44fb27b35f2c54c473a37ce676
|
||
```
|
||
|
||
This key decrypts the **manifest file** (`7a7d99099b035b2c6512b6ebeeea6df1ede70fbb`).
|
||
|
||
### Per-File Keys (for the 18 payload files)
|
||
|
||
The decrypted manifest (`0xF00DBEEF` magic, 2192 bytes) contains 19 entries, each 100 bytes (0x64):
|
||
|
||
```
|
||
Offset Size Field
|
||
0x00 6 Header/padding (first entry has flags 0x0013, rest 0x0000)
|
||
0x06 2 Flags (iOS version / architecture identifier)
|
||
0x08 32 Per-file ChaCha20 key
|
||
0x28 48 Filename (40-char hex hash + ".min.js" + NUL + padding)
|
||
```
|
||
|
||
Each payload file has its own unique 32-byte ChaCha20 key. The nonce remains all-zeros.
|
||
|
||
### Base URL
|
||
|
||
`fqMaGkNg()` decodes to `./7a7d99099b035b2c6512b6ebeeea6df1ede70fbb.min.js`. After `resolveUrl()` and base extraction, the base URL is `./` (same directory as `group.html`), meaning all payloads are served from `https://sadjd.mijieqi.cn/<hash>.min.js`.
|
||
|
||
## F00DBEEF Container Format
|
||
|
||
Each decompressed payload is a custom container:
|
||
|
||
```
|
||
Offset Size Field
|
||
0x00 4 Magic: 0xF00DBEEF (little-endian, reads as ef be 0d f0)
|
||
0x04 4 Entry count (uint32)
|
||
0x08 16*N Entry table (N = entry count)
|
||
+0x00 4 Field 1 (upper 16 bits = segment type)
|
||
+0x04 4 Field 2 (flags, typically 0x00000003)
|
||
+0x08 4 Data offset within file
|
||
+0x0C 4 Data size
|
||
[data] Concatenated entry payloads (Mach-O dylibs, config blobs)
|
||
```
|
||
|
||
### Segment Types
|
||
|
||
| Type | Description | Typical Size |
|
||
|------|-------------|-------------|
|
||
| 0x08 | Main implant dylib (targets `powerd`, HTTP C2, crash log paths) | ~196–228 KB |
|
||
| 0x09 | Kernel/sandbox escape dylib (entitlements, mount manipulation) | ~230–334 KB |
|
||
| 0x0f | Persistence dylib (hooks `launchd`, `powerd`, `AppleCredentialManagerDaemon`) | ~191–192 KB |
|
||
| 0x0a | Additional exploit/persistence module (newer iOS variants) | ~50–68 KB |
|
||
| 0x05 | Data blob (likely kernel offsets/gadgets) | ~24 KB |
|
||
| 0x07 | Small config/metadata blobs | 44 or 468 bytes |
|
||
|
||
## Manifest Flags & Payload Targeting
|
||
|
||
The 2-byte flags field (offset 0x06 in each manifest entry) encodes the target iOS version and architecture:
|
||
|
||
| Flags | Hash | iOS Target | Arch | Entries |
|
||
|-------|------|-----------|------|---------|
|
||
| 0xf230 | `48000486...` | iOS 15.x | arm64 | 5 (3 dylib) |
|
||
| 0xf330 | `b442ab11...` | iOS 15.x | arm64e | 5 (3 dylib) |
|
||
| 0xf240 | `5258f6e3...` | iOS 16.0–16.2 | arm64 | 5 (3 dylib) |
|
||
| 0xf340 | `a78a9419...` | iOS 16.0–16.2 | arm64e | 5 (3 dylib) |
|
||
| 0xf270 | `38af3c8b...` | iOS 16.6–17.0 | arm64 | 5 (3 dylib) |
|
||
| 0xf370 | `13344176...` | iOS 16.6–17.0 | arm64e | 7 (4 dylib) |
|
||
| 0xf280 | `226cbd84...` | iOS 16.3–16.5 | arm64 | 5 (3 dylib) |
|
||
| 0xf380 | `ae7efd66...` | iOS 16.3–16.5 | arm64e | 5 (3 dylib) |
|
||
| 0xf290 | `7a1cef00...` | iOS 17.0–17.2 | arm64 | 5 (3 dylib) |
|
||
| 0xf390 | `377bed74...` | iOS 17.0–17.2 | arm64e | 7 (4 dylib) |
|
||
| 0xf275 | `e9f89858...` | Extended variant | arm64 | 6 (4 dylib) |
|
||
| 0xf375 | `f4120dc6...` | Extended variant | arm64e | 6 (4 dylib) |
|
||
| 0xf373 | `c8a14d79...` | Extended variant | arm64e | 6 (4 dylib) |
|
||
| 0xf383 | `1b2cbbde...` | Extended variant | arm64e | 6 (4 dylib) |
|
||
| 0xa205 | `f8a86cf3...` | Older/special | arm64 | 2 (1 dylib) |
|
||
| 0xa305 | `72a5ac81...` | Older/special | arm64e | 2 (1 dylib) |
|
||
| 0xa306 | `980c77f1...` | Older/special | arm64e | 2 (1 dylib) |
|
||
| 0xa303 | `5e89f83e...` | Older/special | arm64 | 3 (2 dylib) |
|
||
| 0xa304 | `2a1d692b...` | Older/special | arm64 | 3 (2 dylib) |
|
||
|
||
## Key Strings Found in Extracted Dylibs
|
||
|
||
### Implant (type 0x08)
|
||
- Targets `powerd` daemon for persistence
|
||
- References `/private/var/mobile/Library/Logs/CrashReporter`
|
||
- References `/private/var/MobileSoftwareUpdate/Update.plist`
|
||
- HTTP/HTTPS C2 communication capability
|
||
- Corellium detection: `/usr/libexec/corelliumd`
|
||
|
||
### Kernel Exploit (type 0x09)
|
||
- Mount manipulation: `/private/var/MobileSoftwareUpdate/mnt1`
|
||
- Entitlement handling: `com.apple.security.sandbox`, `libCoreEntitlements.dylib`
|
||
- Factory mount access: `/private/var/factory_mount`
|
||
|
||
### Persistence (type 0x0f)
|
||
- Hooks into `launchd`, `powerd`, `AppleCredentialManagerDaemon`, `nanoregistrylaunchd`
|
||
- Loads system libraries: `libdyld.dylib`, `libSystem.B.dylib`, `libobjc.A.dylib`
|
||
|
||
## File Layout
|
||
|
||
```
|
||
coruna-main/
|
||
├── group.html # Main exploit entry point
|
||
├── platform_module.js # Platform detection, key derivation
|
||
├── utility_module.js # Crypto helpers, Int64, LZW
|
||
├── Stage3_VariantB.js # Sandbox escape + MachOPayloadBuilder
|
||
├── other/
|
||
├── downloaded/ # 17 files fetched from C2 server
|
||
│ └── <hash>.min.js # Raw encrypted payloads
|
||
├── extracted/ # Base64-decoded qbrdr payloads (from repo JS files)
|
||
│ └── <hash>.bin
|
||
└── payload/ # All 19 decrypted + decompressed F00DBEEF containers
|
||
├── bootstrap.dylib # Bootstrap dylib to validate and load other dylibs
|
||
├── 7a7d...payload # Decrypted manifest (F00DBEEF with 19 download entries)
|
||
├── <hash>.bin # F00DBEEF container
|
||
└── <hash>/ # Extracted entries per container
|
||
├── entry0_type0x08.dylib # Kernel exploit runner -> powerd injector
|
||
├── entry1_type0x09.dylib # Kernel exploit <- what jailbreak developers are most interested in
|
||
├── entry2_type0x0f.dylib # powerd implant
|
||
├── entry3_type0x07.bin
|
||
└── ...
|
||
```
|
||
|
||
## Reproduction Steps
|
||
|
||
1. **Decode master key** from `fqMaGkN4([3436285875, ...])` in `group.html:293` → UTF-16LE → 32 bytes
|
||
2. **Decrypt manifest** (`7a7d...`): ChaCha20(master_key, nonce=0) → check `0x0BEDF00D` → LZMA decompress
|
||
3. **Parse manifest**: 19 entries at offset 0x120, each 0x64 bytes; per-file key at entry+8 (32 bytes)
|
||
4. **Download missing payloads** from `https://sadjd.mijieqi.cn/<hash>.min.js` (raw binary)
|
||
5. **Decrypt each payload**: ChaCha20(per_file_key, nonce=0) → check `0x0BEDF00D` → LZMA decompress
|
||
6. **Parse F00DBEEF containers**: extract Mach-O dylibs by entry table offsets
|