Nmap has been a very-effective tool for network discovery and security analysis for about 20 years now. The goal of the project was to implement certain network discovery techniques which are available in Nmap. A full implementation of Nmap would take quite a long time of development, hence in this project, we have decided to stick with certain techniques that are popularly used.
The motivation of the project was to get an idea of how such network discovery tools are implemented, and to objectively see how hard implementing a suite like Nmap would actually be.
The size of the project stands at around 2000 lines of code written in C++ and Python. The tools implemented as a part of the project are:
-
Host discovery utility
-
Port scanning techniques:
SYN
,FIN
,NULL
,XMAS
andDecoy
scan -
A TCP sniffer
-
ARP Poisoner (with ARP Doctor)
A full documentation of the code is available to be auto-generated for user convenience. This is discussed later.
The code is organized in the following manner:
-
ping.h/.cpp
consists of ICMP ping preliminaries required for the host discovery. -
discover.h/.cpp
consists of code required for host discovery using ICMP messages. -
packet.h/.cpp
consists of code which enables socket creation, packet processing (e.g., checksum calculation), layer-wise packet assembly. -
scan.h/.cpp
consists of code required for scanning techniques. -
sniff.h/.cpp
consists of code required for the TCP sniffing utility -
error.h/.cpp
consists of error handling utilities. -
logger.h/.cpp
consists of logging utilities. -
arpPoison.py
is a Python module consists of code required for a Man-in-the-Middle Attack using ARP poisoning.
The main host discovery takes place with the discover_host()
function.
The algorithm followed by the function is as follows:
- Get CIDR string and validate CIDR
IPaddr
,Mask
=CIDR_string
- Get list of
IPaddr
s usingIPaddr
andMask
- Populate a
Queue
with requests active
= Empty Set- Open request and get the
IPaddr
. Ping theIPaddr
and await reply- If
IPaddr
replies- add
IPaddr
toactive
.
- add
- Else
- If non-zero trials for the request left, add to the queue. Otherwise, don’t.
- If
- Return
active
This a utility file. This helps create socket descriptors, assemble packets at a header level (TCP
, IP
), and calculate checksums.
The source code has been adapted and modified to requirements based on other code available online (Linux kernel). These links are available in the references and the function descriptions.
This is a utility file to handle ICMP
packets. This file consists of functions to create sockets, assemble ICMP
packets, and handling sending and receiving of ping messages.
Here is where the port scanning techniques take place. The most important function is scan
. Here, too, we follow a round robin approach with requests, hence the algorithm is very similar to that specified above. Instead of CIDR
, we will instead have a user specified range of ports, and instead of pinging we would be sending specialized packets pertaining to each technique. We have incorporated parallelism by chunking the ports to be scanned evenly across multiple threads. The scanning techniques are briefly explained below:
-
In SYN scanning, the attacking client attempts to set up a
TCP/IP
connection with a server at every possible port. This is done by sending aSYN
(synchronization) packet, to mimic initiating a three-way handshake, to every port on the server. If the server responds with aSYN/ACK
(synchronization acknowledged) packet from a particular port, it means the port is open. If the server responds with aRST
(reset) packet from a particular port, it means the port is closed. -
In FIN scanning, the specifications of RFC 793 are exploited. RFC 793 states that: “Traffic to a closed port should always return
RST
” and “If neither of theSYN
orRST
bits is set then drop the segment and return”. Now, if you send a packet with aFIN
bit set to 1, then for closed ports you will receive aRST
, but for open ports / filtered ports you will not receive anything. Thus, if there is no firewall, then there is a good chance that timeout indicates activity of the port. -
Both these work in the same way as FIN Scan, thereby exploiting the specifications of RFC 793.
-
To prevent a filtering of the attacker’s side, there is a utility called Decoy Scan. This sends spoofed packets mimicking random
IP
address to the victimIP
address, thus making it hard to pinpoint the attacker.
This is a sniffing utility. Sent and Received packets are analyzed in a process_packet
function. This sniffer only analyzes TCP
packets, since most of our scans are designed to work with the TCP
header.
This has two key functions - poisoner
and doctor
. poisoner
will tell the victim that the MAC address of the gateway is its own, and will tell the gateway that the victims’s MAC address is its own. Now packets inbound to the victim from anywhere will be intercepted by the attacker. Messages from the victim to the gateway, will be intercepted by the attacker as well.
To revert the changes made, the “doctor” broadcasts messages as the gateway and victim asking for the MAC addresses of the victim and gateway respectively, thus restoring the ARP tables.
This is a simple man-in-the-middle attack, which is why we decided to add it in this suite. The network library ScaPy was used for this module alone. The main file will execute the attack for a given duration, after which the attacked reprises the role of a “doctor” and reverts the changes
Certain design decisions were made as a part of the project. We chose C++ over Python, despite the huge module support from Python, since we believed that C++ was closer to the Linux Network API than Python, thus leaving room for less error. We have also made use of threads for parallel port scanning, and Python’s threading module is not as efficient as C++, which is also another reason to migrate to C++.
However, for the ARP poisoning tool, we decided to go with Python instead of C++, since it would require dealing with the link-layer packets, which could cause a lot of pointer manipulation, which is tedious and error-prone.
We have also effectively modularized our code, so that the utilities designed could also be used for other purposes than their intended usage as well. For example, we have a ICMP message sender which can be used in place of the iputils
utility ping
.
In the host discovery and port scanning techniques, we have decided to use a Round-robin technique. Here, we create a queue of “requests”, and these “requests” are processed in a round-robin fashion. If a “request” is successfully completed or has run out of trials, then we remove it from the queue. Other alternatives were to wait until a reply arrived for a given request - which is susceptible to timeouts. This round-robin scheme allows us to pipeline the process as well.
We have also made use of raw sockets (specified using SOCK_RAW
). This helps us modify the properties of the socket to our will (for example: sending spoofed packets), which is why we decided to use them over traditional stream or datagram sockets. Using raw sockets, we were able to populate the header appropriately as per requirements (for TCP, IP, and ICMP).
We have made use of OpenStack
. Using this, we have created a private subnet, which behaves as the test-bed for our tools.
We have also made use of tcpdump
and WireShark
for monitoring the network traffic during the time of the attack.
For specifying input to the executable, we have used a JSON (Javascript Object Notation) parser designed by Niels Lohmann. The file json.h
is code for the aforementioned parser.
The documentation in generated using Doxygen.
We had a working prototype a firewall, which will be able to detect port-scanners. We were able to test it completely, which is why we haven’t submitted it as a part of the project.
Furthermore, we also would like improve this by adding an OS
fingerprinting technique, wherein specifics of the networking API
s of
multiple OSes are exploited to possibly find of the OS
operating at
each port.
Please note that an illustrated version of this document is available in main.pdf