>_ DevTrendses

Idioma

Inicio

Lenguajes

Secciones

Frontend Backend Móvil DevOps AI / ML GameDev Blockchain Embebidos Seguridad
C

Cómo inyectar código en el kernel de Linux sin código fuente ni símbolos de depuración

Imagina este escenario: tienes un binario del kernel de Linux ya listo para un dispositivo móvil o una placa ARM64 embebida. No hay código fuente disponible, la información de depuración fue eliminada durante la compilación, y necesitas interceptar una llamada al sistema o agregar tu propia lógica directamente a nivel del kernel.

El enfoque habitual en estos casos es largo y tedioso. Tienes que hacer ingeniería inversa del binario en IDA Pro o Ghidra, encontrar manualmente los offsets de las funciones y escribir parches frágiles. Un desarrollador con el sobrenombre bmax121 decidió simplificar este proceso y publicó el proyecto KernelPatch.

Por qué se necesita KernelPatch

KernelPatch es un framework para parchear y enganchar el kernel de Linux cuando todo lo que tienes para trabajar es una imagen del kernel despojada como vmlinux o boot.img.

Cuando un kernel se compila sin información de depuración, las herramientas tradicionales de depuración como kprobes se niegan a funcionar sin soluciones alternativas adicionales. KernelPatch resuelve este problema por sí mismo. Recupera la tabla de símbolos directamente del binario del kernel y proporciona una API conveniente para modificar código ejecutable.

El proyecto es útil para ingenieros reversos, investigadores de seguridad de Android y desarrolladores de software de bajo nivel para sistemas embebidos.

Qué puede hacer el framework

El desarrollador incorporó varias capacidades importantes en el proyecto para trabajar con el kernel:

  • Búsqueda de símbolos sin código fuente. La herramienta analiza estructuras internas de kallsyms dentro de la imagen ya lista. No necesita archivos externos de System.map ni código fuente. Un enfoque similar se utiliza en la utilidad vmlinux-to-elf, de la cual el autor tomó prestadas algunas ideas.
  • Dos modos de inyección de código. Puedes hacer un parche estático a la imagen (por ejemplo, sobrescribir boot.img en disco) o cargar código dinámicamente en tiempo de ejecución.
  • Intercepción de funciones mediante inline hook. Para la arquitectura ARM64, la interceptación de llamadas se basa en el trabajo de android-inline-hook. El framework maneja correctamente las instrucciones de direccionamiento relativo (b, bl, adrp) para no romper la ejecución del código original.
  • Hooks de llamadas al sistema. Reemplazar direcciones en la tabla de sys_call_table solo toma un par de comandos.
  • Módulo SU integrado. Para dispositivos Android, el proyecto incluye su propia implementación de superusuario.

Cómo funciona internamente

Para cargar módulos de usuario (KPM — Kernel Patch Modules) sobre la marcha, el framework necesita memoria dentro del kernel. Para esto, KernelPatch incluye un asignador TLSF compacto. Ayuda a asignar páginas de memoria ejecutable (ROX) directamente en el espacio de direcciones del kernel.

Al realizar un inline hook, el framework lee las primeras instrucciones de la función objetivo, forma un llamado trampolín a partir de ellas y escribe un salto hacia tu manejador. Cuando tu código termina de ejecutarse, el control regresa a la función original a través del trampolín.

+-------------------+      +---------------------+      +-------------------+
|  Целевая функция  | ---> |  Ваш обработчик KPM | ---> | Трамплин (Orig)   |
|  (инструкция JMP) |      |  (пользовательский) |      |  (продолжение)    |
+-------------------+      +---------------------+      +-------------------+

Requisitos del sistema y limitaciones

El proyecto tiene límites claros de aplicabilidad que es importante conocer de antemano:

  • Solo la arquitectura ARM64 es compatible. Aún no hay versiones para x86_64 o RISC-V.
  • Rango de versiones del kernel — desde Linux 3.18 hasta 6.6.
  • El requisito principal es que el kernel debe estar compilado con el indicador CONFIG_KALLSYMS=y.

Si el proveedor del firmware eliminó completamente CONFIG_KALLSYMS en la etapa de compilación, KernelPatch no podrá analizar automáticamente las direcciones de las funciones. Afortunadamente, este indicador se deja habilitado en la mayoría de los firmwares de dispositivos Android y ARM.

Ejemplo de uso práctico

Digamos que necesitas rastrear todos los lanzamientos de procesos a través de la llamada sys_execve en un dispositivo Android de prueba, pero no quieres recompilar el kernel desde el código fuente del proveedor.

Antes, habrías tenido que construir un módulo cargable de kernel completo (LKM), lidiar con problemas de verificación de firma digital y la ausencia de un cargador de módulos en el kernel.

Con KernelPatch, el proceso se ve diferente:

  1. Escribes un pequeño código en C con la lógica de interceptación.
  2. Lo compilas en formato KPM.
  3. Alimentas la utilidad KernelPatch con tu boot.img y el módulo KPM compilado.
  4. Obtienes un boot.img modificado y lo flasheas a través de fastboot.

Después de que el dispositivo arranca, cada llamada a execve pasa primero por tu manejador.

El README del proyecto es bastante lacónico, y toda la documentación detallada tendrá que estudiarse a partir del código fuente y los archivos en la carpeta doc/. Sin embargo, el proyecto se está desarrollando activamente y ya ha recopilado más de 14,000 estrellas en GitHub.

Por cierto, si no necesitas el framework en sí para experimentos, sino más bien un root listo para usar para Android basado en él, el autor recomienda buscar su proyecto adyacente — APatch. Esta es una alternativa de más alto nivel a Magisk y KernelSU, utilizando KernelPatch como base.

Para investigadores de seguridad y desarrolladores de ARM64, KernelPatch es un gran hallazgo, que ahorra docenas de horas al analizar kernels de código cerrado.

Proyectos relacionados