Simple example of Linux kernel module

Igor Plastov
4 min readDec 30, 2024

--

Today we are create a kernel module that, when loaded, would discard all outgoing and incoming packets containing the IP Security (RFC 1108) option.

It would be safe to use virtual PC for experiments. You can use for example Oracle VirtualBox for creation virtual PC with OS Ubuntu.

Source code of kernel module with comments is shown below:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>

#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/string.h>
#include <linux/byteorder/generic.h>

/* Type of Extended Security Option */
#define IPOPT_EXT_SEC (5 |IPOPT_CONTROL|IPOPT_COPY)


#define M_NAME "Security filter"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Plastov Igor");
MODULE_DESCRIPTION(M_NAME);
MODULE_VERSION("0.01");


/* Structure for registering a function to intercept incoming IP-packets. */
struct nf_hook_ops input_bundle;

/* Structure for registering outgoing IP packet interception function. */
struct nf_hook_ops output_bundle;


int count_security_options(uint8_t *options, size_t length)
{
uint8_t type, len;
size_t offset = 0;
int res = 0;
size_t i;
while (offset < length)
{
type = options[offset];

if (type == IPOPT_END)
{
/* End of Option List*/
break;
}

if (type == IPOPT_NOP)
{
/* No Operation */
printk("Option Type: NOP\n");
offset += 1;
continue;
}

printk("Option Type: %hhu\n", type);
len = options[offset + 1]; // Длина опции
if (len == 0)
{
printk("Wrong Option Length: %hhu\n", len);
break;
}
else
{
printk("Option Length: %hhu\n", len);
}

if ((type == IPOPT_SEC) || (type == IPOPT_EXT_SEC))
{
res = res + 1;
if (len > 1)
{
printk("Security Option Data: ");
for (i = 2; i < len; ++i)
{
printk("%02X ", options[offset + i]);
}
printk("\n");
}
}
offset += len;
}
return res;
}


unsigned int my_nf_hook(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
/* If the header does not contain a capabilities label, return NF_ACCEPT,
to accept the package. */
int res = NF_ACCEPT;
struct iphdr *iph; /* Pointer to the IP packet header. */

if (skb == NULL)
{
return res;
}

iph = ip_hdr(skb);
if (iph == NULL)
{
return res;
}

/* Packet analysis. */
if (iph->protocol <= IPPROTO_MPLS )
{
uint8_t ihl = iph->ihl;
size_t options_length = (ihl * 4) - sizeof(struct iphdr);

if (options_length > 0)
{
uint8_t *options = (uint8_t *) skb->data + sizeof(struct iphdr);
if (count_security_options(options, options_length) > 0)
{
struct tcphdr *tcph = tcp_hdr(skb); /* Pointer to the TCP packet header. */
printk(KERN_INFO M_NAME " TCP packet received: src=%pI4, dst=%pI4, sport=%u, dport=%u\n",
&iph->saddr, &iph->daddr, ntohs(tcph->source), ntohs(tcph->dest));
printk(KERN_INFO M_NAME " packet has security options, dropped.\n");

/* Return NF_DROP to drop the packet. */
res = NF_DROP;
}
}
}

return res;
}


static int __init km_init(void)
{
pr_info(M_NAME " module is loaded.\n");

/* Filling out the structure for registering the hook function
We indicate the name of the function that will process the packages. */
input_bundle.hook = (nf_hookfn*)my_nf_hook;

/* We indicate where the function will fire. */
input_bundle.hooknum = NF_INET_PRE_ROUTING;

/* Specify the protocol family. */
input_bundle.pf = NFPROTO_IPV4;

/* We set the highest priority for the function. */
input_bundle.priority = NF_IP_PRI_FIRST;

/* Filling out the structure for registering the hook function
We indicate the name of the function that will process the packages. */
output_bundle.hook = (nf_hookfn*)my_nf_hook;

/* We indicate where the function will fire. */
output_bundle.hooknum =NF_INET_LOCAL_OUT;

/* Specify the protocol family. */
output_bundle.pf = NFPROTO_IPV4;

/* We set the highest priority for the function. */
output_bundle.priority = NF_IP_PRI_FIRST;

/* Let's register. */
nf_register_net_hook(&init_net, &input_bundle);
nf_register_net_hook(&init_net, &output_bundle);
printk(KERN_INFO M_NAME " module activated.");
return 0;
}


static void __exit km_exit(void)
{
/* We remove the hook function from the chain. */
nf_unregister_net_hook(&init_net, &input_bundle);
nf_unregister_net_hook(&init_net, &output_bundle);
printk(KERN_INFO M_NAME " module deactivated.");
}

module_init(km_init);
module_exit(km_exit);

Building

We will run our module on virtual PC. So we should Install on it tools for compilation:

sudo apt-get install make

Install header files:

sudo apt-get install linux-headers-$(uname -r)

Create some folder place to it source code of module.

For kernel module building we will use makefile. Create in the folder file Makefile with code shown below:

# Add a module to the list of modules to compile
# skm.o – means that the module needs to be built automatically from skm.c
obj-m += skm.o

# Connect the Makefile from kernel-headers using -C,
# which is in the /lib/modules/[kernel version]/build directory
# use the modules target from it to build modules in the obj-m list
# M=$(PWD) we pass the current directory where the sources of our module are located
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

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

To compile Just do in console:

make

In result we will get compiled our module file:

skm.ko

Installation

Now we will install our kernel module:

sudo insmod ./skm.ko

To check result we can request list of installed kernel modules and filter it by grep:

sudo lsmod | grep "skm"

De installation

If something goes wrong or module need some changes de install it by command:

sudo rmmod ./skm.ko

Testing

To check module work we should generate corresponding IP-packets containing mandate options. One of methods just use tool Scapy. We should install it on a host PC:

sudo apt-get install python3-scapy

Now create python script using Scapy, name it by name ysend.py. Don’t forget to insert IP-address of virtual machine to variable ip:

1 from scapy.all import *

#ip=IP(dst='192.168.56.133', options=IPOption_Security(security=0x56))
#ip=IP(dst='192.168.56.134', options=IPOption_Security(security=0))
ip=IP(dst='insert here IP-address of virtual machine', options=IPOption_NOP() / IPOption_Security(security=0))
SYN=TCP(sport=1030, dport=9090, flags='S', seq=10)
SYNACK=sr1(ip/SYN)
my_ack = SYNACK.seq + 1
ACK=TCP(sport=1030, dport=9090, flags='A', seq=11, ack=my_ack)
send(ip/ACK)

payload = 'SEND TCP'
PUSH=TCP(sport=1030, dport=9090, flags='PA', seq=11, ack=my_ack)
packet = ip/PUSH/payload
send(packet)
ls(packet)

Incoming packets drop check

On the host PC we start sending packets like this:

sudo python3 ysend.py

Result we can see by Wireshark:

Packets sent by ysend.py blue mark selects one of those pakets

On virtual PC view log by command:

sudo dmesg

and check that log contains messages like:

Security filter packet has security options, dropped.

Filter is works!

That is all. You can get found update of code on my Github:

https://github.com/chetverovod/securitykernelmodule

Happy new year!

--

--

Igor Plastov
Igor Plastov

Written by Igor Plastov

Electronics engineer who became a programmer.

No responses yet