SOCKET PROGRAMMING ON UNIX - TCP SYN PORT SCANNING(PART 3/3)
2020-06-16
In the previous part of this tutorial
we saw what raw sockets actually are and how to use them to build something
useful(i.e. a tcpdump clone). In the last part of this guide,
we will extend our knowledge about raw sockets by writing a simple TCP SYN port scanner.
Before getting into the actual code, let us understand how does a port scanner work and
what are the main techniques to perform port scanning.
>A port scanner is a tool designed to look for open ports on a remote server. These tools are mainly used by system administrators and security researchers to get an idea of which network services are being executed on a given host.
A quick overview of Nmap
The most used and accurate port scanner available on UNIX systems is nmap. Nmap comes with a lot of options and a lot of different scanning techniques; you can install it on your computer, and you can try to perform a port scan on one of your server. If you do not have any server available, you can use the nmap playground: a testing environment provided by nmap.org itself that allow you to legally test their software. Go ahead and try it out:
$> nmap -p0- -v -A -T4 scanme.nmap.org
Starting Nmap 7.91 ( https://nmap.org ) at 2021-06-09 12:26 CEST
NSE: Loaded 153 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 12:26
Completed NSE at 12:26, 0.00s elapsed
Initiating NSE at 12:26
Completed NSE at 12:26, 0.00s elapsed
Initiating NSE at 12:26
Completed NSE at 12:26, 0.00s elapsed
Initiating Ping Scan at 12:26
Scanning scanme.nmap.org (45.33.32.156) [2 ports]
Completed Ping Scan at 12:26, 0.17s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 12:26
Completed Parallel DNS resolution of 1 host. at 12:26, 0.35s elapsed
Initiating Connect Scan at 12:26
Scanning scanme.nmap.org (45.33.32.156) [65536 ports]
Discovered open port 80/tcp on 45.33.32.156
Discovered open port 22/tcp on 45.33.32.156
Connect Scan Timing: About 8.18% done; ETC: 12:33 (0:05:48 remaining)
Stats: 0:00:57 elapsed; 0 hosts completed (1 up), 1 undergoing Connect Scan
Connect Scan Timing: About 14.86% done; ETC: 12:33 (0:05:27 remaining)
Connect Scan Timing: About 25.51% done; ETC: 12:33 (0:05:07 remaining)
Connect Scan Timing: About 33.58% done; ETC: 12:33 (0:04:27 remaining)
Connect Scan Timing: About 41.16% done; ETC: 12:33 (0:03:56 remaining)
Connect Scan Timing: About 49.22% done; ETC: 12:33 (0:03:21 remaining)
Connect Scan Timing: About 58.11% done; ETC: 12:33 (0:02:42 remaining)
Connect Scan Timing: About 66.19% done; ETC: 12:33 (0:02:10 remaining)
Connect Scan Timing: About 74.02% done; ETC: 12:33 (0:01:40 remaining)
Discovered open port 9929/tcp on 45.33.32.156
Connect Scan Timing: About 81.40% done; ETC: 12:33 (0:01:12 remaining)
Discovered open port 31337/tcp on 45.33.32.156
Connect Scan Timing: About 89.89% done; ETC: 12:33 (0:00:39 remaining)
Completed Connect Scan at 12:33, 386.55s elapsed (65536 total ports)
Initiating Service scan at 12:33
Scanning 4 services on scanme.nmap.org (45.33.32.156)
Completed Service scan at 12:33, 6.37s elapsed (4 services on 1 host)
NSE: Script scanning 45.33.32.156.
Initiating NSE at 12:33
Completed NSE at 12:33, 5.61s elapsed
Initiating NSE at 12:33
Completed NSE at 12:33, 0.71s elapsed
Initiating NSE at 12:33
Completed NSE at 12:33, 0.00s elapsed
Nmap scan report for scanme.nmap.org (45.33.32.156)
Host is up (0.17s latency).
Other addresses for scanme.nmap.org (not scanned): 2600:3c01::f03c:91ff:fe18:bb2f
Not shown: 65530 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 1024 ac:00:a0:1a:82:ff:cc:55:99:dc:67:2b:34:97:6b:75 (DSA)
| 2048 20:3d:2d:44:62:2a:b0:5a:9d:b5:b3:05:14:c2:a6:b2 (RSA)
| 256 96:02:bb:5e:57:54:1c:4e:45:2f:56:4c:4a:24:b2:57 (ECDSA)
|_ 256 33:fa:91:0f:e0:e1:7b:1f:6d:05:a2:b0:f1:54:41:56 (ED25519)
25/tcp filtered smtp
80/tcp open http Apache httpd 2.4.7 ((Ubuntu))
|_http-favicon: Nmap Project
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: Go ahead and ScanMe!
9929/tcp open nping-echo Nping echo
11211/tcp filtered memcache
31337/tcp open tcpwrapped
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
NSE: Script Post-scanning.
Initiating NSE at 12:33
Completed NSE at 12:33, 0.00s elapsed
Initiating NSE at 12:33
Completed NSE at 12:33, 0.00s elapsed
Initiating NSE at 12:33
Completed NSE at 12:33, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 400.47 seconds
In this example we told nmap to scan every possible TCP port(-p0-
),
to enable OS/service detection(-A
),
to be verbose(-v
)
and to be as quick as possible(-T4
).
This gave us a lot of information about our target machine!
Port scanning techniques
The port scanner we are going to build will only cover one of these scanning methods(i.e. the most simple) but still, it is relevant to understand the main differences between these approaches.ICMP scan
Ping scan(or ICMP scan) is a type of scan used to make sure a host is up and running. It works by sending a ICMP echo request to the target; if you get any response, then the host is alive. The main downfall is that nowadays the majority of firewalls are configured to drop any ICMP packets, so you cannot rely on this approach to check if a host is alive.Half-Open TCP SYN scan
This is probably the most common(and the fastest) port scanning technique. Before explaining how does it work, we need to make a step back and understand how a TCP connection is established:Three-way handshake proces
The RFC793 standard defines how a TCP connection is made. The process is usually referred as three-way handshake process since it consists in three different steps.- The client requests to establish a new TCP connection by sending a SYN(synchronized) packet to the remote host;
- The server acknowledges the request by sending a SYN-ACK packet back to the client;
- The client sends an ACK packet to complete the connection process.
TCP Connect scan
This is type of scan is basically like the previous one, except for the fact that we complete whole handshake process. This is a much slower process(since we need to send more packets for each port we intend to scan) but is indeed more stealthy from the previous one. In fact, the Half-Open SYN scan could be easily detected from an experienced sysadmin since it creates a lot of "interrupted" attempts of connections. Needless to say, neither the TCP connect scan is the most silent method of scanning a network: this type of scan logs a lot of successful attempts of establish a TCP connection without any exchange of data. Again, an experienced sysadmin can easily spot this kind of requests between a bunch of legit ones.Stealthy scanning techniques
So far we saw what are the basic port scanning techniques used by the majority of port scanner such as nmap. They all have, however, a big downfall: they are not stealthy at all. They are usually being logged and are easily spottable by a system administrator. To avoid this, other types of port scanning techniques has been developed by exploiting the characteristics of the TCP RFC to differentiate between open and closed ports. Another aspects of stealthy scans is that they typically do not require the handshaking process since they exploit some loophole of the RFC standard.NULL scan
In the previous part of this tutorial, we developed a simple packet sniffer which printed out some information about the captured packets such as the IP header, the TCP header and the actual payload. The TCP header contained exactly 6 flags(URGENT, ACKNOWLEDGE, PUSH, RESET, SYNCHRONIZE, FINISH), below a brief explanation about their meaning:- URG: This flag informs the receiver that the data contained within the packet must be prioritized. Nowadays is quite obsolete, and honestly I did not find any real-world usage of it;
- ACK: Acknowledge the client that data has been received;
- PSH: When you send some data across a network, the transport layer waits to send the packet until the data segment has reached its maximum capacity. This is done to minimize the number of packets transmitted over a network(and so to avoid to jam the bandwidth). This is not desirable in all this situation where we want to send data as fast as possible(e.g., real time chatting applications such as Whatsapp, Telegram, etc.). To avoid this problem, we can set the PSH flag to 1 and the kernel will send out the packet as fast as possible;
- RST: Terminate the connection;
- SYN: Ask the remote server to establish a new connection;
- FIN: Ask the server to terminate the connection.
FIN scan
A FIN scan sends a TCP packet with only the FIN flag(Finish flag, used to request the termination of the connection) set. Again, since this is an illegal request for the RFC793 standard, the server must send us an RST packet if the port is closed and nothing if the port is either open or filtered.XMAS scan
An XMAS scan is another stealthy port scanning technique that sends a TCP packet with URG, PSH and FIN flags set to 1. The reason behind its name is that, if you try to analyze the TCP header of a TCP packet with these flags set to 1, it looks like a Christmas tree: Again, since this kind of packets are considered illegal by the RFC793 standard, the server must send back a RST response on closed port and nothing on open/filtered port.The major disadvantage about these approaches is that not all operating systems implements RFC793 standard equally. Some systems(such as Microsoft Windows), send an RST response to any invalid packet even if the port is open(in the previous sections we assumed that open/filtered ports would have just ignored the illegal packet). This leads to the situation where all ports are being marked as closed. However, this downfall could be used to determine the host's operating system: if you find at least one open port using this method, then it must not be Microsoft Windows.
Let's build a TCP SYN port scanner
Ok enough theory for today, let's dive into the logic behind the port scanner:- Take an IP address/hostname and a list of ports from
argv
array; - For each port, start to sniff for SYN-ACK packets on another thread;
- For each port, send a SYN packet;
- Wait for a response(either open or closed port) from the child thread;
0-65535
);
instead, we ask the user for a list of ports, and we check whether
they are open or not.
The code is heavily commented and is based on the code from the two previous parts of this tutorial, therefore you should be able to understand it by yourself.
main.c
/* SPS:
* A SYN TCP "Half-Open" port scanner written in C
* This port scanner is made for educational purposes
* and should not be used on production
* environments. Refer to the following link for
* further information: https://blog.marcocetica.com/posts/socket_tutorial_part3/
*
* Developed by Marco Cetica <ceticamarco@gmail.com> 2021
*/
#include <stdio.h> // printf, puts
#include <getopt.h> // getopt_long
#include <string.h> // memset
#include <stdlib.h> // malloc, atoi
#include <unistd.h> // close syscall
#include <ctype.h> // isdigit
#include <sys/socket.h> // socket APIs
#include <arpa/inet.h> // inet_ntoa
#include <netinet/tcp.h> // TCP header
#include <netinet/ip.h> // IP header
#include <netdb.h> // gethostbyname
#include <time.h> // for localtime
#include "src/scanner.h"
#define HELPER_MSG(name) printf("Try \"%s --help\" for more information.\n", name)
#define MAX_PORTBUF_SIZE 1024
#define BUF_SIZE 65536
#define DNS_SERVER "1.1.1.1"
#define DNS_SERVER_PORT 53
#define VERSION "0.0.1"
typedef enum {false, true} bool; // Implement bool type
// Private methods
static unsigned int parse_ports_list(char *port_list, int *formatted_port_list);
static const char *resolve_hostname(const char *address);
static void get_client_ip(char *ip_addr);
void helper() {
puts("SPS is a SYN TCP port scanner for GNU/Linux systems\n"
"-s, --hostname HOST | Set hostname to scan\n"
"-p, --ports <PORT1,PORT2,...> | Check if port is open\n"
"-h, --help | Print this helper\n"
"-a, --about | About this tool\n"
"Example: ./sps -s scanme.nmap.org -p 22,80\n"
);
}
int main(int argc, char **argv) {
// Compute execution time
double duration = 0.0;
clock_t begin = clock();
if(argc < 2) { // Check argument count
HELPER_MSG(argv[0]);
return 1;
}
int sock_fd = 0; // Raw socket file descriptor
struct in_addr server_ip;
int ports[MAX_PORTBUF_SIZE] = {0}; // Port to be scanned
unsigned int p_count = 0;
const char *host; // Target host
char ip_addr[MAX_PORTBUF_SIZE]; // Local IP address
int opt = 0;
const char *short_opts = "p:s:ha";
bool is_hostopt_enable = false, is_portopt_enable = false;
struct option long_opts[] = {
{"hostname", required_argument, NULL, 's'},
{"ports", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{"about", no_argument, NULL, 'a'},
{NULL, 0, NULL, 0}
};
// parse command line parameters
while((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
switch (opt) {
case 's': {
// Check if host is null
if(optarg[0] == '\0') {
printf("Error: \"-s\" parameter requires exactly one value.\n");
return 1;
}
// Save host parameter
host = optarg, is_hostopt_enable = true;
}
break;
case 'p': {
// Check if port list is empty
if(optarg[0] == '\0') {
printf("Error: \"-p\" parameter requires at least one value.\n");
return 1;
}
// Parse port list
p_count = parse_ports_list(optarg, ports);
is_portopt_enable = true;
}
break;
case 'a':
#ifdef __STDC_VERSION__
printf("SRS - a SYN TCP port scanner for GNU/Linux systems.\n\
Developed by Marco Cetica 2021\n\
STDC_VERSION: %ld\n", __STDC_VERSION__);
#else
puts("SRS - a SYN TCP port scanner for GNU/Linux systems.\n\
Developed by Marco Cetica 2021\n");
#endif
return 0;
case 'h':
helper();
return 0;
case ':':
case '?':
default:
return 1;
}
}
// Check if both host and port options are specified
if(is_hostopt_enable && is_portopt_enable) {
// If host is a IPv4 address, store it
if(inet_addr(host) != (in_addr_t)-1)
server_ip.s_addr = inet_addr(host);
else { // Otherwise, parse it first
if(resolve_hostname(host) != NULL)
server_ip.s_addr = inet_addr(resolve_hostname(host));
else {
printf("Unable to resolve host \"%s\"\n", host);
return 1;
}
}
// Open raw socket
sock_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if(sock_fd < 0) {
perror("Unable to create socket");
return 1;
}
// Prepare TCP/IP Header
char datagram[DATAGRAM_BUF_SIZE];
struct iphdr *ip_head = (struct iphdr*)datagram; // IP header
struct tcphdr *tcp_head = (struct tcphdr*)(datagram + sizeof(struct ip)); // TCP header
get_client_ip(ip_addr);
setup_datagram(datagram, server_ip, ip_addr, ip_head, tcp_head);
// Print some info on the terminal
time_t t = time(NULL);
struct tm tm_s = *localtime(&t);
printf("Starting SPS %s at %d-%02d-%02d %02d:%02d CEST\n",
VERSION,
(tm_s.tm_year + 1900),
(tm_s.tm_mon + 1),
(tm_s.tm_mday),
(tm_s.tm_hour),
(tm_s.tm_min)
);
printf("PORT\t\tSTATE\n");
// For each port, set header's parameters and send SYN packet to host
for(size_t current_port = 0; current_port < p_count; current_port++) {
if(scan_port(sock_fd, datagram, server_ip, ip_addr, tcp_head, ports[current_port]) != 0) {
perror("Unable to send SYN packet");
return 1;
}
}
// Finally, close raw socket
close(sock_fd);
} else {
printf("Error: both \"-p\" and \"-s\" must be specified.\n");
HELPER_MSG(argv[0]);
return 1;
}
clock_t end = clock();
duration += (double)(end - begin) / CLOCKS_PER_SEC;
printf("\nSPS done: %d ports scanned in %f seconds.\n", p_count, duration);
return 0;
}
// Breaks "<PORT1,PORT2,...,PORTn>" into an array of integers
unsigned int parse_ports_list(char *port_list, int *formatted_port_list) {
char *token, *next;
const char *separator = ",";
unsigned int count = 0;
// Start parsing string
token = strtok(port_list, separator);
while(token != NULL) {
// Check if token is a number
strtol(token, &next, 10);
if((next == token) || (*next != '\0')) {
printf("Error: port parameter must contains numeric values only.\n");
exit(1);
} else
formatted_port_list[count++] = atoi(token);
// Get next value
token = strtok(NULL, separator);
}
// Return number of ports for later usage
return count;
}
// Convert a domain name into an IP address(IPv4).
const char *resolve_hostname(const char *address) {
struct hostent *host;
if((host=gethostbyname(address)) == NULL) // Get host struct
return NULL;
return inet_ntoa(*((struct in_addr*)host->h_addr));
}
// Retrieve IP address of client
void get_client_ip(char *ip_addr) {
// To get local ip address, we can send a UDP packet
// to a DNS server, wait for its reply and then
// read the "sin_addr" field from the header of
// the packet.
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if(sock < 1) {
perror("Unable to create UDP socket");
exit(1);
}
struct sockaddr_in ip_client, name;
memset(&ip_client, 0, sizeof(ip_client));
// Configure the header
ip_client.sin_addr.s_addr = inet_addr(DNS_SERVER);
ip_client.sin_port = htons(DNS_SERVER_PORT);
ip_client.sin_family = AF_INET;
// Establish a new connection
int res = connect(sock, (struct sockaddr*)&ip_client, sizeof(ip_client));
if(res < 0) {
perror("Unable to connect to remote host");
exit(1);
}
socklen_t name_len = sizeof(name);
res = getsockname(sock, (struct sockaddr*)&name, &name_len);
if(inet_ntop(AF_INET, &name.sin_addr, ip_addr, INET_ADDRSTRLEN) == NULL) {
perror("Unable to retrieve IP address of the client");
exit(1);
}
// Close socket
close(sock);
}
scanner.h
#ifndef SCANNER_H
#define SCANNER_H
#pragma once
#include <stdio.h> // printf, puts
#include <string.h> // memset
#include <stdlib.h> // malloc, atoi
#include <unistd.h> // close syscall
#include <sys/socket.h> // socket APIs
#include <arpa/inet.h> // inet_ntoa
#include <netinet/tcp.h> // TCP header
#include <netinet/ip.h> // IP header
#include <pthread.h> // pthread_{create,join}
#include "sniffer.h"
#define DATAGRAM_BUF_SIZE 4096
typedef enum {false, true} bool; // Implement bool type
// Needed for checksum computation
struct pseudo_header {
unsigned int source_addr;
unsigned int dest_addr;
unsigned char plc;
unsigned char prt;
unsigned short tcp_len;
struct tcphdr tcp;
};
struct target_header {
struct in_addr target_ip;
unsigned int target_port;
};
struct datagram_header {
char datagram[DATAGRAM_BUF_SIZE];
struct iphdr *ip_head;
struct tcphdr *tcp_head;
};
void setup_datagram(char *datagram, struct in_addr server_ip, const char *client_ip, struct iphdr *ip_head, struct tcphdr *tcp_head);
unsigned short scan_port(int sock_fd, char *datagram, struct in_addr server_ip, const char *client_ip, struct tcphdr *tcp_head, unsigned int target_port);
unsigned short compute_checksum(unsigned short *dgm, int bytes);
#endif
scanner.c
#include "scanner.h"
void setup_datagram(char *datagram, struct in_addr server_ip, const char *client_ip, struct iphdr *ip_head, struct tcphdr *tcp_head) {
// CLear datagram buffer
memset(datagram, 0, DATAGRAM_BUF_SIZE);
// Setup IP header
ip_head->ihl = 5; // HELEN
ip_head->version = 4;
ip_head->tos = 0; // Type of service
ip_head->tot_len = (sizeof(struct ip) + sizeof(struct tcphdr));
ip_head->id = htons(36521);
ip_head->frag_off = htons(16384);
ip_head->ttl = 64;
ip_head->protocol = IPPROTO_TCP;
ip_head->check = 0;
ip_head->saddr = inet_addr(client_ip);
ip_head->daddr = server_ip.s_addr;
ip_head->check = compute_checksum((unsigned short*)datagram, ip_head->tot_len >> 1);
// Setup TCP header
tcp_head->source = htons(46300); // Source port
tcp_head->dest = htons(80);
tcp_head->seq = htonl(1105024978);
tcp_head->ack_seq = 0;
tcp_head->doff = (sizeof(struct tcphdr) / 4);
tcp_head->fin = 0;
tcp_head->syn = 1; // Set SYN flag
tcp_head->rst = 0;
tcp_head->psh = 0;
tcp_head->ack = 0;
tcp_head->urg = 0;
tcp_head->window = htons(14600); // Maximum window size
tcp_head->check = 0;
tcp_head->urg_ptr = 0;
}
unsigned short scan_port(int sock_fd, char *datagram, struct in_addr server_ip, const char *client_ip, struct tcphdr *tcp_head, unsigned int target_port) {
struct sockaddr_in ip_dest;
struct pseudo_header psh;
// Create new thread
pthread_t sniff_th;
struct target_header th;
if(pthread_create(&sniff_th, NULL, sniffer_thread_callback, &th) < 0) {
perror("Unable to create sniffer thread");
return 1;
}
// Save target IP and port for later usage
th.target_ip = server_ip;
th.target_port = target_port;
// Setup packet info
ip_dest.sin_family = AF_INET;
ip_dest.sin_addr.s_addr = server_ip.s_addr;
// Setup TCP header
tcp_head->dest = htons(target_port); // Set target port
tcp_head->check = 0;
// Configure pseudo header(needed for checksum)
psh.source_addr = inet_addr(client_ip);
psh.dest_addr = ip_dest.sin_addr.s_addr;
psh.plc = 0;
psh.prt = IPPROTO_TCP;
psh.tcp_len = htons(sizeof(struct tcphdr));
// Copy TCP header into our pseudo header
memcpy(&psh.tcp, tcp_head, sizeof(struct tcphdr));
tcp_head->check = compute_checksum((unsigned short*)&psh, sizeof(struct pseudo_header));
// Send packet to target
if(sendto(sock_fd, datagram, sizeof(struct iphdr) + sizeof(struct tcphdr), 0, (struct sockaddr*)&ip_dest, sizeof(ip_dest)) < 0) {
perror("Unable to send SYN packet");
return 1;
}
// Wait for sniffer thread to receive a response
pthread_join(sniff_th, NULL);
return 0;
}
// Compute checksum of IP header
// Refer to https://www.ietf.org/rfc/rfc793.txt for reference
unsigned short compute_checksum(unsigned short *dgm, int bytes) {
register long sum = 0;
register short answer;
unsigned int odd_byte;
while(bytes > 1) {
sum += *dgm++;
bytes -= 2;
}
if(bytes == 1) {
odd_byte = 0;
*((unsigned char*)&odd_byte) = *(unsigned char*)dgm;
sum += odd_byte;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
answer = (short)~sum;
return answer;
}
sniffer.h
#ifndef SNIFFER_H
#define SNIFFER_H
#pragma once
#include <stdio.h> // printf, puts
#include <string.h> // memset
#include <stdlib.h> // malloc, atoi
#include <unistd.h> // close syscall
#include <sys/socket.h> // socket APIs
#include <arpa/inet.h> // inet_ntoa
#include <netinet/tcp.h> // TCP header
#include <netinet/ip.h> // IP header
#include "scanner.h"
#define BUF_SIZE 65536
enum status {
CLOSED,
OPEN
};
void *sniffer_thread_callback(void *ptr);
#endif
sniffer.c
#include "sniffer.h"
static void sniff_network(struct in_addr server_ip, const unsigned int port);
static void print_result(unsigned int port, enum status st);
void *sniffer_thread_callback(void *ptr) {
struct target_header *th = ptr;
sniff_network(th->target_ip, th->target_port);
return (void*)NULL;
}
void sniff_network(struct in_addr server_ip, const unsigned int port) {
int sock_raw;
int saddr_size, data_size;
struct sockaddr saddr;
unsigned char *buf = (unsigned char*)malloc(BUF_SIZE);
// Create new raw socket
sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if(sock_raw < 0) {
perror("Unable to create socket");
exit(1);
}
saddr_size = sizeof(saddr);
// Start receiving packets
data_size = recvfrom(sock_raw, buf, BUF_SIZE, 0, (struct sockaddr*)&saddr, (socklen_t*)&saddr_size);
if(data_size < 0) {
perror("Unable to receive packets");
exit(1);
}
// Process data
struct iphdr *ip_head = (struct iphdr*)buf;
struct sockaddr_in source;
unsigned short ip_head_len = ip_head->ihl*4;
struct tcphdr *tcp_head = (struct tcphdr*)(buf + ip_head_len);
memset(&source, 0, sizeof(source));
source.sin_addr.s_addr = ip_head->saddr;
if(ip_head->protocol == IPPROTO_TCP) {
// Now check whether it's a SYN-ACK packet or not
if(tcp_head->syn == 1 && tcp_head->ack == 1 && source.sin_addr.s_addr == server_ip.s_addr)
print_result(port, OPEN);
else
print_result(port, CLOSED);
}
free(buf);
}
// Print the scan result just like Nmap
void print_result(unsigned int port, enum status st) {
printf("%d/tcp\t\t%s\n", port, (st == OPEN ? "open" : "closed"));
}
Makefile
TARGET = sps
CC = gcc
CFLAGS = -Wall -Wextra -Werror -pedantic-errors -std=gnu11
LFLAGS = -lpthread
all: $(TARGET)
$(TARGET): main.o scanner.a sniffer.a
$(CC) $(CFLAGS) $(LFLAGS) $^ -o $@
main.o: main.c
$(CC) $(CFLAGS) -c $< -o $@
sniffer.a: sniffer.o
ar rcs $@ $^
scanner.a: scanner.o
ar rcs $@ $^
scanner.o: src/scanner.c src/scanner.h
$(CC) $(CFLAGS) -c -o $@ $<
sniffer.o: src/sniffer.c src/sniffer.h
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f *.o *.a $(TARGET)
Below there's the project structure:
├── main.c
├── Makefile
└── src
├── scanner.c
├── scanner.h
├── sniffer.c
└── sniffer.h
Finally, let's compile the whole thing with
$> make clean all
Let's try our tool to scan scanme.nmap.org
:
$> sudo ./sps -s scanme.nmap.org -p 80,22,9929,11211,31337
Starting SPS 0.0.1 at 2021-06-16 23:21 CEST
PORT STATE
80/tcp open
22/tcp open
9929/tcp open
11211/tcp closed
31337/tcp open
SPS done: 5 ports scanned in 0.004033 seconds.
We did it! We got the same result(more or less) as the previous step.