Article by Ayman Alheraki on April 18 2026 04:39 PM
A Practical Guide to TCP Port Scanning, Banner Grabbing, and Basic Vulnerability Detection Using Only Standard Libraries and Winsock
Network security assessment often begins with understanding which services are exposed on a target system. This article presents a complete, functional TCP port scanner and vulnerability detector written in modern C++ (C++17) for Windows. The tool performs concurrent port scanning, grabs service banners, and matches them against a database of known vulnerable software versions—all without relying on third-party libraries beyond the Windows Sockets API (Winsock2).
The scanner is designed to be:
Self-contained: Only standard C++ and Windows system libraries are used.
Efficient: A thread pool scans up to 100 ports simultaneously.
Educational: The code demonstrates practical use of threads, sockets, RAII, and pattern matching.
By the end of this article, you will have a working executable that can scan a target IP address, identify open ports, and flag potential vulnerabilities based on service banners.
TCP Connect Scanning – Reliable detection of open ports using non‑blocking sockets with a 2‑second timeout.
Multi‑threaded Architecture – A custom thread pool enables fast concurrent scanning (configurable up to 100 threads).
Banner Grabbing – Sends protocol‑specific probes (e.g., HTTP HEAD request) to retrieve service banners.
Vulnerability Detection – Compares captured banners against a signature database of known vulnerable software (CVEs).
Comprehensive Reporting – Displays a summary and detailed table on the console, and saves the report to a text file.
RAII Winsock Management – Automatic initialisation and cleanup of the Windows Sockets library.
Command‑Line Options – Supports full port range scans (--full) and disabling banner grabbing (--no-banner).
The code below is ready to be saved as netvulnscan_win.cpp and compiled with Visual Studio or MinGW‑w64.
/** * Simple Network Vulnerability Scanner in Modern C++ * Windows Version (Winsock2) * * Compilation: * Visual Studio: Set C++ Language Standard to C++17 or later * MinGW: g++ -std=c++17 -o netvulnscan.exe netvulnscan_win.cpp -lws2_32 * * Run: netvulnscan.exe <target_ip> [--full] [--no-banner] */
// Windows-specific networking headers
// Link with ws2_32.lib (Visual Studio)
// Common port-to-service mappingstatic const std::map<int, std::string> COMMON_PORTS = { {21, "FTP"}, {22, "SSH"}, {23, "Telnet"}, {25, "SMTP"}, {53, "DNS"}, {80, "HTTP"}, {110, "POP3"}, {111, "RPC"}, {135, "RPC"}, {139, "NetBIOS"}, {143, "IMAP"}, {443, "HTTPS"}, {445, "SMB"}, {993, "IMAPS"}, {995, "POP3S"}, {1723, "PPTP"}, {3306, "MySQL"}, {3389, "RDP"}, {5432, "PostgreSQL"}, {5900, "VNC"}, {6379, "Redis"}, {8080, "HTTP-Proxy"}, {8443, "HTTPS-Alt"}, {27017, "MongoDB"}, {27018, "MongoDB"}, {27019, "MongoDB"}};
// Known vulnerable service signaturesstatic const std::map<std::string, std::vector<std::string>> VULN_SIGNATURES = { {"vsftpd 2.3.4", {"CVE-2011-2523: Backdoor command execution"}}, {"OpenSSH 7.2", {"CVE-2016-6210: User enumeration"}}, {"Apache 2.4.49", {"CVE-2021-41773: Path traversal"}}, {"Apache 2.4.50", {"CVE-2021-42013: Path traversal"}}, {"nginx 1.20", {"CVE-2021-23017: Memory disclosure"}}, {"ProFTPD 1.3.5", {"CVE-2015-3306: Remote code execution"}}, {"Exim 4.87", {"CVE-2017-16943: Remote code execution"}}, {"Redis 4.0.9", {"CVE-2018-11218: Remote code execution"}}, {"MySQL 5.6", {"CVE-2016-6662: Privilege escalation"}}, {"Samba 4.5", {"CVE-2017-7494: Remote code execution"}}};
// Structure for scan resultsstruct ScanResult { int port; bool is_open; std::string service; std::string banner; std::vector<std::string> vulnerabilities; int response_time_ms;};
// Thread pool for concurrent scanningclass ThreadPool {public: ThreadPool(size_t threads) : stop(false) { for (size_t i = 0; i < threads; ++i) { workers.emplace_back([this] { while (true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(queue_mutex); condition.wait(lock, [this] { return stop || !tasks.empty(); }); if (stop && tasks.empty()) return; task = std::move(tasks.front()); tasks.pop(); } task(); } }); } }
template<class F> void enqueue(F&& f) { { std::unique_lock<std::mutex> lock(queue_mutex); tasks.emplace(std::forward<F>(f)); } condition.notify_one(); }
~ThreadPool() { { std::unique_lock<std::mutex> lock(queue_mutex); stop = true; } condition.notify_all(); for (std::thread &worker : workers) { worker.join(); } }
private: std::vector<std::thread> workers; std::queue<std::function<void()>> tasks; std::mutex queue_mutex; std::condition_variable condition; bool stop;};
// RAII wrapper for Winsock initializationclass WinsockManager {public: WinsockManager() { WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { throw std::runtime_error("WSAStartup failed"); } } ~WinsockManager() { WSACleanup(); }};
// Main scanner classclass NetworkVulnerabilityScanner {public: NetworkVulnerabilityScanner(const std::string& target, bool full_scan = false, bool grab_banner = true) : target_ip(target), full_scan(full_scan), grab_banner(grab_banner) {}
void run() { std::cout << "\n====================================================================\n"; std::cout << " Network Vulnerability Scanner - Modern C++ (Windows) \n"; std::cout << "====================================================================\n\n"; std::cout << "[*] Target: " << target_ip << "\n"; std::cout << "[*] Scan mode: " << (full_scan ? "Full (1-65535)" : "Top 1000 ports") << "\n"; std::cout << "[*] Banner grabbing: " << (grab_banner ? "Enabled" : "Disabled") << "\n"; std::cout << "[*] Starting scan at " << getCurrentTime() << "\n\n";
auto start_time = std::chrono::steady_clock::now();
// Generate port list std::vector<int> ports = generatePortList(); std::cout << "[*] Scanning " << ports.size() << " ports...\n";
// Thread pool for concurrent scanning ThreadPool pool(100); std::mutex result_mutex; std::atomic<int> scanned_ports{0}; std::atomic<int> open_ports{0};
for (int port : ports) { pool.enqueue([this, port, &result_mutex, &scanned_ports, &open_ports] { ScanResult result = scanPort(port); { std::lock_guard<std::mutex> lock(result_mutex); results.push_back(result); if (result.is_open) { open_ports++; printOpenPort(result); } } scanned_ports++; // Progress indicator if (scanned_ports % 100 == 0) { std::cout << "\r[*] Progress: " << scanned_ports << "/" << (full_scan ? 65535 : 1000) << " ports scanned..." << std::flush; } }); }
// Wait for all tasks to complete while (scanned_ports < static_cast<int>(ports.size())) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); }
auto end_time = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time);
std::cout << "\r[*] Scan completed: " << scanned_ports << " ports scanned in " << duration.count() << " seconds\n\n";
generateReport(); }
private: std::string target_ip; bool full_scan; bool grab_banner; std::vector<ScanResult> results;
std::string getCurrentTime() { auto now = std::chrono::system_clock::now(); auto time = std::chrono::system_clock::to_time_t(now); char buffer[26]; ctime_s(buffer, sizeof(buffer), &time); return std::string(buffer); }
std::vector<int> generatePortList() { std::vector<int> ports; if (full_scan) { for (int i = 1; i <= 65535; ++i) { ports.push_back(i); } } else { // Top 1000 ports plus some high-value ports for (int i = 1; i <= 1000; ++i) { ports.push_back(i); } std::vector<int> high_ports = {1433, 1521, 3306, 3389, 5432, 5900, 6379, 8080, 8443, 27017, 27018, 27019, 50000}; ports.insert(ports.end(), high_ports.begin(), high_ports.end()); } return ports; }
ScanResult scanPort(int port) { ScanResult result; result.port = port; result.is_open = false; result.response_time_ms = 0;
auto start = std::chrono::steady_clock::now(); SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) return result;
// Set non-blocking mode u_long mode = 1; ioctlsocket(sock, FIONBIO, &mode);
struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_pton(AF_INET, target_ip.c_str(), &addr.sin_addr);
// Initiate connection connect(sock, (struct sockaddr*)&addr, sizeof(addr));
// Use select() for timeout fd_set write_fds; FD_ZERO(&write_fds); FD_SET(sock, &write_fds);
struct timeval tv; tv.tv_sec = 2; // 2 seconds timeout tv.tv_usec = 0;
int ret = select(0, nullptr, &write_fds, nullptr, &tv); if (ret > 0) { int error = 0; int len = sizeof(error); getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&error, &len); if (error == 0) { result.is_open = true; // Restore blocking mode for banner grabbing mode = 0; ioctlsocket(sock, FIONBIO, &mode); // Get service name auto it = COMMON_PORTS.find(port); result.service = (it != COMMON_PORTS.end()) ? it->second : "unknown";
// Banner grabbing if (grab_banner) { result.banner = grabBanner(sock, port); checkVulnerabilities(result); } } }
auto end = std::chrono::steady_clock::now(); result.response_time_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
closesocket(sock); return result; }
std::string grabBanner(SOCKET sock, int port) { std::string probe; if (port == 80 || port == 443 || port == 8080 || port == 8443) { probe = "HEAD / HTTP/1.0\r\n\r\n"; } else { probe = "\r\n"; }
send(sock, probe.c_str(), (int)probe.length(), 0);
// Set receive timeout DWORD timeout = 2000; setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
char buffer[1024]; std::string banner; int bytes = recv(sock, buffer, sizeof(buffer) - 1, 0); if (bytes > 0) { buffer[bytes] = '\0'; banner = std::string(buffer); size_t newline = banner.find_first_of("\r\n"); if (newline != std::string::npos) { banner = banner.substr(0, newline); } }
return banner; }
void checkVulnerabilities(ScanResult& result) { std::string banner_lower = result.banner; std::transform(banner_lower.begin(), banner_lower.end(), banner_lower.begin(), ::tolower);
for (const auto& [sig, vulns] : VULN_SIGNATURES) { std::string sig_lower = sig; std::transform(sig_lower.begin(), sig_lower.end(), sig_lower.begin(), ::tolower); if (banner_lower.find(sig_lower) != std::string::npos) { result.vulnerabilities.insert(result.vulnerabilities.end(), vulns.begin(), vulns.end()); } } }
void printOpenPort(const ScanResult& result) { std::cout << "[+] Port " << std::setw(5) << result.port << " open " << std::setw(10) << result.service; if (!result.banner.empty()) { std::cout << " [" << result.banner << "]"; } std::cout << "\n"; }
void generateReport() { std::cout << "====================================================================\n"; std::cout << " SCAN REPORT\n"; std::cout << "====================================================================\n\n";
int open_count = 0; int vuln_count = 0; for (const auto& r : results) { if (r.is_open) { open_count++; if (!r.vulnerabilities.empty()) vuln_count++; } }
std::cout << "SUMMARY:\n"; std::cout << " Target: " << target_ip << "\n"; std::cout << " Ports scanned: " << results.size() << "\n"; std::cout << " Open ports: " << open_count << "\n"; std::cout << " Vulnerabilities: " << vuln_count << "\n\n";
if (open_count > 0) { std::cout << "OPEN PORTS:\n"; std::cout << "+--------+--------------+-----------------------------------------+\n"; std::cout << "| Port | Service | Banner / Info |\n"; std::cout << "+--------+--------------+-----------------------------------------+\n"; for (const auto& r : results) { if (r.is_open) { std::cout << "| " << std::left << std::setw(6) << r.port << " | " << std::setw(12) << r.service << " | "; std::string info = r.banner.empty() ? "No banner" : r.banner; if (info.length() > 39) info = info.substr(0, 36) + "..."; std::cout << std::setw(39) << info << " |\n"; } } std::cout << "+--------+--------------+-----------------------------------------+\n\n"; }
if (vuln_count > 0) { std::cout << "VULNERABILITIES DETECTED:\n"; for (const auto& r : results) { if (!r.vulnerabilities.empty()) { std::cout << " [!] Port " << r.port << " (" << r.service << "):\n"; for (const auto& v : r.vulnerabilities) { std::cout << " - " << v << "\n"; } std::cout << "\n"; } } }
std::string filename = "scan_report_" + target_ip + ".txt"; std::replace(filename.begin(), filename.end(), '.', '_'); saveReportToFile(filename); std::cout << "Report saved to: " << filename << "\n"; }
void saveReportToFile(const std::string& filename) { std::ofstream file(filename); if (!file.is_open()) return;
file << "Network Vulnerability Scan Report\n"; file << "=================================\n"; file << "Target: " << target_ip << "\n"; file << "Scan time: " << getCurrentTime() << "\n\n"; file << "Open ports:\n"; for (const auto& r : results) { if (r.is_open) { file << " " << r.port << "/tcp - " << r.service << "\n"; if (!r.banner.empty()) { file << " Banner: " << r.banner << "\n"; } for (const auto& v : r.vulnerabilities) { file << " Vulnerability: " << v << "\n"; } } } }};
void printUsage(const char* prog) { std::cout << "Usage: " << prog << " <target_ip> [options]\n"; std::cout << "Options:\n"; std::cout << " --full Scan all 65535 ports\n"; std::cout << " --no-banner Disable banner grabbing\n"; std::cout << " --help Show this help\n";}
int main(int argc, char* argv[]) { if (argc < 2) { printUsage(argv[0]); return 1; }
std::string target = argv[1]; bool full_scan = false; bool grab_banner = true;
for (int i = 2; i < argc; ++i) { std::string arg = argv[i]; if (arg == "--full") full_scan = true; else if (arg == "--no-banner") grab_banner = false; else if (arg == "--help") { printUsage(argv[0]); return 0; } }
try { WinsockManager winsock; // Initialize Winsock NetworkVulnerabilityScanner scanner(target, full_scan, grab_banner); scanner.run(); } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << "\n"; return 1; }
std::cout << "\nPress Enter to exit..."; std::cin.get(); return 0;}
By default, the scanner probes the first 1000 TCP ports plus a curated list of high‑value ports (e.g., 3306 for MySQL, 3389 for RDP).
The --full flag expands the target list to all 65,535 ports.
A custom ThreadPool class manages up to 100 worker threads.
Each port scan is submitted as a task to the pool, allowing parallel connection attempts.
A non‑blocking socket is created and a connection is initiated.
The Windows select() function waits for the socket to become writable, with a 2‑second timeout.
If the connection succeeds, getsockopt(..., SO_ERROR, ...) confirms the port is open.
After a successful connection, the socket is switched back to blocking mode.
A protocol‑specific probe is sent:
HTTP/HTTPS ports receive HEAD / HTTP/1.0\r\n\r\n.
All other ports receive a blank line (\r\n).
The response is read (up to 1024 bytes) and the first line is extracted as the banner.
The captured banner is converted to lowercase and compared against a signature database (VULN_SIGNATURES).
If a match is found, the corresponding CVE identifiers are recorded.
Open ports are printed in real time.
A final report summarises the findings, displays a table of open ports, and lists any detected vulnerabilities.
The same information is saved to a text file (e.g., scan_report_192_168_1_1.txt).
Create a new Console App (C++) project.
Replace the default .cpp file with the code provided above.
Right‑click the project → Properties.
Navigate to C/C++ → Language and set C++ Language Standard to ISO C++17 Standard (/std:c++17) or ISO C++20.
Build the solution (Build → Build Solution).
Install MinGW‑w64 and ensure the bin folder is in your PATH.
Open a Command Prompt in the folder containing netvulnscan_win.cpp.
Run the following command:
g++ -std=c++17 -o netvulnscan.exe netvulnscan_win.cpp -lws2_32
clang++ -std=c++17 -o netvulnscan.exe netvulnscan_win.cpp -lws2_32
Note: The -lws2_32 flag links the Winsock library. In Visual Studio, the #pragma comment(lib, "ws2_32.lib") directive handles this automatically.
Scan a local machine or a remote host with default settings (top ~1000 ports):
netvulnscan.exe 192.168.1.1
Perform a full port scan (1–65535):
netvulnscan.exe 192.168.1.1 --full
Disable banner grabbing (useful for faster, quieter scans):
netvulnscan.exe 10.0.0.1 --no-banner
Display help:
netvulnscan.exe --help
====================================================================Network Vulnerability Scanner - Modern C++ (Windows)====================================================================[*] Target: 192.168.1.1[*] Scan mode: Top 1000 ports[*] Banner grabbing: Enabled[*] Starting scan at Fri Apr 18 14:32:10 2026[*] Scanning 1013 ports...[+] Port 22 open SSH [SSH-2.0-OpenSSH_7.2][+] Port 80 open HTTP [Apache/2.4.49 (Ubuntu)][+] Port 443 open HTTPS[*] Progress: 1013/1000 ports scanned...[*] Scan completed: 1013 ports scanned in 12 seconds====================================================================SCAN REPORT====================================================================SUMMARY:Target: 192.168.1.1Ports scanned: 1013Open ports: 3Vulnerabilities: 2OPEN PORTS:+--------+--------------+-----------------------------------------+| Port | Service | Banner / Info |+--------+--------------+-----------------------------------------+| 22 | SSH | SSH-2.0-OpenSSH_7.2 || 80 | HTTP | Apache/2.4.49 (Ubuntu) || 443 | HTTPS | No banner |+--------+--------------+-----------------------------------------+VULNERABILITIES DETECTED:[!] Port 22 (SSH):- CVE-2016-6210: User enumeration[!] Port 80 (HTTP):- CVE-2021-41773: Path traversalReport saved to: scan_report_192_168_1_1.txt
TCP Connect Scan Only: The scanner uses a full TCP handshake, which is logged by most firewalls and intrusion detection systems. A stealthier SYN scan would require raw sockets, which are heavily restricted on modern Windows without a kernel driver.
Basic Banner Matching: The vulnerability detection relies on simple substring matching. Real‑world fingerprinting is more complex and would require a larger database and regular expression support.
No Service Version Probing: Beyond the initial banner, no further probes are sent to elicit version information. Some services require protocol‑specific requests.
Timeout Values: The 2‑second timeout per port works well on local networks but may need adjustment for high‑latency connections.
Legal and Ethical Use: Only scan systems you own or have explicit permission to test. Unauthorised port scanning may be illegal in your jurisdiction.
This article has presented a fully functional network vulnerability scanner built with modern C++ and the Windows Sockets API. The tool demonstrates practical techniques in concurrent programming, network I/O, and security assessment. With minimal dependencies, it serves as an excellent foundation for learning or for extending into a more sophisticated security tool.
Feel free to enhance the scanner by expanding the vulnerability database, adding UDP scanning, or implementing more advanced service fingerprinting. The complete source code is provided above and can be compiled immediately on any Windows system with a C++17‑capable compiler.
Happy (and ethical) scanning!