Prerequisites
Introduction
A port scanner is a program which probes a server for open ports by attempting a connection to the server via each port in turn. The program usually then reports which ports were open and which were closed. More complex port scanners, such as Nmap, can probe for other information as well. Port scanning is usually performed by system administrators to verify network security or by attackers looking for an open port through which to compromise the security of a server. Open ports may constitute security vulnerabilities as they allow remote machines to connect. Unexpectedly-open ports may indicate malicious software listening for instructions.
My First Port Scanner
As before, scanning ports is as simple as attempting a connection to an address and port. If the connection attempt is successful, the port must be open. Otherwise the port is presumed to be closed. Here is an example function using the SFML Network module (
documentation) to check if a port is open:
1 2 3 4 5 6 7
|
bool port_is_open(const std::string& address, int port)
{
sf::TcpSocket socket;
bool open = (socket.connect(sf::IpAddress(address), port) == sf::Socket::Done);
socket.disconnect();
return open;
}
| |
Breakdown:
- First, we create an instance of sf::TcpSocket (documentation) which allows us to connect to a remote socket
- Then, we connect the socket. We convert the string 'address' to an sf::IpAddress (documentation) instance by calling the constructor for sf::IpAddress. If the explicit constructor call is left out, it will be performed implicitly by the compiler anyway. After attempting a connection we check whether the connection succeeded by comparing the return value of the sf::TcpSocket::connect function (documentation) with the enumerator value sf::Socket::Done (documentation). If the two are equal, it means the connection succeeded and the port is open. In this case, the variable 'open' is set to true.
- Next, we disconnect the socket using the sf::TcpSocket::disconnect function (documentation). This would be done automatically in the destructor if we left the explicit call out.
- Finally, we return the value of 'open' to the calling function.
This is actually the only networking code we need to write. We can also simplify it to a single line:
1 2 3 4
|
static bool port_is_open(const std::string& address, int port)
{
return (sf::TcpSocket().connect(address, port) == sf::Socket::Done);
}
| |
Here we're creating a new sf::TcpSocket, connecting to the address and port and then returning true or false depending on whether the connection succeeded. We get rid of the unneeded explicit sf::IpAddress constructor call as well as the call to sf::TcpSocket::disconnect(). We can use the function in a program like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
#include <iostream>
#include <SFML/Network.hpp>
#include <string>
static bool port_is_open(const std::string& address, int port)
{
return (sf::TcpSocket().connect(address, port) == sf::Socket::Done);
}
int main()
{
std::cout << "Port 80 : ";
if (port_is_open("localhost", 80))
std::cout << "OPEN" << std::endl;
else
std::cout << "CLOSED" << std::endl;
return 0;
}
|
Port 80 : OPEN | |
Try compiling and running this program. It will test if port 80 on your computer is open. Note that "localhost" means the local computer; you can also use the IP address 127.0.0.1 or ::1 (the IPv6 version, although SFML does not yet support IPv6) for the same purpose. You can change "localhost" to the IP address or web address of another website (leaving out the "http://" and any path information), but be careful -
portscanning websites without permission can get you into trouble in some countries as it can be considered hacking. Luckily the website of the port scanner Nmap has a page specifically for testing port scanners. Try changing "localhost" to "scanme.nmap.org". Just don't scan it too much (the page says "a few times per day").
Improved Port Scanner
Now that we've successfully developed a program that can scan a port on an address, we can modify our program to let the user specify a port and address to scan:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
#include <iostream>
#include <SFML/Network.hpp>
#include <string>
static bool port_is_open(const std::string& address, int port)
{
return (sf::TcpSocket().connect(address, port) == sf::Socket::Done);
}
int main()
{
std::string address;
int port;
// Get the address.
std::cout << "Address: " << std::flush;
std::getline(std::cin, address);
// Get the port.
std::cout << "Port: " << std::flush;
std::cin >> port;
// Scan!
std::cout << "Scanning " << address << "...\n" << "Port " << port << " : ";
if (port_is_open(address, port))
std::cout << "OPEN" << std::endl;
else
std::cout << "CLOSED" << std::endl;
return 0;
}
|
Address: 127.0.0.1
Port: 80
Scanning 127.0.0.1...
Port 80 : OPEN | |
Remember that 127.0.0.1 is equivalent to localhost. Also, port 80 may not be open on your computer. It's only open on mine because I'm running Apache HTTP server (port 80 is the main port commonly used for HTTP, i.e. websites; the other port commonly used for HTTP is port 8080).
Also, we can easily test this code on Nmap's "ScanMe" page:
Address: scanme.nmap.org
Port: 80
Scanning scanme.nmap.org...
Port 80 : OPEN |
It might take slightly longer this time because you aren't scanning your own computer, this time you're connecting to another computer via the Internet.
A Fistful of Ports
Scanning one port at a time is tedious, we want to let the user scan lots of ports. One way to do this would be to let the user enter as many ports as they want and then have them all scanned in one go. The problem with this is that the user might want to scan a lot of ports, and would have to enter each one. We could also let the user specify a range of ports, say, 0-100, but then they couldn't specify values outside of that range. We're going to go one better and let them do both - specify ranges AND individual ports in a list like this: '80,8080'; a range, like this: '20-80'; or a list containing ranges: '20-80,8080'. This code gets kind of complex due to the use of templates, std::vector, std::stringstream and the C++11 range-based for-loop, so if you skipped the "prerequisites" section and don't know how to use those, go back to the top of this article and learn. Otherwise, read on.
First we need a function to split strings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
// Splits a string into tokens arround a delimiter (default: space),
// optionally allowing empty tokens.
static std::vector<std::string> split(const std::string& string,
char delimiter = ' ',
bool allow_empty = false)
{
std::vector<std::string> tokens;
std::stringstream sstream(string);
std::string token;
while (std::getline(sstream, token, delimiter)) {
if (allow_empty || token.size() > 0)
tokens.push_back(token);
}
return tokens;
}
| |
The data type of the ports will be a string, but we want an integer, so we will also need a function to convert strings to integers:
1 2 3 4 5 6 7 8
|
// Converts a string to an integer.
static int string_to_int(const std::string& string)
{
std::stringstream sstream(string);
int i;
sstream >> i;
return i;
}
| |
If we have a range of ports, we will need a function to generate all the values in that range:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
// Swaps two values.
template <typename T>
static void swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
// Generates a vector containing a range of values.
template <typename T>
static std::vector<T> range(T min, T max)
{
if (min > max)
swap(min, max);
if (min == max)
return std::vector<T>(1, min);
std::vector<T> values;
for (; min <= max; ++min)
values.push_back(min);
return values;
}
| |
Finally, we need a function to actually parse the list of ports using the above functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
// Parses a list of ports containing numbers and ranges
static std::vector<int> parse_ports_list(const std::string& list)
{
std::vector<int> ports;
// Split list items.
for (const std::string& token : split(list, ',')) {
// Split ranges.
std::vector<std::string> strrange = split(token, '-');
switch (strrange.size()) {
// Only one value (add to end of 'ports').
case 0: ports.push_back(string_to_int(token)); break;
case 1: ports.push_back(string_to_int(strrange[0])); break;
// Two values (range - add everything in that range).
case 2:
{
int min = string_to_int(strrange[0]),
max = string_to_int(strrange[1]);
for (int port : range(min, max))
ports.push_back(port);
break;
}
default:
break;
}
}
return ports;
}
| |
Our finished program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
|
#include <iostream>
#include <SFML/Network.hpp>
#include <sstream>
#include <string>
#include <vector>
static bool port_is_open(const std::string& address, int port)
{
return (sf::TcpSocket().connect(address, port) == sf::Socket::Done);
}
static std::vector<std::string> split(const std::string& string,
char delimiter = ' ',
bool allow_empty = false)
{
std::vector<std::string> tokens;
std::stringstream sstream(string);
std::string token;
while (std::getline(sstream, token, delimiter)) {
if (allow_empty || token.size() > 0)
tokens.push_back(token);
}
return tokens;
}
static int string_to_int(const std::string& string)
{
std::stringstream sstream(string);
int i;
sstream >> i;
return i;
}
template <typename T>
static void swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
template <typename T>
static std::vector<T> range(T min, T max)
{
if (min > max)
swap(min, max);
if (min == max)
return std::vector<T>(1, min);
std::vector<T> values;
for (; min <= max; ++min)
values.push_back(min);
return values;
}
static std::vector<int> parse_ports_list(const std::string& list)
{
std::vector<int> ports;
for (const std::string& token : split(list, ',')) {
std::vector<std::string> strrange = split(token, '-');
switch (strrange.size()) {
case 0: ports.push_back(string_to_int(token)); break;
case 1: ports.push_back(string_to_int(strrange[0])); break;
case 2:
{
int min = string_to_int(strrange[0]),
max = string_to_int(strrange[1]);
for (int port : range(min, max))
ports.push_back(port);
break;
}
default:
break;
}
}
return ports;
}
int main()
{
std::string address;
std::string port_list;
std::vector<int> ports;
std::cout << "Address: " << std::flush;
std::getline(std::cin, address);
std::cout << "Port: " << std::flush;
std::getline(std::cin, port_list);
ports = parse_ports_list(port_list);
std::cout << "Scanning " << address << "...\n";
for (int port : ports) {
std::cout << "Port " << port << " : ";
if (port_is_open(address, port))
std::cout << "OPEN\n";
else
std::cout << "CLOSED\n";
}
std::cout << std::flush;
return 0;
}
|
Address: 127.0.0.1
Port: 20-80,8080
Scanning 127.0.0.1...
Port 20 : CLOSED
Port 21 : CLOSED
Port 22 : OPEN
Port 23 : CLOSED
Port 24 : CLOSED
Port 25 : CLOSED
Port 26 : CLOSED
Port 27 : CLOSED
Port 28 : CLOSED
Port 29 : CLOSED
Port 30 : CLOSED
Port 31 : CLOSED
Port 32 : CLOSED
Port 33 : CLOSED
Port 34 : CLOSED
Port 35 : CLOSED
Port 36 : CLOSED
Port 37 : CLOSED
Port 38 : CLOSED
Port 39 : CLOSED
Port 40 : CLOSED
Port 41 : CLOSED
Port 42 : CLOSED
Port 43 : CLOSED
Port 44 : CLOSED
Port 45 : CLOSED
Port 46 : CLOSED
Port 47 : CLOSED
Port 48 : CLOSED
Port 49 : CLOSED
Port 50 : CLOSED
Port 51 : CLOSED
Port 52 : CLOSED
Port 53 : OPEN
Port 54 : CLOSED
Port 55 : CLOSED
Port 56 : CLOSED
Port 57 : CLOSED
Port 58 : CLOSED
Port 59 : CLOSED
Port 60 : CLOSED
Port 61 : CLOSED
Port 62 : CLOSED
Port 63 : CLOSED
Port 64 : CLOSED
Port 65 : CLOSED
Port 66 : CLOSED
Port 67 : CLOSED
Port 68 : CLOSED
Port 69 : CLOSED
Port 70 : CLOSED
Port 71 : CLOSED
Port 72 : CLOSED
Port 73 : CLOSED
Port 74 : CLOSED
Port 75 : CLOSED
Port 76 : CLOSED
Port 77 : CLOSED
Port 78 : CLOSED
Port 79 : CLOSED
Port 80 : OPEN
Port 8080 : CLOSED | |
Et voila! We now have a successful port scanner which can scan any number of ports the user desires.
For a Few Ports More
Our program can be modified not to show closed ports by changing
89 90 91 92 93 94 95 96
|
std::cout << "Scanning " << address << "...\n";
for (int port : ports) {
std::cout << "Port " << port << " : ";
if (port_is_open(address, port))
std::cout << "OPEN\n";
else
std::cout << "CLOSED\n";
}
| |
to
89 90 91 92 93
|
std::cout << "Showing open ports on " << address << "...\n";
for (int port : ports) {
if (port_is_open(address, port))
std::cout << "Port " << port << " : OPEN\n";
}
| |
This makes the output far more legible. Scanning every port on my computer:
Address: localhost
Port: 0-65535
Showing open ports on localhost...
Port 22 : OPEN
Port 53 : OPEN
Port 80 : OPEN
Port 139 : OPEN
Port 445 : OPEN
Port 631 : OPEN
Port 3306 : OPEN
Port 17500 : OPEN |
Note that there are 65535 ports. If we scanned all of them with the previous version of the port scanner, it would produce 65,535 lines of output which would make it very difficult to get the desired information.
We can format our output nicely by adding
#include <iomanip>
at the top of the file, and
size_t width = digits(maximum(ports));
just before the for-loop in the main function, and then changing the line that displays the port status to
std::cout << "Port " << std::setw(width) << port << " : OPEN\n";
. You will also need to add these two functions just before main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
// Gets the maximum value in a vector.
template <typename T>
static T maximum(const std::vector<T>& values)
{
T max = values[0];
for (T value : values) {
if (value > max)
max = value;
}
return max;
}
// Counts the digits in a number.
template <typename T>
static size_t digits(T value)
{
size_t count = (value < 0) ? 1 : 0;
if (value == 0)
return 0;
while (value) {
value /= 10;
++count;
};
return count;
}
| |
Now the output will be nicely formatted:
Address: localhost
Port: 0-65535
Showing open ports on localhost...
Port 22 : OPEN
Port 53 : OPEN
Port 80 : OPEN
Port 139 : OPEN
Port 445 : OPEN
Port 631 : OPEN
Port 3306 : OPEN
Port 17500 : OPEN
Port 35723 : OPEN
Port 43351 : OPEN |
Here is the code for our improved port scanner:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <SFML/Network.hpp>
#include <sstream>
#include <string>
#include <vector>
static bool port_is_open(const std::string& address, int port)
{
return (sf::SocketTCP().connect(address, port) == sf::Socket::Done);
}
static std::vector<std::string> split(const std::string& string,
char delimiter = ' ',
bool allow_empty = false)
{
std::vector<std::string> tokens;
std::stringstream sstream(string);
std::string token;
while (std::getline(sstream, token, delimiter)) {
if (allow_empty || token.size() > 0)
tokens.push_back(token);
}
return tokens;
}
static int string_to_int(const std::string& string)
{
std::stringstream sstream(string);
int i;
sstream >> i;
return i;
}
template <typename T>
static void swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
template <typename T>
static std::vector<T> range(T min, T max)
{
if (min > max)
swap(min, max);
if (min == max)
return std::vector<T>(1, min);
std::vector<T> values;
for (; min <= max; ++min)
values.push_back(min);
return values;
}
static std::vector<int> parse_ports_list(const std::string& list)
{
std::vector<int> ports;
for (const std::string& token : split(list, ',')) {
std::vector<std::string> strrange = split(token, '-');
switch (strrange.size()) {
case 0: ports.push_back(string_to_int(token)); break;
case 1: ports.push_back(string_to_int(strrange[0])); break;
case 2:
{
int min = string_to_int(strrange[0]),
max = string_to_int(strrange[1]);
for (int port : range(min, max))
ports.push_back(port);
break;
}
default:
break;
}
}
return ports;
}
template <typename T>
static T maximum(const std::vector<T>& values)
{
T max = values[0];
for (T value : values) {
if (value > max)
max = value;
}
return max;
}
template <typename T>
static size_t digits(T value)
{
size_t count = (value < 0) ? 1 : 0;
if (value == 0)
return 0;
while (value) {
value /= 10;
++count;
};
return count;
}
int main()
{
std::string address;
std::string port_list;
std::vector<int> ports;
std::cout << "Address: " << std::flush;
std::getline(std::cin, address);
std::cout << "Port: " << std::flush;
std::getline(std::cin, port_list);
ports = parse_ports_list(port_list);
std::cout << "Showing open ports on " << address << "...\n";
size_t width = digits(maximum(ports));
for (int port : ports) {
if (port_is_open(address, port))
std::cout << "Port " << std::setw(width) << port << " : OPEN\n";
}
std::cout << std::flush;
return 0;
}
| |
You may want to be able to use the program easily in scripts and run it easily from other programs, in which case you can modify it to use command-line for the address and port information. Here is a modified main function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
int main(int argc, char* argv[])
{
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " address port(s)\n"
<< "Examples:\n"
<< "\t" << argv[0] << " 127.0.0.1 80\n"
<< "\t" << argv[0] << " localhost 80,8080\n"
<< "\t" << argv[0] << " 192.0.43.10 0-65535\n"
<< "\t" << argv[0] << " example.com 0-21,80,8080"
<< std::endl;
std::exit(EXIT_FAILURE);
}
std::string address = argv[1];
std::vector<int> ports = parse_ports_list(std::string(argv[2]));
std::cout << "Showing open ports on " << address << "...\n";
size_t width = digits(maximum(ports));
for (int port : ports) {
if (port_is_open(address, port))
std::cout << "Port " << std::setw(width) << port << " : OPEN\";
}
std::cout << std::endl;
return 0;
}
| |
And here is one that allows both:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
int main(int argc, char* argv[])
{
std::string address;
std::vector<int> ports;
if (argc == 3) {
address = argv[1];
ports = parse_ports_list(std::string(argv[2]));
} else {
std::string port_list;
std::cout << "Address: " << std::flush;
std::getline(std::cin, address);
std::cout << "Port: " << std::flush;
std::getline(std::cin, port_list);
ports = parse_ports_list(port_list);
}
std::cout << "Showing open ports on " << address << "...\n";
size_t width = digits(maximum(ports));
for (int port : ports) {
if (port_is_open(address, port))
std::cout << "Port " << std::setw(width) << port << " : OPEN\n";
}
std::cout << std::flush;
return 0;
}
| |
Here is the final (for now) iteration of the port scanner, which allows both command-line and interactive invocation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <SFML/Network.hpp>
#include <sstream>
#include <string>
#include <vector>
static bool port_is_open(const std::string& address, int port)
{
return (sf::SocketTCP().connect(address, port) == sf::Socket::Done);
}
static std::vector<std::string> split(const std::string& string,
char delimiter = ' ',
bool allow_empty = false)
{
std::vector<std::string> tokens;
std::stringstream sstream(string);
std::string token;
while (std::getline(sstream, token, delimiter)) {
if (allow_empty || token.size() > 0)
tokens.push_back(token);
}
return tokens;
}
static int string_to_int(const std::string& string)
{
std::stringstream sstream(string);
int i;
sstream >> i;
return i;
}
template <typename T>
static void swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
template <typename T>
static std::vector<T> range(T min, T max)
{
if (min > max)
swap(min, max);
if (min == max)
return std::vector<T>(1, min);
std::vector<T> values;
for (; min <= max; ++min)
values.push_back(min);
return values;
}
static std::vector<int> parse_ports_list(const std::string& list)
{
std::vector<int> ports;
for (const std::string& token : split(list, ',')) {
std::vector<std::string> strrange = split(token, '-');
switch (strrange.size()) {
case 0: ports.push_back(string_to_int(token)); break;
case 1: ports.push_back(string_to_int(strrange[0])); break;
case 2:
{
int min = string_to_int(strrange[0]),
max = string_to_int(strrange[1]);
for (int port : range(min, max))
ports.push_back(port);
break;
}
default:
break;
}
}
return ports;
}
template <typename T>
static T maximum(const std::vector<T>& values)
{
T max = values[0];
for (T value : values) {
if (value > max)
max = value;
}
return max;
}
template <typename T>
static size_t digits(T value)
{
size_t count = (value < 0) ? 1 : 0;
if (value == 0)
return 0;
while (value) {
value /= 10;
++count;
};
return count;
}
int main(int argc, char* argv[])
{
std::string address;
std::vector<int> ports;
if (argc == 3) {
address = argv[1];
ports = parse_ports_list(std::string(argv[2]));
} else {
std::string port_list;
std::cout << "Address: " << std::flush;
std::getline(std::cin, address);
std::cout << "Port: " << std::flush;
std::getline(std::cin, port_list);
ports = parse_ports_list(port_list);
}
std::cout << "Showing open ports on " << address << "...\n";
size_t width = digits(maximum(ports));
for (int port : ports) {
if (port_is_open(address, port))
std::cout << "Port " << std::setw(width) << port << " : OPEN\n";
}
std::cout << std::flush;
return 0;
}
| |
Feel free to use or modify all the above code for any purpose (disclaimer: I will not be liable for any evil performed using my code).
Attached you can find the source of the original program that I wrote, which turned into the above code as I wrote the article. That program is distributed under the FreeBSD license.
To-Do
Attachments:
[cppscan.cpp]
[cppscan.h]
[main.cpp]