Introduction

In this lab, we will start by getting our hands dirty with network programming using the scapy package. Sniffing and spoofing packets are the essential tools available to a security attacker and defender. Therefore, our journey of learning the ins and outs of network security starts with sniffing and spoofing network packets.

Learning Objectives

The goal of this lab is to introduce you to the concepts of packet sniffing and packet spoofing. After completion of this lab, you should be able to write code to capture packets off the wire using python and the scapy package. You should also be able to create both real and counterfeit packets and send them to their destination.

The network topology

In this lab, we will be using the following network topology. It is composed of three machines, an attacker, a victim, and a normal client connected to a local LAN as show in the figure below.

network topology

We assume that you only have control over the attacker machine (though you can log in to the client and victim to send ICMP packets), the client and the victim machines are out of your reach and you must not install any additional software on them. You have full control over the attacker machine so you can configure it and install software as you please.

Starting the DETER experiment

Log in to your DETERLab account, in the top menu, expand the Experimentation tab and then click on Begin an experiment. Give your experiment a unique name. I suggest you use the following format <user_id>-lab1 and replace <user_id> with your Rose ID. Make sure that there are no spaces in the name and there are no special characters.

Enter a description in the Description category. I recommend that you enter your name and your partner’s name if you are working in a group, that would be very helpful for me in debugging things around.

In the Your NS file: row, write down

 /proj/csse490/labs/lab1.tcl

in the On Server text box.

Leave everything else as defaults and then click on Submit. You should be moved to another page where you will see some logs being shown. Once the experiment has been created, then you will receive an automated email from DETER, and things should be ready for you.

Swapping the experiment in

After the experiment has been created, it will show up in the home page for your DETER account (you can always click on My DeterLab menu item to go the home page). Click on the experiment EID (second column in the table) and then you will be taken to the experiment page.

From the experiment page, click on Swap Experiment In in the left-hand side menu in yellow. You will be taken to a page that shows logs coming up. Sit back and wait for the experiment to be swapped in, this can take a while (a few minutes) so grab a cup of coffee in the meantime.

Once the experiment has been swapped in, you are ready to go to access the experiment machines. You will receive an email from DETER saying that the experiment has been successfully swapped in.

Once you are done with your experiment, you can choose the Swap Experiment Out menu item form the left-hand side menu to swap the experiment out and release the machines that you were using. Please be mindful of other DETER users and swap your experiment out when you are done using it. The experiment will be automatically swapped out after 4 hours of inactivity in case you forget.

Reaching the attacker machine

To reach the attacker machine (and any other machine on the experiment), first ssh into the main DETER users machine using

ssh deter

and then from that machine, use

ssh attacker.noureddi-lab1.csse490

and replace noureddi-lab1 with your experiment name. In the same way, you can reach the other machines using

ssh client.noureddi-lab1.csse490

and

ssh victim.noureddi-lab1.csse490

Sniffing packets

Let’s first start simple and just sniff some packets off the network. Open up a terminal window and start a tmux session using

tmux new-session -s lab1

This will start a new tmux session which you can save and regenerate at other times (unless you reboot your machine or you kill the session). You can view how to use tmux by checking out this cheat sheet.

Login to the attacker machine in one pane and into the victim machine on another pane (or in a completely different terminal window). We will sniff packets on the attacker machine and generate packets from the victim machine.

Sniffing on the attacker’s end

On the attacker machine, first record the interface connected to the local LAN using

$ ifconfig -a

and then record the name of the interface that has the IP address 10.1.1.2, let’s assume it is eth0 in our case.

First, try to sniff a packet from the python command line using ipython3:

$ sudo ipython3

then from inside ipython use the following:

In [1]: from scapy.all import *
WARNING: No route found for IPv6 destination :: (no default route?). This
affects only IPv6

In [2]: pkt=sniff(iface='eth0')

At this point, the program will hang waiting for packets to capture, leave it running and jump into the victim machine.

Generating packets on the victim’s end

From the victim machine, ping the attacker’s machine with 1 packet using

$ ping -c1 attacker
PING attacker-lan0 (10.1.1.2) 56(84) bytes of data.
64 bytes from attacker-lan0 (10.1.1.2): icmp_seq=1 ttl=64 time=0.186 ms

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

Printing the sniffed packets

Now from the attacker machine, hit Ctrl-c to exit out of the sniffing mode, and then type pkt.show() to show the packets that you can captured. In my case, the output was

In [2]: pkt=sniff(iface='eth0')
^C
In [3]: pkt.show()
0000 00:0e:0c:66:89:6a > 01:80:c2:00:00:0e (0x88cc) / Raw
0001 2c:76:8a:38:51:aa > 01:80:c2:00:00:0e (0x88cc) / Raw
0002 00:0e:0c:66:89:6a > 01:80:c2:00:00:0e (0x88cc) / Raw
0003 2c:76:8a:38:51:aa > 01:80:c2:00:00:0e (0x88cc) / Raw
0004 00:0e:0c:66:89:6a > 01:80:c2:00:00:0e (0x88cc) / Raw
0005 2c:76:8a:38:51:aa > 01:80:c2:00:00:0e (0x88cc) / Raw
0006 00:0e:0c:66:89:6a > 01:80:c2:00:00:0e (0x88cc) / Raw
0007 2c:76:8a:38:51:aa > 01:80:c2:00:00:0e (0x88cc) / Raw
0008 00:0e:0c:66:89:6a > 01:80:c2:00:00:0e (0x88cc) / Raw
0009 Ether / IP / ICMP 10.1.1.3 > 10.1.1.2 echo-request 0 / Raw
0010 Ether / IP / ICMP 10.1.1.2 > 10.1.1.3 echo-reply 0 / Raw
0011 2c:76:8a:38:51:aa > 01:80:c2:00:00:0e (0x88cc) / Raw

Depending on how long you keep the sniffing active, you might see more or less packets. The only two packets that we care about are the ICMP echo-request and echo-reply packets.

You can then access individual packets by indexing into the pkt array of packets using normal array access patterns. For example, to print the ICMP echo request packet in my case, you can use

In [4]: pkt[9].show()
###[ Ethernet ]###
  dst       = 00:0e:0c:66:89:6a
  src       = 00:04:23:ae:d0:49
  type      = IPv4
###[ IP ]###
  version   = 4
  ihl       = 5
  tos       = 0x0
  len       = 84
  id        = 39613
  flags     = DF
  frag      = 0
  ttl       = 64
  proto     = icmp
  chksum    = 0x89e5
  src       = 10.1.1.3
  dst       = 10.1.1.2
  \options \
###[ ICMP ]###
  type = echo-request
  code = 0
  chksum = 0x9715
  id = 0xdba
  seq = 0x1
###[ Raw ]###
  load = b'9\t+b\x00\x00\x00\x00"\xf1\r\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f
  !"#$%&\'()*+,-./01234567'

You can also view more information about the packet using ls as follows:

In [5]: ls(pkt[9])
dst        : DestMACField         = '00:0e:0c:66:89:6a' (None)
src        : SourceMACField       = '00:04:23:ae:d0:49' (None)
type       : XShortEnumField      = 2048            (36864)
--
version    : BitField             = 4               (4)
ihl        : BitField             = 5               (None)
tos        : XByteField           = 0               (0)
len        : ShortField           = 84              (None)
id         : ShortField           = 39613           (1)
flags      : FlagsField           = 2               (0)
frag       : BitField             = 0               (0)
ttl        : ByteField            = 64              (64)
proto      : ByteEnumField        = 1               (0)
chksum     : XShortField          = 35301           (None)
src        : Emph                 = '10.1.1.3'      (None)
dst        : Emph                 = '10.1.1.2'      ('127.0.0.1')
options    : PacketListField      = []              ([])
--
type       : ByteEnumField        = 8               (8)
code       : MultiEnumField       = 0               (0)
chksum     : XShortField          = 38677           (None)
id         : ConditionalField     = 3514            (0)
seq        : ConditionalField     = 1               (0)
ts_ori     : ConditionalField     = None            (30633707)
ts_rx      : ConditionalField     = None            (30633707)
ts_tx      : ConditionalField     = None            (30633708)
gw         : ConditionalField     = None            ('0.0.0.0')
ptr        : ConditionalField     = None            (0)
reserved   : ConditionalField     = None            (0)
addr_mask  : ConditionalField     = None            ('0.0.0.0')
unused     : ConditionalField     = None            (0)
--
load       : StrField             =
b'9\t+b\x00\x00\x00\x00"\xf1\r\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f
!"#$%&\'()*+,-./01234567' (b'')

Putting things into a script

You can also write python scripts that can achieve the outcome above, for example, try the following script

#!usr/bin/env python3
from scapy.all import *

def print_pkt(pkt):
    pkt.show()

pkt = sniff(iface='eth0', filter='icmp', prn=print_pkt)

This will start sniffing on eth0, but then the filter='icmp' parameter will only care about ICMP packets (to reduce the amount of packets that we capture). The parameter prn=print_pkt sets a callback function that gets executed every time the sniffer captures a packet.

You can run the above script using

$ sudo python3 sniff.py

This will show every ICMP packet captured by the sniffer until it is terminated. Try to ping the attacker from the victim again to verify that it is working correctly.

Generating packets: Let’s ping the victim

Now let’s generate packets from the attacker and ping the victim. Note that if you are using VSCode to code using scapy, you should also import the following

from scapy.layers.inet import IP, ICMP

otherwise the editor’s IntelliSense stuff will not work (for some weird reason).

First let’s generate an IP packet using

a = IP()

Next, set the destination of the IP packet to be the victim’s IP address

a.dst = '10.1.1.3'

Recall that you can use ls(a) to view the field in a certain packet. Next, create an ICMP header using

b = ICMP()

You do not need to change any of the default values in the ICMP header.

Then, let’s concatenate the two packets together. To do so, scapy has the division operator overloaded, so we can do something like

pkt = a / b

and scapy will encapsulate b inside an IP packet a. Finally, you can send the packet using

send(pkt)

But this will not wait for a response. To cause scapy to wait for a response from the victim machine, using sr1 to send a packet and then wait for a response packet as follows:

reply = sr1(pkt)
print("Received a response packet:")
reply.show()

This will show the response packet, if any, from the victim machine.

In my setup, running the above script generates the following output:

$ sudo python3 icmp_gen.py
WARNING: No route found for IPv6 destination :: (no default route?). This
affects only IPv6
Begin emission:
..Finished to send 1 packets.
Received 3 packets, got 1 answers, remaining 0 packets
Received pkt:
###[ IP ]###
  version   = 4
  ihl       = 5
  tos       = 0x0
  len       = 28
  id        = 30992
  flags     =
  frag      = 0
  ttl       = 64
  proto     = icmp
  chksum    = 0xebca
  src       = 10.1.1.3
  dst       = 10.1.1.2
  \options   \
###[ ICMP ]###
  type      = echo-reply
  code      = 0
  chksum    = 0xffff
  id        = 0x0
  seq       = 0x0
###[ Padding ]###
  load      = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

Traceroute

The objective of this step is to estimate the number of routers between your machine and the google DNS server located at 8.8.8.8. Note that you need to do this from your local machine, not from the DETER machines.

The idea is quite straightforward: just send a packet (any type) to the destination, with its Time-To-Live (TTL) field set to 1 first. This packet will be dropped by the first router, which will send us an ICMP error message, telling us that the time-to-live has been exceeded. That is how we get the IP address of the first router. We then increase our TTL field to 2, send out another packet, and get the IP address of the second router. We will repeat this procedure until our packet finally reaches the destination. It should be noted that this experiment only gets an estimated result, because in theory, not all these packets take the same route (but in practice, they may within a short period of time).

Running the traceroute.py script on my machine gives me the following results:

$ python traceroute.py
*** 192.168.1.1 ***
*** 142.254.224.97 ***
*** 74.128.8.145 ***
*** 65.29.11.6 ***
*** 66.109.6.54 ***
*** 66.109.5.136 ***
*** 24.30.200.171 ***
*** 74.125.251.147 ***
*** 142.251.231.67 ***
Destination reached: 8.8.8.8

Spoofing packets from non-existing host

In the final step, you need to convince the victim that the host 10.1.1.129 exists on the network, even though no host with this IP address exists on the network. We will do this by spoofing both ARP packets and ICMP packets.

You will need to first understand how the ping process works. To do so, start a sniffer for all packets on the attacker machine, and then from the victim, ping the non-existent host using

$ ping -c1 10.1.1.129

Observe the packets that you see on the attacker’s end. How can you convince the victim that the host 10.1.1.129 exists?

Hint: You will need to forge packets at two different layers or protocols, one ARP packet and one ICMP packet.

With my script running on the attacker machine, trying to ping the non-existent host from the victim results in

$ ping -c1 10.1.1.129
PING 10.1.1.129 (10.1.1.129) 56(84) bytes of data.
From 10.1.1.2: icmp_seq=1 Redirect Host(New nexthop: 10.1.1.129)
64 bytes from 10.1.1.129: icmp_seq=1 ttl=64 time=147 ms

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

You do not need to worry about the Redirect Host packet, it might show up and it might not. You only care that the ping packet was replied to by the non-existent host and that now the victim is convinced that the host 10.1.1.129 exists on the local network.

Submission Instructions

In addition to your source code, for each part in this lab, submit a screenshot(s) showing successful implementation of the attack or utility in question. I ask for screenshots because the nature of DETER makes it almost impossible for me to regenerate your results unless you make them very modular, which is not very practical for this class.

Put your screenshots into a PDF file and then submit that to gradescope alongside your code.

Video submission (Optional)

If you would rather record a video showing successful exploitation, that is totally fine by me. Then please submit a PDF file containing a link to your video instead of a written report.

Feedback

I would really appreciate your feedback on this lab. Recall that this is the first time we offer this class and that we are trying to make it as good of an experience for you as possible. Therefore, I will solicit your feedback on every lab and I would highly appreciate if you can fill them out. You can find the feedback form at the following link. The form is completely anonymous.