Sunday, December 6, 2015

MITM 101: ARPSpoofing

In TCP 102, we went over how to capture the IPv4 header and TCP headers on our local interface. But how do we capture them from a remote host? We have to become... the man in the middle (MITM).

Figure 1: Totally inconspicuous eavesdropping 

The Address Resolution Protocol (ARP):

ARP is as old as the internet itself (1982). It's a core technology like TCP/IP and is very important for packet switched networks, like the ones we use today. Without it our interfaces will have no idea what router to hop to next. You may be familiar with DNS (Domain Name System) that resolves an IP address to a domain name (like   

ARP works kind of the same way, in concept at least: It resolves a MAC address to an IP address.
On your local machine, the ARP cache is stored in a table. You can view it with arp:

The ARP Request:

On your local network, a machine might want to send a message to another machine. The host machine might only know the IP address, but the MAC address is required to actually send the message at the data link layer. In this case, the host machine will broadcast an ARP request. Everything on that subnet will receive this message. If you remember from TCP 102, I said to note what type of packet this was. If we go back to this screenshot, you can see its 0x0806:
Figure 2: Address Resolution packet; 0x0806

The ARP Reply:

The intended recipient will send a reply back across the network with their MAC address via Unicast.
Figure 3: "Hey who the $@#! is at"

The ARP Cache:

Obviously, it would suck down our precious 1980s bandwidth if we sent an ARP request every single time we wanted to contact a host on our network. Because of this, the machines on the local network will cache the ARP replies for a short amount of time. When any device wants to send some data to another device, it must first determine the MAC address, even if it knows it's IP address. You can see the problem here. What if we just... told everyone we're someone we're not? After all, who's going to stop us? I got reported by sudo once. Nothing bad happened, so why can't I say I'm Jim for a day.
Figure 4: I guess I don't get 21:9 aspect ratio this year.

The ARP Header:

Well, just like TCP and IP, ARP has a header too.

Figure 5: Note the addresses wrap around to another offset.

We'll use this information to craft our very own evil ARP reply. We've gone in detail over TCP and IP headers in TCP 102, so this one shouldn't need much explaining.

Anatomy of the MITM:

Here's our new fancy arpspoofer code:

We'll also need the sniffer code from TCP 102:

Here's how my virtual lab is setup:
Figure 6: The lab setup.

If we just launched are ARP poisoning attack and our sniffer with default configuration of our interfaces, we would intercept the traffic from the victim and drop it. We don't want to do that! They wouldn't be able to get anywhere and we wouldn't be able to harvest useful information. We need to set our interfaces into IPv4 forwarding mode. This can be done by pasting the following line into the terminal:
echo 1 > /proc/sys/net/ipv4/ip_forward
That will allow us to forward traffic on our interface to the gateway.

Now that we have our interface configured and our code ready, let's analyze what will happen when we launch it:
Figure 7: An ARP poisoning attack.
We simply tell the gateway we're the victim and we tell the victim we're the gateway. That's it! With the ipv4 forwarding rule enabled, it will allow us to send and receive traffic on behalf of someone else. 

Launching the MITM:

First, fire up our script. It will begin sniffing traffic on the interface of our Kali VM.
Next, launch the and paste in the MAC for your listening interface, victim and gateway.
Now, on your target VM, browse to a site, like Reddit:
Figure 8: All your internets are belong to me.

If we capture the traffic in wireshark you can see that I'm now the victim and the gateway according to who you ask:
Figure 9: The IPs and MACs of my lab network
Figure 10: We tell the Victim that we are the Gateway.

At this point, we have full control over the victim's traffic. Although we are just passively listening, we can modify and manipulate any part of their traffic if we want. There is little to no validation on plain-text traffic. We could say, modify the DNS requests to point them to another IP and redirect them to our evil Reddit site, or if they are using a plaintext http:// login site we could sniff their credentials. This type of attack is fairly dated but there are many tools and tactics we can deploy that allow us to intercept TLS or SSL encrypted streams. The sslstrip attack is still extremely effective. All it does is change an https:// link to http://. With a bit of work you could take the code I've provided you and do exactly that. On most sites they will fail to login but they'll still have POSTed their login credentials to you. What I do is have them post their credentials once. If they fail I reset the connection and let them login over https://. They'll get to login and just think they fat fingered their password, none the wiser I just harvested them.  

Also, using a self signed certificate and presenting it to the victim instead of the remote servers certificate still works wonders. Many people are stupid, to put it bluntly, and click-through the giant red "omg this cert is broken" page that Chrome and Firefox will display. It's because of this reason that the browsers started removing click-through options.

Now you know why Public Key cryptography is so damn important. DNS requests, HTTP traffic, etc. all get signed. If you modify any part of it, the signature is invalidated and our victim will know they are being eavesdropped upon. But you can still leave all the encrypted data in-tact and listen to where they are going.  

Figure 11: Obviously, tweezers are the best tool for grabbing passwords and ruining monitors.

Analyzing the Code:

So what went on here? Most of this information isn't new by this point. What is new is the concept of crafting our own packet, though.

This is technically new. We pass in our interface (eth0 in this case) and htons(0x0800). Remember that 0x0800 is IPv4. The args are accepted like this: (interfaceName, protocolNumber).
On line 9 we define a new method, pack_addr, that address to networks (aton) an IP address passed in as an argument, then returns the packed result.

On line 14 we define another method, pack_mac, that takes a MAC address passed in as a string and converts it to it's packed Hex form. It will replace any ":" characters with '', or nothing.

Lines 22 thru 26 just pack the input you supply into their network form using the previously described methods. Take care to input it correctly. I'd just grab your HW addresses directly from ifconfig or arp.

On lines 29 thru 31 we are constructing the Ethernet headers. These are a header type that rests above IPv4. We haven't dealt with them yet but they work just like the TCP and IPv4 headers, except they have a fixed preamble that we do not modify. We can ignore the preamble and don't need to supply it when crafting the packet. Here is how they are constructed:
Figure 12: An Ethernet Header

We construct this by supplying the Source address + Dest Address + Protocol code into a variable. Remember from TCP 102, that the arp_code 0x0806 is a type of protocol code:
 Figure 13: You can see the 0x0806 entry for ARP, from TCP 102.

LInes 34 thru 38 specify options for our ARP header. Refer back up to figure 5 to help map the ARP header to these variables:

  • htype: Hardware Type
  • protype: Protocol Type
  • hsize: Hardware Address Length
  • psize: Protocol Address Length (IP)
  • opcode: Operation Code
On lines 41 and 42 we actually craft the packet. We first add our ethernet header, and then we add all the information in order needed to complete the ARP header. Refer back to Figure 5 if you need help mapping each variable we are adding together. Essentially they are the same, but we flip flop our dest and source addresses/MACs.

On lines 46 and 47 we send these in an infinite loop to keep the gateway and victims ARP cache flooded with our MAC address instead of the correct ones.  

A Word of Warning:

Don't go sniffing all willy-nilly and violating someone's privacy. I obviously can't stop you if you do but I'm also not responsible for any damage you may cause, or any legal consequences. Only use these for learning purposes and on a virtual network you own. I ran this within my own virtual environment where I would not cause any harm to any other machines.   
Figure 14: You're 1337 now, so grab your balaclava and crowbar.


  1. I learned a lot from this article. Thanks! However, I have the same setup as you (Ubuntu VM and Kali VM running on same host), and without doing anything I could already see the Ubuntu VM's traffic on my Kali VM. See my post here on SuperUser: I'm not sure you can actually truly do this attack with two VMs running on the same host in this way.

    After I switched the Kali VM to use a bridge adapter and used a separate laptop as a victim, the attack then almost worked. I could see the traffic on my Kali VM, but for some reason it wasn't forwarding the traffic. I made sure to do echo 1 > /proc/sys/net/ipv4/ip_forward, but that didn't help.

    By the way, I took out the loop at the bottom of the script that just keeps sending the ARP replies because it ended up causing my host machine to crash. I noticed that if you send the packets just once, then you have like a 10 second window before the ARP cache gets put back correctly, so you can keep doing it this way or make a timer in the script.

    1. The VMs I was running it on were pretty beefy, so it could spam an arpspoof with while True. I updated the original script on Github with a wait timer. It's easy enough, just import time and add time.sleep(seconds) in the while True loop.