After having started to use libraries like libpcap or WinDivert, I got curious about low-level networking protocols like Ethernet, IP, ICMP, ARP, TCP or UDP. Especially the Address Resolution Protocol (ARP) fascinated me, as I have previously used tools like Ettercap or Cain & Abel to experiment with Man-in-the-middle attacks using ARP poisoning, however, I did not know how ARP poisoning worked in detail.

Basically, using an ARP request, you can request the MAC address of a particular host behind a local IPv4 address. You will then receive an ARP response containing the MAC address from that host.

The idea behind ARP-spoofing or ARP-request-posioning is to redirect the local network traffic through your system by forcing everyone to belive you are the gateway. To do that, you can simply send a modifed ARP-response that contains your MAC address, but the IPv4 address of the gateway.

The core of harpoon requires libnet, which uses libpcap to construct and send raw network packets, including ARP packets.

Sending modified arp packets requires just a few lines of code:

void main()
    char errbuf[LIBNET_ERRBUF_SIZE];
    auto handle = libnet_init(LIBNET_LINK, nullptr, errbuf);

    libnet_build_arp(1, 0x0800, 6, 4, ARP_REPLY, sha, spa, tha, tpa, NULL, 0, handle, 0);
    libnet_build_ethernet(tha, sha, 0x0806, NULL, 0, handle, 0);



The libnet_build_arp function requires our MAC address (sha), the gateway IPv4 address (spa), the target MAC address (tha, which has to be set to broadcast: FF:FF:FF:FF:FF:FF) and the IPv4 address of the target to attack (tpa), which can either be a broadcast address ( to attack everyone, or a specific host in the network.

Although, this won’t work, as the system will quickly rebuild its ARP table with the correct MAC address of the gateway. To prevent it from doing that, the packet has to be sent continuously in a loop every few milliseconds.

Now that all the traffic is sent to our system, we can go a step further and decide whether the traffic should pass through or if it should be dropped. On POSIX systems this can simply be controlled by enabling or disabling the ip-forwarding sysctrl:

int state = 0; // or 1
sysctlbyname("net.inet.ip.forwarding", NULL, NULL, &state, sizeof(state));

On Windows however, this is slightly more complex: The Routing and Remote Access service is responsible for forwarding packets. In order to forward the traffic, this service has to be started, if it’s not running. To drop all packets, it has to be stopped. The corresponding code can be found here

Finally, I thought it would be neat to wrap it up in a nice GUI. To stay platform independent, I decided to go with the nuklear UI toolkit. However, a rendering backend has to be implemented manually. The proper solution would have been to use either OpenGL or Vulkan, but ironically, I chose DirectX11 (for simplicity), which broke the cross-platform compatibility. This is what the final result looks like:


It was at this point, where the tool started to drift into a Windows-only state. As you can see, it discovers a list of hosts in the local network. This was implemented using the NT API only, as I was too lazy to implement a platform independent solution.

To make this tool somewhat usefull, I added the ability to dump the redirected traffic in a pcap file, which can be viewed using e.g. Wireshark. Using a custom, rather complex filter, I was able separate my own packets, from the foreign ones directed through the system. Unfortunately, this was done using even more Windows-only APIs.

In the future, I should maybe implement the missing cross-platform parts, but other than that I’m still very happy that the tool works the way it should. On the other hand though, it’s very frightening to see how easy it is to get control over the network with just a few lines of code. This shows once again how important it is to stay up-to-date with latest encryption technologies, to make any kind of sniffed data useless.

This time, the whole source code is publicly available on GitHub.