Last active
February 21, 2025 03:54
-
-
Save SWORDIntel/bdefd06fe252b10964b8cfb66d9a3e44 to your computer and use it in GitHub Desktop.
Card Sort
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import os | |
import sys | |
import socket | |
import datetime | |
import subprocess | |
import tkinter as tk | |
from tkinter import filedialog | |
import pandas as pd | |
import ipaddress | |
def is_valid_ip(ip_str): | |
"""Return True if ip_str is a valid IPv4 or IPv6 address.""" | |
try: | |
ipaddress.ip_address(ip_str) | |
return True | |
except ValueError: | |
return False | |
def reverse_lookup(ip): | |
"""Perform reverse DNS lookup on the given IP and return the hostname or a failure message.""" | |
try: | |
print(f"[*] Performing reverse lookup for IP: {ip}") | |
hostname, _, _ = socket.gethostbyaddr(ip) | |
print(f"[+] Reverse lookup successful: {ip} -> {hostname}") | |
return hostname | |
except Exception as e: | |
print(f"[-] Reverse lookup failed for IP {ip}: {e}") | |
return "No PTR record" | |
def run_nmap(ip): | |
""" | |
Run an nmap scan on the given IP using a SYN scan with host discovery disabled. | |
Returns the raw output from nmap as a string. | |
""" | |
command = ["nmap", "-sS", "-Pn", "-n", ip] | |
print(f"[*] Running nmap scan on {ip} with command: {' '.join(command)}") | |
try: | |
result = subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True) | |
print(f"[+] nmap scan complete for {ip}") | |
return result.strip() | |
except Exception as e: | |
print(f"[-] nmap scan failed for {ip}: {e}") | |
return f"Error: {e}" | |
def select_input_file(): | |
"""Prompt the user to select the input text file via a file selection dialog.""" | |
root = tk.Tk() | |
root.withdraw() # Hide the main Tk window. | |
print("[*] Please select the input .txt file.") | |
file_path = filedialog.askopenfilename(filetypes=[("Text files", "*.txt")]) | |
if not file_path: | |
print("[-] No file selected. Exiting.") | |
sys.exit(1) | |
print(f"[+] Selected file: {file_path}") | |
return file_path | |
def parse_pipe_delimited_file(file_path): | |
""" | |
Reads a file where each line is pipe-delimited. | |
Generates generic headers (Column 1, Column 2, …) based on the first line. | |
For each record: | |
- Adds a "Line Number" column. | |
- Scans all fields for a valid IP; if one is found, performs a reverse lookup. | |
- Adds a "Reverse Lookup" column with the result. | |
Returns a tuple: (header, list_of_records). | |
""" | |
records = [] | |
print(f"[*] Reading file: {file_path}") | |
with open(file_path, "r") as f: | |
lines = [line.strip() for line in f if line.strip()] | |
print(f"[*] Total non-empty lines found: {len(lines)}") | |
if not lines: | |
print("[-] File is empty. Exiting.") | |
sys.exit(1) | |
# Generate header based on number of fields in the first line. | |
first_line_fields = lines[0].split("|") | |
num_fields = len(first_line_fields) | |
header_fields = [f"Column {i+1}" for i in range(num_fields)] | |
# Final header: add "Line Number" at beginning and "Reverse Lookup" at end. | |
final_header = ["Line Number"] + header_fields + ["Reverse Lookup"] | |
print(f"[*] Generated header columns: {final_header}") | |
# Process each line. | |
for idx, line in enumerate(lines, start=1): | |
print(f"\n[*] Processing line {idx}: {line}") | |
fields = [field.strip() for field in line.split("|")] | |
# Ensure record has the same number of fields; pad if needed. | |
if len(fields) < num_fields: | |
print(f"[*] Line {idx} has fewer fields than expected ({len(fields)} < {num_fields}). Padding with empty strings.") | |
fields.extend([""] * (num_fields - len(fields))) | |
elif len(fields) > num_fields: | |
print(f"[*] Line {idx} has more fields than expected ({len(fields)} > {num_fields}).") | |
# Look for the first valid IP among the fields. | |
valid_ip = None | |
for field in fields: | |
if is_valid_ip(field): | |
valid_ip = field | |
break | |
if valid_ip: | |
print(f"[*] Valid IP detected in line {idx}: {valid_ip}") | |
rev_result = reverse_lookup(valid_ip) | |
else: | |
print(f"[-] No valid IP found in line {idx}.") | |
valid_ip = "No valid IP" | |
rev_result = "No valid IP" | |
# Construct the record with line number, all fields, and the reverse lookup result. | |
record = [idx] + fields + [rev_result] | |
print(f"[+] Processed record for line {idx}: {record}") | |
records.append(record) | |
return final_header, records | |
def run_nmap_for_records(df): | |
""" | |
For each record in the DataFrame, scan for a valid IP (by checking each field until one is found). | |
If a valid IP is found, run an nmap scan and record the output. | |
Returns a new DataFrame with columns: 'Line Number', 'IP', 'Nmap Scan'. | |
""" | |
nmap_results = [] | |
# Assuming that the valid IP was determined during parsing, we can search for it. | |
# We will look at all fields (ignoring "Line Number" and "Reverse Lookup") to find a valid IP. | |
data_columns = df.columns[1:-1] # all data columns except line number and reverse lookup. | |
print(f"[*] Running nmap scans for each record by scanning fields: {list(data_columns)}") | |
for index, row in df.iterrows(): | |
line_num = row["Line Number"] | |
found_ip = None | |
for col in data_columns: | |
candidate = row[col] | |
if is_valid_ip(str(candidate)): | |
found_ip = str(candidate) | |
break | |
if found_ip: | |
print(f"\n[*] Running nmap for line {line_num}, IP: {found_ip}") | |
scan_output = run_nmap(found_ip) | |
else: | |
print(f"[-] No valid IP found for line {line_num}. Skipping nmap scan.") | |
found_ip = "No valid IP" | |
scan_output = "No valid IP, skipped nmap scan" | |
nmap_results.append({"Line Number": line_num, "IP": found_ip, "Nmap Scan": scan_output}) | |
return pd.DataFrame(nmap_results) | |
def write_to_excel(header, records, input_file_path): | |
""" | |
Writes the processed data to an Excel workbook with three worksheets: | |
- "All Data": all processed records. | |
- "Sorted Data": records sorted by 'State' and 'Country' if those columns exist (or a copy otherwise). | |
- "Nmap Scan": containing nmap scan results for each valid IP. | |
The output file is named after the input file's base name with the current date appended. | |
""" | |
base_name = os.path.splitext(os.path.basename(input_file_path))[0] | |
date_str = datetime.datetime.now().strftime("%Y%m%d") | |
output_file = f"{base_name}_{date_str}.xlsx" | |
print(f"[*] Generating output file: {output_file}") | |
# Create DataFrame for all data. | |
df_all = pd.DataFrame(records, columns=header) | |
print(f"[*] Created DataFrame for All Data with {len(df_all)} records.") | |
# Attempt to sort by 'State' and 'Country' if those columns exist. | |
state_col = None | |
country_col = None | |
for col in df_all.columns: | |
if col.lower() == "state": | |
state_col = col | |
if col.lower() == "country": | |
country_col = col | |
if state_col and country_col: | |
print("[*] Found 'State' and 'Country' columns. Sorting records accordingly.") | |
df_sorted = df_all.sort_values(by=[state_col, country_col]) | |
else: | |
print("[-] 'State' and/or 'Country' columns not found. 'Sorted Data' will mirror 'All Data'.") | |
df_sorted = df_all.copy() | |
# Run nmap scan on each record. | |
print("[*] Starting nmap scans for valid IPs in all records...") | |
df_nmap = run_nmap_for_records(df_all) | |
print(f"[+] Completed nmap scans for {len(df_nmap)} records.") | |
# Write to Excel workbook with three sheets. | |
print(f"[*] Writing data to Excel workbook: {output_file}") | |
with pd.ExcelWriter(output_file, engine="openpyxl") as writer: | |
df_all.to_excel(writer, sheet_name="All Data", index=False) | |
df_sorted.to_excel(writer, sheet_name="Sorted Data", index=False) | |
df_nmap.to_excel(writer, sheet_name="Nmap Scan", index=False) | |
print(f"[+] Data successfully written to {output_file}") | |
def main(): | |
print("[*] Starting processing script.") | |
input_file = select_input_file() | |
header, records = parse_pipe_delimited_file(input_file) | |
write_to_excel(header, records, input_file) | |
print("[*] Script completed successfully.") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment