Compile kernel module without kernel headers
Go to file
Andriy Petrov 02efdca8d0 rewrites
2024-04-17 14:26:30 +02:00
.gitignore Initial commit 2024-04-17 12:02:06 +02:00
bmod.c PLT section 2024-04-17 12:34:47 +02:00
Makefile initial commit 2024-04-17 12:10:24 +02:00
README.md rewrites 2024-04-17 14:26:30 +02:00

Kernel module compilation depends on kernel headers. This is a deliberate design choice made by kernel developers. As result, there is no even a slight guarantee of an ABI compatibility even between minor kernel versions.

But what to do if, for the variety of reasons, for the kernel you are interest in, headers are unavailable?

The code in this repo gives an answer to this question. It focuses on Android, but most of the information and techniques discussed here can be easily applied to the generic Linux kernel as well.

ELF symbols stealing

The main idea, is to use Android NDK to compile generic a Linux kernel module with pre-embed ELF symbols collected from some existing kernel module. See, for example, /vendor/lib/modules/*.ko for a possible donor. The idea is to provide just enough information, so that the LKM loader would be able to recognise and resolve all the necessary dependencies and default, and in the end to load our kernel agnostic module.

PLT section

This part is pretty straight forward, we simply have to define a few ELF symbols, that Android LKM is expecting to find within normal module. We are going to use __attribute__ compiler keyword for this.

// insmod error: module PLT section(s) missing
__attribute__((section(".plt")))
char plt = 0;

__attribute__((section(".init.plt")))
char initplt = 0;

__attribute__((section(".text.ftrace_trampoline")))
char textftrace_trampoline = 0; // TODO: probably an overkill

.modinfo

objdump -s -j .modinfo $KMODULE
 met.ko:     file format elf64-little

 Contents of section .modinfo:
  0000 7061726d 74797065 3d6d6574 5f6d696e  parmtype=met_min
  0010 6f723a69 6e740061 7574686f 723d4454  or:int.author=DT
  0020 5f444d35 00646573 63726970 74696f6e  _DM5.description
  0030 3d4d4554 5f434f52 45006c69 63656e73  =MET_CORE.licens
  0040 653d4750 4c007665 726d6167 69633d34  e=GPL.vermagic=4
  0050 2e31342e 3138362d 70657266 2d303032  .14.186-perf-002
  0060 38382d67 37646633 34643261 65366462  88-g7df34d2ae6db
  0070 20534d50 20707265 656d7074 206d6f64   SMP preempt mod
  0080 5f756e6c 6f616420 6d6f6476 65727369  _unload modversi
  0090 6f6e7320 61617263 68363400 6e616d65  ons aarch64.name
  00a0 3d6d6574 00646570 656e6473 3d007372  =met.depends=.sr
  00b0 63766572 73696f6e 3d353333 42423745  cversion=533BB7E
  00c0 35383636 45353246 36334239 41434342  5866E52F63B9ACCB
  00d0 00 

This ELF section essentially is a collection of 'TAG=VALUE' entries separated by NULL character '\0'. The most important part is vermagic, since the loader is using it do identify which kernel headers were used for the module compilation and compares it against his own vermagic variable. They must be identical.

cat /proc/version

.gnu.linkonce.this_module

This ELF section contains information that the LKM loader used to identify:

  • name of the module
  • a pointer to the initialization procedure module_init
  • a pointer to the de-initialization procedure module_cleanup

section size

objdump -h $KMODULE | grep .gnu.linkonce.this_module
 .gnu.linkonce.this_module 00000340  0000000000000000  0000000000000000 .... 
                           ^^^^^^^^ section size

name offset

objdump -s $KMODULE -j .gnu.linkonce.this_module
  Contents of section .gnu.linkonce.this_module:
  0000 00000000 00000000 00000000 00000000  ................
  0010 00000000 00000000 6d657400 00000000  ........met.....
                         ^^^^^^^^ offset 0x18 bytes

entry points

readelf -a $KMODULE -W
  Relocation section '.rela.gnu.linkonce.this_module' at offset 0x109b40 contains 2 entries:
  <--- Offset --->  Info             Type                   Symbol's Value   Symbol's Name + Addend
  0000000000000150  000006c500000101 R_AARCH64_ABS64        0000000000000000 init_module + 0
  0000000000000310  000006ee00000101 R_AARCH64_ABS64        0000000000000000 cleanup_module + 0

__versions

In its essence is a byte-array for declaring external dependencies required by the module:

  • first 4 bytes of CRC encoded function signature
  • a ASCII encoded function name
objdump -s $KMODULE -j '__versions'    #dump in binary form
 met.ko:     file format elf64-little

 Contents of section __versions:
  0000 e8d6fc68 00000000 6d6f6475 6c655f6c  ...h....module_l
  0010 61796f75 74000000 00000000 00000000  ayout...........
  0020 00000000 00000000 00000000 00000000  ................
  0030 00000000 00000000 00000000 00000000  ................
  0040 6c9bdf85 00000000 73747273 65700000  l.......strsep..
  0050 00000000 00000000 00000000 00000000  ................
  0060 00000000 00000000 00000000 00000000  ................
  0070 00000000 00000000 00000000 00000000  ................
  0080 1ee414e9 00000000 73747263 70790000  ........strcpy..
  0090 00000000 00000000 00000000 00000000  ................
  00a0 00000000 00000000 00000000 00000000  ................
  00b0 00000000 00000000 00000000 00000000  ................
modprobe --dump-modversions $KMODULE   #dump in readable form

module_layout

A default first entry of a section. Used by LKM loader to determine ABI of a module (the one used during it's compile time)

printk()

In order for module to use any externally declared symbol module must:

  1. Use a C-style function declaration in it's source code
   int printk(const char *fmt, ...);
  1. Add another entry to the __versions section
   char __attribute__((section("__versions"))) ____versions[] = {
  /* module_layout */     // <<-- MUST BE PROVIDED
  0xe8, 0xd6, 0xfc, 0x68, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x6c,
  0x61, 0x79, 0x6f, 0x75, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  
  /* printk */
  0xa1, 0x58, 0x55, 0x98, 0x00, 0x00, 0x00, 0x00, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x6b, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

Misc commands

  • Module loading
insmod module_name.ko
  • List loaded modules
lsmod
  • Logs
dmesg | grep insmod
  • Module unloading
rmmod MODULE_NAME        # from .gnu.linkonce.this_module section