1 Overview

Packet sniffing and spoofing are two important concepts in network security; they are two major threats in network communication. Being able to understand these two threats is essential for understanding security measures in networking. There are many packet sniffing and spoofing tools, such as Wireshark, Tcpdump, Netwox, Scapy, etc. Some of these tools are widely used by security experts, as well as by attackers. Being able to use these tools is important for students, but what is more important for students in a network security course is to understand how these tools work, i.e., how packet sniffing and spoofing are implemented in software.

The objective of this lab is two-fold: learning to use the tools and understanding the technologies underlying these tools. For the second object, students will write simple sniffer and spoofing programs, and gain an in-depth understanding of the technical aspects of these programs.

This lab covers the following topics:

2 Environment Setup using Container

In this lab, we will use two machines that are connected to the same LAN. We can either use two VMs or use two containers. Figure 1 depicts the lab environment setup using containers. We will do all the attacks on the attacker container, while using the other container as the user machine.

Lab Enviroment Setup

Figure 1: The Lab Container Enviroment

2.1 Container Setup and Commands

Please download the Labsetup.zip file to your VM from the lab’s website, unzip it, enter the Labsetup folder, and use the docker-compose.yml file to set up the lab environment. Detailed explanation of the content in this file and all the involved Dockerfile can be found from the user manual, which is linked to the website of this lab. If this is the first time you set up a SEED lab environment using containers, it is very important that you read the user manual.

Optionally, you can use wget from the terminal inside the VM.

$ wget https://seedsecuritylabs.org/Labs_20.04/Files/Sniffing_Spoofing/Labsetup.zip --no-check-certificate

2.2 About the Attacker

In this lab, we can either use the VM or the attacker container as the attacker machine. If you look at the Docker Compose file, you will see that the attacker container is configured differently from the other containers.

2.2.1 Shared Folder

When we use the attacker container to launch attacks, we need to put the attacking code inside the attacker container. Code editing is more convenient inside the VM than in containers, because we can use our favorite editors. In order for the VM and container to share files, we have created a shared folder between the VM and the container using the Docker volumes. If you look at the Docker Compose file, you will find out that we have added the following entry to some of the containers. It indicates mounting the ./volumes folder on the host machine (i.e., the VM) to the /volumes folder inside the container. We will write our code in the ./volumes folder (on the VM), so they can be used inside the containers.

volumes:
  - ./volumes:/volumes

2.2.2 Host mode

In this lab, the attacker needs to be able to sniff packets, but running sniffer programs inside a container has problems, because a container is effectively attached to a virtual switch, so it can only see its own traffic, and it is never going to see the packets among other containers. To solve this problem, we use the host mode for the attacker container. This allows the attacker container to see all the traffic. The following entry used on the attacker container:

network_mode: host

When a container is in the host mode, it sees all the host’s network interfaces, and it even has the same IP addresses as the host. Basically, it is put in the same network namespace as the host VM. However, the container is still a separate machine, because its other namespaces are still different from the host.

2.2.3 Getting the network interface name

When we use the provided Compose file to create containers for this lab, a new network is created to connect the VM and the containers. The IP prefix for this network is 10.9.0.0/24, which is specified in the docker-compose.yml file. The IP address assigned to our VM is 10.9.0.1. We need to find the name of the corresponding network interface on our VM, because we need to use it in our programs. The interface name is the concatenation of br- and the ID of the network created by Docker. When we use ifconfig to list network interfaces, we will see quite a few. Look for the IP address 10.9.0.1

$ ifconfig

Another way to get the interface name is to use the "docker network" command to find out the network ID ourselves (is this case the name of the network is seed-net):

$ docker network ls
NETWORK ID  NAME    DRIVER  SCOPE 
a82477ae4e6b    bridge  bridge  local 
e99b370eb525    host    host    local 
df62c6635eae    none    null    local 
c93733e9f913    seed-net    bridge  local

3 Sniffing and Spoofing in Python

3.1 Using Scapy to Sniff and Spoof Packets

Many tools can be used to do sniffing and spoofing, but most of them only provide fixed functionalities. Scapy is different: it can be used not only as a tool, but also as a building block to construct other sniffing and spoofing tools, i.e., we can integrate the Scapy functionalities into our own program. In this set of tasks, we will use Scapy for each task.

To use Scapy, we can write a Python program, and then execute this program using Python. See the following example. We should run Python using the root privilege because the privilege is required for spoofing packets. At the beginning of the program (Line (1)), we should import all Scapy’s modules.

# nano mycode.py //Optionally, use another editor of your choice

--------------------------------------------(Editor begins here)

# !/usr/bin/env python3

from scapy.all import * #Line (1)

a = IP()
a.show()

-------------------------------------------- (Editor ends here)

# python3 mycode.py
###[ IP ]###

version = 4
ihl = None ...

//Other way we can run this python program is to make the program executable

# chmod a+x mycode.py
#mycode.py

We can also get into the interactive mode of Python and then run our program one line at a time at the Python prompt. This is more convenient if we need to change our code frequently in an experiment.

# python3 // Start the python3 repl
>>> from scapy.all import *
>>> a = IP()
>>> a.show()
###[ IP ]###
version = 4
ihl = None 
...

3.2 Sniffing Packets

Wireshark is the most popular sniffing tool, and it is easy to use. We will use it throughout the entire lab. However, it is difficult to use Wireshark as a building block to construct other tools. We will use Scapy for that purpose. The objective of this task is to learn how to use Scapy to do packet sniffing in Python programs. A sample code is provided in the following:

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

def print_pkt(pkt): 
    pkt.show() 

pkt = sniff(iface=’br-c93733e9f913’, filter=’icmp’, prn=print_pkt)

The code above will sniff the packets on the br-c93733e9f913 interface. Please read the instructions in the lab setup section regarding how to get the interface name. If we want to sniff on multiple interfaces, we can put all the interfaces in a list, and assign it to iface. See the following example:

iface=[’br-c93733e9f913’, ’enp0s3’]

3.2.1 Capture some packets

In the above program, for each captured packet, the callback function print pkt() will be invoked; this function will print out some of the information about the packet. Run the program with the root privilege and demonstrate that you can indeed capture packets. After that, run the program again, but without using the root privilege; describe and explain your observations

// Make the program executable
# chmod a+x sniffer.py

// Run the program with the root privilege 
# sniffer.py

// Switch to the "seed" account, and
// run the program without the root privilege
# su seed
$ sniffer.py

Deliverables

In your lab report, include the following:

  1. A screenshot of some packets captured when running as root.
  2. A screenshot of the programing running as seed, and an explanation of the difference.

3.2.2 Capturing specific packets

Usually, when we sniff packets, we are only interested in certain types of packets. We can do that by setting filters in sniffing. Scapy’s filter uses the BPF (Berkeley Packet Filter) syntax; you can find the BPF manual from the Internet. Please set the following filters and demonstrate your sniffer program again (each filter should be set separately):

Deliverables

In your lab report, include the following:

  1. The filter for, and a screenshot of, capturing an ICMP packet.
  2. The filter for, and a screenshot of, a password captured from a telnet session.
  3. The filter for, and a screenshot of, a packet from a specified subnet other than the one the VM is attached to.

3.3 Spoofing ICMP Packets

As a packet spoofing tool, Scapy allows us to set the fields of IP packets to arbitrary values. The objective of this task is to spoof IP packets with an arbitrary source IP address. We will spoof ICMP echo request packets, and send them to another VM on the same network. We will use Wireshark to observe whether our request will be accepted by the receiver. If it is accepted, an echo reply packet will be sent to the spoofed IP address. The following code shows an example of how to spoof an ICMP packets.

>>> from scapy.all import * 
>>> a = IP() #Line (1)
>>> a.dst = '10.0.2.3' #Line (2)
>>> b = ICMP() #Line (3)
>>> p = a/b #Line (4)
>>> send(p) #Line (5)

In the code above, Line ➀ creates an IP object from the IP class; a class attribute is defined for each IP header field. We can use ls(a) or ls(IP) to see all the attribute names/values. We can also use a.show() and IP.show() to do the same. Line ➁ shows how to set the destination IP address field. If a field is not set, a default value will be used.

>>> ls(a)
version     : BitField (4 bits)     = 4             (4) 
ihl         : BitField (4 bits)     = None          (None) 
tos         : XByteField            = 0             (0) 
len         : ShortField            = None          (None) 
id          : ShortField            = 1             (1) 
flags       : FlagsField (3 bits)   = <Flag 0()>    (<Flag 0()>) 
frag        : BitField (13 bits)    = 0             (0) 
ttl         : ByteField             = 64            (64) 
proto       : ByteEnumField         = 0             (0) 
chksum      : XShortField           = None          (None) 
src         : SourceIPField         = ’127.0.0.1’   (None) 
dst         : DestIPField           = ’127.0.0.1’   (None) 
options     : PacketListField       = []            ([]) 

Line ➂ creates an ICMP object. The default type is echo request. In Line ➃, we stack a and b together to form a new object. The / operator is overloaded by the IP class, so it no longer represents division; instead, it means adding b as the payload field of a and modifying the fields of a accordingly. As a result, we get a new object that represents an ICMP packet. We can now send out this packet using send() in Line ➄. Please make any necessary change to the sample code, and then demonstrate that you can spoof an ICMP echo request packet with an arbitrary source IP address.

Deliverables

In your lab report, include the following:

  1. Screenshot of the python program used to spoof the packet.
  2. Screenshot of wireshark packet trace showing your program successfully spoofing an ICMP Echo request (use an arbitrary IP address as the source). This packet should be sent to another container. Show the wireshark evidence of the return packet coming back to the spoofed IP.

4 Sniffing and Spoofing in C

As we saw in the TCP lab, many exploit programs are still written in C despite the fact that they can be more complex than python for reasons of performance or ability to perform low level operations. In this section, We will explore building sniffing and spoofing programs in C.

4.1 Packet Sniffing with pcap

Sniffer programs can be “easily” written using the pcap library. With pcap, the task of sniffers becomes invoking a simple sequence of procedures in the pcap library. At the end of the sequence, packets will be put in a buffer for further processing as soon as they are captured. All the details of packet capturing are handled by the pcap library. Tim Carstens has written a tutorial on how to use the pcap library to write a sniffer program. The tutorial is available at http://www.tcpdump.org/pcap.htm

4.1.1 Understanding sniffex

Please download the sniffex.c program from the link in the tutorial mentioned above, compile and run it.

Deliverables

In your lab report, include the following:

  1. A screenshot showing that sniffex runs successfully and produces expected results. Hint – you may want to change the default behavior to sniff only 1 packet to make this easier to capture.
  2. A description of the sequence of library calls essential for sniffer programs. This should be a SUMMARY, not a detailed explanation like in the tutorial
  3. Why do you need the root privilege to run sniffex? Where does the program fail if executed without the root privilege?
  4. Turn on and turn off the promiscuous mode in the sniffer program. Can you demonstrate the difference when this mode is on and off? Please describe how you demonstrate this.

4.2 Packet Filtering

Please write filter expressions for your sniffer program to capture each of the following

  1. ICMP packets between two specific hosts
  2. TCP packets in the port range [10, 100]

Deliverables

In your lab report, include the following:

  1. Capture the ICMP packets between two specific hosts.
  2. Capture the TCP packets that have a destination port range from to port 10 – 100.

4.3 Spoofing

When a normal user sends out a packet, operating systems usually do not allow the user to set all the fields in the protocol headers (such as TCP, UDP, and IP headers). OSes will set most of the fields, while only allowing users to set a few fields, such as the destination IP address, the destination port number, etc. However, if users have the root privilege, they can set any arbitrary field in the packet headers. This is called packet spoofing, and it can be done through raw sockets.

Raw sockets give programmers the absolute control over the packet construction, allowing programmers to construct any arbitrary packet, including setting the header fields and the payload. Using raw sockets is quite straightforward (aside from the C syntax :-); it involves four steps:

  1. Create a raw socket.
  2. Set the socket option(s).
  3. Construct the packet
  4. Send the packet over the socket.

There are many online tutorials that can teach you how to use raw sockets in C programming. Here are two good ones:

http://recursos.aldabaknocking.com/libpcapHakin9LuisMartinGarcia.pdf
http://www.tenouk.com/Module43a.html

Please read them, and learn how to write a packet spoofing program (recitation will also be very helpful). You may also want to read the guidelines section at the end of this lab for further tips.

A sample file (and associated header file) are available here to get you started
icmpspoof.c
seedheaders.h

Deliverables

Write a spoofing program in C, using the sample file and header file provided above. In your lab report, include, along with your program as a separate file,

  1. Screenshot of wireshark packet trace showing your program successfully sending a spoofed packet.
  2. Screenshot of wireshark packet trace showing your program successfully spoofing an ICMP Echo request (use another virtual machine’s IP address as the source). This packet should be sent to a real machine on the internet, like google. Show the wireshark evidence of the return packet coming back to the spoofed IP.
  3. Can you set the IP packet length field to a value different than the actual packet size? If so, are there any limitations on what size you set it to? Demonstrate the results of your attempt in a wireshark screenshot.
  4. Why do you need root privilege to run the programs that use raw sockets? Where does the program fail if you execute it without root?

4.4 Sniff and then Spoof

In this task, you will combine the sniffing and spoofing techniques to implement the following sniff-and-then-spoof program. You need two containers on the same LAN. From container A, you ping an IP X. This will generate an ICMP echo request packet. If X is alive, the ping program will receive an echo reply, and print out the response. Your sniff-and-then-spoof program runs on container B, which monitors the LAN through packet sniffing. Whenever it sees an ICMP echo request, regardless of what the target IP address is, your program should immediately send out an echo reply using the packet spoofing technique. Therefore, regardless of whether machine X is alive or not, the ping program will always receive a reply, indicating that X is alive. You need to write such a program, and include screenshots in your report to show that your program works.

Please also attach the C code (with adequate amount of comments) as a separate file.

ARP Requests and ICMP

When you ping a non-existing IP in the same LAN, an ARP request will be sent first. If there is no reply, the ICMP requests will not be sent later. So in order to avoid that ARP request which will stop the ICMP packet, you need to change the ARP cache of the victim container by adding another MAC to the IP address mapping entry. The command is as follows:

$ sudo arp -s 10.0.2.99 AA:AA:AA:AA:AA:AA

IP 10.0.2.99 is the non-existing IP on the same LAN. AA:AA:AA:AA:AA:AA is a spoofed MAC address.

Deliverables

Write the sniff-and-spoof program in C. In your lab report, include the following:

  1. Screenshot of a machine pinging a nonexistent IP address and getting results returned.
  2. Code of your program (submit as a separate file)

5 Guidelines and Additional Information

5.1 Filling in Data in Raw Packets

When you send out a packet using raw sockets, you basically construct the packet inside a buffer, so when you need to send it out, you simply give the operating system the buffer and the size of the packet. Working directly on the buffer is not easy, so a common way is to typecast the buffer (or part of the buffer) into structures, such as IP header structure, so you can refer to the elements of the buffer using the fields of those structures. You can define the IP, ICMP, TCP, UDP and other header structures in your program. The following examples show how you can construct an UDP packet:

struct udpheader {
u_int16_t udp_sport; /* source port */
    u_int16_t udp_dport; /* destination port */
u_int16_t udp_ulen; /* udp length */
u_int16_t udp_sum; /* checksum */
}

struct ipheader {
unsigned char iph_protocol;
......
}

// This buffer will be used to construct raw packet.
char buffer[1024];

// Typecasting the buffer to the IP header structure
struct ipheader *ip = (struct ipheader *) buffer;

// Typecasting the buffer to the UDP header structure
struct udpheader *udp = (struct udpheader *) (buffer
+ sizeof(struct ipheader));

// Assign value to the IP and UDP header fields.
ip->iph_protocol = IPPROTO_UDP;
udp->sport = 22;

More details on the structure of the various packet types will be provided in recitation.

5.2 Network/Host Byte Order and the Conversions

You need to pay attention to the network and host byte orders. If you use x86 CPU, your host byte order uses Little Endian, while the network byte order uses Big Endian. Whatever the data you put into the packet buffer has to use the network byte order; if you do not do that, your packet will not be correct. You actually do not need to worry about what kind of Endian your machine is using, and you actually should not worry about if you want your program to be portable. What you need to do is to always remember to convert your data to the network byte order when you place the data into the buffer, and convert them to the host byte order when you copy the data from the buffer to a data structure on your computer. If the data is a single byte, you do not need to worry about the order, but if the data is a short, int, long, or a data type that consists of more than one byte, you need to call one of the following functions to convert the data:

htonl(): convert unsigned int from host to network byte order
ntohl(): reverse of htonl()
htons(): convert unsigned short int from host to network byte order
ntohs(): reverse of htons()

You may also need to use inet addr(), inet network(), inet ntoa(), inet aton() to convert IP addresses from the dotted decimal form (a string) to a 32-bit integer of network/host byte order. You can get their manuals from the Internet, or by using the man-pages.

6 Submission

You will need to submit a written lab report through UBLearns, containing all of the deliverable elements above, by the due date in UBLearns. You also need to submit two separate files, one for spoofing, and one for sniffing+spoofing.

7 Scoring

Deliverables Points
3.2.1.1 3
3.2.1.2 3
3.2.2.1 3
3.2.2.2 3
3.2.2.3 3
3.3.1 3
3.3.3 3
4.1.1 3
4.1.2 3
4.1.3 3
4.1.4 3
4.2.1 3
4.2.2 3
4.3.1 3
4.3.2 3
4.3.3 3
4.3.4 3
4.4.1 3
4.4.2 3
Total 57