tools tips

SecurityTrails Blog · Aug 27 · by Nicolas Pence

RustScan: Empowering Nmap One Scan at a Time

Reading time: 13 minutes

We all love Nmap, we really do. It’s one of the first and most famous OSINT-like security tools created, written almost 23 years ago (at the time of this writing) by Gordon “Fyodor” Lyon.

Since September of 2007, it’s been maintained and upgraded by Lyon and a number of developers, many from Google’s Summer of Code program. And on top of its long history, it continues to offer many cool features that are still extremely helpful when conducting mapping of network-facing services, for all sorts of assessments that can improve your attack surface reduction tasks.

Rustscan vs Nmap

So this is a post about Nmap? No! Actually, today we’re featuring RustScan, an Nmap enhancer that helps you avoid time-wasting by rapidly discovering a target’s open ports and combining that with the power of Nmap.

RustScan’s inner workings

When running, RustScan conducts a preliminary scan using its own internal discovery technique: it creates sockets against its targets and waits for their responses.

Once this first scanning stage is completed it executes a second round using Nmap as the tool with specific tags that, by default, aim to discover the targets’ operating systems.

Rustscan Inner Workings

These settings are configured by default and can be tweaked if necessary on every scan. You can also run scans without using Nmap if you like.

How is this done? As its name suggests, RustScan is coded in the Rust programming language sponsored by Mozilla, and offers to be your go-to solution for speed, process parallelism, and memory safety.

Let’s explore how to use this tool.

Installing RustScan

To keep installation as simple as possible for this article, we’ve decided to use the docker image. You can, however, check out the different options on the GitHub official docs.

For our example, we’ll do a docker pull command to download the image to our operating system, plus a simple shell alias to use the whole environment as if it were installed locally.

To install, we’ll use the stable version of the docker image rustscan:alpine

    $ docker pull rustscan/rustscan:alpine
    alpine: Pulling from rustscan/rustscan
    df20fa9351a1: Pull complete
    5142bdf25b68: Pull complete
    1e5ef797c470: Pull complete
    a7cbe06aad30: Pull complete
    Digest: sha256:14153bde95e02388c24eabf657ca86f3f24fa2e5d3c2d2df56a8c52ebc552597
    Status: Downloaded newer image for rustscan/rustscan:alpine
    docker.io/rustscan/rustscan:alpine

Once it’s downloaded you can check to see if everything is downloaded properly, by doing:

    $ docker images rustscan/rustscan:alpine
    REPOSITORY TAG IMAGE ID CREATED SIZE
    rustscan/rustscan alpine cc8b419ab531 10 days ago 39.3MB

Then we can add a simple alias line to our favourite profile file. For our case we’ll use the ~/.bash_profile file:

    # RustScan
    alias rustscan='docker run -it --rm --name rustscan rustscan/rustscan:alpine  rustscan'

Then (in case you’re running bash as a shell) you can run the alias directly with the specified line or simply run the following to make it work:

    $ source ~/.bash_profile

Your RustScan installation is now ready to use. Note that using the –help flag will show every option, as there are a few (especially compared with Nmap itself). This helps make learning as simple as possible.

    $ rustscan --help
    rustscan 1.4.0
    Fast Port Scanner built in Rust. WARNING Do not use this program against sensitive infrastructure since the specified
    server may not be able to handle this many socket connections at once

    USAGE:
    rustscan [FLAGS] [OPTIONS] <ip> [command]...

    FLAGS:
    -h, --help Prints help information
    -i, --ipv6 IPv6 mode
    -q, --quiet Quiet mode. Only output the ports. No Nmap. Useful for grep or outputting to a file
    -V, --version Prints version information

    OPTIONS:
    -b, --batch-size <batch-size> The batch size for port scanning, it increases or slows the speed of scanning.
            Depends on the open file limit of your OS. If you do 65535 it will do every port
            at the same time. Although, your OS may not support this [default: 4500]
    -t, --timeout <timeout> The timeout in milliseconds before a port is assumed to be closed [default: 1500]
    -u, --ulimit <ulimit> Automatically ups the ULIMIT with the value you provided

    ARGS:
    <ip> The IP address to scan
    <command>... The Nmap arguments to run. To use the argument -A, end RustScan's args with '-- -A'. Example:
            'rustscan -T 1500 127.0.0.1 — -A -sC'. This command adds -Pn -vvv -p $PORTS automatically to
            nmap. For things like --script '(safe and vuln)' enclose it in quotations marks \"'(safe and
            vuln)'\"")

Running a scan

Starting the scanning is easy and straightforward. Simply place your desired target after the command name and this alone will activate the first round of analysis.

When running the tool with the -q flag we see RustScan at a more basic level of function. This will create a port scanning task against a desired target and show the open ports found.

    $ rustscan -q 192.168.100.44
    1290,8121,5555,7100,8000,41591,51010,51020,51030,51040,52333,53000,56789

As shown above, the output shows a comma-separated list of ports being discovered by RustScan’s internal port-scanning engine, demonstrating how quick this tool can be.

One important detail to note is that all these scans are being made using TCP connections, with no UDP capabilities so far. Once the scan is completed it will stop, and there will be no data exchange between the tool and Nmap.

Running rustscan

As shown above, the scan starts and once the first phase is finished with the list of the open ports it shows the “Starting Nmap” message. Then the second phase initiates.

Analyzing results

As the tool claims to speed up Nmap scanning times, we made some simple comparisons to find out if this is true, and to see how these two ways of “invocation approaches” can be compared.

For the first test, we tried scanning a host with some TCP ports open and checked the time declared by the tool. See the output below:

    $ rustscan 192.168.100.46
     _____ _ _____
    | __ \ | | / ____|
    | |__) | _ ___| |_| (___ ___ __ _ _ __
    | _ / | | / __| __|\___ \ / __/ _` | '_ \
    | | \ \ |_| \__ \ |_ ____) | (_| (_| | | | |
    |_| \_\__,_|___/\__|_____/ \___\__,_|_| |_|
    Faster nmap scanning with rust.

    Your file description limit is higher than the batch size. You can potentially increase the speed by increasing the batch size, but this may cause harm to sensitive servers. Your limit is 1048576, try batch size 1048575.
    Open 56789
    Open 56790
    Starting nmap.
    The Nmap command to be run is -A -vvv -vvv -Pn -p 56789,56790 192.168.1.46
    Starting Nmap 7.70 ( https://nmap.org ) at 2020-08-04 18:51 UTC
    NSE: Loaded 148 scripts for scanning.
    NSE: Script Pre-scanning.
    NSE: Starting runlevel 1 (of 2) scan.
    Initiating NSE at 18:51
    Completed NSE at 18:51, 0.00s elapsed
    NSE: Starting runlevel 2 (of 2) scan.
    Initiating NSE at 18:51
    Completed NSE at 18:51, 0.00s elapsed
    Initiating Parallel DNS resolution of 1 host. at 18:51
    Completed Parallel DNS resolution of 1 host. at 18:51, 1.57s elapsed
    DNS resolution of 1 IPs took 1.58s. Mode: Async [#: 1, OK: 1, NX: 0, DR: 0, SF: 0, TR: 1, CN: 0]
    Initiating SYN Stealth Scan at 18:51
    Scanning 192.168.1.46 (192.168.1.46) [2 ports]
    Discovered open port 56789/tcp on 192.168.1.46
    Discovered open port 56790/tcp on 192.168.1.46
    Completed SYN Stealth Scan at 18:51, 0.23s elapsed (2 total ports)
    Initiating Service scan at 18:51
    Scanning 2 services on 192.168.1.46 (192.168.1.46)
    Completed Service scan at 18:51, 0.61s elapsed (2 services on 1 host)
    Initiating OS detection (try #1) against 192.168.1.46 (192.168.1.46)
    adjust_timeouts2: packet supposedly had rtt of -169969 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -169969 microseconds. Ignoring time.
    Retrying OS detection (try #2) against 192.168.1.46 (192.168.1.46)
    adjust_timeouts2: packet supposedly had rtt of -810874 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -810874 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -813392 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -813392 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -809724 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -809724 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -806796 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -806796 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -815282 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -815282 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -794449 microseconds. Ignoring time.
    adjust_timeouts2: packet supposedly had rtt of -794449 microseconds. Ignoring time.
    Initiating Traceroute at 18:51
    Completed Traceroute at 18:51, 0.03s elapsed
    Initiating Parallel DNS resolution of 2 hosts. at 18:51
    Completed Parallel DNS resolution of 2 hosts. at 18:51, 0.01s elapsed
    DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 1, NX: 0, DR: 0, SF: 0, TR: 1, CN: 0]
    NSE: Script scanning 192.168.1.46.
    NSE: Starting runlevel 1 (of 2) scan.
    Initiating NSE at 18:51
    Completed NSE at 18:51, 5.56s elapsed
    NSE: Starting runlevel 2 (of 2) scan.
    Initiating NSE at 18:51
    Completed NSE at 18:51, 0.00s elapsed
    Nmap scan report for 192.168.1.46 (192.168.1.46)
    Host is up, received user-set (0.025s latency).
    Scanned at 2020-08-04 18:51:14 UTC for 12s

    PORT STATE SERVICE REASON VERSION
    56789/tcp open tcpwrapped syn-ack ttl 37
    56790/tcp open tcpwrapped syn-ack ttl 37
    Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
    OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
    No OS matches for host
    TCP/IP fingerprint:
    SCAN(V=7.70%E=4%D=8/4%OT=56790%CT=%CU=34866%PV=Y%DS=2%DC=T%G=N%TM=5F29AE2E%P=x86_64-pc-linux-gnu)
    SEQ(SP=8A%GCD=1%ISR=8E%TI=RD%CI=RI%TS=U)
    OPS(O1=M5B4W2L%O2=M5B4W2L%O3=M5B4W2L%O4=M5B4W2L%O5=M5B4W2L%O6=M5B4)
    WIN(W1=FFFF%W2=FFFF%W3=FFFF%W4=FFFF%W5=FFFF%W6=FFFF)
    ECN(R=Y%DF=N%T=26%W=FFFF%O=M5B4W2L%CC=N%Q=)
    T1(R=Y%DF=N%T=26%S=O%A=S+%F=AS%RD=0%Q=)
    T2(R=N)
    T3(R=N)
    T4(R=Y%DF=N%T=26%W=0%S=A%A=S%F=AR%O=%RD=0%Q=)
    T5(R=Y%DF=N%T=26%W=0%S=A%A=S+%F=AR%O=%RD=0%Q=)
    T6(R=Y%DF=N%T=26%W=0%S=A%A=S%F=AR%O=%RD=0%Q=)
    U1(R=Y%DF=N%T=26%IPL=164%UN=0%RIPL=4801%RID=9C54%RIPCK=I%RUCK=EC14%RUD=G)
    IE(R=N)

    Network Distance: 2 hops
    TCP Sequence Prediction: Difficulty=137 (Good luck!)
    IP ID Sequence Generation: Randomized

    TRACEROUTE (using port 56789/tcp)
    HOP RTT ADDRESS
    1 2.81 ms 172.17.0.1 (172.17.0.1)
    2 5.57 ms 192.168.1.46 (192.168.1.46)

    NSE: Script Post-scanning.
    NSE: Starting runlevel 1 (of 2) scan.
    Initiating NSE at 18:51
    Completed NSE at 18:51, 0.00s elapsed
    NSE: Starting runlevel 2 (of 2) scan.
    Initiating NSE at 18:51
    Completed NSE at 18:51, 0.00s elapsed
    Read data files from: /usr/bin/../share/nmap
    OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
    Nmap done: 1 IP address (1 host up) scanned in 18.69 seconds
        Raw packets sent: 75 (6.082KB) | Rcvd: 85 (4.926KB)

As shown above, RustScan found two open ports which were delivered to the Nmap executable and were analyzed again to find service labels and possible fingerprints that could help in identifying the target’s operating system.

Additionally, it realizes a trace-route that shows how many hops are between you and the scanned target, with extra information such as latency.

Tool comparison

For the sake of entertainment, we compared three scans to see if we’d get interesting results by running RustScan vs. RustScan+Nmap vs. Nmap alone.

Our conclusions can serve as a fun, educational exercise for our readers:

  • RustScan internal scan running with -q flag

    $ time rustscan -q 192.168.100.15
    22,80,111,139,445,4200,5000,6600,36207
    
    real    0m31.149s
    user    0m0.044s
    sys 0m0.031s
  • RustScan + Nmap scan running the default configuration which is TCP socket creation with DNS resolution (inside docker container).

    $ time rustscan 192.168.100.15
    .----.  .-. .-. .----..---. .----. .---. .--. .-. .-.
    | {} }| { } |{  {__ {_ _}{ {__ / ___} / {} \ | `| |
    | .-.  \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
    `-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
    Faster  Nmap scanning with Rust.
    ________________________________________
    : https://discord.gg/GFrQsGy :
    : https://github.com/RustScan/RustScan :
    --------------------------------------
    https://admin.tryhackme.com
    
    [~] The config file is expected to be at "/home/rustscan/.config/rustscan/config.toml"
    [!] Host "rustscan" could not be resolved.
    [~] File limit higher than batch size. Can increase speed by increasing batch size '-b 1048476'.
    Open 192.168.100.15:22
    Open 192.168.100.15:80
    Open 192.168.100.15:111
    Open 192.168.100.15:139
    Open 192.168.100.15:445
    Open 192.168.100.15:4200
    Open 192.168.100.15:5000
    Open 192.168.100.15:6600
    Open 192.168.100.15:36207
    [~] Starting Nmap
    [>] The Nmap command to be run is nmap -vvv -p 22,80,111,139,445,4200,5000,6600,36207 192.168.100.15
    
    Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-27 01:12 UTC
    Initiating Ping Scan at 01:12
    Scanning 192.168.100.15 [2 ports]
    Completed Ping Scan at 01:12, 0.00s elapsed (1 total hosts)
    Initiating Parallel DNS resolution of 1 host. at 01:12
    Completed Parallel DNS resolution of 1 host. at 01:12, 0.00s elapsed
    DNS resolution of 1 IPs took 0.00s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
    Initiating Connect Scan at 01:12
    Scanning 192.168.100.15 [9 ports]
    Discovered open port 22/tcp on 192.168.100.15
    Discovered open port 139/tcp on 192.168.100.15
    Discovered open port 111/tcp on 192.168.100.15
    Discovered open port 80/tcp on 192.168.100.15
    Discovered open port 445/tcp on 192.168.100.15
    Discovered open port 5000/tcp on 192.168.100.15
    Discovered open port 4200/tcp on 192.168.100.15
    Discovered open port 36207/tcp on 192.168.100.15
    Discovered open port 6600/tcp on 192.168.100.15
    Completed Connect Scan at 01:12, 0.01s elapsed (9 total ports)
    Nmap scan report for 192.168.100.15
    Host is up, received syn-ack (0.0023s latency).
    Scanned at 2020-08-27 01:12:39 UTC for 0s
    
    PORT STATE SERVICE REASON
    22/tcp open ssh syn-ack
    80/tcp open http syn-ack
    111/tcp open rpcbind syn-ack
    139/tcp open netbios-ssn syn-ack
    445/tcp open microsoft-ds syn-ack
    4200/tcp open vrml-multi-use syn-ack
    5000/tcp open upnp syn-ack
    6600/tcp open mshvlm syn-ack
    36207/tcp open unknown syn-ack
    
    Read data files from: /usr/bin/../share/nmap
    Nmap done: 1 IP address (1 host up) scanned in 0.09 seconds
    
    real 0m24.874s
    user 0m0.043s
    sys 0m0.035s
  • Nmap scan running TCP handshake discovery on all ports without ICMP discovery or reverse DNS resolution, only showing open ports and running the scan sequentially as RustScan does:

    $ nmap -sT -vvv -p- -T5 -n -Pn --open -r 192.168.100.15
    Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-26 22:14 -03
    Initiating Connect Scan at 22:14
    Scanning 192.168.100.15 (192.168.100.15) [65535 ports]
    Discovered open port 22/tcp on 192.168.100.15
    Discovered open port 80/tcp on 192.168.100.15
    Discovered open port 111/tcp on 192.168.100.15
    Discovered open port 139/tcp on 192.168.100.15
    Discovered open port 445/tcp on 192.168.100.15
    Discovered open port 4200/tcp on 192.168.100.15
    Discovered open port 5000/tcp on 192.168.100.15
    Discovered open port 6600/tcp on 192.168.100.15
    Discovered open port 36207/tcp on 192.168.100.15
    Completed Connect Scan at 22:15, 8.72s elapsed (65535 total ports)
    Nmap scan report for 192.168.100.15 (192.168.100.15)
    Host is up, received user-set (0.0013s latency).
    Scanned at 2020-08-26 22:14:53 -03 for 9s
    Not shown: 65526 closed ports
    Reason: 65526 conn-refused
    PORT STATE SERVICE REASON
    22/tcp open ssh syn-ack
    80/tcp open http syn-ack
    111/tcp open rpcbind syn-ack
    139/tcp open netbios-ssn syn-ack
    445/tcp open microsoft-ds syn-ack
    4200/tcp open vrml-multi-use syn-ack
    5000/tcp open upnp syn-ack
    6600/tcp open mshvlm syn-ack
    36207/tcp open unknown syn-ack
    
    Read data files from: /usr/local/bin/../share/nmap
    Nmap done: 1 IP address (1 host up) scanned in 8.80 seconds

As shown above, the results may vary by quite a bit. We recommend that you perform your own tests. Despite the time differences between all these scans (and on behalf of RustScan) the fact that is being executed inside a docker container can definitely add up to the final scanning engagement time.

Summary

Today we’ve covered another information gathering tool, RustScan, which promises to make port scanning faster. While not untrue, this statement poses a few accuracy issues, as the high scanning speed can provide false positives or, what could be potentially worse, false negatives.

RustScan is still a good addition to your toolbox. It’s especially useful for local environments where you need extra speed on your scans. But if you need more complex configurations, such as changing the technique used to discover ports or to work on different protocols such as UDP, it might be better to stick with what’s familiar, good old Nmap.

All in all, it could be very interesting to see the Rust code in this tool’s repo, and to explore what tweaks could be made to its configuration to make it even faster!