Ein Blog

Für ein CTF musste ich letztens einen Netfilter bauen, der ein- und ausgehende IP-Pakete patcht. Genauer gesagt mussten Daten in TCP-Header geändert werden (mehrere Reserved-Bits mussten gesetzt werden).

Nachdem ich hoffnungslos versucht hab, irgendwelche random TCP/IP-Userspace-Stacks mit Raw-Sockets oder TUN/TAP-Devices zum Laufen zu bringen, war die “einfache” Lösung ein Netfilter-Kernelmodul. Da man dazu immer mal wieder verschiedene Varianten findet und 90% der Tech-Blogs da draußen nicht in der Lage sind, Zeilenumbrüche und Quotes ordentlich zu formatieren, hier eine “einfache” Lösung (basiert teilweise auf dieser):

ultra_hack.c:

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

MODULE_LICENSE("GPL-3.0");
MODULE_AUTHOR("Ultra Hacker 3000");
MODULE_DESCRIPTION("Super-TCP-Hack");

static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
	if (!skb)
		return NF_ACCEPT;

	struct iphdr *iph = ip_hdr(skb);
	if (iph->protocol != IPPROTO_TCP)
		return NF_ACCEPT;

	struct tcphdr *tcp_header = tcp_hdr(skb);

	tcp_header->res1 = 0b1010;

	printk(KERN_DEBUG "ultra hack is hacking.\n");

	return NF_ACCEPT;
}

static struct nf_hook_ops nf_hook_data = {
	.hook = hook_func,
	.hooknum = NF_INET_POST_ROUTING,
	.pf = PF_INET,
	.priority = NF_IP_PRI_FIRST,
};

static int __init init_function(void)
{
	printk(KERN_INFO "ultra hack module is loading.\n");
	nf_register_net_hook(&init_net, &nf_hook_data);
	return 0;
}

static void __exit exit_function(void)
{
	printk(KERN_INFO "ultra hack module is unloading.\n");
	nf_unregister_net_hook(&init_net, &nf_hook_data);
}

module_init(init_function);
module_exit(exit_function);

Ein paar Anmerkungen:

  • PF_INET steht für IPv4
  • die hooknum gibt die Stelle an, an der der Filter eingesetzt wird (das kann man noch Pre-Routing machen etc, einfach nach anderen Konstanten schauen).
  • Die Signatur der hook_func ändert sich gerne mal bei Kernel-Updates. Die aus dem Beispiel funktioniert für 5.11.0.

Hier noch die Makefile dazu:

obj-m := ultra_hack.o
LKM-objs += ultra_hack.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
	rm -r -f *.mod.c .*.cmd *.symvers *.o
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

Zum Laden, Entfernen etc:

# kompilieren
make all

# laden
sudo insmod ultra_hack.ko

# schauen, ob es da ist
lsmod

# schauen, ob printk-Nachrichten auftauchen
sudo dmesg -w

# entfernen
sudo rmmod ultra_hack

Man kann das auch mit nfqueue und Python machen.