Comment injecter du code dans le noyau Linux sans code source ni symboles de débogage
Imaginez ce scénario : vous avez un binaire Linux prêt à l'emploi pour un appareil mobile ou une carte ARM64 embarquée. Aucun code source n'est disponible, les informations de débogage ont été supprimées lors de la compilation, et vous devez intercepter un appel système ou ajouter votre propre logique directement au niveau du noyau.
L'approche habituelle dans ce cas est longue et fastidieuse. Vous devez désassembler le binaire dans IDA Pro ou Ghidra, trouver manuellement les décalages des fonctions et écrire des correctifs fragiles. Un développeur sous le pseudonyme bmax121 a décidé de simplifier ce processus et a publié le projet KernelPatch.
Pourquoi KernelPatch est nécessaire
KernelPatch est un framework pour corriger et intercepter le noyau Linux lorsque vous ne disposez que d'une image de noyau strippée comme vmlinux ou boot.img.
Lorsqu'un noyau est compilé sans informations de débogage, les outils de débogage traditionnels comme kprobes refusent de fonctionner sans contournements supplémentaires. KernelPatch résout ce problème automatiquement. Il récupère la table des symboles directement depuis le binaire du noyau et fournit une API pratique pour modifier le code exécutable.
Le projet est utile pour les ingénieurs reverse, les chercheurs en sécurité Android et les développeurs de logiciels de bas niveau pour les systèmes embarqués.
Ce que le framework peut faire
Le développeur a intégré plusieurs capacités importantes dans le projet pour travailler avec le noyau :
- Recherche de symboles sans code source. L'outil analyse les structures internes
kallsymsdans l'image prête à l'emploi. Il n'a pas besoin de fichiersSystem.mapexternes ni de code source. Une approche similaire est utilisée dans l'utilitairevmlinux-to-elf, dont l'auteur s'est inspiré. - Deux modes d'injection de code. Vous pouvez appliquer un correctif statique à l'image (par exemple, écraser
boot.imgsur le disque) ou charger du code dynamiquement à l'exécution. - Interception de fonctions via inline hook. Pour l'architecture ARM64, l'interception d'appels repose sur le travail de
android-inline-hook. Le framework gère correctement les instructions d'adressage relatif (b,bl,adrp) pour ne pas perturber l'exécution du code original. - Hooks d'appels système. Remplacer les adresses dans la table
sys_call_tablene nécessite que quelques commandes. - Module SU intégré. Pour les appareils Android, le projet inclut sa propre implémentation superutilisateur.
Comment cela fonctionne en interne
Pour charger les modules utilisateur (KPM — Kernel Patch Modules) à la volée, le framework a besoin de mémoire à l'intérieur du noyau. Pour cela, KernelPatch inclut un allocateur TLSF compact. Il permet d'allouer des pages mémoire exécutables (ROX) directement dans l'espace d'adressage du noyau.
Lors de la réalisation d'un inline hook, le framework lit les premières instructions de la fonction cible, forme un trampoline à partir de celles-ci, et écrit un saut vers votre gestionnaire. Lorsque votre code termine son exécution, le contrôle revient à la fonction originale via le trampoline.
+-------------------+ +---------------------+ +-------------------+
| Целевая функция | ---> | Ваш обработчик KPM | ---> | Трамплин (Orig) |
| (инструкция JMP) | | (пользовательский) | | (продолжение) |
+-------------------+ +---------------------+ +-------------------+
Exigences système et limitations
Le projet a des limites d'applicabilité claires qu'il est important de connaître à l'avance :
- Seule l'architecture ARM64 est prise en charge. Il n'existe pas encore de versions pour x86_64 ou RISC-V.
- Plage de versions du noyau — de Linux 3.18 à 6.6.
- La condition principale est que le noyau doit être compilé avec le flag
CONFIG_KALLSYMS=y.
Si le fournisseur de firmware a complètement supprimé CONFIG_KALLSYMS lors de la compilation, KernelPatch ne pourra pas analyser automatiquement les adresses des fonctions. Heureusement, ce flag reste activé dans la plupart des firmwares d'appareils Android et ARM.
Exemple d'utilisation pratique
Disons que vous devez suivre tous les lancements de processus via l'appel sys_execve sur un appareil Android de test, mais que vous ne voulez pas recompiler le noyau à partir du code source du fournisseur.
Auparavant, vous auriez dû construire un module chargeable de noyau complet (LKM), gérer les problèmes de vérification de signature numérique et l'absence de chargeur de modules dans le noyau.
Avec KernelPatch, le processus est différent :
- Vous écrivez un petit code C avec la logique d'interception.
- Vous le compilez au format KPM.
- Vous fournissez à l'utilitaire KernelPatch votre
boot.imget le module KPM compilé. - Vous obtenez un
boot.imgmodifié et le flashez via fastboot.
Une fois l'appareil démarré, chaque appel à execve passe d'abord par votre gestionnaire.
Le README du projet est assez laconique, et toute la documentation détaillée devra être étudiée à partir du code source et des fichiers du dossier doc/. Le projet n'en reste pas moins en développement actif et compte déjà plus de 14 000 étoiles sur GitHub.
Au fait, si vous n'avez pas besoin du framework lui-même pour vos expérimentations, mais plutôt d'un root prêt à l'emploi pour Android basé dessus, l'auteur recommande de regarder son projet adjacent — APatch. C'est une alternative de plus haut niveau à Magisk et KernelSU, utilisant KernelPatch en arrière-plan.
Pour les chercheurs en sécurité et les développeurs ARM64, KernelPatch est une trouvaille précieuse, permettant d'économiser des dizaines d'heures lors de l'analyse de noyaux à code fermé.
Projets similaires