先决条件
- SFML 2.0 Release Candidate: [ 下载 | 文档 | 教程 ]
- 模板 [ 教程 ]
- std::vector [ 文档 ]
- std::stringstream [ 文档 ]
- C++11 基于范围的 for 循环 [ 维基百科 ]
简介
端口扫描器是一个程序,它通过逐个端口尝试连接服务器来探测服务器的开放端口。该程序通常会报告哪些端口是开放的,哪些是关闭的。更复杂的端口扫描器,如 Nmap,还可以探测其他信息。端口扫描通常由系统管理员执行以验证网络安全,或由攻击者寻找开放端口以破坏服务器安全。开放端口可能构成安全漏洞,因为它们允许远程计算机连接。意外开放的端口可能表明恶意软件正在监听指令。
我的第一个端口扫描器
和之前一样,扫描端口就像尝试连接到地址和端口一样简单。如果连接尝试成功,则端口必须是开放的。否则,则假定端口是关闭的。下面是一个使用 SFML 网络模块 (
文档) 来检查端口是否开放的示例函数。
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;
}
|
细分
- 首先,我们创建一个 sf::TcpSocket 实例 (文档),它允许我们连接到远程套接字。
- 然后,我们连接套接字。通过调用 sf::IpAddress 的构造函数,我们将字符串 'address' 转换为 sf::IpAddress (文档) 实例。如果省略了显式的构造函数调用,编译器也会隐式执行。在尝试连接后,我们通过将 sf::TcpSocket::connect 函数 (文档) 的返回值与枚举值 sf::Socket::Done (文档) 进行比较来检查连接是否成功。如果两者相等,则表示连接成功,端口是开放的。在这种情况下,变量 'open' 被设置为 true。
- 接下来,我们使用 sf::TcpSocket::disconnect 函数 (文档) 断开套接字连接。如果我们省略了显式调用,析构函数会自动完成此操作。
- 最后,我们将 'open' 的值返回给调用函数。
这实际上是我们唯一需要编写的网络代码。我们也可以将其简化为一行。
1 2 3 4
|
static bool port_is_open(const std::string& address, int port)
{
return (sf::TcpSocket().connect(address, port) == sf::Socket::Done);
}
|
这里我们创建了一个新的 sf::TcpSocket,连接到地址和端口,然后根据连接是否成功返回 true 或 false。我们去掉了不必要的显式 sf::IpAddress 构造函数调用以及对 sf::TcpSocket::disconnect() 的调用。我们可以像这样在程序中使用该函数。
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 |
尝试编译并运行此程序。它将测试您计算机上的 80 端口是否开放。请注意,“localhost”表示本地计算机;您也可以使用 IP 地址 127.0.0.1 或 ::1(IPv6 版本,尽管 SFML 尚不支持 IPv6)来实现相同目的。您可以将“localhost”更改为另一个网站的 IP 地址或 Web 地址(省略“http://”和任何路径信息),但请小心 - **未经许可扫描网站可能会在某些国家/地区给您带来麻烦**,因为它可能被视为黑客行为。幸运的是,端口扫描器 Nmap 的网站有一个专门用于测试端口扫描器的页面。尝试将“localhost”更改为“scanme.nmap.org”。只是不要扫描太多次(页面上说“每天几次”)。
改进的端口扫描器
既然我们已经成功开发了一个可以扫描地址上端口的程序,我们就可以修改我们的程序,让用户指定要扫描的端口和地址。
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 |
请记住,127.0.0.1 等同于 localhost。另外,您的计算机上的 80 端口可能不开放。它只在我这里开放,因为我运行的是 Apache HTTP 服务器(80 端口是通常用于 HTTP,即网站的主要端口;另一个常用的 HTTP 端口是 8080)。
此外,我们也可以轻松地在 Nmap 的“ScanMe”页面上测试此代码。
Address: scanme.nmap.org
Port: 80
Scanning scanme.nmap.org...
Port 80 : OPEN |
这次可能需要一些时间,因为您不是扫描自己的计算机,而是通过 Internet 连接到另一台计算机。
端口如雨后春笋
一次扫描一个端口很乏味,我们希望让用户扫描很多端口。一种方法是让用户输入任意数量的端口,然后一次性扫描所有这些端口。这样做的问题是用户可能想扫描很多端口,而不得不一一输入。我们也可以让用户指定一个端口范围,例如 0-100,但那样他们就无法指定该范围之外的值。我们将做得更好,让他们同时做到这两点——在列表中指定范围和单独的端口,例如:“80,8080”;一个范围,例如:“20-80”;或者一个包含范围的列表:“20-80,8080”。此代码由于使用了模板、std::vector、std::stringstream 和 C++11 基于范围的 for 循环而变得相当复杂,因此,如果您跳过了“先决条件”部分且不知道如何使用它们,请回到本文的顶部进行学习。否则,请继续阅读。
首先我们需要一个分割字符串的函数。
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;
}
|
端口的数据类型将是字符串,但我们想要整数,所以我们还需要一个将字符串转换为整数的函数。
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;
}
|
如果我们有一个端口范围,我们将需要一个函数来生成该范围内的所有值。
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;
}
|
最后,我们需要一个函数来使用上述函数实际解析端口列表。
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;
}
|
我们的最终程序
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 |
就这样!我们现在有了一个成功的端口扫描器,它可以扫描用户所需的任意数量的端口。
再扫描几个端口
我们可以通过更改以下代码来修改我们的程序,使其不显示关闭的端口:
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";
}
|
为
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";
}
|
这使得输出更加清晰。扫描我计算机上的所有端口。
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 |
请注意,共有 65535 个端口。如果我们使用先前版本的端口扫描器扫描所有端口,它将生成 65,535 行输出,这将使得获取所需信息非常困难。
我们可以通过在文件顶部添加
#include <iomanip>
,在主函数中的 for 循环之前添加
size_t width = digits(maximum(ports));
,然后更改显示端口状态的行以
std::cout << "Port " << std::setw(width) << port << " : OPEN\n";
来漂亮地格式化我们的输出。您还需要在 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;
}
|
现在输出将得到很好的格式化。
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 |
这是我们改进的端口扫描器的代码。
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;
}
|
您可能希望能够轻松地在脚本中使用该程序,并从其他程序轻松运行它,在这种情况下,您可以修改它以使用命令行来获取地址和端口信息。下面是一个修改后的 main 函数。
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;
}
|
下面是一个允许两者同时使用的版本。
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;
}
|
这是端口扫描器的最终(目前)版本,它允许命令行和交互式调用。
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;
}
|
随意出于任何目的使用或修改以上所有代码(免责声明:对于使用我的代码执行的任何恶意行为,我概不负责)。
您可以找到我编写的原始程序的源代码,该程序在编写本文时演变成了上述代码。该程序是在 FreeBSD 许可下分发的。
待办事项
附件:[cppscan.cpp] [cppscan.h] [main.cpp]