Charlie The Sniffer is a basic packet sniffer tool that I have created for a university project. It is launched from the command line interface. It provides the live output of the sniffed traffic and the output in a pcap file.
This tool is for educational purpose only. Before use it, you must ensure that you have the right permissions to run it. I am not responsible in any way for any illegal and/or not authorized action that you will carry out.
- Installation
- Usage
- Dependencies
- Network packets format
- The program behaviour
- Improvements
- References
First of all, you have to download libpcap-dev on your host, to do so type:
$ sudo apt install libpcap-dev
Then, reach the directory where Charlie is, then compile from terminal:
$ gcc CharlieTheSniffer.c -o CharlieTheSniffer -lpcap
Once you have complete the installation process, you can launch it with:
$ ./CharlieTheSniffer
By doing so, it will provide the helping message:
--------------- CharlieTheSniffer ---------------
Usage:
# ./CharlieTheSniffer [-s] [-i ID] [-h]
-s show interfaces
-i <ID> to select a interface by giving its name
-h show this helping message
Example:
# ./CharlieTheSniffer -i eth0
Tips:
If you don't want the output on terminal you can direct it to a file with redirection power:
$ sudo charliethesniffer.c -i eth0 > file_with_output
Disclaimer:
You MUST use this tool only within environment where
you have officially the rights to do so.
Remember that you have to run it as root/sudoer.
To tune the sniffing process you can tweak two options:
- The number of packets that the tool should capture, change the value of PCKCNT
#define PCKCNT 100
- The packet buffer timeout, change the valure of MS
#define MS 1000
OS: Linux (Tested on Linux pop-os 5.8.0-7630-generic)
Libraries: libpcap
Privileges: root
Before going deeper with the code, in order to gain a better understanding of the logic behind the code, we have to understand how the data transmitted are formatted. The protocols supported at the moment are:
- TCP
- IP
- ICMP
- IGMP
- UDP
So let's begin.
The Transmission Control Protocol packet is composed by a header, that includes all the options and details on how the packet should be handled, and a payload, containing the actual data transmitted.
Let's take a look at the header structure (source: RFC 793):
Just to clarify, the first number row (0-3), are bytes (It indicates, the second number row are bits. Giving that, this is the map that we must follow to gather the right information from the TCP header. For example, we will need to know what will be the source port and the destination port of the given packet. In this case, we will have to fetch from the header the first 16 bytes for the former and the second 16 bytes for the latter. And so on for every other information that we will need.
The Internet Protocol packet is similar to the TCP packet, it is composed by a header and a payload. Obviously, the packet fields will differ but the logic will be the same.
Give it a look (source: RFC 791):
The Internet Control Message Protocol is a little bit different. It does not rely on a dedicated packet structure, but it is integrated with the IP header where we will find the number 1 inside the protocol flag, then the first octet of the data portion of the datagram is used as the ICMP type field, that will affect the following bits. The data field will differ based on which message will be provided.
The most common ICMP message is the ECHO/ECHO REPLY, unchained by the well known PING command.
It will be as follow (source RFC 792):
Next, we have the Internet Group Message Protocol, as ICMP it is integrated with IP and its protocol numer is 2.
The structure is quite straightforward (source RFC 2236):
The User Datagram Protocol is the opposite of the TCP. While TCP is connection-oriented, UDP is connectionless. It does not mean that there won't be any connection as suggested, but that for each connection that will be provided it won't verify the receipt. For this reason and for more technical others, the packet (called datagram in connectionless protocols) structure is quite simpler.
Here we don't have only a header and a payload, infact the UDP datagram is shipped with a pseudoheader prefix generated from the IP header, that it is necessary to calculate the checksum.
UDP datagram payload(source RFC 768):
UDP pseudo header (source RFC 768):
This sniffer has been created to be used from a command line interface. It means that in order to use it you have to provide some options and arguments in addition to the program name. We are going to look them in details later.
Now, we have to focus on the behaviour at the mid-high level, it is as follow:
- Launch the program with the choosen interface and options;
- Charlie will check if the device is available, if it is not, it will tell you and exit;
- Otherwise, if it is available, it will prepare the device so it will be able to sniff, if negative it will tell you and stop;
- If it succeeds, it will go on a sort of loop and start the actual sniffing;
- Now, for every captured packet, it will check the protocol then it will print out on terminal/file the whole TCP or UDP packet (headers + payloads)
Once we have the high level in mind, let's see what functions we are going to use. First, we are going to see the functions created by me. I am going to show them with the aid of a table. The table is quite self explanatory, but just to be clear in the first column I've put the function category that we are going to find inside the code comments. These categories are just a logical subdivisione made by me to have a cleaner code.
Let's see the table:
Function category | Function name | Task |
---|---|---|
support | showInterfaces() | it prompts the available interfaces using the pcap_findalldevs() |
support | usage() | it issues the helping message |
support | showError() | it prompts the error in a standardized and simple format |
support | showStatus() | it prompts the status in a standardized and simple format |
sniffer | cookingPreSniffer() | it prepares the dev to sniff, then it launches the actual sniffing task |
sniffer | packetHandler() | it handles actions to be done against every captured packet |
sniffer | printTcpHeader() | it prints the TCP header |
sniffer | printIpHeader() | it prints the IP header |
sniffer | printIcmpHeader() | it prints the ICMP header |
sniffer | printUdpDatagram() | it prints the UDP datagram |
sniffer | printPayload() | it prints the payload |
Second, let's take a look at the functions used from the libpcap library. Again, I am going to use a table. This time, in the first column I will put one of my function where we can find the caller to the libpcap function.
Position | Function name | Task |
---|---|---|
showInterfaces() | pcap_findalldevs() | it constructs a list of network devices that can be opened with other functions |
cookingPreSniffer() | pcap_open_live() | it is used to obtain a packet capture handle to sniff packet on the network |
cookingPreSniffer() | pcap_dump_open() | it opens a pcap file to start writing captured packet |
cookingPreSniffer() | pcap_loop() | it starts the actual sniffing and it does it by looping |
cookingPreSniffer() | pcap_dump_close() | it ends the writing process and closes the pcap file |
For this section, I will let the code comments speak for me as I think that is unecessary write each function prototype and show each arguments here.
I find all this stuff quite interesting, but it is useless if we don't know how to get exactly the right information from a packet composed by multiple bytes. To do so, we just have to think about the packet structure. It is just a sequence of bytes, so if we want to move get some portion of it all we need is to do some basic maths. Just a couple of sums/diffs and we well have what we want. Let's see it in action inside a table:
Variable | Location in bytes |
---|---|
Ethernet size | X |
IP header length | X + Ethernet Size |
TCP header length | X + Ethernet Size + IP header length |
Payload | X + Ethernet Size + IP header length + TCP header length |
This is a list of the next possible improvements to do:
- Tweaking functionalities as command line options
- Moving the IP header construction inside the packetHandler function
- Support for other protocols
- Support for "infinite" sniffing until CTRL + C hotkey
Here you will find documents and other references that I used to bring CharlieTheSniffer to life:
- Official website for LibPcap
- pcap_open_live man page
- pcap_findalldevs man page
- pcap_loop
- pcap_dump_open
- pcap_dump_close
- Protocol numbers
- RFC 768
- RFC 791
- RFC 792
- RFC 793
- RFC 2236
- Tutorials Point for C
- Write a good C main fun
- University studying materials
Btw, KISS is your best friend.