diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ff6b1c..2647919 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.9 # Replace with your desired Python version + python-version: 3.9 - name: Checkout repository uses: actions/checkout@v2 @@ -47,5 +47,5 @@ jobs: with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: dist/main - asset_name: scanman-linux # Replace with your desired executable name + asset_name: scanman-linux asset_content_type: application/octet-stream diff --git a/README.md b/README.md index 7ed271c..11c61e4 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ GUI Interface for vulnerability scanning # TODO - Progressbar -- Pakke sammen program til kjørbar fil -- Checkbox for å velge om tcp eller udp scan +- Flytte TCP og UDP scan knapper nærmere hverandre - Custom top 10, 100 & 1000 port - Endre navn på results faner når en ny scan blir kjørt - Ha navn som source IP? @@ -14,4 +13,6 @@ GUI Interface for vulnerability scanning - Spørsmål om man vi lagre/slette outout filer når man lokker programmet - Kan være i preferences - Fjerne stjerne (*) import -- Installere depedencies (nmap) \ No newline at end of file +- Installere depedencies (nmap) +- Default satt scan rate + - Hvis ikke rate er satt, så fjern --min-rate parameteret diff --git a/main.py b/main.py index 68c3f55..a9e6b6c 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,7 @@ import sys import subprocess +# import ipaddress +# import socket from datetime import datetime from PyQt6.QtWidgets import * @@ -13,7 +15,6 @@ def __init__(self): self.setCentralWidget(central_widget) self.setGeometry(0, 0, 850, 500) - # Create the layout for the central widget self.dialogLayout = QVBoxLayout(central_widget) @@ -30,8 +31,20 @@ def __init__(self): formLayout.addRow("IP address:", self.ip_address_input) formLayout.addRow("Ports:", self.ports_input) formLayout.addRow("Rate:", self.rate_input) + self.rate_input.setPlaceholderText("Optional") self.dialogLayout.addLayout(formLayout) + # Create checkboxes for either a TCP or UDP scan. Only one can be selected at a time, and the default is TCP. Make the boxes right next to each other. + self.tcp_checkbox = QCheckBox("TCP") + self.udp_checkbox = QCheckBox("UDP") + self.tcp_checkbox.setChecked(True) # Set TCP default + self.tcp_checkbox.toggled.connect(lambda: self.udp_checkbox.setChecked(not self.tcp_checkbox.isChecked())) # Toggle the other checkbox when this one is clicked + self.udp_checkbox.toggled.connect(lambda: self.tcp_checkbox.setChecked(not self.udp_checkbox.isChecked())) # The same for the other checkbox + checkbox_layout = QHBoxLayout() + checkbox_layout.addWidget(self.tcp_checkbox) + checkbox_layout.addWidget(self.udp_checkbox) + self.dialogLayout.addLayout(checkbox_layout) + buttons = QDialogButtonBox() buttons.setStandardButtons( QDialogButtonBox.StandardButton.Cancel @@ -58,13 +71,14 @@ def buttonOK_clicked(self): ip_address = self.ip_address_input.text() ports = self.ports_input.text() rate = self.rate_input.text() + protocol = "TCP" if self.tcp_checkbox.isChecked() else "UDP" # Validate input (you may want to add further validation logic) - if not ip_address or not ports or not rate: - QMessageBox.warning(self, "Input Error", "Please fill in all the fields.") + if not ip_address or not ports: + QMessageBox.warning(self, "Input Error", "Please fill in IP and ports.") return - + try: # Construct the nmap command @@ -72,18 +86,22 @@ def buttonOK_clicked(self): now = datetime.now() dt_string = now.strftime("%d-%m-%Y-%H-%M-%S") filename = "scan_output-" + dt_string + ".txt" - nmap_cmd = ["nmap", "-p", ports, "--min-rate", rate, "--stats-every", "1s", "-oG", filename, ip_address] + + if rate: + if protocol == "TCP": nmap_cmd = ["nmap", "-sV", "-p", ports, "--min-rate", rate, "--stats-every", "1s", "-oG", filename, ip_address] + else: nmap_cmd = ["nmap", "-sU", "-p", ports, "--min-rate", rate, "--stats-every", "1s", "-oG", filename, ip_address] + else: + if protocol == "TCP": nmap_cmd = ["nmap", "-sV", "-p", ports, "--stats-every", "1s", "-oG", filename, ip_address] + else: nmap_cmd = ["nmap", "-sU", "-p", ports, "--stats-every", "1s", "-oG", filename, ip_address] result = subprocess.run(nmap_cmd, capture_output=True, text=True) - # Open a new tab with the contents of the output file with open(filename, "r") as file: output_data = file.read() - self.create_output_tab(output_data) + self.create_output_tab(output_data, ip_address) #QMessageBox.information(self, "Scan complete", "The scan is complete.") - except subprocess.CalledProcessError as e: @@ -94,7 +112,7 @@ def buttonOK_clicked(self): - def create_output_tab(self, content): + def create_output_tab(self, content, ip_address): # Create a new tab and add a text area to display the content results_tab = QWidget() results_layout = QVBoxLayout() @@ -104,20 +122,41 @@ def create_output_tab(self, content): results_tab.setLayout(results_layout) # Add the results tab to the tab widget - self.tab_widget.addTab(results_tab, "Results") + self.tab_widget.addTab(results_tab, ip_address) def buttonCancel_clicked(self): + pass + + + + + # if exit button is clicked, ask if user wants to exit + def closeEvent(self, event): message = QMessageBox() message.setMinimumSize(700, 700) message.setWindowTitle("Scanman") - message.setText("Are you sure you want to exit?") - message.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) - message.setDefaultButton(QMessageBox.StandardButton.No) - message.setIcon(QMessageBox.Icon.Warning) - x = message.exec() - if x == QMessageBox.StandardButton.Yes: - self.close() + + + # if a result tab is open, ask if user wants to save the results file, then exit + if self.tab_widget.count() > 0: + message.setText("Would you like to save the results?") + message.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + message.setDefaultButton(QMessageBox.StandardButton.Yes) + message.setIcon(QMessageBox.Icon.Warning) + x = message.exec() + if x == QMessageBox.StandardButton.Yes: + self.save_results() + event.accept() + else: + event.accept() + # delete the results file + # os.remove("scan_output.txt") + + + def save_results(self): + pass + def _createMenu(self): menu = self.menuBar().addMenu("&Menu") @@ -150,6 +189,5 @@ def _createStatusBar(self): if __name__ == "__main__": app = QApplication([]) window = Window() - window.show() sys.exit(app.exec()) diff --git a/requirements.txt b/requirements.txt index c38347f..13a7508 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ PyQt6 -python-nmap \ No newline at end of file +python-nmap +ipaddress \ No newline at end of file