initial commit

This commit is contained in:
Andriy Petrov 2024-04-17 12:10:24 +02:00
parent 6c6af00e2a
commit 859ca1d5a2
3 changed files with 298 additions and 2 deletions

22
Makefile Normal file
View File

@ -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

146
README.md
View File

@ -1,3 +1,145 @@
# standalone-kernel-module
Compile kernel module without kernel headers
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
```

132
bmod.c Normal file
View File

@ -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");
}