Skip to content

Instantly share code, notes, and snippets.

@SWORDIntel
Last active February 21, 2025 03:54
Show Gist options
  • Save SWORDIntel/bdefd06fe252b10964b8cfb66d9a3e44 to your computer and use it in GitHub Desktop.
Save SWORDIntel/bdefd06fe252b10964b8cfb66d9a3e44 to your computer and use it in GitHub Desktop.
Card Sort
#!/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