ARP poisoning is a well-known technique used to perform MITM (man-in-the-middle) or DoS (denial of service) attacks on a LAN. You might think that every security researcher and every hacker knows this technique inside out. After all it is pretty simple. Send a spoofed ARP reply packet announcing "hey, you may or may not have asked for it, but I am X.X.X.X and my MAC address is YY:YY:YY:YY:YY:YY" and the target host receiving this packet will start sending traffic destined to X.X.X.X to whoever owns YY:YY:YY:YY:YY:YY on the LAN, typically a malicious host intercepting and/or modifying sensitive traffic. It is that simple. Or is it? A little-known variation of this classic poisoning attack is based on sending spoofed requests instead of replies. I am going to demonstrate that contrary to what the current literature describes, poisoning with ARP requests is noticeably more powerful and robust. Amongst other things, not only it works against all network stacks I tested (Windows 7 to 2000, Linux, OpenBSD, FreeBSD) but it also allows something that classic poisoning is unable to do: adding new ARP entries, in addition to overwriting existing ones.
Let me start by quoting RFC 826, documenting ARP. It is not every day that one gets the opportunity to scrutinize a 30 years old RFC:
When an address resolution packet is received, the receiving Ethernet module gives the packet to the Address Resolution module which goes through an algorithm similar to the following. Negative conditionals indicate an end of processing and a discarding of the packet.
?Do I have the hardware type in ar$hrd?
Yes: (almost definitely)
- [optionally check the hardware length ar$hln]
?Do I speak the protocol in ar$pro?
- [optionally check the protocol length ar$pln]
- Merge_flag := false
- If the pair <protocol type, sender protocol address> is already in my translation table, update the sender hardware address field of the entry with the new information in the packet and set Merge_flag to true.
?Am I the target protocol address?
- If Merge_flag is false, add the triplet <protocol type, sender protocol address, sender hardware address> to the translation table.
- ?Is the opcode ares_op$REQUEST? (NOW look at the opcode!!)
Notice that the <protocol type, sender protocol address, sender hardware address> triplet is merged into the table before the opcode is looked at. This is on the assumption that communcation is bidirectional; if A has some reason to talk to B, then B will probably have some reason to talk to A.
The RFC describes that the sender IP and MAC addresses are added to the table before looking at the opcode indicating whether it is an ARP request or reply. In other words: ARP poisoning can be performed with ARP requests, in addition to ARP replies. Another consequence of this behavior is that it is possible to insert new entries in the ARP table, in addition to overwriting existing ones, something that is usually described as impossible.
You might be wondering if modern network stacks really do implement ARP this way. The answer is yes. I have successfuly exploited many different stacks in the past 10 years with this very technique. Namely:
- Windows 2000, XP, 2003, Vista, 2008, 7
- Linux kernel 2.4, 2.6
- OpenBSD 4.6 (also tested 2.x and 3.x back in the days)
- FreeBSD 4.x, 5.x (I have not tested newer versions but they are likely vulnerable as well)
It is interesting that RFC 826 documents the behavior pretty clearly, yet no ARP poisoning tool seems to purposefully exploit this fact. To be more correct, Ettercap is probably the only one (see arp_poison_request setting), however even its implementation seems to be just a curiosity that has fallen into disuse, forgotten, and disabled by default (arp_poison_request=0 in etter.conf). As of May 10, 2010, before publishing this text, Googling "arp_poison_request" returned no results other than a few default etter.conf files accidentally indexed by the Googlebot. It is as if its developers realized it could work, but being unsure of its usefulness they disabled it by default and documented it as only "useful against targets that cache even arp request values". Not only they did not seem to realize it works against virtually any target, but more importantly I find no public mention of the multiple advantages of poisoning via ARP requests!
Let me demonstrate the technique with the generic packet injection tool nemesis that I am going to use to craft an ARP packet from scratch.
To insert or overwrite the entry (10.2.44.90, 00:de:ad:be:ef:00) in the ARP table of the target (10.2.32.1, 00:30:48:xx:xx:xx), run this command from the attacker's machine:
$ nemesis arp \
-h 00:de:ad:be:ef:00 -S 10.2.44.90 \
-m 0:0:0:0:0:0 -D 10.2.32.1 \
-d eth0 -M 00:30:48:xx:xx:xx
This will send the request "arp who-has 10.2.32.1 tell 10.2.44.90" while 10.2.44.90 pretends to have the MAC address 00:de:ad:be:ef:00. The Ethernet frame will have the destination MAC address 00:30:48:xx:xx:xx (to only poison this target instead of the whole broadcast domain), and will have the source MAC address of the attacker's eth0 NIC (which is 00:1f:e2:xx:xx:xx, but this is irrelevant). Here is a representation of the ARP request:
Right before receiving the ARP request, the target does not know 10.2.44.90. The target in my example runs OpenBSD 4.6, so we can verify that it does not know about this IP with the arp command:
$ arp -n 10.2.44.90
10.2.44.90 (10.2.44.90) -- no entry
After receiving the ARP request, it gets poisoned:
$ arp -n 10.2.44.90
? (10.2.44.90) at 00:de:ad:be:ef:00 on bge0
Notice how this is a new entry that was created. Classic ARP poisoning cannot create new entries like this. At this point, another very nice thing about this poisoning technique is that the target will of course send back an ARP reply to 00:de:ad:be:ef:00. If you are the attacker, you will get a confirmation of successful poisoning, How practical! Here is the ARP reply:
A couple remarks:
- An alternative version of the attack is that it can also work no matter what target IP address is used in the ARP request. In other words in this example if an "arp who-has x.x.x.x tell 10.2.44.90" was sent for any value of x.x.x.x, it would still poison, except that of course no ARP reply will be sent back because x.x.x.x is not an existent host. This alternative version works at least against Linux and OpenBSD. It does not work against Windows 7. I have not tested any other Unix or Windows version.
- In my example the ARP request packet had an Ethernet frame source MAC address of 00:1f:e2:xx:xx:xx which was different from the ARP sender MAC address 00:de:ad:be:ef:00. I made them different to demonstrate this is possible. But in practice they are often the same (ie. an attacker would want to inject his MAC 00:1f:e2:xx:xx:xx in the table instead of 00:de:ad:be:ef:00). However if the attacker really wants to inject 00:de:ad:be:ef:00, he may prefer to set the Ethernet frame source MAC address to 00:de:ad:be:ef:00 in order to match the ARP sender MAC address. This would be stealthier. With nemesis this can be by adding -H 00:de:ad:be:ef:00.
Poisoning with ARP requests is advantageous for at least 4 reasons.
Firstly, it can be used to inject new entries in ARP tables, in addition to overwriting existing ones. Classic poisoning with ARP replies can only do the latter; the entry has to exist in the table already (even if marked "incomplete" in the output of the arp command) before it can be poisoned. Classic poisoning makes the attacker blind as he does not know if the lack of intercepted network traffic is because an ARP entry was poisoned but there is no traffic, or because there was no ARP entry which caused the poisoning attempt to be unsuccessful. Poisoning with ARP requests makes ARP poisoning more powerful.
Secondly, the ARP reply sent back by the target is a nice side-effect that be used by the attacker to get a positive validation of the success of ARP poisoning. It makes ARP poisoning more robust.
Thirdly, the alternate version of the attack where an arbitrary x.x.x.x ARP target IP address is used in the ARP request makes it possible to poison many hosts on a LAN at once with a single ARP request packet that is naturally broadcasted to the entire LAN (like the normal usage of ARP). With classic poisoning, one would have to either broadcast an ARP reply (replies are not expected to be broadcasted, which may trigger IDS alerts, etc), or send one ARP reply to every host that needs to be poisoned (the volume of ARP replies might also trigger alerts, etc).
Fourthly, noone seems to be very much aware of this ARP poisoning technique. Therefore the technique may be useful to evade detection as noone expects it to be used. Tcpdump does not show the ARP sender MAC address (not to be confused with the Ethernet frame source MAC address), even in verbose mode (-vvv), so a human reading logs might miss ARP poisoning attempts. Intrusion Detection Systems analyzing network traffic might not inspect "obviously unmalicious" ARP requests.
I hope my blog post will raise awareness of poisoning based on ARP requests!