>_ DevTrendspl

Język

Strona główna

Języki

Sekcje

Frontend Backend Mobilne DevOps AI / ML GameDev Blockchain Systemy wbudowane Bezpieczeństwo
C

Jak wstrzykiwać kod do jądra Linux bez kodu źródłowego i symboli debugowania

Wyobraź sobie taką sytuację: masz gotowy obraz jądra Linux dla urządzenia mobilnego lub wbudowanego boarda ARM64. Nie masz dostępu do kodu źródłowego, informacje debugowania zostały usunięte podczas kompilacji, a ty musisz przechwycić wywołanie systemowe lub dodać własną logikę bezpośrednio na poziomie jądra.

Zwyczajowe podejście w takich przypadkach jest czasochłonne i żmudne. Musisz zreversować binarkę w IDA Pro lub Ghidrze, ręcznie znaleźć offsety funkcji i pisać niestabilne łaty. Programista działający pod nickiem bmax121 postanowił uprościć ten proces i wydał projekt KernelPatch.

Dlaczego KernelPatch jest potrzebny

KernelPatch to framework do łatania i hakowania jądra Linux, gdy jedynym dostępnym materiałem jest obraz jądra bez symboli, taki jak vmlinux lub boot.img.

Gdy jądro jest budowane bez informacji debugowania, tradycyjne narzędzia debugujące, takie jak kprobes, odmawiają współpracy bez dodatkowych obejść. KernelPatch rozwiązuje ten problem samodzielnie. Odzyskuje tablicę symboli bezpośrednio z pliku binarnego jądra i udostępnia wygodne API do modyfikacji kodu wykonywalnego.

Projekt jest przydatny dla reverserów, badaczy bezpieczeństwa Androida oraz programistów oprogramowania niskiego poziomu dla systemów wbudowanych.

Co potrafi ten framework

Autor zbudował w projekcie kilka kluczowych funkcjonalności do pracy z jądrem:

  • Wyszukiwanie symboli bez kodu źródłowego. Narzędzie parsuje wewnętrzne struktury kallsyms w gotowym obrazie. Nie potrzebuje zewnętrznych plików System.map ani kodu źródłowego. Podobne podejście jest wykorzystywane w narzędziu vmlinux-to-elf, z którego autor zaczerpnął część pomysłów.
  • Dwa tryby wstrzykiwania kodu. Możesz wykonać statyczną łatę obrazu (na przykład nadpisać boot.img na dysku) lub załadować kod dynamicznie w czasie działania.
  • Przechwytywanie funkcji przez inline hook. Dla architektury ARM64 przechwytywanie wywołań bazuje na pracy android-inline-hook. Framework poprawnie obsługuje instrukcje z adresowaniem relatywnym (b, bl, adrp), aby nie zakłócić wykonywania oryginalnego kodu.
  • Hooki wywołań systemowych. Zastąpienie adresów w tabeli sys_call_table zajmuje zaledwie kilka poleceń.
  • Wbudowany moduł SU. Dla urządzeń z Androidem projekt zawiera własną implementację superusera.

Jak to działa wewnętrznie

Aby ładować moduły użytkownika (KPM — Kernel Patch Modules) na bieżąco, framework potrzebuje pamięci wewnątrz jądra. W tym celu KernelPatch zawiera kompaktowy alokator TLSF. Pomaga przydzielać strony pamięci wykonywalnej (ROX) bezpośrednio w przestrzeni adresowej jądra.

Podczas wykonywania inline hooka framework odczytuje pierwsze instrukcje funkcji docelowej, tworzy z nich tak zwany trampoline i zapisuje skok do twojego handlera. Gdy twój kod zakończy wykonywanie, sterowanie wraca do oryginalnej funkcji przez trampoline.

Projekt ma jasno określone granice stosowalności, które warto poznać zawczasu:

  • Obsługiwana jest tylko architektura ARM64. Nie ma jeszcze wersji dla x86_64 ani RISC-V.
  • Zakres wersji jądra — od Linux 3.18 do 6.6.
  • Głównym wymaganiem jest, aby jądro było zbudowane z flagą CONFIG_KALLSYMS=y.

Jeśli producent firmware całkowicie usunął CONFIG_KALLSYMS na etapie kompilacji, KernelPatch nie będzie w stanie automatycznie parsować adresów funkcji. Na szczęście ta flaga jest włączona w większości firmware urządzeń z Androidem i ARM.

Praktyczny przykład użycia

Załóżmy, że chcesz śledzić wszystkie uruchomienia procesów przez wywołanie sys_execve na testowym urządzeniu z Androidem, ale nie chcesz przebudowywać jądra z kodu źródłowego dostarczonego przez producenta.

Wcześniej musiałbyś zbudować pełny moduł ładujący jądra (LKM), zmierzyć się z problemami weryfikacji podpisu cyfrowego i brakiem loadera modułów w jądrze.

Z KernelPatch proces wygląda inaczej:

  1. Piszesz niewielki kod w C z logiką przechwytywania.
  2. Kompilujesz go do formatu KPM.
  3. Podajesz narzędziu KernelPatch swój boot.img i skompilowany moduł KPM.
  4. Otrzymujesz zmodyfikowany boot.img i wgrywasz go przez fastboot.

Po uruchomieniu urządzenia każde wywołanie execve przechodzi najpierw przez twój handler.

Dokumentacja projektu README jest dość zwięzła, a całą szczegółową dokumentację trzeba będzie przestudiować w kodzie źródłowym i plikach w folderze doc/. Mimo to projekt aktywnie się rozwija i zdobył już ponad 14 000 gwiazdek na GitHubie.

A przy okazji, jeśli nie potrzebujesz samego frameworka do eksperymentów, ale raczej gotowego roota dla Androida opartego na nim, autor zaleca przyjrzenie się jego pobocznemu projektowi — APatch. To wyżej poziomowa alternatywa dla Magisk i KernelSU, korzystająca z KernelPatch pod spodem.

Dla badaczy bezpieczeństwa i programistów ARM64 KernelPatch to świetne znalezisko, oszczędzające dziesiątki godzin podczas analizy jąder o zamkniętym kodzie źródłowym.

Powiązane projekty