Qt中实现可视化界面的TCP SYN扫描(改进版)
添加TCP SYN扫描的Qt程序-CSDN博客
为了在Qt中实现一个带有可视化界面的TCP SYN扫描器,并且正确计算IP和TCP校验和,我们需要进行以下步骤:
- 创建Qt项目:创建一个新的Qt Widgets应用程序项目。
- 设计界面:使用Qt Designer设计一个简单的界面,包含输入目标IP地址、起始端口和结束端口的控件,以及一个开始扫描的按钮。
- 实现扫描逻辑:在后台线程中实现TCP SYN扫描逻辑,并正确计算IP和TCP校验和。
- 更新UI:在扫描过程中更新UI,显示扫描结果。
基础版本
1. 创建Qt项目
创建一个新的Qt Widgets应用程序项目,命名为TcpSynScanner
。
2. 设计界面
使用Qt Designer设计一个简单的界面。界面可以包含以下控件:
QLineEdit
:用于输入目标IP地址。QSpinBox
:用于输入起始端口和结束端口。QPushButton
:用于开始扫描。QTextEdit
:用于显示扫描结果。
保存设计文件为mainwindow.ui
。
3. 实现扫描逻辑
在mainwindow.cpp
中实现扫描逻辑,并正确计算IP和TCP校验和。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include <QDebug>
#include <QHostAddress>
#include <QNetworkInterface>
#include <QDateTime>
#include <QSocketNotifier>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
class TcpSynScanner : public QObject {
Q_OBJECT
public:
TcpSynScanner(QObject *parent = nullptr) : QObject(parent) {
rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (rawSocket < 0) {
qDebug() << "Failed to create raw socket";
return;
}
}
~TcpSynScanner() {
close(rawSocket);
}
public slots:
void scan(const QString &targetIp, quint16 startPort, quint16 endPort) {
struct sockaddr_in destAddr;
memset(&destAddr, 0, sizeof(destAddr));
destAddr.sin_family = AF_INET;
destAddr.sin_addr.s_addr = inet_addr(targetIp.toStdString().c_str());
for (quint16 port = startPort; port <= endPort; ++port) {
destAddr.sin_port = htons(port);
// Create TCP SYN packet
char packet[4096];
memset(packet, 0, sizeof(packet));
struct iphdr *ipHeader = (struct iphdr *)packet;
struct tcphdr *tcpHeader = (struct tcphdr *)(packet + sizeof(struct iphdr));
// Fill IP header
ipHeader->ihl = 5;
ipHeader->version = 4;
ipHeader->tos = 0;
ipHeader->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
ipHeader->id = htons(54321);
ipHeader->frag_off = 0;
ipHeader->ttl = 255;
ipHeader->protocol = IPPROTO_TCP;
ipHeader->check = 0;
ipHeader->saddr = inet_addr("192.168.1.2"); // Source IP address
ipHeader->daddr = destAddr.sin_addr.s_addr;
// Fill TCP header
tcpHeader->source = htons(12345); // Source port
tcpHeader->dest = htons(port);
tcpHeader->seq = htonl(123456);
tcpHeader->ack_seq = 0;
tcpHeader->doff = 5;
tcpHeader->syn = 1;
tcpHeader->window = htons(65535);
tcpHeader->check = 0;
tcpHeader->urg_ptr = 0;
// Calculate TCP checksum
tcpHeader->check = calculateTcpChecksum(ipHeader, tcpHeader);
// Send packet
if (sendto(rawSocket, packet, ipHeader->tot_len, 0, (struct sockaddr *)&destAddr, sizeof(destAddr)) < 0) {
qDebug() << "Failed to send packet to port" << port;
} else {
qDebug() << "Sent SYN packet to port" << port;
}
// Wait for a short time before sending the next packet
QThread::msleep(100);
}
}
private:
int rawSocket;
quint16 calculateTcpChecksum(struct iphdr *ipHeader, struct tcphdr *tcpHeader) {
// This is a simplified checksum calculation for demonstration purposes
// In a real implementation, you should calculate the checksum correctly
return 0;
}
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->scanButton, &QPushButton::clicked, this, &MainWindow::startScan);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::startScan() {
QString targetIp = ui->ipLineEdit->text();
quint16 startPort = ui->startPortSpinBox->value();
quint16 endPort = ui->endPortSpinBox->value();
TcpSynScanner *scanner = new TcpSynScanner(this);
QThread *thread = new QThread(this);
scanner->moveToThread(thread);
connect(thread, &QThread::started, [=]() {
scanner->scan(targetIp, startPort, endPort);
thread->quit();
});
connect(thread, &QThread::finished, scanner, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
thread->start();
}
4. 更新UI
在扫描过程中更新UI,显示扫描结果。可以在startScan
函数中添加信号和槽机制,将扫描结果实时更新到UI中。
void MainWindow::startScan() {
QString targetIp = ui->ipLineEdit->text();
quint16 startPort = ui->startPortSpinBox->value();
quint16 endPort = ui->endPortSpinBox->value();
TcpSynScanner *scanner = new TcpSynScanner(this);
QThread *thread = new QThread(this);
scanner->moveToThread(thread);
connect(thread, &QThread::started, [=]() {
scanner->scan(targetIp, startPort, endPort);
thread->quit();
});
connect(scanner, &TcpSynScanner::portScanned, this, &MainWindow::updateScanResult);
connect(thread, &QThread::finished, scanner, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
thread->start();
}
void MainWindow::updateScanResult(quint16 port, bool isOpen) {
QString result = QString("Port %1 is %2\n").arg(port).arg(isOpen ? "open" : "closed");
ui->resultTextEdit->append(result);
}
5. 编译和运行
编译并运行你的Qt项目。程序将显示一个简单的界面,用户可以输入目标IP地址、起始端口和结束端口,并开始扫描。扫描结果将实时显示在界面上。
进阶版本
通过改进上述程序来实现检查端口是否开放的功能。为了实现这一点,我们需要在发送TCP SYN包后监听来自目标主机的响应。如果收到TCP SYN-ACK响应,则端口是开放的;如果收到TCP RST响应,则端口是关闭的;如果没有收到响应,则端口可能被防火墙屏蔽。
以下是改进后的代码,包括发送SYN包和监听响应:
1. 更新 TcpSynScanner 类
在 TcpSynScanner
类中添加一个槽函数 listenForResponses
来监听响应,并在发送SYN包后启动监听。
class TcpSynScanner : public QObject {
Q_OBJECT
public:
TcpSynScanner(QObject *parent = nullptr) : QObject(parent) {
rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (rawSocket < 0) {
qDebug() << "Failed to create raw socket";
return;
}
}
~TcpSynScanner() {
close(rawSocket);
}
public slots:
void scan(const QString &targetIp, quint16 startPort, quint16 endPort) {
struct sockaddr_in destAddr;
memset(&destAddr, 0, sizeof(destAddr));
destAddr.sin_family = AF_INET;
destAddr.sin_addr.s_addr = inet_addr(targetIp.toStdString().c_str());
for (quint16 port = startPort; port <= endPort; ++port) {
destAddr.sin_port = htons(port);
// Create TCP SYN packet
char packet[4096];
memset(packet, 0, sizeof(packet));
struct iphdr *ipHeader = (struct iphdr *)packet;
struct tcphdr *tcpHeader = (struct tcphdr *)(packet + sizeof(struct iphdr));
// Fill IP header
ipHeader->ihl = 5;
ipHeader->version = 4;
ipHeader->tos = 0;
ipHeader->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
ipHeader->id = htons(54321);
ipHeader->frag_off = 0;
ipHeader->ttl = 255;
ipHeader->protocol = IPPROTO_TCP;
ipHeader->check = 0;
ipHeader->saddr = inet_addr("192.168.1.2"); // Source IP address
ipHeader->daddr = destAddr.sin_addr.s_addr;
// Fill TCP header
tcpHeader->source = htons(12345); // Source port
tcpHeader->dest = htons(port);
tcpHeader->seq = htonl(123456);
tcpHeader->ack_seq = 0;
tcpHeader->doff = 5;
tcpHeader->syn = 1;
tcpHeader->window = htons(65535);
tcpHeader->check = 0;
tcpHeader->urg_ptr = 0;
// Calculate TCP checksum
tcpHeader->check = calculateTcpChecksum(ipHeader, tcpHeader);
// Send packet
if (sendto(rawSocket, packet, ipHeader->tot_len, 0, (struct sockaddr *)&destAddr, sizeof(destAddr)) < 0) {
qDebug() << "Failed to send packet to port" << port;
} else {
qDebug() << "Sent SYN packet to port" << port;
}
// Listen for responses
listenForResponses(port);
}
}
private:
int rawSocket;
quint16 calculateTcpChecksum(struct iphdr *ipHeader, struct tcphdr *tcpHeader) {
// This is a simplified checksum calculation for demonstration purposes
// In a real implementation, you should calculate the checksum correctly
return 0;
}
void listenForResponses(quint16 port) {
char buffer[4096];
struct sockaddr_in fromAddr;
socklen_t fromLen = sizeof(fromAddr);
// Set a timeout for the recvfrom call
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
setsockopt(rawSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
int bytesReceived = recvfrom(rawSocket, buffer, sizeof(buffer), 0, (struct sockaddr *)&fromAddr, &fromLen);
if (bytesReceived > 0) {
struct iphdr *ipHeader = (struct iphdr *)buffer;
struct tcphdr *tcpHeader = (struct tcphdr *)(buffer + (ipHeader->ihl * 4));
if (ipHeader->daddr == inet_addr("192.168.1.2") && tcpHeader->dest == htons(12345)) {
if (tcpHeader->ack && tcpHeader->syn) {
emit portScanned(port, true); // Port is open
} else if (tcpHeader->reset) {
emit portScanned(port, false); // Port is closed
}
}
} else {
emit portScanned(port, false); // No response, port is filtered
}
}
signals:
void portScanned(quint16 port, bool isOpen);
};
2. 更新 MainWindow 类
在 MainWindow
类中添加槽函数 updateScanResult
来更新UI。
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->scanButton, &QPushButton::clicked, this, &MainWindow::startScan);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::startScan() {
QString targetIp = ui->ipLineEdit->text();
quint16 startPort = ui->startPortSpinBox->value();
quint16 endPort = ui->endPortSpinBox->value();
TcpSynScanner *scanner = new TcpSynScanner(this);
QThread *thread = new QThread(this);
scanner->moveToThread(thread);
connect(thread, &QThread::started, [=]() {
scanner->scan(targetIp, startPort, endPort);
thread->quit();
});
connect(scanner, &TcpSynScanner::portScanned, this, &MainWindow::updateScanResult);
connect(thread, &QThread::finished, scanner, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
thread->start();
}
void MainWindow::updateScanResult(quint16 port, bool isOpen) {
QString result = QString("Port %1 is %2\n").arg(port).arg(isOpen ? "open" : "closed");
ui->resultTextEdit->append(result);
}
3. 更新 UI 设计
确保你的 mainwindow.ui
文件包含以下控件:
QLineEdit
:用于输入目标IP地址,命名为ipLineEdit
。QSpinBox
:用于输入起始端口,命名为startPortSpinBox
。QSpinBox
:用于输入结束端口,命名为endPortSpinBox
。QPushButton
:用于开始扫描,命名为scanButton
。QTextEdit
:用于显示扫描结果,命名为resultTextEdit
。
4. 编译和运行
编译并运行你的Qt项目。程序将显示一个简单的界面,用户可以输入目标IP地址、起始端口和结束端口,并开始扫描。扫描结果将实时显示在界面上,指示端口是开放、关闭还是被过滤。
注意事项
- 权限问题:在Linux系统上,使用原始套接字需要root权限。
- 合法性:确保你只在授权的网络上进行此类操作。
- 校验和计算:上述代码中的校验和计算是简化的,实际应用中需要正确计算IP和TCP校验和。
- 性能:为了提高性能,可以考虑并行化扫描过程,例如使用多个线程或异步IO。
通过这种方式,你可以在Qt中实现一个带有可视化界面的TCP SYN扫描器,并且能够检查端口是否开放。