Hoe code injecteren in de Linux-kernel zonder broncode en debug-symbolen
Stel je dit scenario voor: je hebt een kant-en-klare Linux-kernel binary voor een mobiel apparaat of embedded ARM64 bord. Er is geen broncode beschikbaar, debug-informatie is verwijderd tijdens het bouwen, en je moet een systeemaanroep onderscheppen of je eigen logica toevoegen direct op kernel-niveau.
De gebruikelijke aanpak in zulke gevallen is langdurig en vervelend. Je moet de binary reverse-engineeren in IDA Pro of Ghidra, handmatig functie-offsets vinden en breekbare patches schrijven. Een ontwikkelaar onder de nickname bmax121 besloot dit proces te vereenvoudigen en bracht het KernelPatch project uit.
Waarom KernelPatch nodig is
KernelPatch is een framework voor het patchen en hooken van de Linux-kernel wanneer je alleen een gestripte kernel-image tot je beschikking hebt, zoals vmlinux of boot.img.
Wanneer een kernel wordt gebouwd zonder debug-informatie, weigeren traditionele debugging-tools zoals kprobes te werken zonder extra workarounds. KernelPatch lost dit probleem zelfstandig op. Het herstelt de symbooltabel direct van de kernel-binary en biedt een handige API voor het wijzigen van uitvoerbare code.
Het project is nuttig voor reverse engineers, Android-beveiligingsonderzoekers en ontwikkelaars van low-level software voor embedded systemen.
Wat het framework kan doen
De ontwikkelaar heeft verschillende belangrijke mogelijkheden in het project ingebouwd voor het werken met de kernel:
- Symboolzoektocht zonder broncode. Het tool parst interne
kallsymsstructuren binnen de kant-en-klare image. Het heeft geen externeSystem.mapbestanden of broncode nodig. Een vergelijkbare aanpak wordt gebruikt in hetvmlinux-to-elfhulpprogramma, waarvan de auteur enkele ideeën heeft geleend. - Twee code-injectie modi. Je kunt een statische patch naar de image maken (bijvoorbeeld
boot.imgop schijf overschrijven) of code dynamisch laden tijdens runtime. - Functie-interceptie via inline hook. Voor de ARM64-architectuur is aanroep-interceptie gebaseerd op het werk van
android-inline-hook. Het framework verwerkt relatieve adresseringsinstructies correct (b,bl,adrp) om de uitvoering van de originele code niet te verstoren. - Systeemaanroep hooks. Het vervangen van adressen in de
sys_call_tabletabel kost slechts een paar commando's. - Ingebouwde SU-module. Voor Android-apparaten bevat het project zijn eigen superuser-implementatie.
Hoe het intern werkt
Om gebruikersmodules (KPM — Kernel Patch Modules) dynamisch te laden, heeft het framework geheugen nodig binnen de kernel. Hiervoor bevat KernelPatch een compacte TLSF-allocator. Het helpt bij het toewijzen van uitvoerbare geheugenpagina's (ROX) direct in de kernel-adresruimte.
Bij het uitvoeren van een inline hook leest het framework de eerste instructies van de doelfunctie, vormt er een zogenaamde trampoline van en schrijft een sprong naar je handler. Wanneer je code klaar is met uitvoeren, keert de controle terug naar de originele functie via de trampoline.
+-------------------+ +---------------------+ +-------------------+
| Целевая функция | ---> | Ваш обработчик KPM | ---> | Трамплин (Orig) |
| (инструкция JMP) | | (пользовательский) | | (продолжение) |
+-------------------+ +---------------------+ +-------------------+
Systeemvereisten en beperkingen
Het project heeft duidelijke grenzen van toepasbaarheid die belangrijk zijn om van tevoren te kennen:
- Alleen de ARM64-architectuur wordt ondersteund. Er zijn nog geen versies voor x86_64 of RISC-V.
- Kernelversiebereik — van Linux 3.18 tot 6.6.
- De hoofdvereiste is dat de kernel moet zijn gebouwd met de
CONFIG_KALLSYMS=yvlag.
Als de firmware-leverancier CONFIG_KALLSYMS volledig heeft verwijderd tijdens de bouwfase, kan KernelPatch niet automatisch functie-adressen parsen. Gelukkig is deze vlag ingeschakeld in de meeste Android- en ARM-apparaatfirmwares.
Praktisch gebruiksvoorbeeld
Stel dat je alle processtarts wilt volgen via de sys_execve aanroep op een test-Android-apparaat, maar je wilt de kernel niet opnieuw bouwen vanuit vendor-broncode.
Voorheen moest je een volledige kernel loadable module (LKM) bouwen, omgaan met problemen met digitale handtekeningverificatie en de afwezigheid van een module-loader in de kernel.
Met KernelPatch ziet het proces er anders uit:
- Je schrijft een kleine C-code met de interceptielogica.
- Compileer het naar KPM-formaat.
- Geef het KernelPatch-hulpprogramma je
boot.imgen de gecompileerde KPM-module. - Ontvang een gewijzigde
boot.imgen flash deze via fastboot.
Nadat het apparaat is opgestart, gaat elke aanroep naar execve eerst door je handler.
De README van het project is vrij beknopt, en alle gedetailleerde documentatie zal uit de broncode en bestanden in de doc/ map moeten worden bestudeerd. Desondanks ontwikkelt het project actief en heeft het al meer dan 14.000 sterren verzameld op GitHub.
Trouwens, als je het framework zelf niet nodig hebt voor experimenten, maar eerder een kant-en-klare root voor Android, raadt de auteur aan om naar zijn aangrenzende project te kijken — APatch. Dit is een hoger-niveau alternatief voor Magisk en KernelSU, dat KernelPatch als basis gebruikt.
Voor beveiligingsonderzoekers en ARM64-ontwikkelaars is KernelPatch een gewondige vondst, die tientallen uren bespaart bij het analyseren van closed-source kernels.
Gerelateerde projecten