HackPy Part 2. – Nmap scanning

In the last post I showed you how to reproduce traceroute’s behavior. Today I’ll show how to do the same with another famous tool – Nmap and specifically its half-open port scanning using Python with Scapy library. This has some great benefits because apart from learning something and having fun, you can extend the desired Nmap’s functionality using Python instead of being forced to resort to NSE and Lua language. Before we begin – remember that you can sign to the newsletter and support me on Patreon.

How does half-open port scanning work?

First, as tech savy people, we’d like to understand what exactly do we need. TCP connection begins with so-called three-way handshake. This is basically sending 3 packets with following control flags set:

  • SYN – sent by client asking to synchronize,
  • SYN+ACK – sent back from server acknowledging the synchronization,
  • ACK – sent again by client acknowledging this acknowledgement.

When everything goes on well, connection is established. However, if a port is closed, server sends RST (reset) back to inform client that it won’t be able to connect on this port. Another option on server side is dropping the packet entirely by firewall. In this case connection is timed out and client receives nothing.

Port scanning techniques

What is important here is that this scan is done using so-called raw sockets meaning that even if we do receive SYN+ACK response, the operating system will not consider it as a valid part of any connections and send RST.

Implementation with Scapy

#!/usr/bin/env python3

import sys

from scapy.config import conf
from scapy.layers.inet import IP, TCP
from scapy.sendrecv import sr1


def main(host: str):
    results = []
    conf.verb = 0
    for port in range(1, 101):
        print(f'scanning port {port}', end='\r')
        response = sr1(IP(dst=host)/TCP(dport=port, flags='S'), verbose=0, timeout=1)
        if response is not None and response.getlayer(TCP).flags == 'SA':
            results.append(f'{port}: OPEN')
    print(' '*80, end='\r')
    print('\n'.join(results))


if __name__ == '__main__':
    try:
        main(sys.argv[1])
    except IndexError:
        print('Usage: python3 nmap.py <host_addr>')

Entire magic is happening in line 15, where we build a TCP/IP packet, defining destination host and port (we are looping over first 100 ports) and explicitly setting “SYN” flag. If we receive a non-null response which has flags set to “SYN+ACK”, we can say the port is open.

Example results for scanning one of my servers that I’m using for side projects is following:

$ sudo python nmap.py 18.197.129.60
22: OPEN
53: OPEN
80: OPEN

That’s all I have for you today when it comes to port scanning with Scapy. I hope you enjoyed this short post. If you don’t want to miss next parts, where I’ll be describing how to build a packet sniffer and analyze low-level network , subscribe to newsletter and visit my Patreon profile.

Additional resources

See also