Lab 06: ARP Cache Poisoning

Table of contents
  1. Lab 06: ARP Cache Poisoning
  2. Introduction
    1. Logistics
    2. Learning objectives
    3. Getting the configuration
    4. Generating your .env file
    5. Network topology
  3. Code introduction
    1. Compilation
    2. Step 0: Running send_arp
      1. Example
    3. Step 1: Understanding the ARP cache
      1. Worksheet questions
    4. Step 2: Packet parameters
    5. Step 3: Forging requests
      1. Incomplete cache
      2. Implementation
      3. Success criteria
      4. Worksheet questions
    6. Step 4: Forging replies
      1. Worksheet questions
    7. Step 5: ARP gratuitous
      1. Worksheet questions

Introduction

In this lab, we will be finally conducting an ARP cache poisoning attack on two hosts to convince each one that an attacker machine is the other. In other words, we’ll convince \(\mathtt{host}_1\) that the attacker is \(\mathtt{host}_2\). At the same time, we’ll convince \(\mathtt{host}_2\) that the attacker is \(\mathtt{host}_1\). This means that the attacker will act as the Man-In-The-Middle (MITM) between \(\mathtt{host}_1\) and \(\mathtt{host}_2\).

Logistics

We will continue with the same set of tools from Lab01, these are namely:

  1. Wireshark to visually see packets and protocols.
    • Install this on your local machine, so you can see things visually.
  2. If you are comfortable with command line, you can also use tshark to observe the same packets and protocols, directly on the server machine.

  3. scp or rsync will prove to be useful to obtain packet captures from the server and download them on your local machine. They should be available by default on your Linux distribution that you are running.

Learning objectives

After completing this lab, you should be able to:

  • Inject different ARP packets into an existing network.

  • Investigate the impact of different ARP packets on a host’s ARP cache.

  • Conduct an Man-In-The-Middle (MITM) attack using ARP cache poisoning.

Getting the configuration

You can find the starter setup for this lab under the 06_Lab06 directory of the csse341-labs repository. If you have set up your private repository correctly, you can fetch the latest version of the labs using the following sequence of commands:

  1. Synchronize with the labs remote using git fetch upstream.

  2. Pull the latest changes from the main branch of the class repository:

    $ git pull upstream main
    
  3. Push the starter setup to your repository so you can start modifying it:

    $ git add 06_Lab06
    $ git push origin main
    

Generating your .env file

Before we spin up our containers, there are some configuration variables that we must generate. To do so, please run the gen_env_file.sh script from the lab repository directory as follows:

$ ./gen_env_file.sh

If run correctly, you will find the following new files:

  1. .env (hidden file - use ls -al to see it) contains your UID and GID variables.

  2. connect_*.sh a utility script to connect to each container in this lab.

  3. run_*.sh is another utility script that allows you to run commands on a container without logging into it.

Network topology

In this lab, we will be working with three machines connected to the same local network. They will live on the same subnet and all can access each other directly. The machines are:

  1. hostA with IP address 10.10.0.4
  2. hostB with IP address 10.10.0.5
  3. attacker with IP address 10.10.0.10

Code introduction

In the volumes/code directory, you will find starter code for packet generation using libpcap. It is largely based on the ping example in the previous lab. In particular, we introduce the send_arp_packets function in include/arp_util.h and src/arp_util.c.

As in the previous lab, we structure the code as follows:

  • The include directory contains definitions for the functions and utility headers.

  • The src directory contains the implementation files. Of particular interest to us are:

    1. src/send_arp.c: This file contains the main function that sends a certain number of ARP packets.
    2. arp_util.c: This file contains the function send_arp_packets that you will be implementing in this lab.

You should only need to modify the arp_util.c file in this lab.

Compilation

As in the previous lab, we will use cmake as our build system to facilitate the resolution of dependencies. To build your code, you should first generate the appropriate makefiles as follows.

On your server (not a container), navigate to the volumes/code directory and then do the following:

$ mkdir build
$ cd build
$ cmake ..

Once we have generated the build files (you only need to do that once), you can compiled your code on any change using make in the build directory.

Step 0: Running send_arp

If you go ahead and compile the code in volumes/code, a new binary called volumes/code/build/bin/send_arp will show up. This is the main executable that we’ll be using in this lab. It accepts a certain number of arguments so that you can run it. Here’s a description of each.

$ ./build/bin/send_arp
Usage: ./build/bin/send_arp [OPTIONS]

Options:
  -s, --source <mac>         Source MAC address
  -d, --destination <mac>    Destination MAC address (default: 0)
  -v, --victim <ip>          Victim's IP address
  -t, --target <ip>          Target's IP address
  -n, --num-packets <count>  Number of packets to send (default: -1)
  -a, --arp <type>           Type of packets to send (request, reply, gratuitous)
  -h, --help                 Display this help message
  1. -s <mac>: This is the source MAC address you’d like to put into your generated packets. This is typically the attacker’s MAC address which you can always obtain from cat /sys/class/net/eth0/address.

  2. -d <mac>: This the destination MAC address you’d like to put into your generated packets. This is an optional argument since some packets we will generated don’t have a dedicated destination MAC address.

  3. -v <ip>: This is the victim’s IPv4 address. The victim machine is the one you are trying to impersonate.

  4. -t <ip>: This is the target’s IPV4 address. The target machine is the one you are trying to deceive.

  5. -n <count>: This is the number of packets you’d like to send. It has a default value of -1 which will then keep sending packets indefinitely.

  6. -a <type: This is the type of ARP packets you’d like to send. It can be one of request, reply, or gratuitous.

You can obtain this help message at any point by running the executable with the -h or --help flag.

Please note that we represent all the fields as strings, except for the number of packets, which is an integer.

Example

Here’s an example of running the send_arp executable on the attacker machine to deceive hostA into thinking that the attacker is hostB. In this scenario, hostB is the victim while hostA is the target.

$ sudo ./bin/send_arp -s $(cat /sys/class/net/eth0/address) -v 10.0.0.5 -t 10.0.0.4 -n 5 -a request

This will generate 5 ARP request packet, targeting hostA, and trying to convince it the attacker is hostB.

Step 1: Understanding the ARP cache

We would like to first understand the behavior of the ARP cache at a host. We will start with a simple experiment and packet capture. Spin up your docker environment using dcupd then grab three terminals. We’ll use two for hostA and one for hostB.

First, let’s check out the content of the ARP table on each container. Run the following command on each host and check that the content of the ARP table are empty.

(hostA) $ arp -a -n

You shouldn’t see anything in the table on either host. If there is anything in the table, you can flush it using sudo ip -s -s neigh flush all.

Starting from an empty ARP table, let’s first ping hostA from hostB, while at the same time capturing traffic on hostA, as follows:

(hostA) $ sudo tcpdump -i eth0 arp or icmp -w /volumes/arpcache.pcap

and then from another terminal on hostA,

(hostA) $ ping -c1 hostB

After the ping is successful, stop tcpdump and look at the packet capture. For this to make sense, please don’t let tcpdump run for long after the ping stops, otherwise it will skew the results.

Check the content of the ARP cache on both hostA and hostB using arp -a -n.

Note that I have added a few helper scripts that would allow you to save some time typing. Those scripts are run_hostA.sh, run_hostB.sh, and run_attacker.sh. Each of those scripts will allow you to run a command on the appropriate container without having to login to it separately.

To run a command using one of these scripts, simply provided it as an argument to the script between quotes. For example, to ping hostB from hostA, you can use:

$ ./run_hostA.sh "ping hostB"
PING hostB (10.10.0.5) 56(84) bytes of data.
64 bytes from hostB.local-net (10.10.0.5): icmp_seq=1 ttl=64 time=0.019 ms
64 bytes from hostB.local-net (10.10.0.5): icmp_seq=2 ttl=64 time=0.088 ms
64 bytes from hostB.local-net (10.10.0.5): icmp_seq=3 ttl=64 time=0.085 ms

--- hostB ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2041ms
rtt min/avg/max/mdev = 0.019/0.064/0.088/0.031 ms

Worksheet questions

By examining the content of the ARP caches on hostA and hostB, and looking at the packet capture, answer the following questions:

  1. How many requests did hostA send to hostB?
  2. What are the content of the caches on both hostA and hostB?
  3. Based on your observation, what did hostB do when it received the ARP request from hostA?
  4. Based on your observations, assuming ARP caches are empty, what can a malicious host do to poison the ARP table of a host on the network?

Step 2: Packet parameters

To aid you in further steps, please complete the ARP packets tables with the parameters in the Ethernet and ARP header for the request and replies between hostA and hostB. To do so, use the previous packet capture (or generate a new one) to examine the packet header and fill out the table.

On your worksheet, fill out the packet content table with the contents of the Ethernet and ARP headers after examining those packets in Wireshark.

Step 3: Forging requests

Our goal now is to experiment with happens when a host on the network sends unsolicited ARP replies for a fake IPv4 address that it doesn’t own. Our first goal for the attacker is to convince hostA that it’s (i.e., the attacker) hostB. Its aim is to creates a fake mapping in hostA’s ARP cache that maps hostB’s IPv4 to the attacker’s MAC address.

Recall that our terminology calls hostA the target while hostB is the victim.

We would like to investigate the impact of sending forged ARP requests in two cases:

  1. The ARP cache on our target (i.e., hostA) is empty.
  2. The ARP cache on our target (i.e., hostA) is already populated.
  3. The ARP cache on our target (i.e., hostA) is incomplete.

Incomplete cache

ARP has a few implementation-specific behaviors that aren’t specified by the RFC. To help us understand those behaviors better, here’s a little experiment. Grab a terminal window on hostA and make sure the ARP cache is empty (by running sudo ip -s -s neigh flus all).

Now try to ping a non-existing host on the network (with no attack running), and then examine the content of the ARP cache using arp -an. You will see that hostA will create an incomplete entry for the unknown host, even if that host didn’t reply to its requests (because it doesn’t exists!).

┌──(netsec㉿hostA)-[/]
└─# arp -an
? (10.10.0.12) at <incomplete> on eth0

Using this observation, design your experiment so that you examine the behavior of the ARP cache under these scenarios:

  1. The ARP cache is empty.
  2. The ARP cache contains an incomplete mapping for the target IPv4 address.
  3. The ARP cache contains a valid mapping for the target IPv4 address.

Describe the experiment that you would like to setup to evaluate the impact of forged ARP requests. Your experiment must be able to address the following requirements:

  • Analyze the behavior of hostA ARP cache in each of the aforementioned scenarios.
  • Use appropriate packet captures to show the impact of ARP requests forged from the attacker to hostA.
  • Analyze if and when the attack might be successful, and what happens if hostB starts communicating with hostA suddenly.

Make sure to check with me if you have any doubts about your experiment or if you have any questions you’re unsure about. It’s crucial that you get the setup right the first time so that you won’t have to redo it later on.

Hint: To create an incomplete entry for hostB in hostA’s cache, we’ll need to take hostB offline for a little while. To do so, simply run docker container stop hostB from the server machine. That will stop the container until you start it again. You can start it later on using docker container start hostB

Here’s a simple test run of the above hint:

$ docker container stop hostB
hostB

$ ./run_hostA.sh "sudo ip neigh flush all"

$ ./run_hostA.sh "arp -a -n"

$ ./run_hostA.sh "ping -c1 10.10.0.5"
PING 10.10.0.5 (10.10.0.5) 56(84) bytes of data.
From 10.10.0.4 icmp_seq=1 Destination Host Unreachable

--- 10.10.0.5 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

$ ./run_hostA.sh "arp -a -n"
? (10.10.0.5) at <incomplete> on eth0

$ docker container start hostB
hostB

$ ./run_hostA.sh "ping -c1 10.10.0.5"
PING 10.10.0.5 (10.10.0.5) 56(84) bytes of data.
64 bytes from 10.10.0.5: icmp_seq=1 ttl=64 time=0.048 ms

--- 10.10.0.5 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.048/0.048/0.048/0.000 ms

Implementation

Once you know what your experiment should look like. You can implement your code to send forged ARP requests in send_arp_packets under the src/ directory. The file already contains send starter code along with TODO labels and some examples of how to do some more tedious address manipulations.

Note that you can assume for now that type is always going to be ARP_PKT_REQUEST. You can modify the functionality later on as you continue through the lab. It’s easier to do since it makes debugging easier and you can be sure that your implementation that’s not dependent on the type variable works well.

To run your program, you can do it in two different ways:

  1. Simply compile it and run it directly in the attacker container (similar to the last lab). Make sure to pass the right arguments to the binary when you launch it.

  2. Use the build_and_run.sh script that I provide you with under the root 06_Lab06/ directory. This script will compile the binary (only if it’s not there, it won’t recompile it if you have modified the code) and then run it directly on the attacker container.

    To change the arguments that you’d like to pass to the executable, you can edit the script by modifying the variables on lines 14-21, reproduced below.

    # TODO: Update these values as desired.
    source=$(cat /sys/class/net/eth0/address)
    # Set the destination MAC address if desired, leave it commented out if not.
    # destination=
    victim=10.10.0.5
    target=10.10.0.4
    num_packets=5
    arp=request
    

    Here’s an example run:

    $ ./build_and_run.sh
    [LOG]: Running the ping executable on the attacker container:
    [LOG:find_pcap_dev:36] Starting sniffer on interface eth0
    [LOG:find_pcap_dev:74] Setup done successfully and ready for listening...
    [LOG:send_arp_packets:160] Done sending packets....
    

Success criteria

We consider our attack to be successful if we can convince hostA to send all traffic intended to go to hostB to the attacker machine instead. To evaluate that, we can do the following:

  • Grab a packet capture on the attacker to verify that hostA is sending the packets intended for hostB to the attacker instead.
  • Check the content of the ARP cache at hostA during the attack using arp -an.
  • Attempt a ping from hostB to hostA, in that case, hostB shouldn’t receive any replies during the attack, since all packets are being sent to the attacker instead.

Worksheet questions

Based on your observations, answer the following questions on the lab questions sheet.

  1. Based on your observations, describe the behavior of hostA when it receives an unsolicited ARP request. Specifically, mention what happens depending on the content of the ARP cache (the three scenarios we mention).
  2. Based on your observations, suggest a way to thwart ARP cache poisoning attacks that use ARP requests.
  3. If hostB decides to start sending ARP requests while you are conducting your attack, what do you anticipate would happen?
    • You don’t have to test this out, just use your judgment as to what you think can happen.

Step 4: Forging replies

Another approach to poisoning the cache at our target machine is to use unsolicited ARP replies. We will now repeat the experiment from Step 3, now using ARP replies instead.

Amend your code in src/arp_util.c (function send_arp_packets) to now support sending ARP replies when the type argument is ARP_PKT_REPLY. You will have to modify some packet content (especially source and destination addresses) when sending a forged reply.

Please don’t replace your code from Step 3, rather amend it to support sending replies. You will use that code later on in the next lab.

Repeat the experiment from Step 3 after you have completed the implementation and collect your results. Remember that we need to test the behavior of hostA’s cache under three scenarios (empty, valid, and incomplete).

Worksheet questions

Based on your observations, answer the following questions on the lab questions sheet.

  1. Based on your observations, describe the behavior of hostA when it receives an unsolicited ARP reply. Specifically, mention what happens depending on the content of the ARP cache (the three scenarios we mention).
  2. Based on your observations, suggest a way to thwart ARP cache poisoning attacks that use ARP replies.
  3. When the attack using ARP replies fails, can you suggest a way to remedy that? In other words, we’d still like to use ARP replies, but we need to force hostA to take those seriously.
    • Hint: You might need to send packets on another layer.
    • Hint: This relates to the incomplete mapping behavior that we’ve seen above.
    • You don’t have to implement this, just suggest a way to make it happen.

Step 5: ARP gratuitous

In the case where both requests and replies don’t work, ARP provides you with yet another way to make announcements, specifically using gratuitous ARP packets. A gratuitous ARP packet is one that a host can send to announce itself on the network (it has useful applications, though not without its drawbacks).

In this step, we would like to experiment with gratuitous ARP packets to see if they can help us impersonate hostB. We will use the same experimental setup as in the steps 3 and 4, except that we will be sending gratuitous ARP packets.

An ARP gratuitous message is an ARP reply with the following characteristics:

  1. It’s an ARP reply, i.e., its code should be the same as a reply packet.
  2. It’s a broadcast packet, i.e., the target Ethernet address should be \(\mathtt{FF:FF:FF:FF:FF:FF}\).
  3. The sender and target IPv4 addresses should be the same; they would be the IPv4 address of the host we’re trying to impersonate (i.e., hostB).
  4. The sender MAC address (in the ARP header) should be the MAC address of the attacker.
  5. The target MAC address of the ARP header should be the broadcast address (i.e., \(\mathtt{FF:FF:FF:FF:FF:FF}\).

Repeat your experiment with gratuitous messages and record your observations. Start by amending your implementation in send_arp_packets to now support sending ARP Gratuitous packets. Recall to clear the cache between experiments using sudo ip -s -s neigh flush all.

Worksheet questions

Based on your observations, answer the following questions on the lab questions sheet.

  1. Based on your observations, describe the behavior of hostA when it receives an unsolicited ARP gratuitous packet. Specifically, mention what happens depending on the content of the ARP cache (the three scenarios we mention).
  2. Thinking like an attacker, which technique of the three would you prefer and why?
  3. Based on all your experiments, without significant change to the ARP protocol, can we effectively thwart such attacks?

    In your answer, try to hit the following points:

    • What’s the main weakness of ARP?
    • Without a third party intervention, can we avoid this weakness?
    • Can someone from the Internet conduct an ARP cache poisoning attack?