Logo
blank Skip to main content

A Brief Tutorial on Modifying Network Traffic in Windows and Linux

Enterprises send gigabytes of sensitive data through their networks daily. The bigger the organization, the more data it needs to process and the more maintenance its network requires. In large enterprises, a poorly optimized network may harm the performance of corporate software. And if it has security vulnerabilities, the network becomes an entryway for hackers.

Intercepting network traffic to analyze and modify it is an important part of improving network security and performance. In this article, we describe how to intercept network traffic and modify it in Windows and Linux (both in user mode and kernel mode). We’ll use Windows Filtering Platform (for Windows) and Netfilter (for Linux). You can find the code for our solutions in the Apriorit repository on GitHub.

This article will be useful for development teams that plan on working with network management solutions and want to improve their knowledge.

4 cases when you might need to modify network traffic

Intercepting and modifying user traffic is a challenging process that can help you improve the protection of your network, manage its overall state, and prevent outside attacks and insider threats.

Here are four common scenarios when you may need to modify incoming network traffic:

Detect security threats. Hackers often listen, intercept, and modify network traffic to perform man-in-the-middle (MITM) and other attacks as well as ARP spoofing. Firewalls and intrusion detection systems are supposed to stop such attacks, but they don’t monitor all traffic that enters your network from outside your organization. Monitoring and modifying traffic helps you detect such threats. You can modify malicious traffic to make it safe or block hacking attempts altogether.

Improve network performance. Adding modifications like classes and marks to traffic makes it easier for your network to understand how to treat different packets. Using marks as priority levels, you can program the devices in your network to prioritize the processing of traffic to make your network more productive. Another way to use traffic marks is to add custom packets to your network, analyze the network’s performance, and detect bottlenecks.

Automate quality assurance (QA) for web-based solutions. Testing the quality of online solutions like websites, applications, and application programming interfaces (APIs) often requires a QA engineer to click through hundreds of pages and check if the tested solution returns the correct result. With tools like Fiddler Everywhere, you can configure traffic flows to imitate testing requests. Such tools also allow you to capture the response of a tested solution, compare it to similar responses from the tested solution, and detect unusual or dangerous traffic.

Limit access to forbidden resources. Many organizations ban access to certain websites and applications to improve network security and employee productivity. Yet tech-savvy employees may find ways around this. To enforce access restrictions, you can create a solution that listens to the network, detects incoming traffic from forbidden addresses, and then censors or blocks it.

Each of these network traffic modification use cases requires applying dedicated software, but all of them are based on network traffic modification technologies. Let’s explore the basic technologies for working with traffic in Windows and Linux, along with practical examples.

Related services
Remote Network Monitoring Solutions

Preparing to modify traffic

In this article, we’ll modify only the Transmission Control Protocol (TCP) header of SYN packets addressed to port 6044 for IPv4. These packets ask the port for a connection. We’ll add the TCP option to the TCP header representing the operating system that initiated the TCP connection. Using our example, you can add your own modifications to other parts of SYN packets.

The TCP protocol allows for adding up to 40 bytes of TCP options data for each TCP header. Some TCP options have a defined kind, others are reserved. The list of options with defined kinds can be found in the TCP documentation.

We’ll use option 100 because it isn’t connected to any specific kind. For our TCP option, we’ll store only one byte representing the operating system: 1 for Windows or 2 for Linux. You can find the full code for our sample solutions in the Apriorit repository on GitHub.

Let’s start working on our solution by looking for technologies for network traffic modification in Windows and start our packet modification.

Read also:

Comparison of User Mode and Kernel Mode Applications for Modifying HTTP Traffic

Modifying network traffic in Windows

Windows offers several network technologies to modify network traffic:

  • Windows Filtering Platform (WFP) is a set of services that provide developers with tools to monitor, filter, and modify network traffic at several layers of the network stack.
  • Transport Driver Interface (TDI) helps drivers communicate and work with various network protocols. This technology is marked for deprecation in Microsoft documentation.
  • Network Driver Interface Specification (NDIS) is a set of libraries that work like an interface between network devices, their drivers, and the network.

We’ll use WFP because it provides us with the easiest way to intercept network traffic. WFP can work with the network from kernel and user modes. Since we’re going to recreate a network packet and modify the NET_BUFFER_LIST structure to change the TCP header, we’ll need to work from the kernel mode and write a WFP callout driver.

WFP can work with different network layers. In our case, we choose to work with the FWPM_LAYER_OUTBOUND_TRANSPORT_V4. This layer is the first where we use a technique to intercept network traffic according to the TCP packet flow description in Microsoft’s documentation.

To get the TCP header in the callout for FWPM_LAYER_OUTBOUND_TRANSPORT_V4, we need to get NET_BUFFER_LIST for the required packet. Let’s do it with this command:

C
NET_BUFFER* netBuffer = NET_BUFFER_LIST_FIRST_NB(netBufferList);

The NET_BUFFER_LIST in SYN packets always contains only one NET_BUFFER structure without any payload. The header is always stored in NET_BUFFER.

Since we’re adding data to our TCP header, the packet with the new header requires more space than the original. That’s why we need to drop the existing packet and create a new one. To drop the existing packet, we can set the action for current callout to FWP_ACTION_BLOCK and absorb the current packet:

C++
classifyOut->actionType = FWP_ACTION_BLOCK;
SetFlag(classifyOut->flags, FWPS_CLASSIFY_OUT_FLAG_ABSORB);

Then we need to calculate the size of the new TCP header and create a new NET_BUFFER_LIST for it. The size of the TCP header is a multiple of 4 bytes, so we need to add padding if the final header size isn’t a multiple of four. Here’s how we do it:

C++
constULONGnewTcpHeaderSize = extraOptionSize + originalTcpHeaderSize + 1;

You can examine the InitializeNetworkBufferListStorage function in the source code from our GitHub repository to get more information on creating and initializing the new NET_BUFFER_LIST.

When the new NET_BUFFER_LIST is ready, we need to:

  1. Copy information from the original NET_BUFFER to the new NET_BUFFER
  2. Add the additional TCP option to the end of the TCP header
  3. Insert the newly created packet back into the network stack

Let’s do it with the following code:

C++
const auto origTcpHeaderSize = GetTcpHeaderSize(origTcpHeader);
RtlCopyMemory(newTcpHeader, &origTcpHeader, origTcpHeaderSize);

// TCP offset is specified in 32-bit words, so we need to multiply its value by 4char* 
extraOptions = reinterpret_cast<char*>(Add2Ptr(newTcpHeader, origTcpHeaderSize));

// TCP options from 79-252 are reserved, so we can use a value from this range
extraOptions[0] = 100;

// Size in bytes of TCP option including Kind and Size fields
extraOptions[1] = 3;

// Set option value 1 for Windows
extraOptions[2] = 1;

// Set padding byte to 0
extraOptions[3] = 0;

// DataSize specifies the size of the TCP header in 32-bit words
newTcpHeader->offset = newTcpHeaderSize / 4;
newTcpHeader->check = 0;
return FwpsInjectTransportSendAsync(g_injectionHandle,
nullptr,
endpointHandle,
0,
&storage.params->params,
AF_INET,
compId,
storage.list,
CompleteCallback,
&storage);

Since we are working on the FWPM_LAYER_OUTBOUND_TRANSPORT_V4 layer, we don’t need to calculate the checksum for the TCP header. Windows will do it for us when we add the packet to the Send queue.

With that, we are finished with modifying network traffic, and the modified packet is returned to the network.

Note: In this example, we make all changes in the callout because we don’t use any long-term operations. But if you plan to add long-term operations like requests to services, remember that they should be processed in a separate worker because our callout can be processed at the DISPATCH level.

When the modified packet is returned to our callout, we need to skip it to avoid creating a never-ending cycle for this packet. We can do it with the following code:

C++
const auto injectionState = FwpsQueryPacketInjectionState0(g_injectionHandle,
static_cast<NET_BUFFER_LIST*>(layerData),
nullptr);
 
// Skip packets injected by our driver
if (FWPS_PACKET_INJECTED_BY_SELF == injectionState ||
FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF == injectionState)
{
return;
}

After that, the modified packet is successfully returned to the network, and our task is completed. Now, let’s look at network traffic modification in Linux.

Read also:
Controlling and Monitoring a Network with User Mode and Driver Mode Techniques: Overview, Pros and Cons, WFP Implementation

Modifying network traffic in Linux

In Linux, we can work with traffic directly from kernel mode or use some additional library to work from user mode. Working with user mode is safer since an error will influence only the process you’re working with. An error in kernel mode will affect the whole system. However, kernel mode doesn’t require you to configure iptables, which makes it useful for tasks you can’t do with iptables and the Netfilter queue, or NFQUEUE. 

Let’s take a look at both approaches, starting with user mode. We’ll use Netfilter to work in both modes. This tool helps us filter packets and log packet filtering, translate network addresses and ports, queue userspace packets, and perform other operations.

Modifying network traffic in user mode

To change network traffic in user mode, we’ll be using NFQUEUE, a module that helps us manage network packets in iptables. We’ve already examined how this module works in our previous article, so in this one, we focus on changing the TCP header.

To work with NFQUEUE, we need to configure iptables for the target port, which is 6044:

ShellScript
iptables -t mangle -A POSTROUTING -p tcp --dport 6044 -j NFQUEUE

Then, we create a new packet buffer for extended TCP header we extended earlies in the callback for NFQUEUE:

C++
pkt_buff * pkBuff = pktb_alloc(AF_INET, rawData, len + extraOptionSize + 1, 0);

As with traffic modification in Windows, we need to take into account that the size of our packet changes from three to four bytes because we added new information to it. Let’s update IP header information and the checksum for the IP header accordingly:

C++
ip->tot_len = htons(ntohs(ip->tot_len) + extraOptionSize + 1);
nfq_ip_set_checksum(ip);

Then, we add a new TCP header option and recalculate the checksum for TCP header:

C++
// TCP offset is specified in 32-bit words, so we need to multiply its value by 4char* 
extraOptions = reinterpret_cast<char*>
(tcp) + tcp->doff * 4;

// TCP options from 79-252 are reserved, so we can use a value from this range
extraOptions[0] = 100;

// Size in bytes of TCP option including Kind and Size fields
extraOptions&[1] = 3;

// Set option value 2 for Linux
extraOptions[2] = 1;

// Set padding byte to 0
extraOptions[3] = 0;

// Update data offset for TCP header
tcp->doff += 1;

// Update TCP header checksum
nfq_tcp_compute_checksum_ipv4(tcp, ip);

Finally, let’s inject the new packet to the network queue and mark it as accepted:

C++
nfq_set_verdict(queue, ntohl(ph->packet_id), NF_ACCEPT, pktb_len(pkBuff), pktb_data(pkBuff));

Read also:
Modifying Network Traffic with NFQUEUE and ARP Spoofing

Modifying traffic in kernel mode

To change traffic in kernel mode, we need to create a kernel module and insert it into the system. Compared to the user mode solution, we don’t have to configure iptables because we will do all filtering in the kernel module.

To intercept the SYN packet, we need to register the Netfilter hook for the NF_IP_LOCAL_OUT, which is the hook called for outbound packets. Here’s how we do it:

C++
nfho = (structnf_hook_ops*)kcalloc(1, sizeof(structnf_hook_ops), GFP_KERNEL);
/* Initialize netfilter hook */
nfho->hook  = (nf_hookfn*)callback;    /* hook function */
nfho->hooknum  = NF_INET_LOCAL_OUT;    /* sent packets */
nfho->pf = PF_INET;                    /* IPv4 */
nfho->priority    = NF_IP_PRI_FIRST;   /* max hook priority */
return nf_register_net_hook(&init_net, nfho);

Network packet data in the kernel is handled using sk_buff structures. We can get the sk_buff structure for the current packet in the callback for the hook in the skb parameter.

To modify the TCP header, we need to copy information from the current sk_buff and add new data to the new buffer. We can do this using the skb_copy_expand function:

C++
new_skb = skb_copy_expand(skb, skb_headroom(skb), skb->tail + 4, GFP_ATOMIC);

Similarly to the user mode example, we also need to modify the IP header’s total length and checksum to make them correspond with the new packet size:

C++
iph->tot_len = htons(ntohs(iph->tot_len) + 4);
iph->check = 0;
iph->check = checksum(0, (uint16_t *)iph, iph->ihl * 4);

Let’s also add options data and modify the TCP checksum and size for the TCP header:

C++
// TCP offset is specified in 32-bit words, so we need to multiply its value by 4
extraOptions = (char*)tcph + tcph->doff * 4;
// TCP options from 79-252 are reserved, so we can use a value from this range
extraOptions[0] = 100;
// Size in bytes of TCP option, including Kind and Size fields
extraOptions[1] = 3;
// Set option value 2 for Linux
extraOptions[2] = 2;
// Set padding byte to 0
extraOptions[3] = 0;
 
// Update data offset for TCP header
tcph->doff += 1;
 
// Update TCP header checksum
tcph->check = 0;
tcph->check = tcp_checksum(iph, tcph);

After that, we need to drop the original packet from the network and insert the new one:

C++
if (ip_local_out(state->net, state->sk, new_skb)) {
kfree_skb(new_skb);
return NF_ACCEPT;
}    
printk(KERN_ALERT "\n Added TCP option for SYN packet for Dest port %d\n", ntohs(tcph->dest));
return NF_DROP;

After insertion, the new packet will be processed by Netfilter, so it will come again to our callback. To avoid that, we need to skip the processing of this packet by checking the last option kind in the packet:

C++
extraOptions = (char*)tcph + tcph->doff * 4 - 4;
if (extraOptions[0] == 100) {
printk(KERN_ALERT "\n Skip the already processed packet that has option\n");
return NF_ACCEPT;
}

After that, our modified traffic packet is returned to the network. You can look at the details of our sample solution in our GitHub repository.

If you’re also interested in Linux drivers, explore our article about designing Wi-Fi drivers for Linux.

Related services
Kernel and Driver Development

Conclusion

Network traffic modification is a challenging task that requires a deep understanding of the network stack and nuances of operating system operations. When done correctly, modifying traffic can help you improve the security and performance of your corporate network and automate certain QA activities.

We’ve described the key techniques for modifying traffic in Windows in Linux to help you understand how they work. Our network monitoring and management experts often use these approaches as the basis for their work to build network monitoring and cybersecurity solutions.

Reach out to our experts and leverage their knowledge to improve your network’s security and performance.

Tell us about your project

Send us a request for proposal! We’ll get back to you with details and estimations.

By clicking Send you give consent to processing your data

Book an Exploratory Call

Do not have any specific task for us in mind but our skills seem interesting?

Get a quick Apriorit intro to better understand our team capabilities.

Book time slot

Contact us