
Amazon Fire 7(Fire OS 7.3.3.1)において、Mali kbase JIT use-after-free CVE-2022-38181を利用し、modprobe_path overwriteチェーンを用いて一時的なrootを取得するカーネルエクスプロイト研究。
AI支援プロジェクト。 この研究、エクスプロイト開発、およびドキュメントは、 モデル GLM-5.3 と DeepSeek V4.1 Flash を使用したAI支援によって作成されました。
最終ファームウェア — Fire OS 7.3.3.1、PS7331.4463N、カーネル 4.9.117(ビルド 2025-05-03、SPL 2024-08-01) における Amazon Fire 7 第9世代(mustang、MT8163、Mali-T720) のルートエクスプロイト研究。
目標: LineageOS。このユニットではブートローダーパスは死んでいる(パッチ済みブートROM — CMDショートによるpreloaderのみ)ため、 残された唯一の経路はソフトウェアカーネルエクスプロイトである。
nix-shell -p android-tools --run './run.sh' # add -p zig too if no zig
成功時:```
/data/metrics/su id # run a command as root
/data/metrics/su # interactive root shell
reclaim はおよそ 3 回に 1 回の起動で成功し、失敗するとタブレットはパニック/再起動します。
run.sh は再起動を待ってリトライするだけです。SELinux はエクスプロイトの一環として
強制的に Permissive にされるため、root は 実行時のみ です — 再起動すると
ストック状態に戻り、run.sh を再実行する必要があります。
ビルド済みの st3 と su (armv7 静的) がコミットされているため、実行に
ツールチェーンは不要です。zig があれば ./run.sh --build で poc/*.c から
再ビルドできます。
GhostLock (下記) は保留中です: MTK の BUG_ON rtmutex 変種 + シェルからの カーネルアドレス開示ゼロ = このビルドではアーキテクチャ上の行き止まり (セッション 2-4)。 kbase JIT UAF は再診断され (destroy-worker の「無条件パニック」は JIT_FREE の 逆参照であり、ログはパニック途中の adbd 死亡により失われた)、ステージ 2 は 現在オラクル実証済みです — SESSION 5 のセクションを参照。
rtmutex remove_waiter() futex-PI スタック UAF (NebuSec 開示 2026-07、修正 3bfdc63936dd
は 2026-04 に着地)。脆弱な範囲 2.6.39–7.1 → 我々の 4.9.117 (2025 年 5 月) は影響を受ける。
我々の正確なビルドで検証済み:
CONFIG_FUTEX=y、rtmutex はコンパイル済み、バグはそのまま存在:
rtmutex.c:1108-1111 は current->pi_lock/current->pi_blocked_on を使用 (本来は
waiter->task であるべき); バグのある呼び出し箇所 rtmutex.c:1723 (rt_mutex_start_proxy_lock エラーパス)WAIT_REQUEUE_PI/CMP_REQUEUE_PI)、デバイスノード不要、
SELinux でゲートされるものは何もない — kbase パスの致命的な障害はここには存在しないsched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) が
sched/core.c:4706 で — 古い pi_blocked_on を逆参照 ✓exp32/main.c の移植rt_waiter フレームオフセット vs do_sys_select fd_set 領域 — 我々の vmlinux を
逆アセンブル (do_sys_select の stack_fds vs futex_wait_requeue_pi フレーム)、
STAMP_NFDS/STAMP_WAITER_OFF を調整可能パラメータとして公開Failed critical init step 3/dev/mali0 ワールド RW + SELinux gpu_device、kbase r26p0-01rel0selroot 2 パケットチェーン: をゼロ化、
偽エントリを に書き換え。、SELinux Permissive。mustang、Fire OS 7.3.3.1 PS7331.4463N/00315758630404.9.117-g08fe75b-dirty、ビルド日時 Sat May 3 01:25:15 UTC 2025 (Linaro GCC 6.3-2017.05)/dev/kb、/dev/dkb (Amazon カーネルバックアップパーティション) root:drmrpc 0660 — ロック済みmali_kbase r26p0-01rel0 (Midgard, Mali-T720)、NVD の影響範囲 r4p0–r31p0 内mali_kbase_mem.c:2721 kbase_jit_destroy_worker が領域を解放、kctx->jit_alloc[id] を決してクリアしないmali_kbase_softjobs.c:1270 kbase_jit_free_finish が古い jit_alloc[ids[j]] を逆参照mali_kbase_mem.c:3138 kbase_jit_backing_lost → destroy パス (reclaim 中に発火)0xc0008000 VA / 0x40080000 PA)ARM_SW_DOMAIN_PAN なし → ret2usr が可能; CONFIG_PANIC_ON_OOPS=y (失敗した試行 = 再起動)SLAB_FREELIST_RANDOM/HARDENED なし、CONFIG_USER_NS/USERFAULTFD/NF_TABLES なしCONFIG_MODULES=y、STATIC_USERMODEHELPER なし → modprobe_path 上書き = root_IOC_TYPE 0x80)MEM_ALLOC union は 32 バイト (in は extent を含む 4 × u64)BASE_MEM_PROT_GPU_RD|WR (ビット 2|3) を含める必要があり、レガシーな R|W ではないmmap(fd, offset=3<<12, PROT_NONE)JOB_SUBMIT のストライドは sizeof(base_jd_atom_v2) = 48 と等しくなければならない (base_jd_prio/base_jd_dep_type は u8 typedef)MEM_JIT_INIT (nr 14, v2 構造体)、alloc/free は JOB_SUBMIT 経由の ソフトジョブ
(BASE_JD_REQ_SOFT_JIT_ALLOC=0x209, ...FREE=0x20a; =ユーザーポインタ, =カウント)パニックは eviction 自体の間に発生する (evictable_reclaim_scan_objects → backing_lost →
destroy worker) — ぶら下がり参照 (jit_alloc[]、evict リスト) は、我々が JIT_FREE を
submit する前に走査される。ステージ 2 はレースに勝つ必要がある: プレッシャーがまだ
実行中の間に、解放された kbase_va_region を我々自身の MEM_ALLOC スプレーで再確保する。
poc/stage2.c — ステージ 2 エクスプロイト (モード: step/uaf/spstep/spfree/spray/keys)
— spray 700 = 完全オラクル実行; 生存して一時停止 (クリーンアップには kill)poc/mustang_jit_uaf.c — ステージ 1 PoC (モード: jit N / control N / pressure N)poc/build.sh — zig クロスビルド (静的 musl armv7)kernel/vmlinux — 正確な OTA ビルドから復元したシンボル (vmlinux-to-elf)kernel/config-* — 実行中デバイスからの ダンプnix-shell -p zig --run 'zig cc -target arm-linux-musleabihf -static -O2 -o juaf poc/mustang_jit_uaf.c' adb push juaf /data/local/tmp/juaf && adb shell chmod 755 /data/local/tmp/juaf adb shell /data/local/tmp/juaf jit 700 # full trigger (~reboots device) adb shell /data/local/tmp/juaf control 700 # no-JIT control adb shell /data/local/tmp/juaf pressure 700 # raw memory-pressure control
## 参考文献
- GHSL-2022-054 アドバイザリ: https://securitylab.github.com/advisories/GHSL-2022-054_Arm_Mali/
- Mo による Pixel 6 エクスプロイトの解説記事: https://github.blog/2023-01-23-pwning-the-all-google-phone-with-a-non-google-bug/
- Fire HD 10 (trona) の前例、同一バグファミリー: ericpardee.github.io/fire-hd-ownership
- Amazon OSS ポータル: amazon.com gp/help/customer/display.html nodeId=200203720
- XDA アンロックスレッド (このハードウェアリビジョンでは無効): xdaforums.com/t/fire-7-2019-mustang-unbrick-downgrade-unlock-root.3944365/
## セッション3 補遺 (詳細なシステムコールスタンプ調査)
コピー元の深さを測定 (絶対値 vs システムコールエントリ時の sp0、waiter の範囲は -0x1d8..-0x1a8):
- sendto sockaddr @ -0xc4 | process_vm iov @ -0x104 | recvmsg iov @ -0x11c
- sendmsg iov @ -0x12c | recvmmsg iov @ -0x154 | select fds @ -0x174
- pselect6 fds @ -0x19c | **sendmmsg iov @ -0x1bc (最良 — waiter+0x00 まであと 0x1c)**
- poll entries @ -0x3e0 (完全に下方、逆側)
今セッションで除外:
- io_submit チェーンは浅すぎる (~-0x130) | semtimedop: CONFIG_SYSVIPC=n (スタブ)
- configfs はマウントされているがサブシステムがゼロ登録 (mkdir 対象なし)
- /sys/kernel/debug, /config: shell に対して SELinux 拒否
- /proc/sys/kernel: getdents は動作 (29 エントリを列挙)、pid_max のみ OPEN 可能;
kptr_restrict/hotplug/hostname/domainname の読み取りはすべて拒否
- 書き込み値は常に waiter+0 (カーネルスタックアドレス、実行可能、シェルコードは
+0x1c): rb_link_node *link = node、insert_color は parent-color を書き込む —
ツリーフィールドスタンプなしでは制御値のバリアントは不可能 (ギャップ -0x1d8..-0x1bc)
- 二重デリファレンスのディスパッチフィールドは *(waiter+0)=1, *(waiter+4/+8)=0 を読む — BLX 1/0
(nf_hooks, net_families, inet[6]_protos, seq_file->op はすべて無効)
- timer_list.function@+0xc と work_struct.func@+0xc は *(waiter+0xc) =
pi_tree 自己ポインタ = 実行可能な waiter+0xc を読むはず — しかし waiter+0 を
timer/work としてキューするパスがない (リンク破壊 / 間接キューソースなし)
- ツリールート非ゼロ (sysctl ハンドラスロット) = .text を rb-tree として
決定論的にポインタチェイス; ゼロワードで終端 — オフラインシミュレーション可能だが、
有用な書き込み可能スロットに着地するのは非現実的
セッション4 に向けた残りの手がかり:
1. ioctl の深いパス: dev_ioctl の ifreq コピー (40B のユーザーデータ) —
SyS_ioctl→sock_ioctl→dev_ioctl チェーンの深さを -0x1d8 と比較測定
2. sendmmsg より 0x1c 深いコピーが他にあるか (今のところ見つかっていない)
3. スタンプ表面の探索が失敗した場合: ウォークチェーン構築を再検討するか、
まだ列挙していない書き込み可能ゼロ呼び出しスロットクラスを探す
## セッション4 — 二つのブレークスルー
### 1. MTK の rtmutex_common.h が謎のすべてだった
MTK は上流の NULL 安全な rt_mutex_top_waiter を以下に置き換えた:```c
w = rb_entry(lock->waiters_leftmost, struct rt_mutex_waiter, tree_entry);
BUG_ON(w->lock != lock); // compiled to: ldr sb,[lock+8]; ldr r3,[sb+0x1c]; cmp; bne→udf#0x12
NULL チェックなし + BUG_ON。全ゼロのアンカーはすべて *(NULL+0x1c) で死ぬ。ガベージ アンカーは udf で死ぬ。ウォークの要件: lock->waiters_leftmost (lock+8) が偽のウェイター W (書き込み可能) を指し、W->lock (+0x1c) == lock でなければならない。
ウォークフローを完全にマッピング (rt_mutex_adjust_prio_chain @ 0xc0189a58):
/proc/self/task//stat のフィールド 28 (kstkesp) は、SHELL コンテキストから syscall でブロックされたスレッドの実際のカーネル SP を返す (検証済み: 非ゼロ値が観測された)。
ウォークは決定論的に発火する (デッドロックプローブ: 毎回クラッシュ、クリーンコード)。 書き込みが着地できないのは、3 方向のカーネルハードニングの偶然による:
スタンプウィンドウ (waiter+0x1c..0x5b) は唯一の制御+既知内容の メモリだが、そのアドレスが我々が必要とする未知数である。自己参照構築は すべてカーネルアドレスを定数としてスタンプする必要がある — リークなしでは循環。
彼らの 5.4 カーネルは UPSTREAM rtmutex_top_waiter (NULL 安全: if (!leftmost) return NULL) — 空ツリーアンカーが生き残り、彼らの書き込みは null_fops
(彼らのカーネルでは書き込み可能) に着地した。彼らでさえディスパッチで
行き詰まっている ("ioctl reboot")。Mustang の 4.9.117 MTK ツリーは BUG_ON
変種 — Fire OS 8 タブレット (GhostLock-5.10) は成功した。彼らの 5.10 カーネルが
upstream スタイルだから。
シェルドメインからテスト済みで死んでいる:
結論: mustang 上の GhostLock は、このカーネルがシェルドメインに公開しない カーネルアドレス開示を必要とする。自己参照の偽ロックはそれなしでは構築できない。
(a) ブート決定論的グラインド: 再起動 → クラッシュオラクルでスタックアドレスを キャリブレーション (~20-30 回の再起動)、再現性を検証。ロングショット — レイトブートの スレッドスタック割り当ては安定しそうにない。 (b) 蓄積された資産で kbase CVE-2022-38181 にピボットバック: 正確なビルドの vmlinux + 完全なソース + ツールチェーン + O_SYNC トレース規律 + 深い 4.9 知識。元のブロッカー (JIT エビクション中の destroy-worker パニック) はスプレイタイミングの問題で、今はよりよく理解されている。 (c) 正直な ~45% で停止: トリガー証明済み、ウォークは命令までマッピング済み、 書き込みは MTK BUG_ON + リークなしでブロック。
推奨: (b) — kbase バグはこの正確なソースで存在が検証されており、 動作するトリガーがあり、そのブロッカーはアーキテクチャ的ではなく機械的。
step モード (alloc id=1 → DONT_NEED → 700MB プレッシャー → MEM_QUERY、free なし)
は生き残る: query=-1 (リージョンは destroy ワーカーによって解放、rbtree クリーン)。
ワーカーパスはプレッシャー下の合法的な JIT_FREE フローとバイト単位で同一。
セッション 1 のクラッシュは常に JIT_FREE ダングリング deref だった; そのログ行は
パニックがフラッシュ中に adbd を殺すため失われた。uaf モード (ベア free → パニック、
同じログカットオフ) でさらに 2 回検証。GHSL-2022-054 フローはこのビルドで完全に
ライブ。
kbase_jit_free(kctx, reg) @ 0xc058495c、完全に制御された偽 reg で:
reg->cpu_alloc NULL → backed size 0 → trim ブロックをスキップ (0xc0584978)kctx+0x147dd (バイト) + kctx+0x147de+bin_id (バイト)mark_reclaim(reg->gpu_alloc) @ 0xc059b158: チェーン
K=*(gpu_alloc+0x38) → *(K+0x1429c)==0 が mm-atomics をスキップ →
atomic_sub nents@K+0x141c8, D=*(K+4) → atomic_sub nents@D+0x538。
nents=0 ではすべての書き込みは no-op ストア (同じ値の strex)。reg->flags |= 0x100000 (偽への書き込み、無害)list_add(gpu_alloc->evict_node, &kctx->evict_list): evict_list ヘッド
@ kctx+0x1427c; gpu_alloc+0x18/0x1c への書き込み (書き込み可能でなければならない)r3=*(reg+0x3c) prev, r2=*(reg+0x38) next
→ *(next+4)=prev; *(prev+0)=next — 2 つの任意 write-what-where、
その後 reg+0x38 を jit_pool_head @ kctx+0x148e8 に再リンク9 候補; S=0xc118b7ec (xfrm データ、このデバイスでは休眠):
*(S+8)=0 (nents), *(S+0x18)=S+0x18 (空の evict_node → WARN なし),
K=*(S+0x38)=0xc118b820 → *(K+0x1429c)=0, K/D+0x141c8/+0x538 すべて
書き込み可能データ内。オラクルターゲットをステージング: init_uts_ns.name.nodename=0xc110d561
("(none)"、uname で読み取り可能)、スクラッチ P=0xc118bd58 (xfrm ゼロ)。
S=0xc111cba4 (tracepoint 隣接) を回避。CONFIG_DEBUG_RODATA=y → すべての書き込み
ターゲットは .data/.bss 内でなければならない (bss 0xc11d9000-0xc12d9000)。
add_key (CONFIG_KEYS=y): シェルで SELinux 拒否。Dead。setxattr 値バッファ: kvmalloc(96)+copy_from_user は SELinux チェックの
前に発生 → 呼び出しが失敗しても alloc ダンスは SELinux プルーフ;
一時的 (syscall 終了時に解放)、バイトは +4..95 に持続 (freelist ptr が
+0..3 = rblink を上書き、kbase_jit_free では未使用)spray 700 実行 2026-09-11: プレッシャー中に 5895 リージョンをスプレイ、ダングリング
id=1 の JIT_FREE がリクレームされたリージョンで完了、その後
JIT_ALLOC(0x40, bin 0) が jit_pool_head をウォークし、スプレイされたリージョン
#4251 の VA (0x142701000) を返した — ダングリングポインタが消費した正確な
リージョン。その後プロセス kill: kctx ティアダウンクリーン、クラッシュなし。
ステージ 2 完了: 制御されたオブジェクトタイプ + 内容での決定論的 UAF リダイレクト。
リージョンタイプのリクレームは合法的ダンスの生存を与えるが、jit_node は INIT された 自己 → アンリンクプリミティブなし。+0x38/+0x3c に生バイトが必要:
*(N+4)=P で P=ユーザーランドシェルコードページ (PAN なし!) —
候補: const fops は .rodata (DEBUG_RODATA) → .data 内の非 const
fn ptr をターゲット、または binfmt formats リストヘッド、または sysctl proc_handler
(テーブル書き込み可能性を検証)。フォールバック: バイトチェーン書き込みによる
modprobe_path (値は書き込み可能アドレスでなければならない — ポインタ形状の
ターゲットを使用)poc/stage3.c):
kern_table[pid_max].proc_handler @ 0xc1113f40 (書き込み可能
.data、文字列ポインタスキャン + handler == proc_dointvec_minmax で検証済み)b +8 でスキップ; W2 は P = handler フィールドに N を書き込む)read /proc/sys/kernel/pid_max
(シェルから読み取り可能); 自身のタスクコンテキストで実行 → creds が我々に適用inotify_handle_event が name_len+0x1d を kmalloc、
name バイト (完全に制御、NUL/スラッシュフリー制約) が event+0x1c に;
name_len=60 → kmalloc-96; キュー済み → 保持; 4 インスタンス → rename ごとに
4 イベント; シェルから SELinux-OK。偽を NUL フリーに再設計: cpu_alloc は
S を指す (nents@S+8 = 0 → NULL と同じセマンティクス)| 変種 | 結果 |
|---|---|
| ストーム中の並行 rename スプレイヤー | rename がストール (journal/GFP_NOFS) → 合計 128 → ガベージ deref |
| 12K イベント事前ドレイン + プレッシャー + 小さなトレーリング |
作業仮説: ストームジャンクレース — destroy ワーカーがスロットを解放する (ストーム中) のと子 kill/静けさの間に、残留リクレーム活動が被害者スラブの 唯一の空きスロットを非ペイロードバイトで取る。リージョンスプレイ (ステージ 2) は ストーム中に継続的に割り当てるため勝つ; rename はできない。
iso モード): 同一タイミング、commit-0 リージョンでのトレーリング
current->mm はカーネルスレッドのもの → エレガントな
「偽のポインタを我々自身のユーザーランド mmap に向ける」設計 (PAN なし!) が
非決定論的に FAULT する。それ以外は完璧な偽で 5/5 クラッシュ。K=*(S+0x38) 読み取り可能、*(K+0x1429c)==0 ランタイムで
(mm-atomics をスキップ)、K+0x141c8 書き込み可能、D=*(K+4) → D+0x538
書き込み可能、nents *(S+8) はできれば 0。9 つのオフライン S 候補は
ファイルバイトに対して検証された — ランタイムドリフト (xfrm/tracepoint init)
がそれらを未検証にする。間違ったチェーン = クラッシュ = 再起動 (~3 分サイクル)。A. Physmap スプレイ偽 (ret2dir、古典的 arm32 no-PAN): 各々が 1 つの推測 アドレス G 用に焼き込まれた偽パターンを含む ~450MB のユーザーページを スプレイ (G&0xfff = 0x141 で NUL フリー名バイト; S=G; K=G-0x141b4 で K+4 がページ内に着地; K+0x141c8/+0x1429c → G+0x10/+0xd4 ページ内; D=G+0x300; はぐれた sub-0 ストアはランダムなマップ済み RAM にヒット - nents=0 で無害)。 スプレイはエビクションプレッシャーを兼ねる (ダーティ anon = エビクト不可 → 追加で ~100-200MB のみ必要)。オッズ ≈ 45% (ページヒット) × ~50% (外部 K+0x1429c ワードがゼロ... 上のレイアウトで K+0x1429c がページ内に保たれれば、 オッズ = ページヒットのみ)。ミス = クラッシュ = 再起動、リトライ。 B. 9 つの静的 S 候補をブルートフォース (xfrm 0xc118b7ec 最初、 tracepoint 隣接 0xc111cba4 2 番目...): 候補ごとに 1 再起動、 良性オラクルペイロード最初、ヒットで兵器。 C. pagemap ベースの正確な G (死亡: PFN マスク済み) — 再訪しない。
/tmp/opencode/ksrc2/kernel/mediatek/mt8163/4.9/ (arch/arm を含む
mustang.dtsi、mm/、fs/eventpoll.c、fs/notify) を ksrc/platform.tar から。
知見:physmap_retouch() を追加 (trailing/deref の前に
すべてのスプレーページをフォールトインし直す)リージョン reclaim: 3/3。イベント: 0/~12 試行、28K の排他的 アロケーションをヘッドスタートと構成的に正しいフェイク付きで含む。もしイベントが p_hit(G)≈0.35 で reclaim するなら、5 回の pmap ミス ≈ 11.6% — 可能だが今や ありそうにない (~10-15%)。イベントが構造的にこのスロットを取れないのか (理由不明 — 同じキャッシュ、同じコンテキスト、同じタイミング)、それとも我々の G 推測が 系統的に外しているのか (highmem 境界の偏り、アロケータ配置)。
vmalloc=496M slub_max_order=0 slub_debug=O loglevel=8 initcall_debug
リージョンは被害者スロットを 3/3 で取る; イベントは 0/13。同じキャッシュ (ソース + disasm レベルで証明済み)、同じプロセスコンテキスト、同じピン留め CPU、同じ kill 後タイミング、排他的ヘッドスタート付きの数千のアロケーション。 メカニズム不明。残る容疑者: アロケーションレート/頻度と部分リストローテーションの 相関 (リージョン ioctl は ~1ms 間隔 vs イベントリネームは ~100µs 間隔 — 逆方向?)、または slub_max_order=0 下の SLUB フリーリスト順序の詳細が 有利に働く…不明瞭。
[+] uname.nodename="X?lhost" (was "localhost") <<< ORACLE HIT
**完全な生バイトチェーンは実機で動作する**: イベントが解放済み
リージョンスロットを回収 → kbase_jit_free が我々の偽物を逆参照 (S=kbm スプレーからの physmap ページ、G=0xc154b2a4) → unlink が我々の 2 つの書き込みを実行する。
良性ペイロード (nodename 書き込み) で繰り返し検証済み。
### 発見のセッションチェーン
1. kbm ページは GFP_HIGHUSER → HIGHMEM であり、physmap からは不可視
(mali_kbase_mem_pool.c:164!) — zonelist スピルで修正: 260 × 2MB
スプレー > highmem-free → 余剰が ZONE_NORMAL (physmap) に着地
2. 最初の武器の試行はクラッシュ: W1 のターゲット N+4 は USER ページ —
unlink は kworker コンテキスト (mm なし) で実行 → フォールト。修正は
ring-0 シェルコードを physmap パターンの page+0x600 に焼き込むこと
(arm32 非 LPAE でのダイレクトマップ RWX) — カーネル常駐コード、
ret2usr 不要
3. ctl_table オフセットバグ: proc_handler は entry+0x14 にあり、+0x18
ではない (元のスキャンは正しかった; 私の define が間違っていた) —
extra1 を書き込んでいた
4. 過圧回帰を発見し巻き戻し: kid バジェット 20×100MB +
末尾 10s が reclaim を壊した; 動作する設定は 2 kids/200MB +
5s/+3200 末尾 (巻き戻し後、良性ヒット 1/1)
5. **diag2: W2 → &pid_max グローバル (0xc1114d7c) → 読み取りが我々の値
を返す (-1055861411 = 0xc110d55d as int32) — 書き込み + 読み戻し
証明済み**
### 残る謎 (クローズまであと 1 実験)
正しいハンドラアドレス (0xc1113f3c) での diag1: W1 が発火 (nodename
が変化)、W2 は実行されたはず (次の命令) — それでも pid_max の読み取り
はクリーンな値を返す → 我々が書き込むハンドラフィールドは、inode が
ディスパッチするものではない。diag3 (キュー済み; ヒットブートが必要):
W2 → entry->data フィールド (0xc1113f2c) を nodename に向ける — もし
読み取りが nodename バイトを int として示せば、我々の entry は生きて
おり、ハンドラオフセットだけがどういうわけか間違っている; 影響が
なければ、inode はシャドウテーブルコピーを使っており、我々は生きた
ものを探し求める。
### ヒット率の現実
ブートごとのコイントス (~25-40%)、クラスタ化; 安全ミス/クラッシュ
ブートが数回連続するのは普通。およそ 3-4 ブートに 1 回がヒット。
動作する設定を正確に保つこと (2 kids、5s 末尾、260 リージョン kbm、
G=0xc154b2a4)。
### セッション 9 TODO
1. ヒットブートで diag3 を完了する ("W1 FIRED" までロールする)
2. entry が生きている場合: ハンドラオフセットを経験的に再確認する
(proc_dostring のアドレスをハンドラとして書き込む... N は有用な
値と等しくなければならない — unlink を使って代わりに entry->data
を書き込み、ピボットする: 例えば、data=selinux_enforcing 隣接...)
3. シャドウテーブルの場合: 生きたものを特定する — kallsyms には
データシンボルがない; 候補: /proc/sys の挙動をスキャンする、または
.data 内のヘッダーリストパターン経由で 2 番目の ctl_table リージョン
を見つける (handler=proc_dointvec_minmax と maxlen=4 を持つ
0x20 ストライドのエントリ — すべてを列挙し、それぞれを diag 書き込み
する)
4. ディスパッチを完全に回避する代替ターゲットクラス: シェルから到達
可能なパスから呼ばれる .data 関数ポインタ (監査が必要)
5. 書き込みプリミティブ自体は完了 — 信頼できるカーネルアドレス
ターゲットが今や root に十分
## セッション 9 — nf-WEAPON: reclaim+unlink+G-hit が武器内で証明済み; 残るはフックウォークのみ
### 新しいトリガー設計 (sysctl ハンドラパスを完全に置き換える)
ユーザーのアイデアをカーネルメモリに翻訳: SUID ファイルなし (システム
は dm-verity RO; プリミティブはカーネル RAM に書き込む)。代わりに:
**偽の netfilter フック**。このカーネルは新しい nf_hook_entries API の
Android-common バックポートを持つが、リンクリストとして実装されている
(nf_hook_slow + ヘルパー 0xc09897d4 の逆アセンブルで検証済み):
- `__ip_local_out(net, sk, skb)` は entries CELL を
**[net+0x58c]** からロードし、state+0x1c に格納し、nf_hook_slow を
呼び出す
- ウォーク: `entry = *cell`; while(entry){ if (state->[4] <= entry->[0x20])
call entry->[0xc](entry->[0x14], skb, state); entry = entry->[0]; }
— つまり **fn@entry+0x0c, priv@entry+0x14, priority@entry+0x20,
next@entry+0x00**; state+4 = INT_MIN 閾値 (常に通過)
- init_net = **0xc1104548** (CONFIG_NET_NS=n → sock_net() が定数を
インライン化; 6678 の movw/movt 参照、ヒストグラムチャンピオン;
nf_hook_slow 自身のリテラルでクロス確認済み)。ターゲット:
**[init_net+0x58c] = 0xc1104ad4**
- LOCAL_OUT フックは送信者のプロセスコンテキストで実行される →
我々の hookfn の commit_creds(prepare_kernel_cred(0)) がパケットを
送信したプロセスを root 化する。トリガー = sendto(127.0.0.1:9) UDP。
### nf モードレイアウト (poc/stage3.c, モード `nf 200`)
- kbm パターンページ (ページ相対、単一の真実の源 — 古い武器には
今修正された 3 つのバグがあった: proc_handler@+0x14 であり +0x18
ではない; W1 ターゲットはカーネルメモリでなければならない (kworker
コンテキスト、mm なし); 焼き込まれたコードは page+0x600 にあり
entry G+0x600=page+0x8a4 と不一致):
- +0x2ac/+0x2bc/+0x2c0/+0x2dc/+0x3a8: 偽の phy-alloc チェーン (変更なし)
- +0x600: nf_code hookfn (マーカーストア + prepare_kernel_cred +
commit_creds + return NF_ACCEPT(1))
- +0x700: 偽の entry {next=0, fn=PM_PAGE+0x600, priv=0, prio=0x100}
- +0x740: cell → PM_PAGE+0x700
- ペイロード: N = PM_PAGE+0x740 (0xc154b740), P = 0xc1104ad4
→ W1: *(cell+4)=P (我々のページに着地), W2: *(init_net+0x58c)=cell
### 自己診断インストルメンテーション (kbm_scan_for)
kbm の CPU マッピングは保持される; free の後、スプレーしたすべての
ページを既知のワードでスキャンする:
- page+0x744 で scan(HOOKS_PTR_ADDR) → reclaim + unlink を証明し、
G の推測を裏付ける物理ページを明らかにする
- page+0x7f0 で scan(0x600d600d) → hookfn が実行されたことを証明する
(nf_code はこのマーカーを 2 番目のアクションとして書き込む)
### 重要な実行 (2026-09-11, セッション 9 後半)```
[+] W1 CONFIRMED: region 135 page+0x185000 <- 0xc1104ad4 (reclaim + G hit!)
[+] nf trigger done, uid=2000 euid=2000
ライブブートで完全な武器チェーンが発火: イベントがスロットを回収し、偽物が実行され、unlink が実行され、G-guess ページ (0xc154b000) は本当に我々のものだった (リージョン 135 ページ 389)。W2 = *(0xc1104ad4)=cell は隣接する命令である — それは実行されたはずだ。しかし UDP sendto は我々を root 化しなかった → 失敗はフックパス内部にある: walk セマンティクス、[state+0x1c] の配管、優先度比較、またはエントリフィールドのいずれか。 (hookfn が実行されたか否かを判別するマーカー実験が追加された; セッション終了前のクラッシュブートのみ — クリーンなデータはまだない。)
pmap 200) が環境の健全性チェック (ウォーム時は
4/4 ヒット; コールド時はクラッシュミス)nf 200 を実行し、W1-CONFIRMED 行が
現れるまで待ち、次に MARKER 行を読む:
a. マーカーが存在、uid!=0 → シェルコードの creds が失敗 (prepare_kernel_cred/commit_creds のアドレス; blx エンコーディングを確認)
b. マーカーが不在 → walk が我々を呼んでいない: マーカーだけを書き込んで 1 を返す hookfn (creds なし) で検証する — それでも不在なら:
ls -la で確認; マーカーファイルを残すpoc/stage3.c モード: pin/root/drain{,2,3,4,5}/iso — 完全な武器 +
オラクル + 分離ハーネス、O_SYNC クラッシュポイントフォレンジックinit_net: 0xc1104548 は __stack_chk_guard である (movw/movt
ヒストグラムはスタックカナリアのロードで汚染されていた — 2025 は nf 計画全体を
これの上に構築した)。本当の init_net = 0xc1185040 (確認済み: ip_send_skb(net,...) が
このリテラルで呼ばれる; ~994 の参照はすべて net スタック内)。IPv4 LOCAL_OUT セル =
init_net+0x58c = 0xc11855cc。nf_iterate は [init_net+0x58c] を
nf_hook_ops ポインタそのものとして扱う — その値から fn@+0xc, priv@+0x14, prio@+0x20
を直接読む。セッション9の偽エントリは PM_PAGE+0x700 にあり、セルが
それを指していた (セルの未使用の next/fn フィールド → → クラッシュ)。
偽エントリは (0xc154b740) に
存在しなければならない。これを修正すると、フック呼び出しが証明された
( モード = SAFE_FN が 200 の sendto すべてをクリーンに処理)。commit_creds(prepare_kernel_cred(0)) / override_creds(&init_cred) は
uid 0 を与えるが、kernel SELinux SID に着地し、この Fire OS ポリシーは
/data や /sys/fs/selinux/enforce への書き込みを許可しない (確認済み: EACCES)。
本物の init SID (7) も拒否される (偽の struct cred テスト)。enforcing_setup
は __init (解放済み → クラッシュ)。mark_reclaim の atomic_sub は nents=1 を必要とし、
それが shrink_cpu_mapping の早期終了を壊す。
勝利: エクスプロイトプロセスは kbm CPU マッピングを保持するので、偽の nf エントリはパケット間でその場で書き換えられる:
selroot モード: entry = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}。*(enforcing)=0 → SELinux Permissive。kbm_cpu[reg]+off 経由でエントリを {fn=commit_creds, priv=&init_cred} に書き換える。commit_creds(&init_cred) → uid 0 で
permissive SELinux → 使用可能な root、すべて1回の回収で、チェーン不要。[+] W1 CONFIRMED: region 67 page+0xad000 <- 0xc11855cc (reclaim + G hit!) [*] sendto 0 -> -1 errno=1 uid=0 euid=0 [+] nf trigger done, uid=0 euid=0 <<< HOOK RAN - commit_creds OK [+] ROOT: uid=0 euid=0 [+] setenforce write=1 [+] su copied bytes=236220
- `getenforce` → **Permissive**、一時停止した st3 は `Uid: 0 0 0 0`、
`CapEff: 3fffffffff`。
- `/data` は **nosuid** でマウントされているため、setuid `su` は機能しない。小さな
`rootshell`(UDP 送信 → 自身に `commit_creds` → `execl sh`)で
対話型 root シェルが得られる: `uid=0(root) context=u:r:kernel:s0`。
- root は `/dev/block/by-name/*` を読み書きできる(`dd if=boot ...` OK)。
### Stage-3 コードの状態 (`poc/stage3.c`)
- モード: `nf <mb> [probe|uprobe|oc|chain|fc <sid>|notrig|selroot]`
- `selroot` が動作する武器。主要な静的値: `init_net=0xc1185040`、
`HOOKS_PTR_ADDR=0xc11855cc`、`ENFORCING_ADDR=0xc1213ea8`、
`ZERO_GADGET=0xc01d503c`、`commit_creds=0xc014993c`、
`init_cred=0xc1114f54`。
- エクスプロイト後は直接 syscall を使用(`system()` は使わない)。kctx を生かしたままにする
(`pause()`)ことで、teardown クラッシュを回避する。
### 残り (Stage 4/5)
- 再起動をまたぐ永続化(verity / boot image / recovery)。セルハイジャック + permissive SELinux は
実行時のみ有効であり、エクスプロイトの再実行には約 1/3 の reclaim コインフリップが必要なため。
- `su` には非 nosuid のホーム(`/system`)か、再トリガーするランチャーが必要になる。
## SESSION 11 — 永続化の偵察 (Track B + Track A) と RE の引き継ぎ
目標は永続的な root だった。2 つのトラックが検討された:
- **Track B**: verified boot(dm-verity / SELinux)を無効化し、`/system` にパッチを当てられるようにする。
- **Track A**: 起動時にエクスプロイトを再実行する。
どちらも同じブロッカーに帰着する: **LK にデバイスを `eng`/`unlocked` として扱わせること。**
### Verified-boot の事実(正確なビルド)
- ブートローダーはロック、AVB `green`、`ro.boot.unlocked_kernel=false`、`ro.boot.secure_cpu=1`、
`rpmb_state=1`。Bootrom はパッチ済み(BROM なし)。preloader は CMD ショート経由のみ。
- `/system` は **lk が構築した kernel cmdline から Android dm-verity によって**マウントされる:
`root=/dev/dm-0 dm="system none ro,0 1 android-verity PARTUUID=b6404ef3-… "`、
`veritykeyid=id:f3530e18f64d11fc25eb2dd762979f078de990bf`、`androidboot.veritymode=eio`、
`skip_initramfs`(system-as-root)。`dm-0` = `system` という名前の verity デバイス、`dm-1` = `/vendor`。
- LK: Amazon **UFBL**、`ro.boot.lk_version=0x0006`、ビルド `0db73c9-20231025_030009`、
preloader `pl_version=0x000a`、ビルド `80c6fcb-20230523_065640`。`/dev/block/by-name/lk` = mmcblk0p5 (1 MB)。
- 完全な GPT(16 パーティション、`persist`/`seccfg`/`nvram`/`protect`/`para` なし):
`proinfo` p0、`PMT` p1、`kb` p2、`dkb` p3、`lk` p4、`tee1` p5、`tee2` p6、`metadata` p7、
`MISC` p8、`reserved` p9、`boot` p10、`recovery` p11、`system` p12、`vendor` p13、
`cache` p14、`userdata` p15。eMMC boot0 (1 MB) = preloader(`EMMC_BOOT` マジック)、
boot1 (4 MB) = IDME ストア。
### LK (UFBL) の静的解析結果
`lk.img` ヘッダ: `88 16 88 58 | 00052974 | "LK"`、0x200 に ARM ベクタテーブル、残りは Thumb-2、
位置非依存/再配置(リテラルプールは `ldr+add pc` を使うため、素朴なベース相対逆アセンブルは失敗する)。
関連する文字列(ファイルオフセット): `amzn_image_verify`、`amzn_verify_unlock`、`amzn_verify_code_internal`、
`unlock_code`、`unlock code error`、`unlock failed`、`$Common Kernel Signing Engineering CA0`、
`seccfg`、`para`、`ENV_v1`、`LK_ENV`、`Kfos_flags`/`Kdev_flags`/`Kusr_flags`/`Kunlock_code`/`Kunlock_version`、
`FOS_FLAGS_{NONE,ADB_ON,ADB_ROOT,CONSOLE_ON,RAMDUMP_ON,VERBOSITY_ON,ADB_AUTH_DISABLE,FORCE_DM_VERITY,DM_VERITY_OFF,BOOT_DEXOPT}`、
`[DM-VERITY] verify for system(root) is enabled`、`[DM-VERITY] verify off by fos_flags`、
`[DM-VERITY] disabled by fos_flags on eng devices or unlocked device`、
`[SELINUX] set to permissive mode by dev_flags`、`androidboot.prod=1|0`、`androidboot.unlocked_kernel=%s`。
**結論: LK は `fos_flags`/`dev_flags` のセキュリティ効果を eng/unlocked でゲートしている。**
### IDME ストア (eMMC **boot1**) — 書き込み可能、永続的、LK と Android が読み取る
- 0x0 にマジック `beefdeed` + `"2.1\0"` + count(0x19=25)、アイテムは 0x10 から。
- アイテム形式: `char name[16]; u32 size; u32 type(=1); u32 magic(=0x124); u8 data[size] (pad4)`。
- アイテムオフセット(初期状態): `board_id@0x10 serial@0x3c mac_addr@0x68 mac_sec@0x94 bt_mac_addr@0xd0
bt_mfg@0xfc product_name@0x198 productid@0x1d4 productid2@0x210 region@0x24c bootmode@0x26c
postmode@0x28c bootcount@0x2ac manufacturing@0x2d0 unlock_code@0x4ec sensorcal@0x908 alscal@0x9c4
KB@0xa00 DKB@0x1e1c device_type_id@0x2238 dev_flags@0x2274 fos_flags@0x2298 usr_flags@0x22bc
wifi_mfg@0x22e0 unlock_version@0x26fc`。値は ASCII(フラグは**16 進文字列**)。
- 実行時の読み取り: `/proc/idme/<name>`(読み取り専用)。前回起動時の値がキャッシュされ、boot1 への書き込みは
次回起動時に有効になる。書き込みパスには `/sys/block/mmcblk0boot1/force_ro` のクリアが必要(root)。
- **LK が boot1 を読み取ることを確認済み**: `serial` を変更すると次回起動時に `ro.boot.serialno` が変化した。
しかし LK は **serial を 16 バイトに切り詰め**、`fos_flags=0x80`、`dev_flags=0xff`、
オール 1 などを無視した — verity/selinux/`prod` は変化なし。よって serial 経由の cmdline インジェクションは失敗する。
### IDME フラグの Android 側コンシューマ
- `/init.fosflags.sh`(サービス `fosflags`、`u:r:fosflags:s0`): `FOS_FLAGS_ADB_ON=0x1`、
`CONSOLE_ON=0x4`、`RAMDUMP_ON=0x8`、`VERBOSITY_ON=0x10`、`ADB_AUTH_DISABLE=0x20`、
`BOOT_DEXOPT=0x100`。確認済み: フラグを設定すると有効になる(`sys.usb=adb`、`noadbauth=1`)。
- **adbd**(ストリップされていない ARM ET_EXEC、`.text` VA 0x8160 / ファイル 0x160、fileoff = VA-0x8000):
- `amzn_is_root_allowed` @0x2d5b8 = `amzn_is_dev_unlocked() && (fos_flags & 0x2)`
- `amzn_is_adb_auth_disable_allowed` @0x2d5e8 = `fos_flags & 0x20`(ゲートなし)
- `amzn_is_dev_unlocked` @0x2d5fc = `/proc/cmdline` に `androidboot.prod=0` **または**
`androidboot.unlocked_kernel=true` が含まれる
- `fos_read_debug_flags` @0x2d724 は `/proc/idme/<name>` を読み取り、**16 進**をパースする
- `restart_root_service` @0xcb74 / `restart_unroot_service` @0xcc64
- 文字列: `amzn_fos: ADB: Auto-root succeeded`、`… eng_device=%d`、`… unlocked_kernel=%d`、
`adbd cannot run as root in production builds`、`ro.debuggable`
- `adb root` → "cannot run as root in production builds"(`ro.debuggable=0`)— つまり auto-root ゲートが
満たされていても、AOSP の prod チェックがコマンドパスをゲートする。
### Session-10 の root が永続化しない理由
- SELinux permissive + セルハイジャック + root は実行時のみ。
- `/data/metrics` は **vpartition**: `/system/bin/vpartition.sh` が毎回の起動時に `/data/vp/metrics.img`
(ext4、non-nosuid/noexec)を `/data/metrics` にマウントする。そこに書かれた `su` は**再起動を
生き残らない**。(setuid `su` が uid 0 を与えたが**ケイパビリティがゼロ**だった理由でもある。)
### Track A(起動時の再エクスプロイト)— ブロック
- 制御可能なコードを実行する init `.rc` トリガーは存在しない(インポートはすべて検証済み、`persist.*` トリガーは
固定サービスの `start` のみ、スクリプトは `/system`/`/vendor` 内)。
- root サービスは `/data` の設定を読むが、そこから exec することは決してない(`perfmonitord`、`amazonfiled`、
`vpartition.sh`、`kisd`、…)。
- 唯一の起動実行者 = **アプリ**だが、エクスプロイトの一時停止フットプリントは **VmRSS 534 MB**
(`kbm` スプレー)→ lmkd が kill する。さらに reclaim を逃すとパニック(`PANIC_ON_OOPS`)→ bootloop。
- **adbd auto-root** は存在するが、LK が構築した cmdline(`prod=0`/`unlocked_kernel=true`)でゲートされている。
### 結論 / 次のターゲット(選択: Track B RE)
すべては LK に `eng`/`unlocked` を報告させることにかかっている。到達可能な範囲:
`androidboot.prod=1|0` と `androidboot.unlocked_kernel=false` は LK によって設定される。LK をリバースして以下を特定する:
1. `fos_flags`/`dev_flags`/`usr_flags`(`K*` アイテム)を読み取る場所と正確なゲート;
2. `prod`/`unlocked` の判定(IDME アイテム? buildvariant? `amzn_verify_unlock` の結果?);
3. `amzn_verify_unlock`(libtomcrypt RSA 検証)のバイパスまたは弱い unlock_code/version パス;
4. `seccfg`/`para`/`ENV_v1`(LK_ENV) ストレージ(ダンプされたどのパーティションにもない — tee 保護の可能性);
5. preloader(`boot0`、`EMMC_BOOT`)のバグ。
これらのいずれかで eng/unlocked を(boot1 または生パーティション書き込み経由で永続的に)設定できれば、
`FOS_FLAGS_DM_VERITY_OFF` が system(root) の verity を無効化し、`/system` に永続的にパッチを当てられる。
### 成果物(このセッションから)
`/tmp/opencode/mustang-dumps/`(ホスト再起動で消去される可能性あり): `lk.img`、`boot1.img`(初期状態)、
`boot.img`、`MISC.img`、`metadata*.img`、`pmt.img`、`mbr.img`、`kb.img`、`dkb.img`、`reserved.img`、
`cache.img`、`boot0.img`、`boot1.img`、`adbd.bin`、`perfmonitord.bin`、`amazonfiled.bin`。
ヘルパー: `tools/findinitnet.py`、`findgadget*.py`、`findstores.py`、`adbd_sym.py`(/tmp 内);
リポジトリには `run.sh`、`poc/stage3.c`(`selroot`)、`poc/su.c`、`rootcmd.sh` がある。
### 便利なコマンド```
# IDME read
/data/metrics/su sh -p -c 'for f in fos_flags dev_flags usr_flags serial region device_type_id unlock_version; do echo -n "$f="; cat /proc/idme/$f; echo; done'
# write boot1 (root; su lives only until reboot -> re-run run.sh first)
/data/metrics/su sh -p -c 'echo 0 > /sys/block/mmcblk0boot1/force_ro; dd if=/data/local/tmp/boot1.img of=/dev/block/mmcblk0boot1 bs=4096 count=4; sync; echo 1 > /sys/block/mmcblk0boot1/force_ro'
# dump a partition to host
adb exec-out '/data/metrics/su dd if=/dev/block/by-name/lk bs=4096 2>/dev/null' > lk.img
目的: LKにデバイスをeng/unlockedとして扱わせる、またはpreloader/LKのバグを見つけることで、verity/SELinuxを永続的に無効化できるようにする。結果: 関連するLKコードパスをエンドツーエンドでリバースしたが、このフリップは利用可能なストアからは到達できない。 デバイスは文鎮化せず、唯一のboot1実験は元の状態に戻された。
lk.imgは小さなARMスタブ(ファイル0x200)で始まる。0x224の再配置コードは0x200からリテラルで指定された宛先へコピーし、リテラルで指定されたエントリへ分岐する:
したがって、オフセット0x200以降では実行時アドレス = 0xFF400000 + ファイルオフセットとなる。スタブ以降はすべてThumb-2であり、位置独立である。文字列はldr rT,[pc,#imm](T1オフセット = imm8*4、ldr.wオフセット = imm12)の後にadd rT, pcを続けて構築され、ターゲットは(add+4) + *poolとなる。ARMスタブとリテラルプールを乗り越える堅牢なスキャナをtools/lk_xref.pyとして追加した(16ビット形式と32ビット形式を処理し、2バイトごとにスキャンする)。以下のオフセットはすべてファイルオフセットであり、実行時アドレスには0xFF400000を加算する。
0x20b4はunlock_code IDME項目(0x400バイト、このユニットではすべてゼロ)を読み取り、amzn_verify_unlock (0x222c -> 0x20f0)を実行する。この関数はlibtomcrypt(数十の/features/libtomcrypt/src/pk/asn1/der/...パスとRSA検証)を駆動し、イメージには証明書素材が埋め込まれている:
0x317d9+にSunnyvale / Amazon Lab126 / "$Common Kernel Signing Engineering CA0"、さらに診断メッセージ
Image FAILED AUTHENTICATION on PRODUCTION device (0x3166e)、
Authentication failed on engineering device with production certificate (0x316a0)、
Image FAILED AUTHENTICATION on ENGINEERING device (0x31703)、
Image AUTHENTICATED with PRODUCTION certificate (0x31736)。
空コード/長さ/バージョンのショートカットは存在しない: verify(zeros) != 0、したがってとなる(で確認済み)。またはをフリップするには、有効なAmazon署名済み(秘密鍵は入手不可)か、検証器におけるコード実行バグのいずれかが必要である。0x20b4/0x222c/0x20f0には静的に悪用可能なもの(境界/サイズ)は見つからなかった。=>
セキュリティフラグは0x57cのゲッターを通じて読み取られる。実証テスト:```
dd if=/dev/block/mmcblk0boot1 ... ; reboot /proc/idme/fos_flags -> 00000080 (persisted, Android sees it) ro.boot.veritymode -> eio (unchanged!) root=/dev/dm-0 dm="system none ro,0 1 android-verity PARTUUID=..." (unchanged) androidboot.prod=1 / secure_cpu=1 / buildvariant=user (unchanged)
`fos_flags=0x80` は `FOS_FLAGS_DM_VERITY_OFF` です。デコードされたゲートは、**もし** getter がそれを返していたなら verity をオフにしていたでしょう。しかし、返しませんでした。このゲートはデッドコードではなくライブです。そのワンタイムキャッシュセンチネルはイメージ内で `-1` (`*(u32*)0x50c74 == 0xffffffff`) であるため、関数は実際に `check_flag("fos_flags",0x80)` パスを実行し、0 を得ました。したがって、getter は(少なくとも verity 保護時点では)boot1 IDME アイテムを**読み取っていません**。
もう一つの候補ストアは **LK env** で、文字通り `"para"` という名前のパーティション(loader 0x12fd4、マジック `ENV_v1`、チェックサム @0x3ffc)からロードされます。LK 自身のパーティションテーブル (0x4fcc0..0x50340) には preloader/proinfo/nvram/protect1/protect2/persist/seccfg/secro/**para**/logo/custom/expdb/tee1/tee2/metadata/system/cache/userdata がリストされていますが、タブレットの実際の GPT には**わずか 16 エントリ**しかなく、すべてタイプ `af3dc60f838472478e793d69d8477de4` です:```
#0 proinfo 0x400 #1 PMT 0x1c00 #2 kb 0x4000 #3 dkb 0x4800
#4 lk 0x5000 #5 tee1 0x5800 #6 tee2 0x8000 #7 metadata 0xa800
#8 MISC 0x1e400 #9 reserved 0x1e800 #10 boot 0x22800 #11 recovery 0x2a800
#12 system 0x34800 #13 vendor 0x644000 #14 cache 0x6b4800 #15 userdata 0x7ae800
この製品には para、seccfg、nvram、protect、persist パーティションは存在しない(また PMT/pmt.img のダンプはすべてゼロである)。したがって LK 環境変数は空であり、Kfos_flags/Kdev_flags キーは決して存在せず、すべての fos_flags/dev_flags チェックは 0 に解決される — IDME 項目の内容とは無関係に。boot1 の IDME 項目は Android(/init.fosflags.sh、adbd、/proc/idme/*)によって消費されるが、LK のセキュリティゲートによっては消費されない。
unlocked_kernel には Amazon 署名された unlock_code(RSA/libtomcrypt、組み込み CA)が必要である。オフラインで偽造することは不可能であり、検証器のバグも見つかっていない。ハードブロック。DM_VERITY_OFF / selinux=permissive フラグは LK 環境変数(para/ENV_v1)から消費されるが、この GPT にはそれが存在しない。IDME の fos_flags は経験的に LK に無視される(0x80 を永続化しても、verity は eio のままだった)。パーティションテーブルを変更しない限り ハードブロック。fos_flags=0x80 が成功したとしても、androidboot.veritymode=disabled と非 dm-0 の root= が設定されるだけで、unlock はされず、SELinux が permissive になるには同じく存在しない環境変数からの dev_flags が依然として必要である。para/ENV_v1 ストアの合成: userdata の後の空き領域(userdata は LBA 0x3a3dfde で終了、ディスク = 30535680 セクター)に para という名前の GPT エントリを追加し(プライマリ + バックアップ GPT の両方を更新する必要がある)、次に fos_flags=0x80 と dev_flags=0x40 を持つ env を作成する(チェックサムは +0x3ffc = 0x3ffc にわたるバイト和)。これが verity-off への唯一残された経路である。リスク: プライマリ/バックアップ GPT を破損すると文鎮化する可能性がある。また、verity ゲートが実際に para を読むことは証明されていない(boot1 IDME ではないことだけが確認されている)。boot0/EMMC_BOOT)のバグ: 今セッションではリバースエンジニアリングされていない。クリーンなコピーとリカバリ経路が存在するまで boot0 への書き込みは禁止である。tools/lk_xref.py — ベース非依存の LK 文字列クロスリファレンスリゾルバ。/tmp/opencode/mustang-dumps/lk.img、boot1.img(クリーン)、boot0.img、mbr.img(GPT)、pmt.img(すべてゼロ)。/tmp/opencode/s12/boot1_f80.img に保存。デバイスはクリーンな boot1 に復元済み(/proc/idme/fos_flags -> 0 を検証済み)。./run.sh で再アーム)```./run.sh --no-build
/data/metrics/su /system/bin/sh -p -c 'cat /proc/cmdline'
for f in fos_flags dev_flags usr_flags unlock_version serial; do cat /proc/idme/$f; echo; done
## セッション 13 — プリローダーは結局到達可能(`1949:20ff` = MTK プリローダー、HID トランスポート)
電源オフの状態で USB に接続すると、タブレットは **`1949:20ff`**(`Lab126`)として列挙される — Android でも `0e8d:0003` ブートROM でも*ない*。ディスクリプタ:```
bInterfaceClass 3 (HID), iConfiguration "HID", iInterface "HID Interface"
HID report descriptor = 05 01 09 00 a1 01 c0 (empty collection!)
EP 0x81 IN interrupt 4 bytes, bInterval 4
EP 0x01 OUT interrupt 4 bytes, bInterval 4
iSerial = GCC0X90805310009 (the IDME serial)
識別。 0x20FF は mtkclient の config/usb_ids.py において 「MTK Preloader」 として記載されています(MediaTek VID 0x0e8d の下: 0xe8d:{0x0003 Brom, 0x2000/0x2001/0x20ff/0x3000 Preloader})。Amazon は preloader PID を維持したまま VID を 0x1949 に変更し、ダミーのレポートディスクリプタを伴う HID エンドポイントペアとして提示しています。したがって、これは MediaTek preloader / USBDL モードであり、LK の下位のステージです — ここでは CMD ショートではなく、電源オフ+接続によって到達します。
ディスクリプタ文字列「HID」/「HID Interface」は lk.img、boot0.img、boot.img、その他のダンプには存在しません。つまり、このモードはまだダンプしていないコンポーネント(bootrom/TEE)によって生成されるか、実行時に組み立てられるかのいずれかです。
これが重要な理由。 aftv2-tools が使用する Amazon preloader は、この正確なバイトストリーム上で、組み込みの Download-Agent 不要コマンドを公開しています:```
handshake : host A0 0A 50 05 -> dev 5F F5 AF FA
0xD1 read32 (addr, n_words) : echo cmd/addr/n, 00 00, nu32, 00 00
0xD4 write32(addr, words[]) : echo cmd/addr/n, 00 00, nu32, 00 00
`aftv2-tools/read_mmc.py` は `read32`/`write32` を使用して MSDC コントローラ(MT8173 ではベース `0x11230000`、MT8163 では要確認)を操作し、DA を介さずに **raw eMMC ブロック**を読み書きするため、AVB/verity が介在しません。mustang の preloader が 0xD1/0xD4 を受け入れる場合、それは RSA アンロックコードや存在しない LK 環境変数に依存しない、永続的なアンロック(`boot` / `lk` のパッチ)への直接的な経路となります。
### 追加されたツール(root 権限が必要。まず USB ノードを chmod すること)```
lsusb -d 1949:20ff # note Bus/Dev, e.g. Bus 001 Device 003
sudo chmod 666 /dev/bus/usb/001/003
# 1) does it answer the MTK handshake? (no DA, no flash access)
nix-shell -p python3Packages.pyusb --run \
'python3 tools/mtk_preloader_hid.py handshake'
# 2) read-only arbitrary memory read
nix-shell -p python3Packages.pyusb --run \
'python3 tools/mtk_preloader_hid.py read32 0x00100000 4'
tools/probe_preloader.py は最小限のハンドシェイク専用プローブです;
tools/mtk_preloader_hid.py は完全なトランスポートです(handshake/read32/
write32;write32 はガードされており、eMMC レジスタマップが確認されるまで
使用すべきではありません)。
read_mmc)、preloader から boot.img/lk にパッチを当てて再起動します。amzn_verify_unlock や LK 環境ゲートには依存しません。futex.c:1975 が this->rt_waiter を渡し、
これは futex.c:2880 の futex_wait_requeue_pi で宣言) → waiter は arm32 select (nr 142) fd_sets を
通じて自身の解放済みフレームを刻印するDEBUG_RT_MUTEXES オフ
→ コンパクトな 48 バイト rt_mutex_waiter (tree_entry@0, pi_tree_entry@0xc, task@0x18,
lock@0x1c, prio@0x20, deadline@0x28)modprobe_path @ 0xc111488c (文字列は
vmlinux 内で自己位置特定; KALLSYMS_ALL オフのためデータシンボルにはこのトリックが必要) → 未知の binfmt exec → root
スクリプト (setenforce 0, OTA 無効化, su)refs/ 内の参照: NebuSec/CyberMeowfia (オリジナル)、GhostLock-5.10 (Fire OS 8 移植、
src/exp32/ に完全な 32 ビット ARM トリガー)、ghostlock-...-4.19-k40 (Qualcomm 4.19 Android 移植)selinux_state.enforcingcommit_creds(&init_cred)uid=0jcnr_extresinfo->gpu_alloc_addr を通じて書き込む (事前に確保して渡す必要がある GPU VA)| 退避可能オブジェクト | プレッシャー | 結果 |
|---|
| なし | 900 MB | 生存 |
| なし | 1300 MB | パニック (システム lowmem バグ — 無関係) |
| 通常領域 + DONT_NEED | 700 MB | 生存 |
| JIT 領域 + DONT_NEED | 700 MB | reclaim パスでパニック |
/proc/config.gzksrc/ — Amazon OSS ソース (platform.tar + 展開済み midgard-r26p0 ツリー)/tmp/opencode/mustang_ota.bin (sha256 6068515a… は fireos-archive と一致)
および 2.2 GB のカーネルソース tarball を ~/Desktop/amazon-mustang/ に保管ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/| deref でクラッシュ |
| + エビクション時に子を kill (10ms ポーリング) | deref でクラッシュ |
| + cpu0 ピン留めライフサイクル (drain3) | deref でクラッシュ |
| 段階的プレッシャー (drain4 v1) | 子が終了時にメモリを解放 → エビクションなし (合法的パスを検証) |
| run | 結果 |
|---|
| pmap G=c2a412a4 400MB | deref でクラッシュ |
| pmap G=c2f4b2a4 480MB | クラッシュ; kill 後 +0 リネーム (480MB が fs を窒息させる) |
| iso2 (スプレー + リージョン + オラクル) | リージョンヒット — スプレーは reclaim を壊さない |
| mix v1 (並行イベント+リージョン) | クラッシュ; 交絡 (スピンするイベントスレッド) |
| pmap G=c2a7d2a4 350MB + retouch | クラッシュ; +4452 リネーム OK |
| mix2 (逐次: 2 秒イベント THEN リージョン) | クラッシュ; +7126 リネーム (28K イベントアロケーション)、2715 リージョン |
fn=0PM_PAGE+NF_CELL_OFFprobekernel_x_end より上で XN: arch/arm/mm/mmu.c
の map_lowmem() はカーネルテキストより下の lowram を MT_MEMORY_RWX にマップするが、
kernel_x_end より上のすべては MT_MEMORY_RW → PMD_SECT_XN (509行目)。
0xc154b600 に焼き込まれたシェルコードはプリフェッチアボートする。ペイロードは
physmap 内のコードではなく、本物のカーネル関数ポインタでなければならない。| リテラル(ファイルオフセット) | 値 | 意味 |
|---|
| 0x270 | 0xFF4002F8 | str r4,[r6] スクラッチ |
| 0x274 | 0xFF40027C | 宛先 (base+0x27C) |
| 0x278 | 0xFF54A440 | コピー終端 (BSS含む) |
| 0x27C | 0xFF400484 | エントリポイント |
| オフセット | 関数 |
|---|
0xdf7c | is_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ]; g = グローバル @0x52838。このユニットでは1。 |
0x20b4 | verify_stored_unlock() = memset(buf,0,0x100); 0x57c経由でIDME/env unlock_code (0x100)を読み取り; bl 0x222c; (verify==0)を返す。 |
0x222c / 0x20f0 | amzn_verify_unlock(code,len) — libtomcrypt RSA/PKCS#1検証(下記参照)。 |
0xda3e | is_unlocked() = is_secure_or_prod() && verify_stored_unlock()。 |
0x29a28 | is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock()); グローバル @0x50c74 にキャッシュされる。 |
0x29974 | SELinux cmdlineビルダー: dev_flags & 0x20 -> androidboot.selinux=enforce、dev_flags & 0x40 -> ...=permissive(それぞれbyte[+0x162]でゲートされる)。 |
0x118xx/0x11bxx | カーネルcmdlineビルダー(unlocked_kernel、prod=1/0、verifiedbootstate、rpmb_state、secure_cpu、バージョン、root=)。 |
0x27af8 | UARTゲート: fos_flags & 0x4 -> printk.disable_uart=0、それ以外は=1。 |
0x12fd4 | LK envローダー: パーティション"para"、0x4000バイト、マジックENV_v1、チェックサム = 0x3ffcにわたるバイトの合計をワード@0x3ffcと比較。 |
0x1efd0 | 名前によるパーティション検索("para"、"boot"などに使用)。 |
0x57c | コールバックスロット@0x58218を介したゲッターディスパッチャ。スロット@0x58200..0x5821cは0x5a8-0x734のテーブルから登録される。 |
0x2a19c | fastboot oem unlock: bl 0x222c(code,len); 成功時には0x408経由でunlock_code (0x100)を書き込む。 |
unlocked_kernel=false/proc/cmdlineandroidboot.unlocked_kernelandroidboot.produnlock_code