
Kernel-Exploit-Forschung, die temporären Root auf dem Amazon Fire 7 (Fire OS 7.3.3.1) über den Mali kbase JIT Use-after-free CVE-2022-38181 erreicht, mit einer modprobe_path-Überschreibungskette.
KI-gestütztes Projekt. Diese Recherche, Exploit-Entwicklung und Dokumentation wurden mit KI-Unterstützung unter Verwendung der Modelle GLM-5.3 und DeepSeek V4.1 Flash erstellt.
Root-Exploit-Recherche für das Amazon Fire 7 9. Gen (mustang, MT8163, Mali-T720) auf der finalen Firmware — Fire OS 7.3.3.1, PS7331.4463N, Kernel 4.9.117 (gebaut 2025-05-03, SPL 2024-08-01).
Ziel: LineageOS. Der Bootloader-Pfad ist auf diesem Gerät tot (gepatchtes Bootrom — nur Preloader via CMD-Kurzschluss), daher ist der einzige verbleibende Weg ein Software-Kernel-Exploit.
nix-shell -p android-tools --run './run.sh' # add -p zig too if no zig
Bei Erfolg:```
/data/metrics/su id # run a command as root
/data/metrics/su # interactive root shell
Der Reclaim gewinnt ungefähr 1 Boot von 3 und ein Verlust führt zu einer Panik/Neustart des Tablets;
run.sh wartet einfach auf den Neustart und versucht es erneut. SELinux wird als
Teil des Exploits auf Permissive gezwungen, daher ist root nur zur Laufzeit — ein Neustart stellt den Auslieferungszustand wieder her und
du führst run.sh erneut aus.
Vorkompiliertes st3 und su (armv7 static) sind eingecheckt, daher wird keine Toolchain zum
Ausführen benötigt. ./run.sh --build baut sie aus poc/*.c neu, wenn du zig hast.
GhostLock (unten) ist geparkt: MTKs BUG_ON-rtmutex-Variante + keine Kernel-Adress-Offenlegung aus der Shell = architektonische Sackgasse in diesem Build (Sessions 2-4). Der kbase JIT UAF wurde neu diagnostiziert (die Destroy-Worker-„bedingungslose Panik" war die JIT_FREE-Dereferenzierung, Log verloren durch adbd-Tod mitten in der Panik) und Stufe 2 ist nun orakel-bewiesen — siehe SESSION 5 Abschnitt.
rtmutex remove_waiter() futex-PI Stack-UAF (NebuSec-Offenlegung 2026-07, Fix 3bfdc63936dd
gelandet 2026-04). Verwundbarer Bereich 2.6.39–7.1 → unser 4.9.117 (Mai 2025) ist betroffen.
Verifiziert auf unserem exakten Build:
CONFIG_FUTEX=y, rtmutex einkompiliert, Bug wörtlich vorhanden:
rtmutex.c:1108-1111 verwendet current->pi_lock/current->pi_blocked_on (sollte
waiter->task sein); fehlerhafte Aufrufstelle rtmutex.c:1723 (rt_mutex_start_proxy_lock Fehlerpfad)WAIT_REQUEUE_PI/CMP_REQUEUE_PI), kein Device-Node,
nichts SELinux-geschützt — die fatalen Hindernisse des kbase-Pfads existieren hier nichtsched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) bei
sched/core.c:4706 — dereferenziert veraltetes ✓exp32/main.crt_waiter Frame-Offset vs do_sys_select fd_set-Bereich — unser vmlinux
disassemblieren (do_sys_select stack_fds vs futex_wait_requeue_pi Frame), STAMP_NFDS/STAMP_WAITER_OFF
als Tunables offenlegenFailed critical init step 3/dev/mali0 world-RW + SELinux gpu_device, kbase r26p0-01rel0mustang, Fire OS 7.3.3.1 PS7331.4463N/00315758630404.9.117-g08fe75b-dirty, gebaut Sat May 3 01:25:15 UTC 2025 (Linaro GCC 6.3-2017.05)/dev/kb, /dev/dkb (Amazon Kernel-Backup-Partitionen) root:drmrpc 0660 — gesperrtmali_kbase r26p0-01rel0 (Midgard, Mali-T720), innerhalb des NVD-betroffenen Bereichs r4p0–r31p0mali_kbase_mem.c:2721 kbase_jit_destroy_worker gibt Region frei, löscht nie kctx->jit_alloc[id]mali_kbase_softjobs.c:1270 kbase_jit_free_finish dereferenziert das veraltete jit_alloc[ids[j]]mali_kbase_mem.c:3138 kbase_jit_backing_lost → Destroy-Pfad (feuert während Reclaim)0xc0008000 VA / 0x40080000 PA)ARM_SW_DOMAIN_PAN → ret2usr gangbar; CONFIG_PANIC_ON_OOPS=y (fehlgeschlagene Versuche = Neustart)SLAB_FREELIST_RANDOM/HARDENED, kein CONFIG_USER_NS/USERFAULTFD/NF_TABLESCONFIG_MODULES=y, kein STATIC_USERMODEHELPER → modprobe_path-Überschreiben = root_IOC_TYPE 0x80)MEM_ALLOC Union ist 32 Bytes (in hat 4 × u64 inkl. extent)BASE_MEM_PROT_GPU_RD|WR (Bits 2|3) enthalten, nicht Legacy R|Wmmap(fd, offset=3<<12, PROT_NONE)JOB_SUBMIT Stride muss gleich sizeof(base_jd_atom_v2) = 48 sein (base_jd_prio/base_jd_dep_type sind u8-Typedefs)MEM_JIT_INIT (nr 14, v2-Struct), Alloc/Free sind Soft-Jobs via JOB_SUBMIT
(BASE_JD_REQ_SOFT_JIT_ALLOC=0x209, ...FREE=0x20a; =User-Ptr, =Anzahl)Panik tritt während der Eviction selbst auf (evictable_reclaim_scan_objects → backing_lost →
Destroy-Worker) — die baumelnden Referenzen (jit_alloc[], Evict-Liste) werden durchlaufen, bevor wir jemals
JIT_FREE submitten. Stufe 2 muss das Rennen gewinnen: die freigegebene kbase_va_region mit unserem
eigenen MEM_ALLOC-Spray reallozieren, während der Druck noch läuft.
poc/stage2.c — Stufe-2-Exploit (Modi: step/uaf/spstep/spfree/spray/keys)
— spray 700 = vollständiger Oracle-Lauf; überlebt und pausiert (kill zum Aufräumen)poc/mustang_jit_uaf.c — Stufe-1-PoC (Modi: jit N / control N / pressure N)poc/build.sh — zig Cross-Build (static musl armv7)kernel/vmlinux — Symbole aus dem exakten OTA-Build wiederhergestellt (vmlinux-to-elf)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
## Referenzen
- GHSL-2022-054 Advisory: https://securitylab.github.com/advisories/GHSL-2022-054_Arm_Mali/
- Mos Pixel-6-Exploit-Writeup: https://github.blog/2023-01-23-pwning-the-all-google-phone-with-a-non-google-bug/
- Fire HD 10 (trona) Präzedenzfall, gleiche Bug-Familie: ericpardee.github.io/fire-hd-ownership
- Amazon OSS-Portal: amazon.com gp/help/customer/display.html nodeId=200203720
- XDA-Unlock-Thread (tot für diese HW-Revision): xdaforums.com/t/fire-7-2019-mustang-unbrick-downgrade-unlock-root.3944365/
## Sitzung 3 Addendum (tiefe Syscall-Stamp-Untersuchung)
Gemessene Copy-Source-Tiefen (absolut vs. Syscall-Eintritt sp0; Waiter spannt -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 (BESTE — 0x1c vor waiter+0x00)**
- poll entries @ -0x3e0 (vollständig darunter; falsche Seite)
In dieser Sitzung ausgeschlossen:
- io_submit-Kette zu flach (~-0x130) | semtimedop: CONFIG_SYSVIPC=n (Stub)
- configfs gemountet, aber NULL Subsysteme registriert (keine mkdir-Ziele)
- /sys/kernel/debug, /config: SELinux-verweigert für Shell
- /proc/sys/kernel: getdents funktioniert (29 Einträge aufgelistet), nur pid_max ÖFFENBAR;
kptr_restrict/hotplug/hostname/domainname-Lesezugriffe alle verweigert
- Schreibwert ist IMMER waiter+0 (Kernel-Stack-Adresse, ausführbar, Shellcode bei
+0x1c): rb_link_node *link = node, insert_color schreibt parent-color — keine
kontrollierter-Wert-Variante möglich ohne Tree-Field-Stamping (Lücke -0x1d8..-0x1bc)
- Double-Deref-Dispatch-Felder lesen *(waiter+0)=1, *(waiter+4/+8)=0 — BLX 1/0
(nf_hooks, net_families, inet[6]_protos, seq_file->op alle tot)
- timer_list.function@+0xc und work_struct.func@+0xc würden *(waiter+0xc) =
pi_tree self-ptr = AUSFÜHRBARER waiter+0xc lesen — aber kein Pfad reiht waiter+0 als
Timer/Work ein (Link-Korruption / keine indirekten Queue-Quellen)
- Tree-Root-Nonzero (sysctl-Handler-Slot) = deterministischer Pointer-Chase durch
.text als rb-tree; terminiert an einem Nullwort — offline-simulierbar, aber Landung
in einem nützlichen beschreibbaren Slot ist unplausibel
Verbleibende Ansätze für Sitzung 4:
1. ioctl-Tiefenpfade: dev_ioctl ifreq-Copy (40B User-Daten) — SyS_ioctl→sock_ioctl→dev_ioctl-Kettentiefe vs. -0x1d8 messen
2. Jede andere 0x1c-tiefere Copy als sendmmsg (bisher nichts gefunden)
3. Falls Stamp-Surface-Suche fehlschlägt: Walk-Chained-Konstrukte überdenken oder
nach beschreibbaren Zero-Called-Slot-Klassen suchen, die noch nicht aufgezählt wurden
## SITZUNG 4 — DIE ZWEI DURCHBRÜCHE
### 1. MTKs rtmutex_common.h ist das ganze Rätsel
MTK ersetzte Upstreams NULL-sicheres rt_mutex_top_waiter durch:```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
NO NULL CHECK + BUG_ON. Jeder All-Null-Anker stirbt bei *(NULL+0x1c); Müll- Anker sterben beim udf. DER WALK ERFORDERT: lock->waiters_leftmost (lock+8) muss auf einen Fake-Waiter W (beschreibbar) zeigen, mit W->lock (+0x1c) == lock.
Walk-Ablauf vollständig kartiert (rt_mutex_adjust_prio_chain @ 0xc0189a58):
/proc/self/task//stat Feld 28 (kstkesp) liefert ECHTEN Kernel-SP für syscall-blockierte Threads aus SHELL-Kontext (verifiziert: Nicht-Null-Werte beobachtet).
Der Walk feuert deterministisch (Dead-Lock-Probe: Crash jedes Mal, sauberer Code). Der Schreibvorgang kann nicht landen wegen einer dreifachen Kernel-Hardening-Koinzidenz:
Das Stamp-Fenster (Waiter+0x1c..0x5b) ist der einzige kontrollierte+inhaltlich bekannte Speicher, aber seine ADRESSE ist das Unbekannte, das wir brauchen. Selbstreferenzielle Konstruktionen erfordern alle das Stempeln einer Kernel-Adresse als Konstante — zirkulär ohne Leak.
Deren 5.4-Kernel hat UPSTREAM rtmutex_top_waiter (NULL-sicher: if (!leftmost) return NULL) — Empty-Tree-Anker überleben, deren Schreibvorgang landete auf null_fops
(beschreibbar auf deren Kernel). Sogar SIE stecken beim Dispatch fest ("ioctl reboot").
Mustangs 4.9.117 MTK-Tree hat die BUG_ON-Variante — Fire OS 8 Tablets
(GhostLock-5.10) hatten Erfolg, weil deren 5.10-Kernel Upstream-Stil sind.
Getestet und tot aus Shell-Domain:
SCHLUSSFOLGERUNG: GhostLock auf mustang erfordert eine Kernel-Adress-Offenlegung, die dieser Kernel der Shell-Domain nicht preisgibt. Das selbstreferenzielle Fake-Lock kann ohne sie nicht konstruiert werden.
(a) Boot-deterministisches Grinding: Reboot → Stack-Adresse via Crash- Oracle kalibrieren (~20-30 Reboots), Reproduzierbarkeit verifizieren. Long Shot — Late-Boot- Thread-Stack-Allokation wahrscheinlich nicht stabil. (b) PIVOT zurück zu kbase CVE-2022-38181 mit angesammelten Assets: Exact-Build- vmlinux + vollständiger Source + Toolchain + O_SYNC-Trace-Disziplin + tiefes 4.9- Wissen. Ursprünglicher Blocker (Destroy-Worker-Panic während JIT-Eviction) ist ein Spray-Timing-Problem, jetzt besser verstanden. (c) Bei ehrlichen ~45% aufhören: Trigger bewiesen, Walk bis zur Instruktion kartiert, Schreibvorgang blockiert durch MTK BUG_ON + kein Leak.
Empfohlen: (b) — der kbase-Bug ist in dieser exakten Source verifiziert vorhanden, hatte einen funktionierenden Trigger, und sein Blocker ist mechanisch, nicht architektonisch.
step-Modus (alloc id=1 → DONT_NEED → 700MB Druck → MEM_QUERY, KEIN free)
überlebt: query=-1 (Region vom Destroy-Worker freigegeben, rbtree-sauber).
Der Worker-Pfad ist byte-identisch zum legalen JIT_FREE-unter-Druck-Ablauf.
Der Crash aus Session 1 war immer der JIT_FREE-Dangling-Deref; seine Log-Zeile ging
verloren, weil die Panic adbd mitten im Flush tötet. Zweimal weiter verifiziert mit uaf-
Modus (bloßes free → Panic, gleicher Log-Abbruch). Der GHSL-2022-054-Ablauf ist vollständig
live auf diesem Build.
kbase_jit_free(kctx, reg) @ 0xc058495c mit vollständig kontrolliertem Fake-reg:
reg->cpu_alloc NULL → Backed Size 0 → Trim-Block übersprungen (0xc0584978)kctx+0x147dd (Byte) + kctx+0x147de+bin_id (Byte)mark_reclaim(reg->gpu_alloc) @ 0xc059b158: Kette
K=*(gpu_alloc+0x38) → *(K+0x1429c)==0 überspringt mm-Atomics →
atomic_sub nents@K+0x141c8, D=*(K+4) → atomic_sub nents@D+0x538.
Mit nents=0 sind alle Schreibvorgänge No-Op-Stores (strex desselben Werts).reg->flags |= 0x100000 (Schreiben in Fake, harmlos)list_add(gpu_alloc->evict_node, &kctx->evict_list): evict_list-Kopf
@ kctx+0x1427c; schreibt in gpu_alloc+0x18/0x1c (muss beschreibbar sein)r3=*(reg+0x3c) prev, r2=*(reg+0x38) next
→ *(next+4)=prev; *(prev+0)=next — zwei beliebige Write-What-Wheres,
dann Relink von reg+0x38 in jit_pool_head @ kctx+0x148e89 Kandidaten; S=0xc118b7ec (xfrm-Daten, ruhend auf diesem Gerät):
*(S+8)=0 (nents), *(S+0x18)=S+0x18 (leerer evict_node → kein WARN),
K=*(S+0x38)=0xc118b820 → *(K+0x1429c)=0, K/D+0x141c8/+0x538 alle in
beschreibbaren Daten. Oracle-Ziele vorbereitet: init_uts_ns.name.nodename=0xc110d561
("(none)", lesbar via uname), Scratch P=0xc118bd58 (xfrm-Nullen).
S=0xc111cba4 (tracepoint-benachbart) vermeiden. CONFIG_DEBUG_RODATA=y → alle Schreib-
Ziele müssen in .data/.bss sein (bss 0xc11d9000-0xc12d9000).
add_key (CONFIG_KEYS=y): SELinux-verweigert für Shell. Tot.setxattr-Value-Buffer: kvmalloc(96)+copy_from_user passiert VOR dem
SELinux-Check → Alloc-Dance ist SELinux-sicher, selbst wenn der Call fehlschlägt;
transient (am Syscall-Ende freigegeben), Bytes bleiben bei +4..95 erhalten (Freelist-Ptr
überschreibt +0..3 = rblink, von kbase_jit_free ungenutzt)spray 700-Lauf 2026-09-11: 5895 Regionen während Druck gesprayt, JIT_FREE
auf baumelndem id=1 vollendet auf einer zurückgewonnenen Region, dann
JIT_ALLOC(0x40, bin 0) durchlief jit_pool_head und lieferte die VA der gesprayten Region
#4251 (0x142701000) — genau die Region, die der Dangling-Pointer konsumierte.
Prozess-Kill danach: kctx-Teardown sauber, kein Crash. Stage 2 vollständig:
deterministische UAF-Umleitung mit kontrolliertem Objekttyp + Inhalt.
Region-Typ-Reclaim liefert Überleben des legalen Tanzes, aber jit_node ist INIT'd self → kein Unlink-Primitiv. Brauchen Raw-Bytes bei +0x38/+0x3c:
*(N+4)=P mit P=Userland-Shellcode-Page (kein PAN!) —
Kandidaten: const fops sind .rodata (DEBUG_RODATA) → Ziel nicht-const
fn ptr in .data, oder binfmt formats-Listenkopf, oder sysctl proc_handler
(Tabellen-Beschreibbarkeit verifizieren). Fallback: modprobe_path via byte-verkettete
Schreibvorgänge (Werte müssen beschreibbare Adressen sein — pointer-förmige Ziele verwenden)poc/stage3.c):
kern_table[pid_max].proc_handler @ 0xc1113f40 (beschreibbar
.data, verifiziert via String-Pointer-Scan + handler == proc_dointvec_minmax)b +8; W2 schreibt N bei P = Handler-Feld)read /proc/sys/kernel/pid_max
(aus Shell lesbar); läuft im eigenen Task-Kontext → Creds gelten für unsinotify_handle_event kmalloct name_len+0x1d,
Name-Bytes (vollständig kontrolliert, NUL/Slash-freie Einschränkung) bei event+0x1c;
name_len=60 → kmalloc-96; in Queue → gehalten; 4 Instanzen → 4 Events pro
Rename; SELinux-OK aus Shell. Fake NUL-frei neu entworfen: cpu_alloc
zeigt auf S (nents@S+8 = 0 → gleiche Semantik wie NULL)| Variante | Ergebnis |
|---|---|
| gleichzeitige Rename-Sprayer während Sturm |
Arbeitshypothese: Sturm-Müll-Race — zwischen dem Freigeben des Slots durch den Destroy-Worker (mitten im Sturm) und Child-Kill/Ruhe nimmt Rest-Reclaim- Aktivität den einzigen freien Slot des Opfer-Slabs mit Nicht-Payload- Bytes. Region-Spray (Stage 2) gewinnt, weil es kontinuierlich WÄHREND des Sturms alloziert; Renames können das nicht.
iso-Modus): identisches Timing, Trailing mit
commit-0-REGIONEN + Stage-2-Pool-Reuse-Oracle → ORACLE-TREFFER
→ Timing/Erreichbarkeit sind IN ORDNUNG; Events sind das Problemcurrent->mm ist das eines Kernel-Threads → das elegante
"die Pointer des Fakes in unser eigenes Userspace-mmap zeigen"-Design (kein PAN!)
FAULTET nichtdeterministisch. 5/5 Crashes mit ansonsten perfektem Fake.K=*(S+0x38) lesbar, *(K+0x1429c)==0 zur Laufzeit
(überspringt mm-Atomics), K+0x141c8 beschreibbar, D=*(K+4) → D+0x538
beschreibbar, nents *(S+8) bevorzugt 0. Die 9 Offline-S-Kandidaten wurden
gegen DATEI-Bytes validiert — Laufzeit-Drift (xfrm/tracepoint-Init)
macht sie unverifiziert. Eine falsche Kette = Crash = Reboot (~3 min Zyklus).A. Physmap-Spray-Fake (ret2dir, klassisch arm32 ohne PAN): ~450MB User-Pages sprayen, jede enthält das Fake-Muster gebacken für EINE geratene Adresse G (G&0xfff = 0x141 für NUL-freie Name-Bytes; S=G; K=G-0x141b4 so dass K+4 in-Page landet; K+0x141c8/+0x1429c → G+0x10/+0xd4 in-Page; D=G+0x300; verirrte sub-0-Stores treffen zufälliges gemapptes RAM - harmlos mit nents=0). Spray dient gleichzeitig als Eviction-Druck (dirty anon = unevictable → nur ~100-200MB extra nötig). Chancen ≈ 45% (Page-Treffer) × ~50% (fremdes K+0x1429c-Wort ist Null... wenn K+0x1429c laut Layout oben in-Page bleibt, Chancen = nur Page-Treffer). Fehlschlag = Crash = Reboot, Retry. B. Die 9 statischen S-Kandidaten brute-forcen (xfrm 0xc118b7ec zuerst, tracepoint-benachbart 0xc111cba4 zweitens...): 1 Reboot pro Kandidat, Benign-Oracle-Payload zuerst, Waffe bei Treffer. C. pagemap-basiertes exaktes G (tot: PFNs maskiert) — nicht erneut angehen.
/tmp/opencode/ksrc2/kernel/mediatek/mt8163/4.9/ (arch/arm inkl.
mustang.dtsi, mm/, fs/eventpoll.c, fs/notify) aus ksrc/platform.tar.
Erkenntnisse:
physmap_retouch() nach dem Kill hinzugefügt (faultet alle Spray-Seiten
vor dem Trailing/Deref wieder ein)Regions reclaimen: 3/3. Events: 0/~12 Versuche inklusive 28K exklusiver Allokationen mit Vorsprung und konstruktionskorrektem Fake. Wenn Events mit p_hit(G)≈0.35 reclaimen, fünf pmap-Fehlschläge ≈ 11.6% — möglich, aber nun unwahrscheinlich (~10-15%). Entweder können Events diesen Slot strukturell nicht belegen (Grund unbekannt — gleicher Cache, gleicher Kontext, gleiches Timing) oder unsere G-Vermutungen treffen systematisch daneben (Highmem-Grenzverschiebung, Allocator-Platzierung).
vmalloc=496M slub_max_order=0 slub_debug=O loglevel=8 initcall_debug
Regions belegen den Opfer-Slot 3/3; Events 0/13. Gleicher Cache (bewiesen auf Quellcode- + Disasm-Ebene), gleicher Prozesskontext, gleiche gepinnte CPUs, gleiches Post-Kill-Timing, Tausende Allokationen mit exklusivem Vorsprung. Mechanismus unbekannt. Verbleibende Verdächtige: Allokationsraten-/Frequenz- Korrelation mit Partial-List-Rotation (Region-ioctls ~1ms auseinander vs. Event-Renames ~100µs auseinander — entgegengesetzte Richtungen?), oder SLUB-Freelist- Ordering-Details unter slub_max_order=0, die... unklar.
[+] uname.nodename="X?lhost" (was "localhost") <<< ORACLE HIT
**Die vollständige Raw-Byte-Kette funktioniert auf dem Live-Gerät**: Das Event reklamiert den
freigegebenen Region-Slot → kbase_jit_free dereferenziert unser Fake (S=physmap-Seite aus
dem kbm-Spray, G=0xc154b2a4) → der Unlink führt unsere beiden Writes aus.
Wiederholt verifiziert mit der benignen Payload (nodename-Write).
### Sitzungskette der Entdeckungen
1. kbm-Seiten waren GFP_HIGHUSER → HIGHMEM, unsichtbar für physmap
(mali_kbase_mem_pool.c:164!) — behoben durch Zonelist-Spill: 260 × 2MB
Spray > highmem-free → Überschuss landet in ZONE_NORMAL (physmap)
2. Erste Waffenversuche stürzten ab: W1-Ziel N+4 war eine USER-Seite —
Unlink läuft im kworker-Kontext (kein mm) → Fault. Behoben durch Einbacken des
Ring-0-Shellcodes IN das physmap-Muster bei page+0x600 (Direct Map RWX auf
arm32 non-LPAE) — kernel-residenter Code, kein ret2usr nötig
3. ctl_table-Offset-Bug: proc_handler liegt bei entry+0x14, nicht +0x18 (der
ursprüngliche Scan hatte es richtig; mein Define war falsch) — schrieb extra1
4. Überdruck-Regression gefunden & zurückgerollt: kid-Budget 20×100MB +
nachlaufende 10s brachen die Reclaim; die funktionierende Konfiguration ist 2 kids/200MB +
5s/+3200 nachlaufend (benigner Treffer 1/1 nach Rollback)
5. **diag2: W2 → &pid_max global (0xc1114d7c) → Read liefert unseren Wert
(-1055861411 = 0xc110d55d als int32) — Write + Readback BEWIESEN**
### Das verbleibende Rätsel (ein Experiment von der Lösung entfernt)
diag1 mit der KORREKTEN Handler-Adresse (0xc1113f3c): W1 feuert (nodename
ändert sich), W2 muss ausgeführt worden sein (nächste Instruktion) — dennoch liefern pid_max-Reads
weiterhin saubere Werte → das Handler-Feld, das wir schreiben, ist nicht das, über das
das Inode dispatcht. diag3 (in Warteschlange; benötigt einen Hit-Boot): W2 →
entry->data FIELD (0xc1113f2c) zeigt auf nodename — wenn der Read dann
nodename-Bytes-als-int zeigt, ist unser entry LIVE und nur der Handler-
Offset ist irgendwie falsch; wenn unbeeinflusst, verwendet das Inode eine Shadow-Table-
Kopie und wir jagen die Live-Version.
### Hit-Rate-Realität
Pro-Boot-Münzwurf (~25-40%), geclustert; mehrere Safe-Miss/Crash-Boots
in Folge sind normal. Ungefähr 1 von 3-4 Boots ist ein Hit. Behalte die funktionierende
Konfiguration EXAKT bei (2 kids, 5s nachlaufend, 260-Region-kbm, G=0xc154b2a4).
### Session-9 TODO
1. diag3 auf einem Hit-Boot abschließen (rollen bis "W1 FIRED")
2. Falls entry live: Handler-Offset empirisch neu prüfen (schreibe
proc_dostrings Adresse als Handler via... N muss einem nützlichen
Wert entsprechen — nutze den Unlink, um stattdessen entry->data zu schreiben und zu pivotieren: z. B.
data=selinux_enforcing-benachbart...)
3. Falls Shadow-Table: die Live-Version lokalisieren — kallsyms hat keine Datensymbole;
Kandidaten: /proc/sys-Verhalten scannen, oder eine zweite ctl_table-
Region über das Header-Listen-Muster in .data finden (0x20-Schritt-Einträge
mit handler=proc_dointvec_minmax und maxlen=4 — ALLE aufzählen und
jeden diag-schreiben)
4. Alternative Zielklasse, die Dispatch vollständig vermeidet: .data-
Funktionszeiger, die von shell-erreichbaren Pfaden aufgerufen werden (Audit nötig)
5. Das Write-Primitiv selbst ist FERTIG — jedes zuverlässige Kernel-Adress-
Ziel genügt jetzt für Root
## SESSION 9 — nf-WEAPON: reclaim+unlink+G-hit IN-WEAPON BEWIESEN; nur der Hook-Walk bleibt
### Das neue Trigger-Design (ersetzt den sysctl-Handler-Pfad vollständig)
Die Idee des Users in Kernel-Speicher übersetzt: keine SUID-Datei (System ist
dm-verity RO; Primitiv schreibt Kernel-RAM). Stattdessen: **Fake-Netfilter-
Hook**. Dieser Kernel hat den Android-common-Backport der NEUEN
nf_hook_entries-API, aber als VERKETTETE LISTE implementiert (verifiziert durch
Disasm von nf_hook_slow + Helper 0xc09897d4):
- `__ip_local_out(net, sk, skb)` lädt die Entries-ZELLE aus
**[net+0x58c]**, speichert in state+0x1c, ruft nf_hook_slow auf
- Walk: `entry = *cell`; while(entry){ if (state->[4] <= entry->[0x20])
call entry->[0xc](entry->[0x14], skb, state); entry = entry->[0]; }
— d. h. **fn@entry+0x0c, priv@entry+0x14, priority@entry+0x20,
next@entry+0x00**; state+4 = INT_MIN-Schwelle (passiert immer)
- init_net = **0xc1104548** (CONFIG_NET_NS=n → sock_net() inlined die
Konstante; 6678 movw/movt-Referenzen, Histogramm-Champion; kreuzbestätigt durch
nf_hook_slows eigenes Literal). ZIEL: **[init_net+0x58c] = 0xc1104ad4**
- LOCAL_OUT-Hook läuft im Prozesskontext des SENDERS → unser hookfn's
commit_creds(prepare_kernel_cred(0)) rootet den Prozess, der das
Paket gesendet hat. Trigger = sendto(127.0.0.1:9) UDP.
### nf-Modus-Layout (poc/stage3.c, Modus `nf 200`)
- kbm-Musterseiten (seiten-relativ, einzige Quelle der Wahrheit — die alte
Waffe hatte DREI Bugs, jetzt behoben: proc_handler@+0x14 nicht +0x18; W1-
Ziel muss KERNEL-Speicher sein (kworker-Kontext, kein mm); eingebackener Code bei
page+0x600 vs. entry G+0x600=page+0x8a4-Mismatch):
- +0x2ac/+0x2bc/+0x2c0/+0x2dc/+0x3a8: Fake-phy-alloc-Kette (unverändert)
- +0x600: nf_code hookfn (Marker-Store + prepare_kernel_cred +
commit_creds + return NF_ACCEPT(1))
- +0x700: Fake-entry {next=0, fn=PM_PAGE+0x600, priv=0, prio=0x100}
- +0x740: cell → PM_PAGE+0x700
- Payload: N = PM_PAGE+0x740 (0xc154b740), P = 0xc1104ad4
→ W1: *(cell+4)=P (landet in unserer Seite), W2: *(init_net+0x58c)=cell
### SELBSTDIAGNOSTIZIERENDE Instrumentierung (kbm_scan_for)
Die kbm-CPU-Mappings werden beibehalten; nach dem Free scannen wir jede gesprayte
Seite nach einem bekannten Wort:
- scan(HOOKS_PTR_ADDR) bei page+0x744 → beweist Reclaim + Unlink + offenbart,
welche phys-Seite die G-Vermutung stützt
- scan(0x600d600d) bei page+0x7f0 → beweist, dass die hookfn AUSGEFÜHRT wurde
(nf_code schreibt diesen Marker als seine 2. Aktion)
### DER RUN, DER ZÄHLT (2026-09-11, späte Session 9)```
[+] W1 CONFIRMED: region 135 page+0x185000 <- 0xc1104ad4 (reclaim + G hit!)
[+] nf trigger done, uid=2000 euid=2000
Die vollständige Waffenkette feuerte bei einem Live-Boot: Das Event reklamierte den Slot, das Fake lief, der Unlink wurde ausgeführt, die G-Guess-Seite (0xc154b000) war tatsächlich unsere (Region 135 Seite 389). W2 = *(0xc1104ad4)=cell ist die benachbarte Instruktion — sie muss ausgeführt worden sein. Dennoch hat uns UDP sendto nicht gerootet → der Fehler liegt INNERHALB des Hook-Pfads: Walk-Semantik, [state+0x1c]-Plumbing, Prioritätsvergleich oder die Entry-Felder. (Das Marker-Experiment zur Unterscheidung hookfn-ran-vs-not wurde hinzugefügt; nur Crash-Boots vor Session-Ende — noch keine sauberen Daten.)
pmap 200) ist der Umgebungs-Sanity-Check (4/4 Treffer wenn
warm; Crash-Miss wenn kalt)nf 200 auf settle-verzögerten Boots laufen lassen, bis die W1-CONFIRMED-Zeile
erscheint, dann die MARKER-Zeile lesen:
a. Marker VORHANDEN, uid!=0 → die Creds des Shellcodes sind fehlgeschlagen (prüfe
prepare_kernel_cred/commit_creds-Adressen; blx-Encodings)
b. Marker FEHLT → der Walk hat uns nie aufgerufen: mit einem ZWEITEN
Marker verifizieren, geschrieben von... nächste Diagnostik: hookfn, der NUR
den Marker schreibt und 1 zurückgibt (keine Creds) — wenn immer noch fehlend:
ls -la verifizieren; Marker-Dateien hinterlassenpoc/stage3.c-Modi: pin/root/drain{,2,3,4,5}/iso — volle Waffe +
Oracle + Isolation-Harness, O_SYNC-Crash-Point-Forensikinit_net: 0xc1104548 ist __stack_chk_guard (das movw/movt-
Histogramm war durch Stack-Canary-Loads verunreinigt — 2025 baute den ganzen nf-Plan darauf auf).
Echtes init_net = 0xc1185040 (bestätigt: ip_send_skb(net,...) aufgerufen
mit diesem Literal; ~994 Refs alle im Net-Stack). IPv4-LOCAL_OUT-Cell =
init_net+0x58c = 0xc11855cc.nf_iterate behandelt [init_net+0x58c] als den
nf_hook_ops-Pointer selbst — es liest fn@+0xc, priv@+0x14, prio@+0x20
direkt aus diesem Wert. Der Fake-Entry aus Session 9 lag bei
PM_PAGE+0x700, wobei die Cell darauf zeigte (unbenutzte next/-Felder
an der Cell → → Crash). Der Fake-Entry MUSS (0xc154b740) liegen. Mit dieser Korrektur wurde
der Hook-Aufruf bewiesen (-Modus = SAFE_FN verarbeitet alle 200 Sendtos sauber).commit_creds(prepare_kernel_cred(0)) / override_creds(&init_cred) gibt
uid 0, landet aber in der kernel-SELinux-SID, der diese Fire-OS-Policy
nicht erlaubt, nach /data oder /sys/fs/selinux/enforce zu schreiben (verifiziert: EACCES).
Echte init-SID (7) ebenfalls verweigert (Fake-struct cred-Test). enforcing_setup
ist __init (freigegeben → Crash). mark_reclaims atomic_sub braucht nents=1, was
den Early-Exit von shrink_cpu_mapping bricht.
Der Gewinn: Der Exploit-Prozess behält die kbm-CPU-Mappings, sodass der Fake-nf- Entry zwischen Paketen in-place umgeschrieben werden kann:
selroot-Modus: Entry = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}.*(enforcing)=0 → SELinux Permissive.kbm_cpu[reg]+off auf {fn=commit_creds, priv=&init_cred} umschreiben.commit_creds(&init_cred) im Task des Senders → uid 0 mit
permissivem SELinux → nutzbares Root, alles in einem Reclaim, keine Kette nötig.[+] 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**; pausiertes st3 ist `Uid: 0 0 0 0`,
`CapEff: 3fffffffff`.
- `/data` ist **nosuid** gemountet, daher kann ein setuid `su` nicht funktionieren. Eine winzige
`rootshell` (UDP senden → `commit_creds` auf sich selbst → `execl sh`) liefert eine
interaktive Root-Shell: `uid=0(root) context=u:r:kernel:s0`.
- Root kann `/dev/block/by-name/*` lesen/schreiben (`dd if=boot ...` OK).
### Stage-3-Codezustand (`poc/stage3.c`)
- Modi: `nf <mb> [probe|uprobe|oc|chain|fc <sid>|notrig|selroot]`
- `selroot` ist die funktionierende Waffe. Wichtige Statics: `init_net=0xc1185040`,
`HOOKS_PTR_ADDR=0xc11855cc`, `ENFORCING_ADDR=0xc1213ea8`,
`ZERO_GADGET=0xc01d503c`, `commit_creds=0xc014993c`,
`init_cred=0xc1114f54`.
- Post-Exploit sind direkte Syscalls (kein `system()`); den kctx am Leben halten
(`pause()`), um einen Teardown-Crash zu vermeiden.
### Verbleibend (Stage 4/5)
- Persistenz über Reboot hinweg (verity / Boot-Image / Recovery), da die Cell-
Hijack + permissive SELinux nur zur Laufzeit gelten und ein erneutes Ausführen des Exploits
den ~1/3-Reclaim-Münzwurf erfordert.
- `su` benötigt ein Nicht-nosuid-Home (`/system`) oder einen Launcher, der erneut auslöst.
## SESSION 11 — PERSISTENCE RECON (Track B + Track A) und die RE-Übergabe
Ziel war persistenter Root. Zwei Tracks wurden abgesteckt:
- **Track B**: Verified Boot deaktivieren (dm-verity / SELinux), damit `/system` gepatcht werden kann.
- **Track A**: den Exploit beim Boot erneut ausführen.
Beide laufen auf denselben Blocker hinaus: **LK dazu bringen, das Gerät als `eng`/`unlocked` zu behandeln.**
### Verified-Boot-Fakten (exakter Build)
- Bootloader gesperrt, AVB `green`, `ro.boot.unlocked_kernel=false`, `ro.boot.secure_cpu=1`,
`rpmb_state=1`. Bootrom gepatcht (kein BROM); Preloader nur über CMD-Kurzschluss.
- `/system` wird von **Android dm-verity aus der von LK erzeugten Kernel-Cmdline** gemountet:
`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` = Verity-Gerät namens `system`; `dm-1` = `/vendor`.
- LK: Amazon **UFBL**, `ro.boot.lk_version=0x0006`, Build `0db73c9-20231025_030009`;
Preloader `pl_version=0x000a`, Build `80c6fcb-20230523_065640`. `/dev/block/by-name/lk` = mmcblk0p5 (1 MB).
- Vollständige GPT (16 Partitionen, kein `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`-Magic);
boot1 (4 MB) = IDME-Speicher.
### LK (UFBL) statische Befunde
`lk.img`-Header: `88 16 88 58 | 00052974 | "LK"`; ARM-Vektortabelle bei 0x200, Rest Thumb-2,
positionsunabhängig/reloziert (Literal Pools nutzen `ldr+add pc`, daher schlägt naives basisrelatives Disasm fehl).
Relevante Strings (Datei-Offsets): `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`.
**Fazit: LK koppelt die Sicherheitswirkungen von `fos_flags`/`dev_flags` an eng/unlocked.**
### IDME-Speicher (eMMC **boot1**) — beschreibbar, persistent, von LK und Android gelesen
- Magic `beefdeed` + `"2.1\0"` + count(0x19=25) bei 0x0; Items ab 0x10.
- Item-Format: `char name[16]; u32 size; u32 type(=1); u32 magic(=0x124); u8 data[size] (pad4)`.
- Item-Offsets (unberührt): `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`. Werte sind ASCII (Flags sind **Hex-Strings**).
- Laufzeit-Lesen: `/proc/idme/<name>` (nur lesbar). Werte des letzten Boots werden gecacht; ein Schreiben nach boot1 wird
beim nächsten Boot wirksam. Der Schreibpfad erfordert das Löschen von `/sys/block/mmcblk0boot1/force_ro` (Root).
- **Bestätigt: LK liest boot1**: Ändern von `serial` änderte `ro.boot.serialno` beim nächsten Boot.
Aber LK **kürzt serial auf 16 Bytes** und ignorierte `fos_flags=0x80`, `dev_flags=0xff`,
alles- Einsen usw. — verity/selinux/`prod` unverändert. Cmdline-Injection über serial schlägt also fehl.
### Android-seitige Konsumenten der IDME-Flags
- `/init.fosflags.sh` (Service `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`. Verifiziert: Setzen der Flags wirkt (`sys.usb=adb`, `noadbauth=1`).
- **adbd** (unstripped ARM ET_EXEC; `.text` VA 0x8160 / Datei 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` (ungated)
- `amzn_is_dev_unlocked` @0x2d5fc = `/proc/cmdline` enthält `androidboot.prod=0` **oder**
`androidboot.unlocked_kernel=true`
- `fos_read_debug_flags` @0x2d724 liest `/proc/idme/<name>` und parst **hex**
- `restart_root_service` @0xcb74 / `restart_unroot_service` @0xcc64
- Strings: `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`) — selbst wenn also das
Auto-Root-Gate erfüllt ist, blockiert die AOSP-Prod-Prüfung den Befehlspfad.
### Warum der Root aus Session 10 nicht persistent ist
- SELinux permissive + Cell-Hijack + Root gelten nur zur Laufzeit.
- `/data/metrics` ist eine **vpartition**: `/system/bin/vpartition.sh` mountet `/data/vp/metrics.img`
(ext4, non-nosuid/noexec) bei jedem Boot nach `/data/metrics`; ein dort geschriebenes `su` überlebt
einen Reboot **nicht**. (Auch der Grund, warum setuid `su` uid 0, aber **null Caps** lieferte.)
### Track A (Re-Exploit zur Bootzeit) — blockiert
- Kein init-`.rc`-Trigger führt kontrollierbaren Code aus (Imports alle verifiziert; `persist.*`-Trigger nur
`start` fester Services; Skripte in `/system`/`/vendor`).
- Root-Services lesen `/data`-Konfigurationen, führen aber niemals daraus aus (`perfmonitord`, `amazonfiled`,
`vpartition.sh`, `kisd`, …).
- Einziger Boot-Executor = eine **App**, aber der pausierte Footprint des Exploits ist **VmRSS 534 MB**
(`kbm`-Spray) → lmkd killt sie; zudem panikt ein verlorener Reclaim (`PANIC_ON_OOPS`) → Bootloop.
- **adbd Auto-Root** existiert, ist aber an die von LK erzeugte Cmdline gekoppelt (`prod=0`/`unlocked_kernel=true`).
### Fazit / nächstes Ziel (gewählt: Track B RE)
Alles hängt daran, LK dazu zu bringen, `eng`/`unlocked` zu melden. In Reichweite:
`androidboot.prod=1|0` und `androidboot.unlocked_kernel=false` werden von LK gesetzt. LK reversen, um zu finden:
1. wo es `fos_flags`/`dev_flags`/`usr_flags` (die `K*`-Items) liest und das exakte Gate;
2. die `prod`/`unlocked`-Bestimmung (IDME-Item? buildvariant? `amzn_verify_unlock`-Ergebnis?);
3. `amzn_verify_unlock` (libtomcrypt RSA verify) für einen Bypass oder einen schwachen unlock_code/version-Pfad;
4. den `seccfg`/`para`/`ENV_v1`(LK_ENV)-Speicher (in keiner gedumpten Partition — vielleicht tee-geschützt);
5. Preloader (`boot0`, `EMMC_BOOT`) für einen Bug.
Wenn irgendetwas davon uns erlaubt, eng/unlocked zu setzen (persistent, via boot1 oder einen Raw-Partition-Schreibvorgang), dann
deaktiviert `FOS_FLAGS_DM_VERITY_OFF` die system(root)-Verity und `/system` kann persistent gepatcht werden.
### Artefakte (aus dieser Session)
`/tmp/opencode/mustang-dumps/` (kann bei Host-Reboot gelöscht werden): `lk.img`, `boot1.img` (unberührt),
`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`.
Helfer: `tools/findinitnet.py`, `findgadget*.py`, `findstores.py`, `adbd_sym.py` (in /tmp);
Repo enthält `run.sh`, `poc/stage3.c` (`selroot`), `poc/su.c`, `rootcmd.sh`.
### Nützliche Befehle```
# 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
Ziel: LK dazu bringen, das Gerät als eng/unlocked zu behandeln, oder einen Preloader-/LK-Bug finden,
damit verity/SELinux dauerhaft deaktiviert werden können. Ergebnis: der relevante
LK-Codepfad wurde end-to-end zurückentwickelt; der Flip ist über die verfügbaren Stores nicht erreichbar.
Kein Gerät wurde gebrickt; das eine boot1-Experiment wurde auf den Ursprungszustand zurückgesetzt.
lk.img beginnt mit einem winzigen ARM-Stub (Datei 0x200). Der Relocator bei 0x224
kopiert von 0x200 an ein literales Ziel und springt zu einem literalen Einstiegspunkt:
Also Laufzeitadresse = 0xFF400000 + Datei-Offset für Offsets >= 0x200.
Alles nach dem Stub ist Thumb-2, positionsunabhängig. Strings werden mit
ldr rT,[pc,#imm] (T1-Offset = imm8*4; ldr.w-Offset = imm12) gefolgt von
add rT, pc gebildet; das Ziel ist (add+4) + *pool. Ein robuster Scanner, der
den ARM-Stub und Literal-Pools übersteht, wurde als tools/lk_xref.py hinzugefügt (behandelt 16- und
32-Bit-Formen, scannt alle 2 Bytes). Alle Offsets unten sind Datei-Offsets;
addiere 0xFF400000 für Laufzeitadressen.
0x20b4 liest das unlock_code-IDME-Element (0x400 Bytes, alles Null auf diesem
Gerät) und führt amzn_verify_unlock aus (0x222c -> 0x20f0). Diese Funktion treibt
libtomcrypt an (Dutzende /features/libtomcrypt/src/pk/asn1/der/...-Pfade und
RSA-Verifikation), und das Image enthält das Zertifikatsmaterial:
Sunnyvale / Amazon Lab126 / "$Common Kernel Signing Engineering CA0" bei
0x317d9+, plus die Diagnosen
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).
Es gibt keine Abkürzung über leeren Code / Länge / Version: verify(zeros) != 0, daher
(bestätigt in ). Ein Umlegen von
oder erfordert entweder einen gültigen
Amazon-signierten (privater Schlüssel nicht verfügbar) oder einen Code-Execution-Bug im
Verifier. Statisch wurde nichts Ausnutzbares (Bounds/Size) in
0x20b4/0x222c/0x20f0 gefunden. =>
Die Security-Flags werden über den Getter bei 0x57c gelesen. Empirischer Test:```
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` ist `FOS_FLAGS_DM_VERITY_OFF`; das dekodierte Gate hätte
Verity deaktiviert, **wenn** der Getter es zurückgegeben hätte. Das tat er nicht. Das Gate ist aktiv, kein toter Code: sein einmaliger Cache-Sentinel ist `-1` im Image
(`*(u32*)0x50c74 == 0xffffffff`), also hat die Funktion tatsächlich den
`check_flag("fos_flags",0x80)`-Pfad ausgeführt und 0 erhalten. Daher liest der Getter (zumindest
zum Zeitpunkt des Verity-Schutzes) **nicht** die boot1-IDME-Einträge.
Der andere Kandidatenspeicher ist die **LK env**, geladen aus einer Partition mit dem buchstäblichen
Namen `"para"` (Loader 0x12fd4, Magic `ENV_v1`, Checksumme @0x3ffc). LKs eigene
Partitionstabelle (0x4fcc0..0x50340) listet preloader/proinfo/nvram/protect1/
protect2/persist/seccfg/secro/**para**/logo/custom/expdb/tee1/tee2/metadata/
system/cache/userdata — aber der tatsächliche GPT des Tablets hat **nur 16 Einträge**, alle
vom Typ `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
Es gibt keine para-, seccfg-, nvram-, protect- oder persist-Partition auf
diesem Produkt (und PMT/pmt.img-Dumps sind alle null). Die LK-Umgebung ist also leer,
die Schlüssel Kfos_flags/Kdev_flags existieren nie, und alle fos_flags/dev_flags-
Prüfungen ergeben 0 — unabhängig davon, was die IDME-Einträge enthalten. Die boot1-
IDME-Einträge werden von Android konsumiert (/init.fosflags.sh, adbd,
/proc/idme/*), aber nicht von LKs Sicherheitsschranken.
unlocked_kernel erfordert einen von Amazon signierten unlock_code (RSA/libtomcrypt,
eingebettete CA). Nicht offline fälschbar; kein Verifier-Bug gefunden. Harte Blockade.DM_VERITY_OFF / selinux=permissive werden aus der LK-Umgebung
(para/ENV_v1) konsumiert, die auf dieser GPT nicht existiert. IDME fos_flags wird
empirisch von LK ignoriert (0x80 persistiert, verity blieb eio). Harte
Blockade, es sei denn die Partitionstabelle wird modifiziert.fos_flags=0x80 würde nur androidboot.veritymode= disabled und ein nicht-dm-0 root= setzen; es würde nicht entsperren, und SELinux
bräuchte weiterhin dev_flags aus derselben fehlenden Umgebung, um permissiv zu werden.para/ENV_v1-Store synthetisieren: einen GPT-Eintrag namens para (primäre
userdata
hinzufügen (userdata endet bei LBA 0x3a3dfde; Disk = 30535680 Sektoren), dann eine Env mit
fos_flags=0x80 und dev_flags=0x40 erstellen (Checksumme bei +0x3ffc = Byte-Summe über
0x3ffc). Dies ist der einzige verbleibende Weg zu verity-off. Risiken: Beschädigung
der primären/Backup-GPT kann zum Brick führen; und es wurde nicht bewiesen, dass die
Verity-Schranke tatsächlich para liest (nur dass es nicht boot1 IDME ist).boot0/EMMC_BOOT)-Bug: in dieser Sitzung nicht reversed. Das Schreiben
von boot0 ist verboten, bis eine unversehrte Kopie und ein Wiederherstellungspfad existieren.tools/lk_xref.py — basisunabhängiger LK-String-Xref-Resolver./tmp/opencode/mustang-dumps/lk.img, boot1.img (unversehrt),
boot0.img, mbr.img (GPT), pmt.img (alles null)./tmp/opencode/s12/boot1_f80.img; Gerät auf unversehrtes boot1 zurückgesetzt
(verifiziert /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
## SITZUNG 13 — der Preloader ist doch erreichbar (`1949:20ff` = MTK-Preloader, HID-Transport)
Während das Tablet ausgeschaltet und per USB angeschlossen ist, enumeriert es als **`1949:20ff`**
(`Lab126`) — *nicht* Android und *nicht* der `0e8d:0003`-Bootrom. Deskriptor:```
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)
Identifikation. 0x20FF ist in mtkclients config/usb_ids.py als "MTK Preloader" aufgeführt (unter MediaTek VID 0x0e8d: 0xe8d:{0x0003 Brom, 0x2000/0x2001/0x20ff/0x3000 Preloader}). Amazon behielt die Preloader-PID bei und änderte die VID auf 0x1949 und präsentiert sie als HID-Endpunktpaar mit einem Dummy-Report-Descriptor. Es handelt sich also um den MediaTek Preloader / USBDL-Modus, eine Stufe unterhalb von LK — hier erreicht durch Ausschalten + Einstecken, nicht durch den CMD-Kurzschluss.
Die Descriptor-Strings "HID"/"HID Interface" sind nicht in lk.img, boot0.img, boot.img oder den anderen Dumps vorhanden, d. h. der Modus wird von einer Komponente erzeugt, die wir nicht gedumpt haben (Bootrom/TEE), oder wird zur Laufzeit zusammengesetzt.
Warum das wichtig ist. Der von aftv2-tools verwendete Amazon-Preloader stellt über genau diesen Byte-Stream integrierte, Download-Agent-lose Befehle bereit:```
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` verwendet `read32`/`write32`, um den MSDC-Controller
(Basis `0x11230000` auf MT8173; für MT8163 verifizieren) anzusprechen und **rohe eMMC-
Blöcke** ohne DA und somit ohne AVB/Verity im Weg zu lesen/schreiben. Wenn der Preloader
von mustang 0xD1/0xD4 akzeptiert, ist das ein direkter Weg zum persistenten Unlock (Patch von `boot` /
`lk`), unabhängig vom RSA-Unlock-Code und der fehlenden LK-Umgebung.
### Hinzugefügte Tools (Root erforderlich; zuerst chmod auf dem USB-Node ausführen)```
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 ist die minimale Handshake-only-Sonde;
tools/mtk_preloader_hid.py ist der vollständige Transport (handshake/read32/
write32; write32 ist abgesichert und sollte nicht verwendet werden, bis die eMMC-Register-
Map bestätigt ist).
read_mmc), dann boot.img/lk vom Preloader aus patchen und neu starten.amzn_verify_unlock oder dem LK-Env-Gate ab.pi_blocked_onfutex.c:1975 übergibt this->rt_waiter,
deklariert in futex_wait_requeue_pi bei futex.c:2880) → Waiter stempelt seinen eigenen freigegebenen Frame
via arm32 select (nr 142) fd_setsDEBUG_RT_MUTEXES aus
→ kompakter 48-Byte rt_mutex_waiter (tree_entry@0, pi_tree_entry@0xc, task@0x18,
lock@0x1c, prio@0x20, deadline@0x28)modprobe_path @ 0xc111488c (String selbst-lokalisiert in
vmlinux; KALLSYMS_ALL aus, daher brauchen Datensymbole diesen Trick) → unknown-binfmt exec → root
script (setenforce 0, OTA deaktivieren, su)refs/: NebuSec/CyberMeowfia (Original), GhostLock-5.10 (Fire OS 8 Port,
vollständiger 32-Bit-ARM-Trigger in src/exp32/), ghostlock-...-4.19-k40 (Qualcomm 4.19 Android Port)selrootselinux_state.enforcingcommit_creds(&init_cred)uid=0jcnr_extresinfo->gpu_alloc_addr geschrieben (eine GPU-VA, die du
vorab allozieren und übergeben musst)| evictable object | pressure | result |
|---|
| none | 900 MB | survived |
| none | 1300 MB | panic (system lowmem bug — unrelated) |
| normal region + DONT_NEED | 700 MB | survived |
| JIT region + DONT_NEED | 700 MB | panic in reclaim path |
kernel/config-* — /proc/config.gz-Dump vom laufenden Gerätksrc/ — Amazon OSS-Quellcode (platform.tar + extrahierter midgard-r26p0-Baum)/tmp/opencode/mustang_ota.bin (sha256 6068515a… stimmt mit fireos-archive überein)
und 2,2 GB Kernel-Source-Tarball aufbewahrt unter ~/Desktop/amazon-mustang/ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/| Renames stocken (Journal/GFP_NOFS) → 128 gesamt → Müll-Deref |
| Pre-Drain 12K Events + Druck + kleines Trailing | Crash beim Deref |
| + Kill-Child-bei-Eviction (10ms Poll) | Crash beim Deref |
| + CPU0-gepinnte Lifecycle (drain3) | Crash beim Deref |
| gestufter Druck (drain4 v1) | Kinder gaben Speicher beim Exit frei → keine Eviction (legaler Pfad validiert) |
| Lauf | Ergebnis |
|---|
| pmap G=c2a412a4 400MB | Crash beim Deref |
| pmap G=c2f4b2a4 480MB | Crash; +0 Post-Kill-Renames (480MB erstickt fs) |
| iso2 (Spray + Regions + Oracle) | REGION HIT — Spray bricht Reclaim nicht |
| mix v1 (gleichzeitige Events+Regions) | Crash; konfundiert (spinnende Event-Threads) |
| pmap G=c2a7d2a4 350MB + Retouch | Crash; +4452 Renames OK |
| mix2 (sequenziell: 2s Events DANN Regions) | Crash; +7126 Renames (28K Event-Allocs), 2715 Regions |
fnfn=0PM_PAGE+NF_CELL_OFFprobekernel_x_end: arch/arm/mm/mmu.c
map_lowmem() mappt Lowram unterhalb des Kernel-Texts MT_MEMORY_RWX, aber alles
oberhalb kernel_x_end MT_MEMORY_RW → PMD_SECT_XN (Zeile 509). Eingebackener
Shellcode bei 0xc154b600 Prefetch-Aborts. Payload muss ein echter Kernel-
Funktionspointer sein, kein Code im Physmap.| Literal (Datei-Offset) | Wert | Bedeutung |
|---|
| 0x270 | 0xFF4002F8 | str r4,[r6] Scratch |
| 0x274 | 0xFF40027C | Ziel (Basis+0x27C) |
| 0x278 | 0xFF54A440 | Kopierende (inkl. BSS) |
| 0x27C | 0xFF400484 | Einstiegspunkt |
| Offset | Funktion |
|---|
0xdf7c | is_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ]; g = global @0x52838. 1 auf diesem Gerät. |
0x20b4 | verify_stored_unlock() = memset(buf,0,0x100); liest IDME/env unlock_code (0x100) über 0x57c; bl 0x222c; gibt (verify==0) zurück. |
0x222c / 0x20f0 | amzn_verify_unlock(code,len) — libtomcrypt RSA/PKCS#1-Verifikation (siehe unten). |
0xda3e | is_unlocked() = is_secure_or_prod() && verify_stored_unlock(). |
0x29a28 | is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock()); gecacht in global @0x50c74. |
0x29974 | SELinux-Cmdline-Builder: dev_flags & 0x20 -> androidboot.selinux=enforce, dev_flags & 0x40 -> ...=permissive (jeweils gated durch byte[+0x162]). |
0x118xx/0x11bxx | Kernel-Cmdline-Builder (unlocked_kernel, prod=1/0, verifiedbootstate, rpmb_state, secure_cpu, Versionen, root=). |
0x27af8 | UART-Gate: fos_flags & 0x4 -> printk.disable_uart=0, sonst =1. |
0x12fd4 | LK-Env-Loader: Partition "para", 0x4000 Bytes, Magic ENV_v1, Checksumme = Summe der Bytes über 0x3ffc verglichen mit Word @0x3ffc. |
0x1efd0 | Partitions-Lookup nach Name (verwendet für "para", "boot", ...). |
0x57c | Getter-Dispatcher über Callback-Slot @0x58218; Slots @0x58200..0x5821c werden aus einer Tabelle bei 0x5a8-0x734 registriert. |
0x2a19c | fastboot oem unlock: bl 0x222c(code,len); schreibt bei Erfolg unlock_code (0x100) über 0x408. |
unlocked_kernel=false/proc/cmdlineandroidboot.unlocked_kernelandroidboot.produnlock_code