Skip to content

Commit df449b6

Browse files
author
HackTricks News Bot
committed
Add content from: Pointer Leaks Through Pointer-Keyed Serialization in NSDicti...
1 parent 10b0e03 commit df449b6

File tree

1 file changed

+59
-4
lines changed

1 file changed

+59
-4
lines changed

src/binary-exploitation/common-exploiting-problems.md

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,14 @@ int snprintf(char *str, size_t size, const char *fmt, ...) {
108108
}
109109
```
110110

111-
112111
</details>
113112

114113
`LD_PRELOAD=./hook.so ./validate_harness payload.json` exfiltrates the internal flag and confirms the crash oracle without patching the binary.
115114
5. **Shrink the fuzz space.** Disassembly exposed an XOR key reused across the flag comparison, meaning the first seven bytes of `flag` were known. Only fuzz the nine unknown bytes.
116-
6. **Embed fuzz bytes inside a valid JSON envelope.** The AFL harness reads exactly nine bytes from `stdin`, copies them into the flag suffix, and hard-codes every other field (constants, tree depths, arithmetic preimage). Any malformed read simply exits, so AFL spends cycles on meaningful testcases.
115+
6. **Embed fuzz bytes inside a valid JSON envelope.** The AFL harness reads exactly nine bytes from `stdin`, copies them into the flag suffix, and hard-codes every other field (constants, tree depths, arithmetic preimage). Any malformed read simply exits, so AFL spends cycles on meaningful testcases:
117116

118117
<details>
119-
<summary>AFL-friendly harness for structured JSON</summary>
118+
<summary>Minimal AFL harness</summary>
120119

121120
```c
122121
#include <stdint.h>
@@ -154,7 +153,62 @@ int main(void) {
154153
155154
This workflow lets you triage anti-debug-protected JNI validators quickly, leak secrets when needed, then fuzz only the meaningful bytes, all without touching the original APK.
156155
157-
### Related pages
156+
## Pointer-Keyed Hash Table Pointer Leaks on Apple Serialization
157+
158+
### Requirements & attack surface
159+
160+
- A service accepts attacker-controlled property lists (XML or binary) and calls `NSKeyedUnarchiver.unarchivedObjectOfClasses` with a permissive allowlist (e.g., `NSDictionary`, `NSArray`, `NSNumber`, `NSString`, `NSNull`).
161+
- The resulting objects are reused and later serialized again with `NSKeyedArchiver` (or iterated in deterministic bucket order) and sent back to the attacker.
162+
- Some key type in the containers uses pointer values as its hash code. Before March 2025, `CFNull`/`NSNull` fell back to `CFHash(object) == (uintptr_t)object`, and deserialization always returned the shared-cache singleton `kCFNull`, giving a stable kernel-shared pointer without memory corruption or timing.
163+
164+
### Controllable hashing primitives
165+
166+
- **Pointer-based hashing:** `CFNull`’s `CFRuntimeClass` lacks a hash callback, so `CFBasicHash` uses the object address as the hash. Because the singleton lives at a fixed shared-cache address until reboot, its hash is stable across processes.
167+
- **Attacker-controlled hashes:** 32-bit `NSNumber` keys are hashed through `_CFHashInt`, which is deterministic and attacker-controllable. Picking specific integers lets the attacker choose `hash(number) % num_buckets` for any table size.
168+
- **`NSDictionary` implementation:** Immutable dictionaries embed a `CFBasicHash` with a prime bucket count chosen from `__CFBasicHashTableSizes` (e.g., 23, 41, 71, 127, 191, 251, 383, 631, 1087). Collisions are handled with linear probing (`__kCFBasicHashLinearHashingValue`), and serialization walks buckets in numeric order; therefore, the order of serialized keys encodes the bucket index that each key finally occupied.
169+
170+
### Encoding bucket indices into serialization order
171+
172+
By crafting a plist that materializes a dictionary whose buckets alternate between occupied and empty slots, the attacker constrains where linear probing can place `NSNull`. For a 7-bucket example, filling even buckets with `NSNumber` keys produces:
173+
174+
```text
175+
bucket: 0 1 2 3 4 5 6
176+
occupancy: # _ # _ # _ #
177+
```
178+
179+
During deserialization the victim inserts the single `NSNull` key. Its initial bucket is `hash(NSNull) % 7`, but probing advances until hitting one of the open indices {1,3,5}. The serialized key order reveals which slot was used, disclosing whether the pointer hash modulo 7 lies in {6,0,1}, {2,3}, or {4,5}. Because the attacker controls the original serialized order, the `NSNull` key is emitted last in the input plist so the post-reserialization ordering is solely a function of bucket placement.
180+
181+
### Resolving exact residues with complementary tables
182+
183+
A single dictionary only leaks a range of residues. To determine the precise value of `hash(NSNull) % p`, build **two** dictionaries per prime bucket size `p`: one with even buckets pre-filled and one with odd buckets pre-filled. For the complementary pattern (`_ # _ # _ # _`), the empty slots (0,2,4,6) map to residue sets {0}, {1,2}, {3,4}, {5,6}. Observing the serialized position of `NSNull` in both dictionaries narrows the residue to a single value because the intersection of the two candidate sets yields a unique `r_i` for that `p`.
184+
185+
The attacker bundles all dictionaries inside an `NSArray`, so a single deserialize → serialize round trip leaks residues for every chosen table size.
186+
187+
### Reconstructing the 64-bit shared-cache pointer
188+
189+
For each prime `p_i ∈ {23, 41, 71, 127, 191, 251, 383, 631, 1087}`, the attacker recovers `hash(NSNull) ≡ r_i (mod p_i)` from the serialized ordering. Applying the Chinese Remainder Theorem (CRT) with the extended Euclidean algorithm yields:
190+
191+
```text
192+
Π p_i = 23·41·71·127·191·251·383·631·1087 = 0x5ce23017b3bd51495 > 2^64
193+
```
194+
195+
so the combined residue uniquely equals the 64-bit pointer to `kCFNull`. The Project Zero PoC iteratively combines congruences while printing intermediate moduli to show convergence toward the true address (`0x00000001eb91ab60` on the vulnerable build).
196+
197+
### Practical workflow
198+
199+
1. **Generate crafted input:** Build the attacker-side XML plist (two dictionaries per prime, `NSNull` serialized last) and convert it to binary format.
200+
```bash
201+
clang -o attacker-input-generator attacker-input-generator.c
202+
./attacker-input-generator > attacker-input.plist
203+
plutil -convert binary1 attacker-input.plist
204+
```
205+
2. **Victim round trip:** The victim service deserializes with `NSKeyedUnarchiver.unarchivedObjectOfClasses` using the allowed classes set `{NSDictionary, NSArray, NSNumber, NSString, NSNull}` and immediately re-serializes with `NSKeyedArchiver`.
206+
3. **Residue extraction:** Converting the returned plist back to XML reveals the dictionary key ordering. A helper such as `extract-pointer.c` reads the object table, determines the index of the singleton `NSNull`, maps each dictionary pair back to its bucket residue, and solves the CRT system to recover the shared-cache pointer.
207+
4. **Verification (optional):** Compiling a tiny Objective-C helper that prints `CFHash(kCFNull)` confirms the leaked value matches the real address.
208+
209+
No memory safety bug is required—simply observing serialization order of pointer-keyed structures yields a remote ASLR bypass primitive.
210+
211+
## Related pages
158212

159213
{{#ref}}
160214
../mobile-pentesting/android-app-pentesting/reversing-native-libraries.md
@@ -169,5 +223,6 @@ This workflow lets you triage anti-debug-protected JNI validators quickly, leak
169223
- [FD duplication exploit example](https://ir0nstone.gitbook.io/notes/types/stack/exploiting-over-sockets/exploit)
170224
- [Socat delete-character behaviour](https://ir0nstone.gitbook.io/hackthebox/challenges/pwn/dream-diary-chapter-1/unlink-exploit)
171225
- [FuzzMe – Reverse Engineering and Fuzzing an Android Shared Library](https://hackmd.io/@sal/fuzzme-mobilehackinglab-ctf-writeup)
226+
- [Pointer leaks through pointer-keyed data structures (Project Zero)](https://projectzero.google/2025/09/pointer-leaks-through-pointer-keyed.html)
172227

173228
{{#include ../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)