From 859ca1d5a23449989d3abe973ff410a3e62fa9c1 Mon Sep 17 00:00:00 2001 From: Andriy Petrov Date: Wed, 17 Apr 2024 12:10:24 +0200 Subject: [PATCH] initial commit --- Makefile | 22 ++++++++ README.md | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++++- bmod.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 bmod.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f57613f --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +TOOLCHAIN=$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64 +TARGET=aarch64-linux-android +API=30 +AR=$(TOOLCHAIN)/bin/llvm-ar +CC=$(TOOLCHAIN)/bin/$(TARGET)$(API)-clang +AS=$(CC) +CXX=$(TOOLCHAIN)/bin/$(TARGET)$(API)-clang++ +LD=$(TOOLCHAIN)/bin/ld.lld +RANLIB=$(TOOLCHAIN)/bin/llvm-ranlib +STRIP=$(TOOLCHAIN)/bin/llvm-strip + +.PHONY: install clean + +all: bmod.o + $(CC) -fno-pic -flto=thin -fvisibility=default -fsanitize=cfi -fsanitize-cfi-cross-dso -fno-common -c bmod.c + $(LD) -r -o bmod.ko bmod.o + +install: + adb push bmod.ko /data/local/tmp + +clean: + rm bmod.o bmod.ko diff --git a/README.md b/README.md index d8de5c2..dfe048c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,145 @@ -# standalone-kernel-module -Compile kernel module without kernel headers \ No newline at end of file +The process of [kernel modules compilation](https://www.kernel.org/doc/html/v5.6/kbuild/modules.html) depends heavily on accessibility of header files provided by kernel. There are exist no guarantees on ABI compatibility even between minor kernel versions. This is [deliberate design choice](https://github.com/torvalds/linux/blob/v4.9/Documentation/stable_api_nonsense.txt) made by kernel developers. Kernel module compilation *depends* on kernel headers. + +> But what to do if, for the variety of reasons, for the kernel you are interest in, headers are unavailable? + +This article focuses on Android, but most of information and techniques discussed here can be easily applied to generic Linux kernel as well. + +# ELF symbols stealing + +The main idea, is to use Android NDK to compile generic Linux kernel module. And embed it with ELF symbols collected from some existing kernel module (see `/vendor/lib/modules/*.ko`). In such a way, that the loader would be able to recognise and resolve all necessary dependencies and definitions. + +## .modinfo + +```bash +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 collection of 'TAG=VALUE' entries separated by NULL character '\0'. The most important part is `vermagic`, since loader is using it do identify which kernel headers were used for module compilation. + +```bash +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 + +```bash +objdump -h $KMODULE | grep .gnu.linkonce.this_module + .gnu.linkonce.this_module 00000340 0000000000000000 0000000000000000 .... + ^^^^^^^^ section size +``` + +### name offset + +```bash +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 + +```bash +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](https://mjmwired.net/kernel/Documentation/kbuild/modules.txt#429) encoded function signature +- a ASCII encoded function name + +```bash +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 ................ +``` + +```bash +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 +```c + int printk(const char *fmt, ...); +``` + +2. Add another entry to the `__versions` section + +# Misc + +## Module loading + +```bash +insmod module_name.ko +``` + +## List loaded modules + +```bash +lsmod +``` + +## Logs + +```bash +dmesg | grep insmod +``` + +## Module unloading + +```bash +rmmod MODULE_NAME # from .gnu.linkonce.this_module section +``` + diff --git a/bmod.c b/bmod.c new file mode 100644 index 0000000..0e3428c --- /dev/null +++ b/bmod.c @@ -0,0 +1,132 @@ +/************************/ +/** **/ +/** PLT section **/ +/** **/ +/************************/ +// 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 irrelevant + + +/************************/ +/** **/ +/** .modinfo **/ +/** **/ +/************************/ +// objdump -s -j .modinfo $KMODULE +#define AUTHOR "someone" +#define DESCRIPTION "Simple =^.^= kernel module" +#define NAME "bmod" +#define LICENSE "GPL" +#define VERMAGIC "4.14.186-perf-00288-g7df34d2ae6db SMP preempt mod_unload modversions aarch64" + +__attribute__((section(".modinfo"))) +char modinfo_strings[] = + "author=" AUTHOR "\0" + "description=" DESCRIPTION "\0" + "license=" LICENSE "\0" + "vermagic=" VERMAGIC "\0" + "name=" NAME "\0" + ; + + +/*******************************/ +/** **/ +/** .gnu.linkonce.this_module **/ +/** **/ +/*******************************/ +// 1 - section size +// objdump -h $KMODULE | grep .gnu.linkonce.this_module +// .gnu.linkonce.this_module 00000340 0000000000000000 0000000000000000 00109800 2**6 +// ^^^^^^^^ section size + +// 2 - section content +// 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..... // <<-- NAME offset 0x18 +// 0020 00000000 00000000 00000000 00000000 ................ + +// 3 - entry points offsets +// 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 + +#define L_ONCE_SIZE 0x340 +#define L_ONCE_OFFSET_NAME 0x18 +#define L_ONCE_OFFSET_INIT 0x150 +#define L_ONCE_OFFSET_CLEANUP 0x310 + +int init_module(void); +void cleanup_module(void); + +__attribute__((section(".gnu.linkonce.this_module"), aligned(8))) +struct module { + char __pad0[L_ONCE_OFFSET_NAME]; + char name[sizeof(NAME)]; + + char __pad1[L_ONCE_OFFSET_INIT - L_ONCE_OFFSET_NAME - sizeof(NAME)]; + int (*init_module)(void); + + char __pad2[L_ONCE_OFFSET_CLEANUP - L_ONCE_OFFSET_INIT - sizeof(void*)]; + void (*cleanup_module)(void); + + char __pad3[L_ONCE_SIZE - L_ONCE_OFFSET_CLEANUP - sizeof(void*)]; +} __attribute__((packed)) +__this_module = { + .name = NAME, + .init_module = &init_module, + .cleanup_module = &cleanup_module, +}; + + +/*******************************/ +/** **/ +/** __versions **/ +/** **/ +/*******************************/ +// objdump -s $KMODULE -j '__versions' #dump binary +// modprobe --dump-modversions $KMODULE #dump readable + +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, +}; + + +/* + * The Module + */ +// load: insmod module_name.ko +// logs: dmesg | grep insmod +// unload: rmmod MODULE_NAME # from .gnu.linkonce.this_module section + +int printk(const char *fmt, ...); +#define MTAG "["NAME"] " + +int init_module(void) { + printk( MTAG"Hello, world!\n"); + + return 0; +} + +void cleanup_module(void) { + printk( MTAG"Goodbye, world!\n"); +}