In one of our recent penetration testing projects, we needed to find an easy yet efficient solution for monitoring and analyzing traffic between the Linux kernel and the user space. We developed an approach that combines the use of Netfilter queue (NFQUEUE) and Address Resolution Protocol (ARP) spoofing, a traffic manipulation technique that works similarly to a Man in the Middle (MITM) attack.
The combination of NFQUEUE and ARP spoofing can be used for analyzing network traffic, investigating data leaks, examining the behavior of a particular application, and analyzing the current state and performance of the system as a whole.
For those who aren’t familiar with netfilter modules, NFQUEUE is a kernel and user mode module for managing network packets in iptables. It allows you to write netfilter target modules in user space. NFQUEUE works through Netlink sockets, although it’s way easier to work through the default library, libnetfilter_queue.so.
Depending on the task at hand, you can use NFQUEUE for traffic filtering or traffic shaping. Also, NFQUEUE targets are widely used by both open source and commercial intrusion prevention systems (IPS). But NFQUEUE isn’t the only option for monitoring and manipulating network traffic. You can also use libcap for data monitoring and libnet for crafting TCP segments for your targets.
Using NFQUEUE for an MITM attack, you can get full visibility over the traffic sent between your targeted hosts. In this case, packet modification is performed in two stages:
- Сonfiguring iptables
- Writing a user space program for using NFQUEUE
Let’s look closely at these stages.
In order to manipulate packets using NFQUEUE, you need to configure an iptables rule first. In the following example, we handle all outgoing TCP traffic to port 5566:
You can specify the queue number and distinguish two different queues using the option --queue-num <num>. By default, the queue number is 0. Now, all traffic that matches the rule will be pushed into queue 0 and will wait until someone handles the queue.
In order to filter iptables in user space with NFQUEUE, you first need to write a special user space program. Below, we provide a detailed tutorial on writing such a program in twelve steps, with code examples and detailed explanations. To see the whole code of an example user space program, scroll down to the end of this section.
Step 1. First, let’s start with a user space application.
At this step, you need to include all necessary headers. In our example, we’ve used pktbuff, libnetfilter_queue_ipv4, and libnetfilter_queue_tcp.
Step 2. Now, let’s specify the open library context that’ll be used for future library calls and configurations.
Step 3. When creating an open queue, we use the following four arguments:
- The nfq handler
- The queue number, which can be set using the option --queue-num when configuring iptables rules
- The callback function that will be called by default when the packet is intercepted
- The context that will be passed to the callback after packet interception
Step 4. Next, we specify how much data we’ll get in the callback. Using the NFQNL_COPY_PACKET function, you can copy the whole packet.
Step 5. Now we need to handle the queue. The nfq_fd() function returns the file descriptor of a Netlink socket. The file descriptor can be used in the epoll() function or select()/poll() function for multiplexing.
All you need to do is read data to the buffer and send it to the nfq_handle_packet() function. This function will parse Netlink structures, retrieve a network packet, and eventually call the callback. In our example, it calls the netfilterCallback() function.
Step 6. At this point, we can implement the netfilterCallback() function.
The main purpose for implementing this callback function is to advance the verdict issued by NFQUEUE. The module can either drop or accept packets, inject packets into a different queue, or re-inject them into iptables (NF_DROP, NF_ACCEPT, NF_QUEUE, or NF_REPEAT, respectively). In our case, we’re interested in passing all packets through.
Note that the nfq_set_verdict() function requires a packet ID. You can retrieve the packet ID from the Netlink message header.
Step 7. Now, let’s investigate and modify a packet. First, we need to get the packet data:
Step 8. Using the data received in Step 7, we can create a pkt_buff struct. This struct works pretty similarly to the Linux kernel sk_buff struct.
The pktb_alloc() function copies your buffer data to keep the raw data untouched. In order to modify a packet, you need to pass new raw data to the nfq_set_verdict() function:
Step 9. Depending on the task at hand, you can manipulate data the way you need. For instance, you can perform SSL stripping. But if you try to change the packet size, then you should handle stream synchronization by yourself (syn and ack numbers, etc.).
Let’s try to reverse the TCP data payload, for example. The pkt_buff struct provides several handy functions. First, let’s use this struct to get the IP header:
Once you get the IP header, you can use and modify it. Next, we should check the transport protocol and retrieve the TCP header.
But before that, you need to call the nfq_ip_set_transport_header() function. Otherwise, the pkt_buff struct won’t parse the next transport header.
Step 10. Let’s get the TCP header.
Now we have the TCP header and can modify it. However, our main interest lies with the TCP payload.
With the help of the nfq_tcp_get_payload() function, we can get a pointer to the payload data. The nfq_tcp_get_payload_len() function will return the length of a whole TCP segment with the TCP header. Therefore, we need to subtract the length of the TCP header.
Step 11. Now, we can finally modify the payload data. In the example below, we reverse it.
Step 12. When modifying the payload data, we need to also recalculate the TCP checksum.
Finally, we can send our verdict using the return nfq_set_verdict() function.
So this is how you can use NFQUEUE for intercepting and modifying packets sent to the user space. However, this approach works only for intercepting routed and forwarded traffic when your machine plays the part of a route or bridge in the network.
Otherwise, you’ll need to intercept the data flow between two hosts located on a local Ethernet network. And this is a task NFQUEUE can’t help you with.
Fortunately, there’s a way to make your targets send their packets through your machine even when you need to monitor and modify traffic between two hosts in a local network. In the next section, we describe how you can intercept packets with the help of ARP spoofing.
ARP spoofing is a well-known cyber attack technique used for redirecting the victim’s traffic to the attacker’s machine by sending falsified ARP messages. We can use this technique to manage data flow in a local Ethernet network and, particularly, to redirect the traffic between two hosts so you can investigate and modify it. ARP spoofing is often used as a pre-condition for executing more complex attacks such as MITM or denial of service.
First of all, let’s look at how the ARP protocol works. All data inside local networks is routed according to MAC addresses. ARP is the protocol that allows you to discover the MAC address associated with a particular IP address. Using this information, it’s possible to link the MAC address of an attacker with the IP address of the victim and redirect traffic sent to the victim to the attacker’s machine.
So in order to redirect traffic to a particular host, we first need to learn its MAC address.
The ARP protocol has two main messages:
- Request: for instance, “Who has IP <target IP> ? Tell <sender IP & sender MAC>”
- Response: for instance, “<sender IP> is at <sender MAC>! To <target IP & target MAC(or broadcast)>”
Below, we provide a simple example of an ARP traffic redirection mechanism.
Let’s assume we have two hosts: host A and host B.
- Host A: IP 18.104.22.168 MAC a:a:a:a:a:a
- Host B: IP 22.214.171.124 MAC b:b:b:b:b:b
Host A wants to send a packet to host B:
- Host A sends an ARP request with the sender IP address 126.96.36.199 and sender MAC address a:a:a:a:a:a, and sets the target IP address as 188.8.131.52.
- Host B sends a response with the sender IP 184.108.40.206 and sender MAC address b:b:b:b:b:b. The target IP address is 220.127.116.11 and the target MAC address is a:a:a:a:a:a.
- Now, host A can send IP packets to host B and use the MAC address b:b:b:b:b:b as the destination in the Ethernet frame.
Now let’s assume we have a third host, host C, with the IP address 18.104.22.168 and the MAC address c:c:c:c:c:c. Here’s what we need to do to redirect traffic from hosts A and B to host C:
- From host C to host A: send an ARP response with the sender IP address 22.214.171.124 and the sender MAC address c:c:c:c:c:c; set the target IP address as 126.96.36.199 and the target MAC address as a:a:a:a:a:a.
- From host C to host B: send an ARP response with the sender IP address 188.8.131.52 and sender MAC address c:c:c:c:c:c; set the target IP address as 184.108.40.206 and the target MAC address as b:b:b:b:b:b.
Now, to send a message from host A to host B, host A will use the MAC address of host C and vice versa. In terms of handling traffic on host C, it’s more like bridge traffic.
Next, you need to enable traffic forwarding:
You can also set an iptables NFQUEUE rule in the PREROUTING, FORWARD, and POSTROUTING chains.
Now let’s see how these techniques work in practice.
First, we need to craft an ARP response packet. In our example, we use a packet socket (man 7 packet).
First, we need to create a socket:
Then, we create an Ethernet ARP reply message:
The craftEthernetArpResponse() function will be examined later.
Finally, we send the packets to the necessary host.
The sll_ifindex interface index should contain the number of the interface. If you know the interface name, you can get the interface number using the getInterfaceIndex() function.
Let’s take a closer look at the implementation of the getInterfaceIndex() function. The implementation of this function requires any socket and the ioctl call SIOGIFINDEX.
Now let’s get back to crafting an Ethernet ARP reply with the help of the craftEthernetArpResponse() function. The whole process consists of three steps:
- Crafting an Ethernet header
- Crafting a basic ARP header
- Pushing data to the buffer
Below, we provide detailed explanations of each step. To see the whole code for writing an ARP sender, scroll down to the end of the section.
Step 1. First we need to craft an Ethernet header:
Step 2. Now we need to create a basic ARP header that contains only the main information: the IP and MAC addresses of our hosts.
Step 3. Then we need to push everything into the buffer:
Now let’s compile that program to arp_sender. The easiest way to intercept traffic between two hosts using the ARP spoofing technique would be to spam these hosts with ARP responses from the third host (the bridge).
Here’s what our ARP attack will look like:
Such an attack will be effective in most Ethernet networks. But in large corporate networks with attentive admins, this attack might have serious consequences :)
Now you know an easy way to monitor packets sent between the Linux kernel and user space with the help of two tools: NFQUEUE and ARP spoofing. When your machine plays the part of a route or bridge between your targets, you can modify packets using a Linux MITM attack with NFQUEUE. NFQUEUE lets you monitor, analyze, filter, and shape network traffic the way you need.
ARP spoofing, in turn, helps redirect packets sent between the target hosts to your machine. Using this technique, you can monitor traffic within your Ethernet network and then use NFQUEUE to modify particular packets.
Have a challenging Linux project in mind? At Apriorit, we have a team of professionals – including ethical hackers – who feel passionate about Linux kernel and driver development and can help with your project.