ENPM809V

Introduction to Kernel Security

Agenda

  • Introduction to the Linux Kernel
  • Building a Kernel Module
  • Character Devices
  • Basic Privilege Escalation

The Kernel

What is the Kenrel?

  • Code in the operating system that interfaces between hardware and higher-level applications.
  • The Linux kernel is a free-open source operating system in Linux Distributions
    • Modular, monolithic, multitasking, Unix-Like

Application

Application

Application

Application

System Call Interface/Interrupt Handling

Kernel Subsystem

Device Drivers

Application

Application

Application

Application

System Call Interface/Interrupt Handling

Kernel Subsystem

Device Drivers

x86 Protection Rings

  • An protection mechanism in x86_64 CPUs to prevent unauthorized access to the kernel.
  • 3 protection rings (but mostly use level 0 and 3)
    • Level 0 = Kernel and Drivers
    • Level 3 = Applications

x86 Protection Rings

  • At ring 3, the CPU can
    • Use most x86 instructions
    • Access unprivileged memory
  • At ring 0, the CPU can
    • Do almost everything at ring 3
    • Access Privileged memory
    • Use Special instructions

Switching Protection Rings

  • Userspace programs can ask the kernel to execute something through a few vectors:
    • System calls - occurs by calls directly from userspace applications
    • Interrupts - occurs indirectly through the use of instruction that cause exceptional conditions

System Calls

  • A userspace program executes the syscall instruction
  • How does this happen?
    1. The address of the instruction following the syscall is placed in to RXC
    2. RIP is now the Kernel's System call handler
      • Provided by the OS at boot time
      • Generally stored in the LSTAR register on x86 machines
    3. Ring level is set to 0 (CPL)
  • After the kernel finishes, RIP is set to whatever is in RCX, transferred back to ring 3

Privileged Instructions

  • Ring 0 Code has access to privileged instructions
    • Reacts to how the system reacts to interrupts/exceptions
      • LIDT - Load Interrupt Descriptor Table Register
      • LLDT - Load Local Descriptor Table
      • LGDT - Load Global Descriptor Table Register
      • LTR - Load Task Register
    • Reading/Writing Mahcine-specific registers
      • RDMSR, WRMSR
    • Virtual machine opcodes
      • VMCALL, VMLAUNCH, VMRESUME, VMXON, VMXOFF
    • Others too...

Working with Linux Kernel

What do you need?

  • Linux Kernel Source
    • Obtainable from apt
    • Get it from kernel.org
  • Elixir - https://elixir.bootlin.com - Helpful source lookup

Compiling the Kernel

  • Why would one want to compile the kernel themselves?
    • Enabling debugging Features
    • Add functionality
    • Change Functionality
    • Building for a new architecture
  • The virtual machine has a customized kernel
    • We will compile it once, but not more than that because it takes. a long time to do

Creating our debugging Environment

  • We will be spending some time creating our debugging environment
    • We will be using VMWare Workstation/Fusion to do this
    • We will create a virtual serial port to communicate over 
    • We will also use dmesg for print statements (quickest way to debug)
    • You might need to continue to this at home
  • Alternatively, you can use pwn.college in practice mode

Creating our debugging Environment

  • The quick and dirty way of doing it - just use dmesg and printf
  • The not-so-quick way - compiling a kernel and enabling kernel gdb
    • https://phoenixnap.com/kb/build-linux-kernel

Kernel Modules

  • The primary way for extending kernel functionality
  • Allows for various different functionality within the Linux kernel
    • Support a new filesystem
    • Implement a device/driver
    • Implement a new protocol
    • New Scheduling algorithm 

Kernel Modules

#include <linux/module.h>

static int __init start(void)
{
    printk(KERN_INFO "Hello World!\n");
    return 0; 
}

static void __exit mod_stop(void)
{
    printk(KERN_INFO "Goodbye World\n");
    return;
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Michael Wittner");
MODULE_DESCRIPTION("Simple Demo.");
module_init(start);
module_exit(mod_stop); 

Defines which functions called on load/removal of a kernel module

Macros for licensing and defining init and exit

Where can you find printk messages?

Kernel Modules

# Basic Makefile for Kernel Modules - Kernel module with one C file

obj-m := example.o # Your C file should match the H file

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    
# Inserting kernel modules

insmod example.ko optparam1="param" optparam2=2

#Removing modules

rmmod 

#If on pwn.college practice mode, do this instead
vm build /path/to/.c/file
vm start
vm connect
#Look at vm --help and vm <command> --help for more details

When a kernel module is inserted...

  • The system call sys_init_module is invoked
  • The code is copied into memory
  • The license is checked
  • The symbols used by the module are checked in the kernel symbol table (and resolved if found)
    • That symbol must be exported, can find it in /proc/kallsyms
  • The module's init function is invoked

Note: to export symbols, use macro EXPORT_SYMBOL

How do I lookup API Calls

  • https://elixir.bootlin.com
  • Clone the repository - https://github.com/torvalds/linux
    • Checkout the tag of the version you are working with.
    • E.g. if working with kernel version 5.4 - git checkout tags/v5.4
    • Grep for the API call you are looking for
  • Google/ChatGPT

Time to build your own Kernel Module!

Classwork

  • You are going to build your own Hello World kernel module!
    • Source code is provided to you via pwn.college
    • Use either your own environment or the pwn.college environment to build a hello world kernel module.

Kernel Data Structures

Many Many Structures

  • Structures contain data for the majority of kernel data
    • Tasks
    • Kthreads 
    • Audit 
    • Files

Many Many Structures

  • Tend to be generalized so that it can be applied anywhere without sacrificing performance
    • Linked lists - /include/linux/list.h
    • Queues - /include/linux/kfifo.h
    • Hash maps - /include/linux/hashtable.h
    • Radix trees - /include/linux/generic-radix-tree.h
    • RB trees - /include/linux/rbtree.h

Slightly Different Than Traditional Datastructures

DATA

Prev

Next

DATA

Prev

Next

DATA

Prev

Next

Slightly Different Than Traditional Datastructures

DATA

Prev

Next

DATA

Prev

Next

typedef struct list_head 
{
    struct list_head *prev;
    struct list_head *next;
};

struct some_other_struct
{
    char *data1;
    int data2;
    struct list_head *head;
}

https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch10s05.html

Embedding Structures

typedef struct example_struct 
{
    struct example_struct *prev;
    struct example_struct *next;
};

struct some_other_struct
{
    char *data1;
    int data2;
    struct example_struct *head;
};
  • Embedding structures is quite common in the Linux Kernel
    • task  -> file
    • task -> audit
    • task -> another task
  • Structures can also be randomized
    • Security Feature __randomize_layout

Struture Randomization

typedef struct example_struct 
{
    struct example_struct *prev;
    struct example_struct *next;
};

struct some_other_struct
{
    char *data1;
    int data2;
    struct example_struct *head;
} __randomize_struct;
  • Many structures are randomized at compile time
    • Difficult to attack based on offset
    • This is where macros come in

offsetof()

  • Finds the offset of a member given a structure type
  • This is defined as a standard part of C
#define offsetof(a,b) ((int)(&(((a*)(0))->b)))

container_of

#define container_of(ptr, type, member) ({ \
    const typeof( ((type *)0)->member ) *__mptr = (ptr); \
    (type *)( (char *)__mptr - offsetof(type,member) ); })
  • Built-in Macro to determine who the parent structure is
  • Takes a pointer to the member (child) structure
    • Subtracts the pointer of the member to the offset it is located in the parent definition. 
    • End result = address to parent

task_struct

  • The task_struct is used to manage tasks
    • A task is the kernel's way of managing processes/execution context
  • Contains MANY data fields ranging from memory, CPU Usage, or other data structures (such as the audit_context)
  • Also contains information like UID and EUID
  • Access the task_struct of the currently running process by using the macro - current
  • Located in /include/linux/shed.h

Kernel Threads

What are they?

  • Kernel threads are tasks. As such they run in their own context
  • API can be found in /include/linux/kthread.h
    • Has functions like kthread_create
  • Kernel threads can only be created by other kernel threads
  • We can track kernel threads through the task_struct
    • Can you figure out how/why? 

API Calls

  • kthread_create - creates a new kernel thread
  • wake_up_process - start a kernel thread (or other task)
  • do_exit - terminate a kernel thread
  • kthread_stop - Flag the kernel thread that it should stop
    • It will wake up a sleeping kthread if necessary to set the flag
  • kthread_should_stop - check to see if the kernel thread should stop
  • allow_signal - indicates that the particular kthread can recieve the indicated signal
  • set_current_state - sets the state (TASK_INTERRUPTABLE) makes it interruptable
  • schedule/ssleep - give up the CPU 

Synchronization

What the Kernel Provides

  • Wait Queues - FIFO based on sleep
  • Completionn Variables - Sleep until a certain condition is met
  • Spinlocks - Very similar to POSIX Spinlocks 
    • If you don't know what it is man pthread_spin_lock
  • Semaphores - Similar to POSIX Semaphores
    • man sem_overview
  • Atomic Operations
  • Mutexes - Similar to POSIX Mutexes

What the Kernel Provides

  • Wait Queues - /include/linux/wait.h
  • Completion Variables - include/linux/completion
  • Spinlocks - /include/linux/spinlock.h
  • Semaphores - /include/linux/semaphore.h
  • Atomic Operations 
    • /include/linux/types.h (for types)
    • /include/asm-generic/atomic-instrumented (operations)
  • Mutexes - /include/linux/mutex.h
  • We are not going to go over these in depth, you need to do your homework on this. 

Devices

What is it?

  • They are just files on the filesystem...like any other file
    • But have different properties
  • File operations are implemented by the kernel module implemented

What is it?

  • The kernel decides what read/write/open means
    • Each device has it implemented differently
  • Each device is defined by a major/minor number
    • ls -l /dev, cat /proc/devices
    • C for character devices
    • B for block devices
  • Inside the kernel, major/minor uses dev_t, an unsigned 32 bit number
    • 12 bits for major
    • 20 bits for minor
    • Helper macros for assignment

We will focus primarily on character devices today.

Creating the Device

  • register_chrdev_region
    • registers a set number range with the kernel
    • Starting at (maj/min) and requesting a given number of devices
  • alloc_chrdev_region
    • Request for a free region in the kernel
    • Starting at (maj/min) and requesting a given number of devices
  • Mknod - Creates the character device file in userspace
    • sudo mknod ./dev c <maj> <min>
    • Also a system call

What happens when we call mknod

  • We create an inode in the VFS. (What's an Inode)?
    • Contains a dev_t to specify the device associated
      • i_rdev
    • For character devices - contains a struct c_dev
      • i_cdev
    • Contain a pointer to the file_operations associated
      • i_fops

Character Devices

  • A type of device that operates character by character
    • Unlike block devices, which work with multiple characters at a time
  • Information about it in the kerenl is contained in a cdev
    • Has pointer to its owner (struct module)
    • a dev_t field
    • and a file_operations structure as a field
    • Allocate it with cdev_alloc
    • Free with kfree
  • Can be embedded within another structure, but needs to be initialized with cdev_init
    • Register the device with cdev_add

File Operations

struct file_operations my_fops = 
{
    .owner = THIS_MODULE,
    .read = read_func,
    .write = write_func,
    .open = open_func,
    .ioctl = ioctl_func,
    .release = release_func,
};

File Operations

  • A structure of function pointers, which will be the operations for interacting with the device
    • Implementation dependent, all based on how the developer wants the behavior to occur
  • Common file operations implemented include the ones listed above and close (try to find file_operations struct on Elixir)

File Structure

  • struct file is a kernel structure associated with an open file.
    • Goes away when all references are closed
  • Can be found in the processes struct file_structs
    • Found in current->files->fd_array[]
  • Contains references to its inode, file operations, mode, and more.
  • Why is this important?
    • All file operations take this structure as a parameter. Why?
    • So they know what file they are operating on.

Character Devices

  • A kernel I/O method that uses a stream of data
  • All operations (reading, writing, etc.) are performed on a per-byte/character basis.
  • Accessed through the Linux FS (/dev/ttyXX)
  • Acts like a file (have to implement open, read, write  for interaction)

Block Device

  • Similar to a character device, but performs operations on chunks of data
    • Typically powers of two (128, 256, etc.)
  • Linux allows block devices to be accessed as a stream of bytes by applications; thus, very similar to character devices
    • The kernel interface must be a full block
  • Accessed through /dev (e.g. /dev/sda)
  • Examples: Disk drive

Network Devices

  • Not accessible by the file system - provides interfaces to various networks instead
  • Facilitates the transmission and reception of data packets
  • Implement a backend for kernel requests for sending and receiving data

Your First Privilege Escalation

Goal

  • We want to execute a root shell
  • We are given the ability to load any arbitrary kernel module
  • We have no vulnerability to exploit

What we need to do?

  • We need to elevate a userspace process to root
  • We need to ensure that when we call /bin/bash, we are root
  • We need to create a kernel module that any userspace process elevate themselves to root.

How do we do this?

API to Change cred in the Kenrel

  • prepare_kernel_cred(struct task_struct *daemon) - Prepare a set of credentials for a kernel service
    • If we make daemon NULL - it will create root credentials
  • commit_creds(struct cred *creds) - set the credentials for the particular process

 

What if we combine these?

  • commit_creds(prepare_kernel_creds(NULL));

Homework - Part 1

  • You are going to create a character device and interact with it.
  • On pwn.college I added a character device challenge in Kernel Internals.
    • Follow the directions in the README and template.
    • Get the flag and submit your code!

Homework - Part 2

  • Second homework contains a kernel driver loaded with privilege escalation.
  • You are going to figure out how to utilize it to elevate your process to root!
    • Then read the flag

Introduction to Kernel Security

By Ragnar Security

Introduction to Kernel Security

  • 177