Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
amazon-mustang-hack — Исследование эксплойта ядра, достигающее временного root на Amazon Fire 7 (Fire OS 7.3.3.1) через use-after-free в JIT Mali kbase CVE-2022-38181, с цепочкой перезаписи modprobe_path. | Kitploit
Инструменты/GitHubGitHub/artur9010/amazon-mustang-hack
Безопасность AndroidПовышение привилегийАнализ уязвимостейЭксплуатацияОбратная инженерияМобильная безопасностьСтатьи и ИсследованияРазработка Полезной НагрузкиЭксплуатация Бинарных Файлов

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
GitHubartur9010/amazon-mustang-hack

amazon-mustang-hack

Исследование эксплойта ядра, достигающее временного root на Amazon Fire 7 (Fire OS 7.3.3.1) через use-after-free в JIT Mali kbase CVE-2022-38181, с цепочкой перезаписи modprobe_path.

РепозиторийСайт
13 ч 56 мин назадЕщё не проверено

Проект с использованием ИИ. Это исследование, разработка эксплойта и документация были выполнены с помощью ИИ с использованием моделей GLM-5.3 и DeepSeek V4.1 Flash.

amazon-mustang-hack

Исследование root-эксплойта для Amazon Fire 7 9-го поколения (mustang, MT8163, Mali-T720) на финальной прошивке — Fire OS 7.3.3.1, PS7331.4463N, ядро 4.9.117 (сборка 2025-05-03, SPL 2024-08-01).

Цель: LineageOS. Путь через загрузчик на этом устройстве мёртв (пропатченный bootrom — только preloader через замыкание CMD), поэтому единственный оставшийся маршрут — программный эксплойт ядра.

Быстрый старт```

one-shot: build, run the exploit (retries across the probabilistic reclaim),

install a setuid-root su and verify it as an unprivileged user

nix-shell -p android-tools --run './run.sh' # add -p zig too if no zig

root@kitploit:~
При успехе:```
/data/metrics/su id     # run a command as root
/data/metrics/su        # interactive root shell

Победа в reclaim происходит примерно 1 загрузка из 3, а проигрыш приводит к панике/перезагрузке планшета; run.sh просто ждёт перезагрузки и повторяет попытку. SELinux принудительно переводится в Permissive в рамках эксплойта, поэтому root доступен только во время выполнения — перезагрузка восстанавливает стоковое состояние, и вы снова запускаете run.sh.

Предсобранные st3 и su (armv7 static) закоммичены, поэтому для запуска тулчейн не нужен. ./run.sh --build пересобирает их из poc/*.c, если у вас есть zig.

Всё ниже — просто лог работы, выполненной моделью, ниже нет ввода человека.

ОСНОВНАЯ ЦЕЛЬ (начиная с сессии 5): kbase CVE-2022-38181 — этап 2 ДОКАЗАН

GhostLock (ниже) отложен: вариант MTK с BUG_ON rtmutex + отсутствие раскрытия адресов ядра из шелла = архитектурный тупик на этой сборке (сессии 2-4). kbase JIT UAF был передиагностирован (паника destroy-worker «безусловная паника» была разыменованием JIT_FREE, лог потерян из-за смерти adbd посреди паники), и этап 2 теперь доказан оракулом — см. раздел СЕССИЯ 5.

ОТЛОЖЕНО: GhostLock, CVE-2026-43499

rtmutex remove_waiter() futex-PI stack-UAF (раскрытие NebuSec 2026-07, исправление 3bfdc63936dd вошло 2026-04). Уязвимый диапазон 2.6.39–7.1 → наша 4.9.117 (май 2025) затронута.

Проверено на нашей точной сборке:

  • 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)
  • Поверхность триггера = чистые futex-сисколлы (WAIT_REQUEUE_PI/CMP_REQUEUE_PI), без узла устройства, ничего не ограничено SELinux — фатальных препятствий пути kbase здесь не существует
  • Потребитель: sched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) в sched/core.c:4706 — разыменовывает устаревший ✓

TODO (план портирования)

  1. Написать триггер (3-поточный requeue-PI дедлок, ядра 0-3) — порт exp32/main.c
  2. Геометрия штампа: смещение фрейма rt_waiter относительно области fd_set do_sys_select — дизассемблировать наш vmlinux (do_sys_select stack_fds против фрейма futex_wait_requeue_pi), выставить STAMP_NFDS/STAMP_WAITER_OFF как настраиваемые параметры
  3. Кодирование fake-writer для arm32 48-байтового waiter → слоты «записать V в ADDR»
  4. 2 слота → modprobe_path, запуск, root-скрипт
  5. Запасные варианты, если select-штамп не достаёт: штамп через setsockopt(MCAST_JOIN_SOURCE_GROUP)

Статус

  • Bootrom (аппаратный метод amonet) — пропатчен на этом устройстве, тупик
  • mtk-su (CVE-2020-0069) — пропатчен, Failed critical init step 3
  • Обзор поверхности атаки — /dev/mali0 world-RW + SELinux gpu_device, kbase r26p0-01rel0
  • CVE-2022-38181 подтверждён в исходниках точной сборки; триггер этапа 1 работает
  • CVE-2026-43499 (GhostLock) проверен, но заблокирован: вариант MTK с BUG_ON rtmutex + отсутствие раскрытия адресов ядра из шелла (сессии 2-4)
  • CVE-2022-38181 этап 2 ДОКАЗАН (сессия 5): паника destroy-worker была ошибочным диагнозом; UAF-редирект на заспреенную область, проверено оракулом
  • Этап 1: триггер + штамп + потребитель (краш = цепочка жива)
  • Этап 2 (путь kbase): UAF-редирект на заспреенную область — ДОКАЗАН в сессии 5
  • Этап 2b: управление слотами на уровне сырых байтов (чередование штампов xattr) → unlink-запись
  • — перехват хука nf LOCAL_OUT, цепочка из 2 пакетов: обнуление , перезапись фейковой записи на . , SELinux Permissive.

Ключевые находки

Устройство / прошивка

  • Модель KFMUWI, устройство mustang, Fire OS 7.3.3.1 PS7331.4463N/0031575863040
  • Ядро 4.9.117-g08fe75b-dirty, собрано Sat May 3 01:25:15 UTC 2025 (Linaro GCC 6.3-2017.05)
  • Amazon молча перевыпустила 7.3.3.1 в мае 2025 (новый incremental, та же строка версии)
  • Ревизия Bootrom после 2020: короткое замыкание на GND на eMMC CMD даёт только preloader (пропатчено)
  • /dev/kb, /dev/dkb (разделы резервных копий ядра Amazon) root:drmrpc 0660 — заблокированы

Почему применим CVE-2022-38181

  • Драйвер: mali_kbase r26p0-01rel0 (Midgard, Mali-T720), внутри уязвимого диапазона NVD r4p0–r31p0
  • Пересборка Amazon от мая 2025 поставила баг 2018 года дословно — без бэкпорта
  • Точный уязвимый код, проверено по исходникам:
    • 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)

Условия эксплуатации (всё проверено по дампу живой конфигурации + OTA vmlinux)

  • armv7 32-бит, non-LPAE → нет KASLR (ядро по фиксированному VA 0xc0008000 / PA 0x40080000)
  • Нет 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
  • 1 ГБ RAM → прямой reclaim (нужен для вытеснения) легко достижим; давление >~1 ГБ само по себе вызывает панику ядра (несвязанный баг lowmem/OOM) — держите спрей ≤ 900 МБ, используйте ~700 МБ

Особенности UAPI, встреченные при разработке PoC (r26p0, _IOC_TYPE 0x80)

  • Объединение MEM_ALLOC — 32 байта (in содержит 4 × u64, включая extent)
  • флаги должны включать BASE_MEM_PROT_GPU_RD|WR (биты 2|3), а не устаревшие R|W
  • mmap tracking-page обязателен перед любым alloc: mmap(fd, offset=3<<12, PROT_NONE)
  • Шаг JOB_SUBMIT должен равняться sizeof(base_jd_atom_v2) = 48 (base_jd_prio/base_jd_dep_type — это typedef u8)
  • JIT: MEM_JIT_INIT (nr 14, структура v2), alloc/free — это soft jobs через JOB_SUBMIT (BASE_JD_REQ_SOFT_JIT_ALLOC=0x209, ...FREE=0x20a; =указатель пользователя, =количество)

Дифференциал этапа 1 (доказательство срабатывания бага)

Паника возникает во время самого вытеснения (evictable_reclaim_scan_objects → backing_lost → destroy worker) — висячие ссылки (jit_alloc[], список evict) обходятся ещё до того, как мы отправим JIT_FREE. Этап 2 должен выиграть гонку: перераспределить освобождённый kbase_va_region нашим собственным спреем MEM_ALLOC, пока давление всё ещё активно.

Артефакты

  • poc/stage2.c — эксплойт этапа 2 (режимы: step/uaf/spstep/spfree/spray/keys) — spray 700 = полный прогон оракула; выживает и приостанавливается (kill для очистки)
  • poc/mustang_jit_uaf.c — PoC этапа 1 (режимы: jit N / control N / pressure N)
  • poc/build.sh — кросс-сборка zig (static musl armv7)
  • kernel/vmlinux — символы, восстановленные из точной OTA-сборки (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

root@kitploit:~
## Ссылки

- Рекомендация 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
- Портал OSS Amazon: amazon.com gp/help/customer/display.html nodeId=200203720
- Тема разблокировки на XDA (мертва для этой ревизии железа): xdaforums.com/t/fire-7-2019-mustang-unbrick-downgrade-unlock-root.3944365/


## Дополнение к сессии 3 (глубокое исследование syscall-stamp)

Измеренные глубины копирования из источника (абсолютные относительно syscall-entry 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 (ЛУЧШИЙ — 0x1c не дотягивает до waiter+0x00)**
- poll entries @ -0x3e0 (полностью ниже; не та сторона)

Исключено в этой сессии:
- цепочка io_submit слишком мелкая (~-0x130) | semtimedop: CONFIG_SYSVIPC=n (заглушка)
- configfs смонтирован, но зарегистрировано НОЛЬ подсистем (нет целей для mkdir)
- /sys/kernel/debug, /config: SELinux запрещает доступ для shell
- /proc/sys/kernel: getdents работает (перечислено 29 записей), открывается только pid_max;
  чтение 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) =
  self-ptr pi_tree = ИСПОЛНЯЕМЫЙ waiter+0xc — но ни один путь не ставит waiter+0 как
  timer/work (повреждение link / нет источников косвенной постановки в очередь)
- Tree-root-nonzero (слот обработчика sysctl) = детерминированная погоня за указателями
  через .text как rb-tree; завершается на нулевом слове — симулируемо офлайн, но попадание
  в полезный записываемый слот неправдоподобно

Оставшиеся зацепки для сессии 4:
1. Глубокие пути ioctl: копирование dev_ioctl ifreq (40B пользовательских данных) — измерить
   глубину цепочки SyS_ioctl→sock_ioctl→dev_ioctl относительно -0x1d8
2. Любое другое копирование на 0x1c глубже, чем sendmmsg (пока ничего не найдено)
3. Если охота за поверхностью штамповки провалится: пересмотреть walk-chained конструкции или
   искать классы записываемых слотов с нулевым вызовом, ещё не перечисленные


## СЕССИЯ 4 — ДВА ПРОРЫВА

### 1. rtmutex_common.h от MTK — вот и вся загадка
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) должен указывать на поддельный waiter W (записываемый) с W->lock (+0x1c) == lock.

Поток обхода полностью отображён (rt_mutex_adjust_prio_chain @ 0xc0189a58):

  • 9b44-9b54: retry head; pi_blocked_on==NULL → чистый выход ret 0
  • 9abc-9adc: orig_waiter==NULL → пропуск проверок pi_waiters (adjust_pi всегда передаёт NULL)
  • 9b10-9b28: проверка prio (prio==task->prio + MIN → выход 9b58)
  • 9b2c-9b38: trylock(lock+0) — тикет; неудача → цикл повтора со счётчиком-предохранителем (9a90-9aa8, лимит @ *(0xc11189c8))
  • 9ba4-9bc0: проверки дедлока
  • 9bcc-9bd8: САМ BUG_ON (leftmost→W→W->lock==lock или смерть)
  • 9bdc-9c04: dequeue (остаточное дерево RB_CLEAR_NODE'd = ПУСТО → безопасный пропуск), запись prio/deadline
  • 9c04 bl: rt_mutex_enqueue → *link = waiter+0 по lock+4 ← ЭТА ЗАПИСЬ
  • 9c3c+: owner==NULL → путь чистого выхода

2. Утечка адреса ядра-стека (убивает требование отсутствия адресов)

/proc/self/task//stat поле 28 (kstkesp) возвращает РЕАЛЬНЫЙ SP ядра для потоков, заблокированных в syscall, из SHELL-контекста (проверено: наблюдались ненулевые значения).

  • waiter блокируется в read(blocking_pipe) → stat → kstkesp
  • база стека = kstkesp & ~0x1fff (arm32 THREAD_SIZE=8192)
  • абс. адрес rt_waiter = база + фиксированная дельта (вычислимо: sp0 = base+0x2000-0x48 pt_regs; waiter = sp0-0x1d8)
  • ВСЕ самореферентные значения штампа становятся вычислимыми!

Полный самосогласованный штамп (после утечки):

  • L = waiter+0x24 (поддельный lock В ОКНЕ — все 4 слова контролируемы)
  • iov[0].base (waiter+0x1c lock) = L
  • iov[0].len (waiter+0x20 prio) = 1 (≠139)
  • W = waiter+0x1c; штамп *(W+0x1c) = *(waiter+0x38) = L (BUG_ON проходит)
  • lock+0 (waiter+0x24) = 0; lock+4 (waiter+0x28) = 0 (запись попадает сюда); lock+8 (waiter+0x2c) = W; lock+0xc (waiter+0x30) = 0 (owner NULL)

Статус оракула

  • краш во время обхода = обход выполнился (проба дедлока: детерминированный краш, чистый код)
  • чистый обход + нет записи = trylock-fail retry-bailout (якорь kptr: слово в рантайме ненулевое)
  • Теперь всё детерминировано после исправления загрязнения.

TODO следующей сессии

  1. Реализовать утечку: waiter блокируется на pipe, main читает stat, вычисляет базу
  2. Заштампить самосогласованное окно, запустить обход → завершение без краша = запись доказана
  3. Превратить в оружие: запись всегда попадает по lock+4 (rb_link_node) — lock должен находиться в окне (только полностью контролируемая память), поэтому исследование выбора цели: либо найти трюк с called-slot-in-window, либо двухэтапная конструкция.

ФИНАЛЬНОЕ СОСТОЯНИЕ СЕССИИ 4 — СТЕНА (точно охарактеризована)

Полная картина

Обход срабатывает детерминированно (проба дедлока: краш каждый раз, чистый код). Запись не может попасть из-за трёхстороннего совпадения усиления защиты ядра:

  1. Вариант MTK rtmutex BUG_ON: lock+8 (leftmost) ДОЛЖЕН указывать на W с *(W+0x1c)==lock. Якоря из нулей/мусора умирают. Статического самореферентного паттерна не существует (просканировано 6571 кандидатов, 0 попаданий). Указатели рантайма неизвестны.
  2. Нет раскрытия адреса ядра из shell:
    • kstkesp на arm32 = USER SP (task_pt_regs->ARM_sp) — не стек ядра. МЁРТВО.
    • dmesg/pstore/pagetypeinfo/kallsyms/stack — всё запрещено.
    • kptr_restrict=1 в рантайме (слова якоря fops также ненулевые в рантайме — прогон якоря kptr завершился через trylock-fail retry-bailout, а не успех trylock)
  3. Таблицы fops в rodata: trylock strex прерывается (краши свипа сессии 3).

Окно штампа (waiter+0x1c..0x5b) — единственная контролируемая память с известным содержимым, но её АДРЕС — это неизвестное, которое нам нужно. Самореферентные конструкции все требуют заштамповать адрес ядра как константу — замкнутый круг без утечки.

Сравнение с gitchw (почему их запись на ARM32 сработала, а наша пока нет)

Их ядро 5.4 имеет UPSTREAM rtmutex_top_waiter (NULL-безопасный: if (!leftmost) return NULL) — якоря пустого дерева выживают, их запись попала в null_fops (записываемый на их ядре). Даже ОНИ застряли на диспетчеризации ("ioctl reboot"). Дерево MTK 4.9.117 у Mustang имеет вариант с BUG_ON — планшеты Fire OS 8 (GhostLock-5.10) преуспели, потому что их ядра 5.10 в upstream-стиле.

Проверенные факты сессии 4

  • Цикл повтора обхода имеет предохранитель-счётчик (лимит @ *(0xc11189c8)); trylock-fail на ненулевых в рантайме словах якоря → чистый выход retry-bailout (прогоны якоря kptr)
  • Путь без requeue (9ce4, ПОЛНЫЙ обход) также разыменовывает leftmost на 9d64 — нет спасения
  • Самоуказатели RB_CLEAR_NODE существуют как остатки в окне (waiter+0 и +0xc содержат свои собственные адреса), но ни одно сравнение-проверка не использует их так, чтобы избежать штампа известных адресов
  • Кандидаты реального мьютекса (цепочка mutex имеет живой waiter = BUG_ON прошёл бы) — но &chain_mutex — это адрес кучи, недостижимый без утечки

ВАРИАНТЫ СЛЕДУЮЩЕЙ СЕССИИ (по рангу)

  1. Охота на указатели ядра в logcat: Amazon HALs/демоны болтливы; любой залогированный указатель ядра (даже устаревший) разблокирует конструкцию. Дёшево проверить.
  2. Поведение /proc/net %pK на ЭТОЙ сборке: некоторые деревья 4.9 печатают нехешированные указатели в /proc/net/tcp,udp,unix для непривилегированных читателей. Проверить вживую.
  3. Пути выхода потока на висящем pi_blocked_on (одноразовые, другие разыменования).
  4. Вернуться к отложенному багу kbase JIT с накопленным знанием 4.9.

ДОПОЛНЕНИЕ К СЕССИИ 4 — ОХОТА ЗА УТЕЧКОЙ: ИСЧЕРПАНА (окончательно)

Проверено и мертво из домена shell:

  • /proc/net/{tcp,unix,packet,netlink,ptype}: %pK-хешировано в 00000000 (kptr_restrict=1)
  • /proc/timer_list: ЧИТАЕМ, но указатели %pK-обнулены (символы видны, адресов нет)
  • logcat: нет указателей ядра в болтовне Amazon/wpa
  • kstkesp (stat f28): USER SP на arm32 (task_pt_regs->ARM_sp)
  • Узлы MTK (/proc/ged, mtk_cmdq_debug, mtktz, ptp, chip, aed, driver/*): все запрещены SELinux
  • /proc/{iomem,vmstat,kmsg,keys,crypto,slabs...}: запрещено
  • /sys/kernel/notes: запрещено
  • CONFIG_VECTORS_BASE=0xffff0000 (высокие векторы — NULL+0x1c даёт сбой)
  • CONFIG_KUSER_HELPERS=y (kuser по 0xffff0000, не страница 0)

ВЫВОД: GhostLock на mustang требует раскрытия адреса ядра, которое это ядро не предоставляет домену shell. Самореферентный поддельный lock не может быть построен без него.

ТОЧКА ПРИНЯТИЯ РЕШЕНИЯ

(a) Детерминированная от загрузки зубрёжка: перезагрузка → калибровка адреса стека через краш- оракул (~20-30 перезагрузок), проверка воспроизводимости. Слабый шанс — выделение стека потока на поздней загрузке вряд ли стабильно. (b) ПОВОРОТ обратно к kbase CVE-2022-38181 с накопленными активами: vmlinux точной сборки + полный исходник + тулчейн + дисциплина трассировки O_SYNC + глубокое знание 4.9. Исходный блокер (паника destroy-worker во время вытеснения JIT) — это проблема тайминга спрея, теперь лучше понятая. (c) Остановиться на честных ~45%: триггер доказан, обход отображён до инструкции, запись заблокирована MTK BUG_ON + отсутствием утечки.

Рекомендуется: (b) — баг kbase подтверждён как присутствующий в этом точном исходнике, имел работающий триггер, и его блокер механический, а не архитектурный.

СЕССИЯ 5 — ЭТАП 2 ДОКАЗАН (вариант b выполнен)

Передиагностика: "безусловной паники destroy" никогда не существовало

Режим step (alloc id=1 → DONT_NEED → давление 700MB → MEM_QUERY, БЕЗ free) выживает: query=-1 (регион освобождён destroy-воркером, rbtree-чисто). Путь воркера байт-в-байт идентичен легальному потоку JIT_FREE-под-давлением. Краш сессии 1 всегда был разыменованием висящего указателя JIT_FREE; его строка лога была потеряна, потому что паника убивает adbd в середине сброса. Проверено ещё дважды в режиме uaf (голый free → паника, тот же обрыв лога). Поток GHSL-2022-054 полностью живой на этой сборке.

Полная инвентаризация примитивов (дизассемблирование точного vmlinux)

kbase_jit_free(kctx, reg) @ 0xc058495c с полностью контролируемым поддельным reg:

  • reg->cpu_alloc NULL → размер backed 0 → блок trim пропущен (0xc0584978)
  • декремент bin: kctx+0x147dd (байт) + kctx+0x147de+bin_id (байт)
  • mark_reclaim(reg->gpu_alloc) @ 0xc059b158: цепочка K=*(gpu_alloc+0x38) → *(K+0x1429c)==0 пропускает mm-атомики → atomic_sub nents@K+0x141c8, D=*(K+4) → atomic_sub nents@D+0x538. При nents=0 все записи — no-op stores (strex того же значения).
  • reg->flags |= 0x100000 (запись в поддельное, безвредно)
  • shrink_cpu_mapping рано выходит, когда new==old (nents=0 → return)
  • list_add(gpu_alloc->evict_node, &kctx->evict_list): голова evict_list @ kctx+0x1427c; записи в gpu_alloc+0x18/0x1c (должны быть записываемыми)
  • Путь WARN (0xc0584bd4) нефатален (нет panic_on_warn) и ПРОДОЛЖАЕТСЯ
  • UNLINK @ 0xc0584b08/b0c: r3=*(reg+0x3c) prev, r2=*(reg+0x38) next → *(next+4)=prev; *(prev+0)=next — две произвольные write-what-where, затем relink reg+0x38 в jit_pool_head @ kctx+0x148e8

Статическая цепочка поддельного gpu_alloc (офлайн-сканирование vmlinux, /tmp/opencode/scan_s.py)

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 для shell. Мёртво.
  • Буфер значения setxattr: kvmalloc(96)+copy_from_user происходит ДО проверки SELinux → танец аллокаций защищён от SELinux, даже когда вызов падает; транзиентный (освобождается в конце syscall), байты сохраняются на +4..95 (указатель freelist затирает +0..3 = rblink, не используется kbase_jit_free)
  • Сам kbase_va_region: kzalloc(72) → kmalloc-96! MEM_ALLOC(va=0x40, commit=0x10) помещает ТОЛЬКО регион в kmalloc-96 (phy alloc → 384) → детерминированный тип реклейма. Жертва реального региона заставляет kbase_jit_free завершиться через полностью легальное состояние (пустой jit_node → само-unlink).
  • Последовательный спрей после давления: ВСЕГДА промахивается — воркер освобождает слот в середине давления в частичные слабы (SLUB: free в неактивный slab ≠ cpu freelist); под давлением наши аллокации падают → нулевой чистый объём → нет ротации
  • Закреплённые спрейеры + caps: всё ещё промах (512-cap исчерпан до вытеснения; воркер может работать на любом cpu)
  • ПОБЕДИТЕЛЬ: спрей commit_pages=0 — нет физических страниц → MEM_ALLOC'и успешны через весь шторм давления → ~6000 чистых аллокаций → ротация частичного списка гарантирована. 8 потоков (2/cpu, cpus 0-3 жёстко заданы — /proc/cpuinfo отфильтрован для shell до 1 ядра, использовать Cpus_allowed_list) + дочерний процесс давления закреплён на cpu0 + батч удержания из 16 аллокаций после join.
  • Ловушка query(jit_va): после реклейма спрей-регионы переиспользуют освобождённый VA в кастомной зоне → query=0 неоднозначен (живой-оригинал vs спрей-покрывающий-VA)

ПОПАДАНИЕ ОРАКУЛА — машинно-проверенное перенаправление

Прогон spray 700 2026-09-11: 5895 регионов распылено во время давления, JIT_FREE на висящем id=1 завершился на реклеймленном регионе, затем JIT_ALLOC(0x40, bin 0) прошёл jit_pool_head и вернул VA распылённого региона #4251 (0x142701000) — именно тот регион, который потребил висящий указатель. Убийство процесса после: teardown kctx чистый, без краша. Этап 2 завершён: детерминированное перенаправление UAF с контролируемым типом объекта + содержимым.

План этапа 3 (unlink сырых байтов)

Реклейм типа региона даёт выживание легального танца, но jit_node ИНИЦИАЛИЗИРОВАН сам на себя → нет примитива unlink. Нужны сырые байты на +0x38/+0x3c:

  1. Штамповка xattr: чередовать всплески аллокаций регионов (чистый объём → ротация slab) со штормами xattr (заштамповать каждый головной слот, байты сохраняются после free) → тихое окно → разыменование
  2. или закреплённые sendmsg cmsgs (optmem_max=10240 → ~106 × 96B удерживается)
  3. затем: W1 *(N+4)=P с P=страница шеллкода юзерленда (нет PAN!) — кандидаты: const fops в .rodata (DEBUG_RODATA) → цель не-const fn ptr в .data, или голова списка binfmt formats, или sysctl proc_handler (проверить записываемость таблицы). запасной вариант: modprobe_path через цепочку байтовых записей (значения должны быть записываемыми адресами — использовать цели в форме указателей)
  4. без KASLR + точный vmlinux: prepare_kernel_cred 0xc0149e3c, commit_creds 0xc014993c

СЕССИЯ 5B — ЭТАП 3: оружие построено, гонка реклейма ещё не выиграна

Сделано

  • Оружие этапа 3 завершено и подготовлено (poc/stage3.c):
    • Цель: kern_table[pid_max].proc_handler @ 0xc1113f40 (записываемый .data, проверено через сканирование строк-указателей + handler == proc_dointvec_minmax)
    • N = точка входа шеллкода 0x11111112 (mmap 0x11111000; W1 затирает entry+4, пропускается b +8; W2 пишет N по P = поле handler)
    • Шеллкод ring0 arm32 закодирован вручную: prepare_kernel_cred(0) + commit_creds + ret 0; триггер = read /proc/sys/kernel/pid_max (читается из shell); выполняется в собственном контексте задачи → creds применяются к нам
    • безвредный режим оракула пишет uts nodename (0xc110d561, невыровненный ок)
  • Выбор примитива спрея:
    • Закрепления NETLINK_USERSOCK sendmsg (копия msg_control в kmalloc-96, удерживается пока заблокирован, никогда не парсится): запрещено SELinux (создание сокета EACCES)
    • unix/UDP sendmsg: парсинг cmsg отравляет байты полезной нагрузки ✗
    • события inotify: inotify_handle_event делает kmalloc name_len+0x1d, байты имени (полностью контролируемые, ограничение без NUL/слэша) на event+0x1c; name_len=60 → kmalloc-96; в очереди → удерживается; 4 экземпляра → 4 события на rename; SELinux-OK из shell. Поддельный переработан без NUL: cpu_alloc указывает на S (nents@S+8 = 0 → та же семантика, что NULL)
  • Механическая цепочка провалидирована сквозь (drain4, прогон без вытеснения): 20K слитых событий + 13.5K многоядерных хвостовых rename + JIT_FREE + оракул + пауза, всё чисто. Лог O_SYNC (/data/local/tmp/s3.log) выживает паники — точная форензика точки краша.

Попытки реклейма освобождённого слота (все промахнулись пока)

вариант

Рабочая гипотеза: гонка штормового мусора — между освобождением слота destroy-воркером (в середине шторма) и kill-child/тишиной остаточная активность реклейма занимает единственный свободный слот slab'а жертвы неполезными байтами. Спрей регионов (этап 2) побеждает, потому что аллоцирует непрерывно ВО ВРЕМЯ шторма; rename не могут.

Пойманные подводные камни

  • баг переключателя спрея: источник rename должен быть именем полезной нагрузки (был временным именем → ENOENT после 2 волн → всего 512 событий)
  • hostname устройства — "localhost"/варьируется — оракул сравнивает до/после
  • приостановленный st3 + pkill → КЛИН устройства (teardown с 33K событий?!) — убивать приостановленные процессы только через перезагрузку; второй жёсткий клин сессии
  • /proc/cpuinfo показывает 1 cpu для shell; использовать Cpus_allowed_list

Следующие шаги (по рангу)

  1. drain4 @ 500MB (порог вытеснения подтверждён там), опрос 1мс, мгновенный kill, хвост на 4-cpu × 3200 — сузить окно шторма
  2. churn штамповки xattr (alloc-copy setxattr происходит ДО проверки SELinux — защищено от SELinux) параллельно со штормом + ротация регионов, завершение-штампом
  3. принять реклейм региона (доказан) + найти примитив второго этапа на состоянии жертвы-региона (анализ двойного jit_free пока отрицательный)

СЕССИЯ 5C — блокер, точно охарактеризован

Эмпирические результаты этой сессии

  • drain4@500 (опрос 1мс, мгновенный kill, +4.5K rename): всё ещё краш на разыменовании
  • перекрытие хвоста с kill (drain5): краши РАНЬШЕ (rename во время шторма восстановления после kill попадают в системный сбой) — перекрытие отброшено
  • ИЗОЛИРУЮЩИЙ ЭКСПЕРИМЕНТ (режим iso): идентичный тайминг, хвост с commit-0 РЕГИОНАМИ + оракул переиспользования пула этапа 2 → ПОПАДАНИЕ ОРАКУЛА → тайминг/достижимость В ПОРЯДКЕ; проблема в событиях
  • события с циклом масок (ротация MOVED_TO/CREATE/DELETE для обхода inotify_merge): всё ещё краш на разыменовании
  • CONFIG_MEMCG=n → события и регионы делят ОДИН kmalloc-96 (теория memcg мертва); inotify_merge также сравнивает имена (теория слияния мертва — наши переключающиеся имена никогда не сливались; события были в очереди и удерживались всё время)
  • /proc/slabinfo отсутствует; /proc/self/pagemap читается, но PFN-обнулён (маскирование после 4.0, нет CAP_SYS_ADMIN)

НАСТОЯЩИЙ БЛОКЕР (две части, обе доказаны)

  1. Завершение soft-job выполняется в контексте WORKER планировщика заданий kbase (jd_run_atom ← диспетчеризация js, mali_kbase_jd.c:81-112/677), а не инлайн в ioctl submit → current->mm принадлежит потоку ядра → элегантный дизайн "направить указатели поддельного в наш собственный userspace mmap" (нет PAN!) СБОИТ недетерминированно. 5/5 крашей с иначе идеальным поддельным.
  2. Следовательно gpu_alloc должен указывать на ПАМЯТЬ ЯДРА с выживаемой цепочкой рантайма: K=*(S+0x38) читается, *(K+0x1429c)==0 в рантайме (пропускает mm-атомики), K+0x141c8 записывается, D=*(K+4) → D+0x538 записывается, nents *(S+8) предпочтительно 0. 9 офлайн S-кандидатов были провалидированы против байтов ФАЙЛА — дрейф рантайма (инициализация xfrm/tracepoint) делает их непроверенными. Неверная цепочка = краш = перезагрузка (~3 мин цикл).

Планы сессии 6 (оба полностью специфицированы)

A. Поддельный physmap-спрей (ret2dir, классический arm32 без PAN): распылить ~450MB пользовательских страниц, каждая содержит поддельный паттерн, запечённый под ОДИН угаданный адрес G (G&0xfff = 0x141 для байтов имени без NUL; S=G; K=G-0x141b4 так что K+4 попадает в страницу; K+0x141c8/+0x1429c → G+0x10/+0xd4 в странице; D=G+0x300; случайные sub-0 stores попадают в случайную отображённую RAM - безвредно при nents=0). Спрей служит также давлением вытеснения (грязный anon = невытесняемый → нужно только ~100-200MB дополнительно). Шансы ≈ 45% (попадание в страницу) × ~50% (чужое слово K+0x1429c равно нулю... если K+0x1429c удерживается в странице по раскладке выше, шансы = только попадание в страницу). Промах = краш = перезагрузка, повтор. B. Перебор 9 статических S-кандидатов (xfrm 0xc118b7ec первым, рядом с tracepoint 0xc111cba4 вторым...): 1 перезагрузка на кандидата, сначала безвредная полезная нагрузка оракула, оружие при попадании. C. точный G на основе pagemap (мертво: PFN замаскированы) — не возвращаться.

СЕССИЯ 5D — поддельный physmap построен; G-свип 0/3; конфаунды устранены

Установлено в этой сессии (всё проверено бинарно/на устройстве)

  • Идентичность кэша ПОДТВЕРЖДЕНА та же: регион = kmem_cache_alloc_trace( kmalloc_caches[7], GFP|0x8000, 0x48) [дизасм kbase_alloc_free_region]; событие = __kmalloc(89, GFP) → kmalloc-96. События и регионы МОГУТ делить кэш жертвы. (MEMCG выкл; один набор кэша.)
  • очередь inotify: ≥5000 событий удерживается, нет переполнения, нет коллапса слияния (прогоны qmeas 1000 и 5000) — спрей событий сохраняется
  • iso2 (physmap-спрей + хвост регионов + оракул): ПОПАДАНИЕ — physmap- спрей НЕ ломает реклейм регионов; машинерия надёжна
  • события vs регионы реклейм: регионы 3/3 (iso, iso2, этап 2), события 0/10 НО 3 прогона pmap объясняются G-промахами при шансах 0.3-0.45 каждый (P(3 промаха|события работают) ≈ 0.2-0.3 — не убедительно)
  • режим mix зашумлён: спрей 480MB душит rename после kill (+0); 350MB ОК (+4452); крутящиеся потоки неудачных rename также нарушают реклейм регионов (mix крашнулся, iso2 чисто)
  • query_commit возвращает -1 также на EINVAL — инструментировано; наблюдаемый EINVAL = промах поиска в rbtree = действительно освобождён ✓ (не ложное срабатывание)

Текущий дизайн pmap (в stage3.c, режим pmap/mix/iso2)

  • G запечён в каждую распылённую страницу (смещение 0x2a4): nents@+0x2ac=0, evict self@+0x2bc/0x2c0, K=G+0x100@+0x2dc, D=G+0x200@+0x3a8; K+0x141c8/-0x1429c попадают ~20 страниц выше (sub-0 store безвреден / чтение должно быть 0-или-валидным). PM_SPRAY_MB 350, G-свип попробованы: c2a412a4, c2f4b2a4, c2a7d2a4 — все краш на разыменовании
  • проверка диапазона physmap: RAM 1GB → physmap ~0xc0000000-0xc3fffffff; carveouts MTK (GPU/M4U/secure) могут занимать куски — G-мины### Полный исходный код ядра теперь извлечён /tmp/opencode/ksrc2/kernel/mediatek/mt8163/4.9/ (arch/arm вкл. mustang.dtsi, mm/, fs/eventpoll.c, fs/notify) из ksrc/platform.tar. Находки:
  • 0x8000 GFP бит = ___GFP_ZERO (просто kzalloc; без разделения кэша)
  • kmalloc-96 — это настоящий 96-байтовый кэш (без HWCACHE_ALIGN на кэшах kmalloc)
  • mustang.dtsi: узел памяти 0x40000000/512MB — расширен preloader'ом (устройство показывает MemTotal 977MB); CONFIG_VMSPLIT_3G, HIGHMEM=y
  • zram0 АКТИВЕН (SwapCached > 0) → "грязный anon = unevictable" было НЕВЕРНО: physmap-спрей выгружается под давлением → G-алиас устаревает → добавлен physmap_retouch() после kill (возвращает все страницы спрея обратно до trailing/deref)

Запуски этой сессии (все с O_SYNC, ~6 перезагрузок)

Вердикт по событиям (байесовский, честный)

Регионы reclaim: 3/3. События: 0/~12 попыток, включая 28K эксклюзивных аллокаций с форой и корректным по построению фейком. Если события reclaim с p_hit(G)≈0.35, пять промахов pmap ≈ 11.6% — возможно, но теперь маловероятно (~10-15%). Либо события структурно не могут занять этот слот (причина неизвестна — тот же кэш, тот же контекст, тот же тайминг), либо наши догадки G систематически промахиваются (смещение на границе highmem, размещение аллокатора).

Дерево решений сессии-7

  1. Сначала устаканить G (дёшево, без эксплойта): временно инструментировать поток ISO2 — trailing региона + оракул — но сделать PAYLOAD-событие physmap-фейком и проверить, даёт ли ЛЮБОЙ G в прочёсываемом диапазоне изменение nodename без гонки регионов (чистый pmap, прочёсывание G по ~0xc1500000-0xc2a00000 центр lowmem, 1 G на перезагрузку, 4-5 перезагрузок)
  2. Если прочёсывание G исчерпано → события объявлены мёртвыми → искать альтернативные аллокаторы kmalloc-96 с сырыми байтами, достижимые из shell (аудит: seq_file, tty ldisc, fdtable, sk_filter (заблокирован: code-field на +0x38), netlink nlmsg (skb ✗), keys (запрещены)) — или пересмотреть двухстадийные примитивы регионов (анализ пока отрицательный)
  3. Рассмотреть разблокировку UART/ramoops через root позже; не гнаться за этим

СЕССИЯ 7 — бомбы в cmdline; загадка событий теперь точно ограничена

mustang_defconfig CONFIG_CMDLINE (истина для размещения):

vmalloc=496M slub_max_order=0 slub_debug=O loglevel=8 initcall_debug

  1. vmalloc=496M → physmap = 0xc0008000..~0xc2080000 ТОЛЬКО (нижние 520MB RAM) — ВСЕ прежние догадки G (0xc2a4xxxx+) были в VMALLOC-ПРОСТРАНСТВЕ. Каждый вывод о "промахе G" из сессий 5D/6 недействителен; интерпретация краша остаётся, но прочёсывание было нацелено не на ту карту.
  2. slub_max_order=0: все slab-страницы order-0
  3. KMALLOC_MIN_SIZE = ARCH_KMALLOC_MINALIGN = 64 (L1_CACHE_SHIFT 6) → специальные случаи kmalloc_index() для 96/192 ОТКЛЮЧЕНЫ → регион kzalloc(0x48=72) → caches[7]; событие __kmalloc(89) → caches[7] (дизасм + include/linux/slab.h:287 проверено) — ОБА в объединённом 128-байтовом кэше "kmalloc-128/96". Идентичность кэша: ПОДТВЕРЖДЕНА повторно как равная.
  4. zram активен → заменили anon physmap-спрей на kbm_spray: 160 × 2MB kbase MEM_ALLOC регионов (GFP_KERNEL → ZONE_NORMAL → только lowmem, закреплены → иммунны к zram), паттерн записан через CPU mmap — правильный диапазон, ~65% покрытия lowmem

Запуски (с O_SYNC)

  • kbm + G=0xc16412a4: краш на deref (+4831 переименований)
  • kbm + G=0xc1c4b2a4 @200MB: НЕТ вытеснения (query=16 — калибровка давления варьируется с закреплённым спреем; легально свободно, выжило)
  • kbm + G=0xc1c4b2a4 @400MB: вытеснение ✓, +4053 переименований, краш на deref
  • Запись G в валидном диапазоне: 0/2. Если события работают с ~65% покрытием: P(2 промаха) ≈ 12%. Вопрос всё ещё открыт, но уже, чем когда-либо.

Вопрос, финальная форма

Регионы занимают слот жертвы 3/3; события 0/13. Тот же кэш (доказано на уровне исходников + дизасма), тот же контекст процесса, те же закреплённые cpus, тот же тайминг после kill, тысячи аллокаций с эксклюзивной форой. Механизм неизвестен. Оставшиеся подозреваемые: корреляция скорости/частоты аллокаций с ротацией частичного списка (ioctl регионов ~1мс друг от друга против переименований событий ~100µs друг от друга — противоположные направления?), или детали упорядочивания SLUB freelist при slub_max_order=0, которые благоприятствуют... неясно.

TODO сессии-8

  1. Эксперимент инструментального уровня: ДВА варианта payload событий с РАЗНЫМИ значениями N/P, чередующимися (два набора имён) — если nodename когда-либо изменится, ПОСЛЕДНИЙ победитель идентифицирован; прочесать G в [0xc1200000..0xc2000000] с kbm-спреем, бюджет 3-4 перезагрузки
  2. Если всё ещё 0/N: отказаться от событий. Альтернативы по рангу: a. массивы pipe_buf через F_SETPIPE_SZ(4096 → 1 buf? нет — 16 bufs = kcalloc(16, 28)=448→512 ✗) — мертво b. аудит fs/notify + fs на другие несущие имя/данные объекты kmalloc-128 (события fanotify? mq выкл; fanotify требует групп...) c. буферы seq_file (kmalloc(PAGE_SIZE) ✗) d. sock-фильтры (коллизия code-field на +0x38 ✗) e. принять reclaim типа региона + сцепить ВТОРОЙ баг/технику
  3. Пересмотреть ПОЧЕМУ регионы побеждают — возможно, инструментировать через несколько jit id: N висячих слотов, гонка регион-против-события на слот, оракул определяет, какой спрей занял какой слот → статистический отпечаток механизма

СЕССИЯ 8 — ДОСТИГНУТА ПРОИЗВОЛЬНАЯ ЗАПИСЬ НА УСТРОЙСТВЕ; остаётся загадка диспетчеризации

ВЕХА```

[+] uname.nodename="X?lhost" (was "localhost") <<< ORACLE HIT

root@kitploit:~
**Полная цепочка сырых байтов работает на живом устройстве**: событие освобождает
слот освобождённой области → kbase_jit_free разыменовывает нашу подделку (S=страница physmap из
спрея kbm, G=0xc154b2a4) → unlink выполняет наши две записи.
Многократно проверено с безобидной полезной нагрузкой (запись 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) → fault. Исправлено запеканием
   shellcode ring-0 В шаблон physmap по адресу page+0x600 (direct map RWX на
   arm32 non-LPAE) — код, резидентный в ядре, ret2usr не нужен
3. Баг смещения ctl_table: proc_handler находится по entry+0x14, а не +0x18 (в
   исходном сканировании было верно; моё определение было неверным) — писали в extra1
4. Обнаружена и откачена регрессия перегрузки: бюджет kid 20×100MB +
   завершающие 10s ломали reclaim; рабочая конфигурация — 2 kid/200MB +
   5s/+3200 завершающих (безобидное попадание 1/1 после отката)
5. **diag2: W2 → глобал &pid_max (0xc1114d7c) → чтение возвращает наше значение
   (-1055861411 = 0xc110d55d как int32) — запись + обратное чтение ДОКАЗАНЫ**

### Оставшаяся загадка (один эксперимент до закрытия)
diag1 с КОРРЕКТНЫМ адресом обработчика (0xc1113f3c): W1 срабатывает (nodename
меняется), W2 должен был выполниться (следующая инструкция) — однако чтения pid_max
по-прежнему возвращают чистые значения → поле обработчика, которое мы пишем, не то,
через которое диспетчеризует inode. diag3 (в очереди; нужен hit boot): W2 →
ПОЛЕ entry->data (0xc1113f2c), указывающее на nodename — если чтение тогда
покажет nodename-байты-как-int, наша entry ЖИВА и лишь смещение обработчика
почему-то неверно; если не затронуто, inode использует теневую копию таблицы
и мы ищем живую.

### Реальность hit-rate
Монетка на каждый boot (~25-40%), кластеризовано; несколько безопасных промахов/падений
подряд — это норма. Примерно 1 из 3-4 boot'ов — попадание. Держите рабочую
конфигурацию ТОЧНО (2 kid, 5s завершающих, 260-регионный kbm, G=0xc154b2a4).

### TODO сессии 9
1. Завершить diag3 на hit boot (перезагружаться до "W1 FIRED")
2. Если entry жива: эмпирически перепроверить смещение обработчика (записать
   адрес proc_dostring как обработчик через... N должен равняться полезному
   значению — использовать unlink, чтобы вместо этого записать entry->data и совершить pivot: например,
   data=selinux_enforcing-соседний...)
3. Если теневая таблица: найти живую — в kallsyms нет символов данных;
   кандидаты: сканировать поведение /proc/sys, или найти вторую область ctl_table
   через шаблон списка заголовков в .data (записи с шагом 0x20
   с handler=proc_dointvec_minmax и maxlen=4 — перечислить ВСЕ и
   diag-записать каждую)
4. Альтернативный класс целей, полностью избегающий диспетчеризации: указатели на функции
   в .data, вызываемые из путей, достижимых из shell (нужен аудит)
5. Сам примитив записи ГОТОВ — теперь достаточно любой надёжной цели
   по адресу ядра для root

## СЕССИЯ 9 — nf-WEAPON: reclaim+unlink+G-hit ДОКАЗАНЫ В ОРУЖИИ; остаётся только обход хуков

### Новый дизайн триггера (полностью заменяет путь через sysctl-обработчик)
Идея пользователя, перенесённая в память ядра: никакого SUID-файла (система
dm-verity RO; примитив пишет в RAM ядра). Вместо этого: **поддельный netfilter
hook**. В этом ядре есть Android-common бэкпорт НОВОГО
API nf_hook_entries, но реализованный как СВЯЗАННЫЙ СПИСОК (проверено
дизассемблированием nf_hook_slow + вспомогательной функции 0xc09897d4):
- `__ip_local_out(net, sk, skb)` загружает ЯЧЕЙКУ entries из
  **[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)) рутует процесс, отправивший
  пакет. Триггер = sendto(127.0.0.1:9) UDP.

### Раскладка режима nf (poc/stage3.c, режим `nf 200`)
- страницы шаблона kbm (относительно страницы, единый источник истины — у старого
  оружия было ТРИ бага, теперь исправленных: proc_handler@+0x14, а не +0x18; цель W1
  должна быть памятью ЯДРА (контекст kworker, нет mm); запечённый код по
  page+0x600 против несоответствия entry G+0x600=page+0x8a4):
  - +0x2ac/+0x2bc/+0x2c0/+0x2dc/+0x3a8: поддельная цепочка phy-alloc (без изменений)
  - +0x600: hookfn nf_code (сохранение маркера + prepare_kernel_cred +
    commit_creds + return NF_ACCEPT(1))
  - +0x700: поддельная entry {next=0, fn=PM_PAGE+0x600, priv=0, prio=0x100}
  - +0x740: ячейка → PM_PAGE+0x700
- полезная нагрузка: N = PM_PAGE+0x740 (0xc154b740), P = 0xc1104ad4
  → W1: *(cell+4)=P (попадает в нашу страницу), W2: *(init_net+0x58c)=cell

### САМОДИАГНОСТИРУЮЩАЯ инструментация (kbm_scan_for)
CPU-отображения kbm сохраняются; после освобождения мы сканируем каждую заспреенную
страницу на известное слово:
- scan(HOOKS_PTR_ADDR) по page+0x744 → доказывает reclaim + unlink + показывает,
  какая физическая страница лежит в основе догадки G
- scan(0x600d600d) по page+0x7f0 → доказывает, что 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-ran-vs-not был добавлен; только crash-boots до конца сессии — чистых данных пока нет.)

Уроки по hit-rate / состоянию загрузки (тяжко добытые)

  • РАБОЧАЯ КОНФИГУРАЦИЯ (не трогать): 2 kids × 100MB ступенчатое давление, завершающие 100×50ms/+3200 переименований, 260×2MB kbm spray, G=0xc154b2a4, предварительный дренаж ~5000 переименований
  • Избыточное давление (20 kids / 10s trailing) ЛОМАЕТ reclaim — откачено
  • Стабилизация загрузки важна: запуски сразу после boot_completed конкурируют со стартовыми аллокациями системы → холодные серии; выждать 60-90s после загрузки перед запуском
  • "W1 did not fire" (проверка nodename) БЕССМЫСЛЕННА для nf payload — W1 пишет в нашу страницу; используйте вместо этого kbm_scan_for
  • Crash-at-free загрузки ≈ мусорный слот или G-miss конверсии; безобидный контроль (pmap 200) — это проверка здравомыслия окружения (4/4 попаданий когда тепло; crash-miss когда холодно)
  • Каталоги /data/local/tmp/.w* очищаются при старте запуска (накопленные каталоги ухудшают reclaim)

TODO для сессии-10 (дерево решений, по порядку)

  1. Запускать nf 200 на загрузках с задержкой стабилизации, пока не появится строка W1-CONFIRMED, затем прочитать строку MARKER: a. маркер ПРИСУТСТВУЕТ, uid!=0 → shellcode's creds не сработали (проверить адреса prepare_kernel_cred/commit_creds; кодировки blx) b. маркер ОТСУТСТВУЕТ → walk так и не вызвал нас: проверить с ВТОРЫМ маркером, записанным... следующая диагностика: hookfn, который ТОЛЬКО пишет маркер и возвращает 1 (без creds) — если всё ещё отсутствует:
    • сдампить наши фактические байты записи через CPU-маппинг прямо перед триггером (они наши для чтения!)
    • проверить, консультируется ли вообще [init_net+0x58c]: использовать запись, чтобы вместо этого повредить что-то наблюдаемое (например, указать её на ячейку, чья *cell = entry с fn = функция ядра вроде kfree → немедленный краш при триггере = поле ДЕЙСТВИТЕЛЬНО консультируется)
    • перепроверить смещение 0x58c: возможно, hooks_ipv4[NF_INET_LOCAL_OUT] находится по другому индексу (NF_INET_POST_ROUTING=4?)
  2. Если walk вызывает нас: исправить creds → root → затем план пользователя: setenforce 0; cp /system/bin/sh /data/local/tmp/su; chown root; chmod 6755; проверить ls -la; оставить файлы-маркеры
  3. Персистентность (после root): патч boot-образа через /dev/block/by-name/boot
    • отключить dm-verity, или в стиле Magisk; su только на /data — это uid0-in- shell-domain после перезагрузки (SELinux снова enforcing) — setenforce 0 работает только во время выполнения
  4. Заметки по очистке: приостановленные процессы st3 имеют повреждённое состояние kctx — убивать только через перезагрузку; nf-перехват ломает хуки LOCAL_OUT для всего трафика — перезагрузиться после root для восстановления

Сегодняшние добавления ассетов

  • Режимы poc/stage3.c: pin/root/drain{,2,3,4,5}/iso — полное оружие + оракул + изоляционный стенд, O_SYNC криминалистика точки краха
  • инфраструктура userland-fake (ufake_prep) — ОСТАВИТЬ, но использовать только если когда-либо будет найден путь context-inline deref
  • tools/: kdis/scan_s/resolve/dumpb/findsysctl офлайн-анализ vmlinux

СЕССИЯ 10 — ROOT ДОСТИГНУТ (2026-09-11)

Три бага, которые блокировали nf-оружие, все исправлены

  1. Неверный init_net: 0xc1104548 — это __stack_chk_guard (гистограмма movw/movt была загрязнена загрузками stack-canary — 2025 построил весь nf-план на этом). Настоящий init_net = 0xc1185040 (подтверждено: ip_send_skb(net,...) вызывается с этим литералом; ~994 ссылки все в сетевом стеке). Ячейка IPv4 LOCAL_OUT = init_net+0x58c = 0xc11855cc.
  2. Баг двойного разыменования: nf_iterate трактует [init_net+0x58c] как сам указатель nf_hook_ops — он читает fn@+0xc, priv@+0x14, prio@+0x20 напрямую из этого значения. Поддельная запись сессии-9 жила по адресу PM_PAGE+0x700, а ячейка указывала на неё (неиспользуемые поля next/ в ячейке → → краш). Поддельная запись ДОЛЖНА жить (0xc154b740). С этим исправлением вызов хука был доказан (режим = SAFE_FN обрабатывает все 200 sendto чисто).

Стена SELinux и обход в 2 пакета

commit_creds(prepare_kernel_cred(0)) / override_creds(&init_cred) даёт uid 0, но попадает в SID SELinux kernel, которому эта политика Fire OS не позволяет писать в /data или /sys/fs/selinux/enforce (проверено: EACCES). Настоящий SID init (7) также запрещён (тест с поддельным struct cred). enforcing_setup является __init (освобождён → краш). atomic_sub в mark_reclaim требует nents=1, что ломает ранний выход shrink_cpu_mapping.

Победа: процесс эксплойта сохраняет CPU-маппинги kbm, поэтому поддельная nf- запись может быть перезаписана на месте между пакетами:

  • режим selroot: entry = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}.
  • Пакет 1: *(enforcing)=0 → SELinux Permissive.
  • Перезаписать запись через kbm_cpu[reg]+off на {fn=commit_creds, priv=&init_cred}.
  • Пакет 2: commit_creds(&init_cred) в задаче отправителя → uid 0 с permissive SELinux → пригодный root, всё за один reclaim, цепочка не нужна.

Проверено на устройстве (2026-09-11)```

[+] 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

root@kitploit:~
- `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`.
- Постэксплуатация — прямые системные вызовы (без `system()`); держите kctx живым
  (`pause()`), чтобы избежать краха при разрушении.

### Оставшееся (Stage 4/5)
- Персистентность после перезагрузки (verity / boot image / recovery), поскольку
  перехват cell + permissive SELinux существуют только во время выполнения, а повторный
  запуск эксплойта требует ~1/3 шанса на возврат памяти.
- `su` потребуется домашний каталог без nosuid (`/system`) или лаунчер, который
  перезапускает триггер.

## SESSION 11 — PERSISTENCE RECON (Track B + Track A) и передача RE

Целью был персистентный root. Были намечены два направления:
- **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 short.
- `/system` монтируется **Android dm-verity из командной строки ядра, собранной lk**:
  `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-устройство с именем `system`; `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"`; таблица векторов ARM по 0x200, остальное 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
- Магия `beefdeed` + `"2.1\0"` + count(0x19=25) по 0x0; элементы с 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 (флаги — **hex-строки**).
- Чтение во время выполнения: `/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`,
  all-ones и т. д. — verity/selinux/`prod` без изменений. Значит, инъекция в cmdline через serial не работает.

### Потребители флагов 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>` и парсит **hex**
  - `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 блокирует путь команды.

### Почему root из Session-10 не сохраняется
- SELinux permissive + перехват cell + root существуют только во время выполнения.
- `/data/metrics` — это **vpartition**: `/system/bin/vpartition.sh` монтирует `/data/vp/metrics.img`
  (ext4, non-nosuid/noexec) в `/data/metrics` при каждой загрузке; `su`, записанный туда, **не**
  выживает перезагрузку. (Также поэтому setuid `su` давал uid 0, но **нулевые capabilities**.)

### Track A (повторный эксплойт при загрузке) — заблокирован
- Ни один триггер init `.rc` не выполняет контролируемый код (все импорты проверены; триггеры `persist.*` запускают только
  `start` фиксированных сервисов; скрипты в `/system`/`/vendor`).
- Root-сервисы читают конфиги из `/data`, но никогда не выполняют их (`perfmonitord`, `amazonfiled`,
  `vpartition.sh`, `kisd`, …).
- Единственный исполнитель при загрузке = **приложение**, но приостановленный след эксплойта — **VmRSS 534 MB**
  (спрей `kbm`) → lmkd убивает его; плюс потерянный reclaim вызывает панику (`PANIC_ON_OOPS`) → bootloop.
- **adbd auto-root** существует, но привязан к cmdline, собранной LK (`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` (проверка RSA libtomcrypt) для обхода или слабого пути unlock_code/version;
4. хранилище `seccfg`/`para`/`ENV_v1`(LK_ENV) (не в каком-либо дампленном разделе — возможно, защищено tee);
5. preloader (`boot0`, `EMMC_BOOT`) на предмет бага.
Если что-либо из этого позволит установить eng/unlocked (персистентно, через boot1 или запись в сырой раздел), тогда
`FOS_FLAGS_DM_VERITY_OFF` отключит verity для system(root) и `/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

СЕССИЯ 12 — LK RE: шлюз eng/unlocked реален, и несуществующие хранилища флага не существуют

Цель: заставить LK рассматривать устройство как eng/unlocked, или найти баг в preloader/LK, чтобы можно было постоянно отключить verity/SELinux. Результат: реверс соответствующего пути кода LK от начала до конца; переключение недостижимо через доступные хранилища. Ни одно устройство не было окирпичено; единственный эксперимент с boot1 был откачен до исходного состояния.

LK — это Thumb-2 PIC, перемещённый на базовый адрес 0xFF400000

lk.img начинается с крошечной заглушки ARM (файл 0x200). Релокатор по адресу 0x224 копирует из 0x200 в литеральный адрес назначения и выполняет переход на литеральный адрес входа:

Таким образом, адрес во время выполнения = 0xFF400000 + смещение в файле для смещений >= 0x200. Всё после заглушки — это 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 для адресов во время выполнения.

Декодированный поток управления (смещение -> значение)

Код разблокировки подписан Amazon-RSA — его нельзя подделать

0x20b4 читает элемент IDME unlock_code (0x400 байт, все нули на этом устройстве) и запускает amzn_verify_unlock (0x222c -> 0x20f0). Эта функция задействует libtomcrypt (десятки путей /features/libtomcrypt/src/pk/asn1/der/... и проверку RSA), и образ содержит встроенный сертификатный материал: Sunnyvale / Amazon Lab126 / "$Common Kernel Signing Engineering CA0" по адресу 0x317d9+, а также диагностические сообщения 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). Нет никакого сокращённого пути для пустого кода / длины / версии: , следовательно (подтверждено в ). Переключение или требует либо действительного подписанного Amazon (закрытый ключ недоступен), либо бага выполнения кода в верификаторе. Ничего эксплуатируемого (границы/размер) не было найдено статически в 0x20b4/0x222c/0x20f0. =>

Флаги verity/SELinux не берутся из хранилища, которое существует

Флаги безопасности читаются через геттер по адресу 0x57c. Эмпирический тест:```

boot1 IDME item fos_flags data (offset 0x22B4, 8 bytes) set to "00000080"

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)

root@kitploit:~
`fos_flags=0x80` — это `FOS_FLAGS_DM_VERITY_OFF`; декодированный шлюз отключил бы
verity, **если бы** геттер его вернул.  Он этого не сделал.  Шлюз является рабочим, а не
мёртвым кодом: его одноразовый кэшированный sentinel равен `-1` в образе
(`*(u32*)0x50c74 == 0xffffffff`), поэтому функция действительно выполнила
путь `check_flag("fos_flags",0x80)` и получила 0.  Следовательно, геттер (по крайней
мере на момент защиты verity) **не** читает элементы IDME из boot1.

Другое возможное хранилище — это **LK env**, загружаемое из раздела, буквально
названного `"para"` (загрузчик 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. Элементы IDME boot1 потребляются Android (/init.fosflags.sh, adbd, /proc/idme/*), но не защитными шлюзами LK.

Заключение — почему постоянный eng/unlock заблокирован

  1. unlocked_kernel требует подписанный Amazon unlock_code (RSA/libtomcrypt, встроенный CA). Не подделать офлайн; баг верификатора не найден. Жёсткая блокировка.
  2. Флаги DM_VERITY_OFF / selinux=permissive потребляются из окружения LK (para/ENV_v1), которого нет на этом GPT. IDME fos_flags эмпирически игнорируется LK (0x80 сохранился, verity остался eio). Жёсткая блокировка, если только не изменить таблицу разделов.
  3. Даже успешный fos_flags=0x80 лишь установил бы androidboot.veritymode=disabled и root= не dm-0; он не разблокировал бы, и SELinux всё ещё нуждался бы в dev_flags из того же отсутствующего окружения, чтобы перейти в permissive.

Оставшиеся пути (будущие, более высокий риск; не предпринимались)

  • Синтезировать хранилище para/ENV_v1: добавить запись GPT с именем para (первичный + резервный GPT должны быть обновлены) в свободном пространстве после userdata (userdata заканчивается на LBA 0x3a3dfde; диск = 30535680 секторов), затем создать env с fos_flags=0x80 и dev_flags=0x40 (контрольная сумма по +0x3ffc = побайтовая сумма по 0x3ffc). Это единственный оставшийся путь к verity-off. Риски: повреждение первичного/резервного GPT может превратить устройство в кирпич; и не доказано, что шлюз verity действительно читает para (только то, что это не boot1 IDME).
  • Баг Preloader (boot0/EMMC_BOOT): не реверсился в этой сессии. Запись boot0 запрещена, пока не существует нетронутой копии и пути восстановления.
  • Исследование верификатора: путь инженерного сертификата (0x316a0/0x31703) достижим только с идентичностью устройства, принятой как "engineering", плюс код, подписанный инженерным ключом; приватный ключ недоступен.

Артефакты / воспроизводимость

  • Добавлен инструмент: tools/lk_xref.py — независимый от базы резолвер строковых xref в LK.
  • Использованные дампы: /tmp/opencode/mustang-dumps/lk.img, boot1.img (нетронутый), boot0.img, mbr.img (GPT), pmt.img (полностью нулевой).
  • Экспериментальный образ boot1 (fos_flags=0x80) сохранён в /tmp/opencode/s12/boot1_f80.img; устройство восстановлено до нетронутого boot1 (проверено /proc/idme/fos_flags -> 0).

Полезные команды (требуется root; перевооружить с помощью ./run.sh)```

re-arm runtime root (~1/3 per boot)

./run.sh --no-build

confirm LK's decisions without a UART

/data/metrics/su /system/bin/sh -p -c 'cat /proc/cmdline'

watch: root=/dev/dm-0 dm="system ... android-verity ..." (verity on)

androidboot.veritymode=eio ; androidboot.selinux=enforce ; prod=1

IDME read (Android copy; NOT what LK's gates use)

for f in fos_flags dev_flags usr_flags unlock_version serial; do cat /proc/idme/$f; echo; done

root@kitploit:~
## СЕССИЯ 13 — preloader всё-таки доступен (`1949:20ff` = MTK preloader, HID-транспорт)

В выключенном состоянии при подключении к USB планшет определяется как **`1949:20ff`**
(`Lab126`) — *не* Android и *не* `0e8d:0003` bootrom.  Дескриптор:```
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 указан как "MTK Preloader" в mtkclient config/usb_ids.py (под MediaTek VID 0x0e8d: 0xe8d:{0x0003 Brom, 0x2000/0x2001/0x20ff/0x3000 Preloader}). Amazon сохранил PID прелоадера и изменил VID на 0x1949, и представляет его как пару HID-эндпоинтов с фиктивным дескриптором отчёта. Таким образом, это режим MediaTek preloader / USBDL, стадия ниже LK — достигаемая здесь выключением питания + подключением, а не замыканием CMD.

Строки дескриптора "HID"/"HID Interface" отсутствуют в lk.img, boot0.img, boot.img или других дампах, то есть режим создаётся компонентом, который мы не дампировали (bootrom/TEE), или собирается во время выполнения.

Почему это важно. Прелоадер Amazon, используемый aftv2-tools, предоставляет встроенные команды, не требующие 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

root@kitploit:~
`aftv2-tools/read_mmc.py` использует `read32`/`write32` для прямого доступа к контроллеру MSDC
(базовый адрес `0x11230000` на MT8173; проверьте для MT8163) и чтения/записи **сырых блоков eMMC**
без DA и, следовательно, без AVB/verity на пути. Если preloader mustang
принимает 0xD1/0xD4, это прямой путь к постоянной разблокировке (патч `boot` /
`lk`), независимо от кода разблокировки RSA и отсутствующего окружения LK.

### Добавленные инструменты (требуется root; сначала chmod на USB-узле)```
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).

Статус / дальнейшие шаги

  • Неподтверждено: действительно ли preloader mustang реализует 0xD1/0xD4 (это решает пробник рукопожатия). Если да, путь чтения/записи eMMC aftv2, вероятно, можно портировать напрямую.
  • Затем: найти базовый адрес MT8163 MSDC (kernel DT или preloader), сдампить раздел (read_mmc), затем пропатчить boot.img/lk из preloader и перезагрузиться.
  • Это более низкая стадия загрузки, чем всё в SESSION 12, поэтому она не зависит от amzn_verify_unlock или шлюза окружения LK.
  • НЕ запускайте записи через SP Flash Tool / mtkclient против него до подтверждения протокола и раскладки eMMC.
Скачать инструмент
pi_blocked_on
  • Прокси-ожидающий живёт на собственном стеке потока-ожидающего (futex.c:1975 передаёт this->rt_waiter, объявленный в futex_wait_requeue_pi в futex.c:2880) → ожидающий штампует свой собственный освобождённый фрейм через arm32 select (nr 142) fd_sets
  • Условия эксплуатации: нет KASLR (фиксированная база 0xc0008000), нет PAN, DEBUG_RT_MUTEXES выключен → компактный 48-байтовый rt_mutex_waiter (tree_entry@0, pi_tree_entry@0xc, task@0x18, lock@0x1c, prio@0x20, deadline@0x28)
  • Минимальная цепочка: 2 write-слота → modprobe_path @ 0xc111488c (строка самоопределяется в vmlinux; KALLSYMS_ALL выключен, поэтому символы данных требуют этого трюка) → exec неизвестного binfmt → root-скрипт (setenforce 0, отключение OTA, su)
  • Ссылки в refs/: NebuSec/CyberMeowfia (оригинал), GhostLock-5.10 (порт для Fire OS 8, полный 32-битный ARM-триггер в src/exp32/), ghostlock-...-4.19-k40 (порт для Qualcomm 4.19 Android)
  • Этап 3: произвольный вызов функции ядра → ROOT (сессия 10)
    selroot
    selinux_state.enforcing
    commit_creds(&init_cred)
    uid=0
  • [_] Этап 4: root-скрипт (su, permissive, OTA off) + персистентность — root получен; персистентность заблокирована (см. СЕССИЯ 11): LK ограничивает verity-off/SELinux-permissive условиями eng/unlocked, у повторной эксплуатации при загрузке нет жизнеспособного исполнителя. Далее: реверс LK/amzn_verify_unlock.
  • Этап 5: собственная цепочка загрузки ОС
  • jc
    nr_extres
  • Результат JIT alloc записывается ядром через info->gpu_alloc_addr (GPU VA, который вы должны предварительно выделить и передать)
  • вытесняемый объектдавлениерезультат
    нет900 МБвыжило
    нет1300 МБпаника (системный баг lowmem — не связано)
    обычный регион + DONT_NEED700 МБвыжило
    JIT-регион + DONT_NEED700 МБпаника в пути reclaim
  • kernel/config-* — дамп /proc/config.gz с работающего устройства
  • ksrc/ — исходники Amazon OSS (platform.tar + распакованное дерево midgard-r26p0)
  • OTA: /tmp/opencode/mustang_ota.bin (sha256 6068515a… совпадает с fireos-archive) и 2.2 ГБ tarball исходников ядра хранятся в ~/Desktop/amazon-mustang/
  • Путь дерева исходников: ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/
  • результат
    параллельные спрейеры rename во время штормаrename застревают (journal/GFP_NOFS) → 128 всего → мусорное разыменование
    предварительный слив 12K событий + давление + небольшой хвосткраш на разыменовании
    + kill-child-при-вытеснении (опрос 10мс)краш на разыменовании
    + жизненный цикл, закреплённый на cpu0 (drain3)краш на разыменовании
    ступенчатое давление (drain4 v1)дочерние процессы освободили память при выходе → нет вытеснения (провалидирован легальный путь)
    запускрезультат
    pmap G=c2a412a4 400MBкраш на deref
    pmap G=c2f4b2a4 480MBкраш; +0 переименований после kill (480MB душит fs)
    iso2 (спрей + регионы + оракул)ПОПАДАНИЕ В РЕГИОН — спрей не ломает reclaim
    mix v1 (конкурентные events+regions)краш; смешанный (крутящиеся потоки событий)
    pmap G=c2a7d2a4 350MB + retouchкраш; +4452 переименований OK
    mix2 (последовательно: 2с events ЗАТЕМ regions)краш; +7126 переименований (28K аллокаций событий), 2715 регионов
    fn
    fn=0
    по адресу ячейки
    PM_PAGE+NF_CELL_OFF
    probe
  • Прямое отображение physmap является XN выше kernel_x_end: arch/arm/mm/mmu.c map_lowmem() отображает lowram ниже текста ядра как MT_MEMORY_RWX, но всё выше kernel_x_end как MT_MEMORY_RW → PMD_SECT_XN (строка 509). Зашитый shellcode по адресу 0xc154b600 вызывает prefetch-abort. Payload должен быть указателем на реальную функцию ядра, а не кодом в physmap.
  • литерал (смещение в файле)значениезначение
    0x2700xFF4002F8str r4,[r6] scratch
    0x2740xFF40027Cадрес назначения (base+0x27C)
    0x2780xFF54A440конец копирования (вкл. BSS)
    0x27C0xFF400484точка входа
    смещениефункция
    0xdf7cis_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ]; g = глобальная переменная @0x52838. 1 на этом устройстве.
    0x20b4verify_stored_unlock() = memset(buf,0,0x100); чтение IDME/env unlock_code (0x100) через 0x57c; bl 0x222c; возврат (verify==0).
    0x222c / 0x20f0amzn_verify_unlock(code,len) — проверка libtomcrypt RSA/PKCS#1 (см. ниже).
    0xda3eis_unlocked() = is_secure_or_prod() && verify_stored_unlock().
    0x29a28is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock()); кэшируется в глобальной переменной @0x50c74.
    0x29974Построитель cmdline для SELinux: 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: раздел "para", 0x4000 байт, магия ENV_v1, контрольная сумма = сумма байтов по 0x3ffc, сравниваемая со словом @0x3ffc.
    0x1efd0поиск раздела по имени (используется для "para", "boot", ...).
    0x57cдиспетчер геттеров через слот обратного вызова @0x58218; слоты @0x58200..0x5821c регистрируются из таблицы по адресу 0x5a8-0x734.
    0x2a19cfastboot oem unlock: bl 0x222c(code,len); при успехе записывает unlock_code (0x100) через 0x408.
    verify(zeros) != 0
    unlocked_kernel=false
    /proc/cmdline
    androidboot.unlocked_kernel
    androidboot.prod
    unlock_code
    переключение eng/unlocked через документированный путь криптографически неосуществимо.
  • Путь A (повторная эксплуатация во время загрузки) поэтому остаётся заблокированным точно так же, как в SESSION 11: его единственный путь разблокировки — тот же шлюз LK.