Full security platform with web dashboard, 16 Flask blueprints, 26 modules, autonomous AI agent, WebUSB hardware support, and Archon Android companion app. Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
418 lines
14 KiB
Python
418 lines
14 KiB
Python
"""
|
|
AUTARCH Analyze Module
|
|
Forensics and analysis tools
|
|
|
|
File analysis, hash generation, string extraction, and more.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import hashlib
|
|
import re
|
|
try:
|
|
import magic
|
|
except ImportError:
|
|
magic = None
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# Module metadata
|
|
DESCRIPTION = "Forensics & file analysis tools"
|
|
AUTHOR = "darkHal"
|
|
VERSION = "1.0"
|
|
CATEGORY = "analyze"
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
from core.banner import Colors, clear_screen, display_banner
|
|
|
|
|
|
class Analyzer:
|
|
"""Forensics and analysis tools."""
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def print_status(self, message: str, status: str = "info"):
|
|
colors = {"info": Colors.CYAN, "success": Colors.GREEN, "warning": Colors.YELLOW, "error": Colors.RED}
|
|
symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"}
|
|
print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}")
|
|
|
|
def run_cmd(self, cmd: str) -> tuple:
|
|
try:
|
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60)
|
|
return result.returncode == 0, result.stdout.strip()
|
|
except:
|
|
return False, ""
|
|
|
|
def get_file_hashes(self, filepath: str) -> dict:
|
|
"""Calculate various hashes for a file."""
|
|
p = Path(filepath)
|
|
if not p.exists() or not p.is_file():
|
|
return {}
|
|
|
|
hashes = {}
|
|
with open(p, 'rb') as f:
|
|
content = f.read()
|
|
hashes['md5'] = hashlib.md5(content).hexdigest()
|
|
hashes['sha1'] = hashlib.sha1(content).hexdigest()
|
|
hashes['sha256'] = hashlib.sha256(content).hexdigest()
|
|
|
|
return hashes
|
|
|
|
def analyze_file(self):
|
|
"""Comprehensive file analysis."""
|
|
print(f"\n{Colors.BOLD}File Analysis{Colors.RESET}")
|
|
filepath = input(f"{Colors.WHITE}Enter file path: {Colors.RESET}").strip()
|
|
|
|
if not filepath:
|
|
return
|
|
|
|
p = Path(filepath).expanduser()
|
|
if not p.exists():
|
|
self.print_status(f"File not found: {filepath}", "error")
|
|
return
|
|
|
|
print(f"\n{Colors.CYAN}{'─' * 50}{Colors.RESET}")
|
|
print(f"{Colors.BOLD}File: {p.name}{Colors.RESET}")
|
|
print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n")
|
|
|
|
# Basic info
|
|
stat = p.stat()
|
|
print(f"{Colors.CYAN}Basic Info:{Colors.RESET}")
|
|
print(f" Path: {p.absolute()}")
|
|
print(f" Size: {stat.st_size:,} bytes")
|
|
print(f" Modified: {datetime.fromtimestamp(stat.st_mtime)}")
|
|
print(f" Created: {datetime.fromtimestamp(stat.st_ctime)}")
|
|
print(f" Mode: {oct(stat.st_mode)}")
|
|
|
|
# File type
|
|
print(f"\n{Colors.CYAN}File Type:{Colors.RESET}")
|
|
try:
|
|
file_magic = magic.Magic(mime=True)
|
|
mime_type = file_magic.from_file(str(p))
|
|
print(f" MIME: {mime_type}")
|
|
|
|
file_magic = magic.Magic()
|
|
file_desc = file_magic.from_file(str(p))
|
|
print(f" Type: {file_desc}")
|
|
except:
|
|
success, output = self.run_cmd(f"file '{p}'")
|
|
if success:
|
|
print(f" Type: {output.split(':', 1)[-1].strip()}")
|
|
|
|
# Hashes
|
|
print(f"\n{Colors.CYAN}Hashes:{Colors.RESET}")
|
|
hashes = self.get_file_hashes(str(p))
|
|
for algo, value in hashes.items():
|
|
print(f" {algo.upper():8} {value}")
|
|
|
|
# Check if executable
|
|
if p.suffix in ['.exe', '.dll', '.so', '.elf', ''] or stat.st_mode & 0o111:
|
|
self.analyze_executable(str(p))
|
|
|
|
def analyze_executable(self, filepath: str):
|
|
"""Additional analysis for executables."""
|
|
print(f"\n{Colors.CYAN}Executable Analysis:{Colors.RESET}")
|
|
|
|
# Strings
|
|
success, output = self.run_cmd(f"strings '{filepath}' 2>/dev/null | head -50")
|
|
if success and output:
|
|
# Look for interesting strings
|
|
interesting = []
|
|
patterns = [
|
|
r'https?://[^\s]+', # URLs
|
|
r'\d+\.\d+\.\d+\.\d+', # IPs
|
|
r'password|passwd|secret|key|token', # Credentials
|
|
r'/bin/sh|/bin/bash|cmd\.exe', # Shells
|
|
]
|
|
for line in output.split('\n'):
|
|
for pattern in patterns:
|
|
if re.search(pattern, line, re.I):
|
|
interesting.append(line.strip())
|
|
break
|
|
|
|
if interesting:
|
|
print(f" {Colors.YELLOW}Interesting strings found:{Colors.RESET}")
|
|
for s in interesting[:10]:
|
|
print(f" {s[:80]}")
|
|
|
|
# Check for packing
|
|
success, output = self.run_cmd(f"readelf -h '{filepath}' 2>/dev/null")
|
|
if success:
|
|
if 'Entry point' in output:
|
|
print(f" ELF executable detected")
|
|
|
|
def extract_strings(self):
|
|
"""Extract strings from file."""
|
|
print(f"\n{Colors.BOLD}String Extraction{Colors.RESET}")
|
|
filepath = input(f"{Colors.WHITE}Enter file path: {Colors.RESET}").strip()
|
|
|
|
if not filepath:
|
|
return
|
|
|
|
p = Path(filepath).expanduser()
|
|
if not p.exists():
|
|
self.print_status(f"File not found", "error")
|
|
return
|
|
|
|
min_len = input(f"{Colors.WHITE}Minimum string length [4]: {Colors.RESET}").strip() or "4"
|
|
|
|
print(f"\n{Colors.CYAN}Extracting strings...{Colors.RESET}\n")
|
|
|
|
success, output = self.run_cmd(f"strings -n {min_len} '{p}' 2>/dev/null")
|
|
if success:
|
|
lines = output.split('\n')
|
|
print(f"Found {len(lines)} strings\n")
|
|
|
|
# Categorize
|
|
urls = [l for l in lines if re.search(r'https?://', l)]
|
|
ips = [l for l in lines if re.search(r'\b\d+\.\d+\.\d+\.\d+\b', l)]
|
|
paths = [l for l in lines if re.search(r'^/[a-z]', l, re.I)]
|
|
emails = [l for l in lines if re.search(r'[\w.-]+@[\w.-]+', l)]
|
|
|
|
if urls:
|
|
print(f"{Colors.CYAN}URLs ({len(urls)}):{Colors.RESET}")
|
|
for u in urls[:10]:
|
|
print(f" {u}")
|
|
|
|
if ips:
|
|
print(f"\n{Colors.CYAN}IP Addresses ({len(ips)}):{Colors.RESET}")
|
|
for ip in ips[:10]:
|
|
print(f" {ip}")
|
|
|
|
if emails:
|
|
print(f"\n{Colors.CYAN}Emails ({len(emails)}):{Colors.RESET}")
|
|
for e in emails[:10]:
|
|
print(f" {e}")
|
|
|
|
if paths:
|
|
print(f"\n{Colors.CYAN}Paths ({len(paths)}):{Colors.RESET}")
|
|
for p in paths[:10]:
|
|
print(f" {p}")
|
|
|
|
# Save option
|
|
save = input(f"\n{Colors.WHITE}Save all strings to file? (y/n): {Colors.RESET}").strip().lower()
|
|
if save == 'y':
|
|
outfile = f"{p.stem}_strings.txt"
|
|
with open(outfile, 'w') as f:
|
|
f.write(output)
|
|
self.print_status(f"Saved to {outfile}", "success")
|
|
|
|
def hash_lookup(self):
|
|
"""Look up hash in threat intel."""
|
|
print(f"\n{Colors.BOLD}Hash Lookup{Colors.RESET}")
|
|
hash_input = input(f"{Colors.WHITE}Enter hash (MD5/SHA1/SHA256): {Colors.RESET}").strip()
|
|
|
|
if not hash_input:
|
|
return
|
|
|
|
# Determine hash type
|
|
hash_len = len(hash_input)
|
|
if hash_len == 32:
|
|
hash_type = "MD5"
|
|
elif hash_len == 40:
|
|
hash_type = "SHA1"
|
|
elif hash_len == 64:
|
|
hash_type = "SHA256"
|
|
else:
|
|
self.print_status("Invalid hash length", "error")
|
|
return
|
|
|
|
print(f"\n{Colors.CYAN}Hash Type: {hash_type}{Colors.RESET}")
|
|
print(f"{Colors.CYAN}Hash: {hash_input}{Colors.RESET}\n")
|
|
|
|
# VirusTotal URL
|
|
print(f"{Colors.DIM}VirusTotal: https://www.virustotal.com/gui/file/{hash_input}{Colors.RESET}")
|
|
print(f"{Colors.DIM}Hybrid Analysis: https://www.hybrid-analysis.com/search?query={hash_input}{Colors.RESET}")
|
|
|
|
def analyze_log(self):
|
|
"""Analyze log files for anomalies."""
|
|
print(f"\n{Colors.BOLD}Log Analysis{Colors.RESET}")
|
|
print(f"{Colors.DIM}Common logs: /var/log/auth.log, /var/log/syslog, /var/log/apache2/access.log{Colors.RESET}\n")
|
|
|
|
filepath = input(f"{Colors.WHITE}Enter log file path: {Colors.RESET}").strip()
|
|
if not filepath:
|
|
return
|
|
|
|
p = Path(filepath).expanduser()
|
|
if not p.exists():
|
|
self.print_status(f"File not found", "error")
|
|
return
|
|
|
|
print(f"\n{Colors.CYAN}Analyzing {p.name}...{Colors.RESET}\n")
|
|
|
|
# Read log
|
|
try:
|
|
with open(p, 'r', errors='ignore') as f:
|
|
lines = f.readlines()
|
|
except Exception as e:
|
|
self.print_status(f"Error reading file: {e}", "error")
|
|
return
|
|
|
|
print(f"Total lines: {len(lines)}")
|
|
|
|
# Extract IPs
|
|
all_ips = []
|
|
for line in lines:
|
|
ips = re.findall(r'\b(\d+\.\d+\.\d+\.\d+)\b', line)
|
|
all_ips.extend(ips)
|
|
|
|
if all_ips:
|
|
from collections import Counter
|
|
ip_counts = Counter(all_ips)
|
|
print(f"\n{Colors.CYAN}Top IP Addresses:{Colors.RESET}")
|
|
for ip, count in ip_counts.most_common(10):
|
|
print(f" {ip:20} {count:>6} occurrences")
|
|
|
|
# Look for error patterns
|
|
errors = [l for l in lines if re.search(r'error|fail|denied|invalid', l, re.I)]
|
|
if errors:
|
|
print(f"\n{Colors.YELLOW}Error/Failure entries: {len(errors)}{Colors.RESET}")
|
|
print(f"{Colors.DIM}Recent errors:{Colors.RESET}")
|
|
for e in errors[-5:]:
|
|
print(f" {e.strip()[:100]}")
|
|
|
|
# Timestamps
|
|
timestamps = []
|
|
for line in lines:
|
|
match = re.search(r'(\w{3}\s+\d+\s+\d+:\d+:\d+)', line)
|
|
if match:
|
|
timestamps.append(match.group(1))
|
|
|
|
if timestamps:
|
|
print(f"\n{Colors.CYAN}Time Range:{Colors.RESET}")
|
|
print(f" First: {timestamps[0]}")
|
|
print(f" Last: {timestamps[-1]}")
|
|
|
|
def hex_dump(self):
|
|
"""Create hex dump of file."""
|
|
print(f"\n{Colors.BOLD}Hex Dump{Colors.RESET}")
|
|
filepath = input(f"{Colors.WHITE}Enter file path: {Colors.RESET}").strip()
|
|
|
|
if not filepath:
|
|
return
|
|
|
|
p = Path(filepath).expanduser()
|
|
if not p.exists():
|
|
self.print_status(f"File not found", "error")
|
|
return
|
|
|
|
offset = input(f"{Colors.WHITE}Start offset [0]: {Colors.RESET}").strip() or "0"
|
|
length = input(f"{Colors.WHITE}Length [256]: {Colors.RESET}").strip() or "256"
|
|
|
|
try:
|
|
offset = int(offset, 0) # Support hex input
|
|
length = int(length, 0)
|
|
except:
|
|
self.print_status("Invalid offset/length", "error")
|
|
return
|
|
|
|
print(f"\n{Colors.CYAN}Hex dump of {p.name} (offset={hex(offset)}, length={length}):{Colors.RESET}\n")
|
|
|
|
with open(p, 'rb') as f:
|
|
f.seek(offset)
|
|
data = f.read(length)
|
|
|
|
# Format hex dump
|
|
for i in range(0, len(data), 16):
|
|
chunk = data[i:i+16]
|
|
hex_part = ' '.join(f'{b:02x}' for b in chunk)
|
|
ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk)
|
|
print(f" {offset+i:08x} {hex_part:<48} {ascii_part}")
|
|
|
|
def compare_files(self):
|
|
"""Compare two files."""
|
|
print(f"\n{Colors.BOLD}File Comparison{Colors.RESET}")
|
|
|
|
file1 = input(f"{Colors.WHITE}First file: {Colors.RESET}").strip()
|
|
file2 = input(f"{Colors.WHITE}Second file: {Colors.RESET}").strip()
|
|
|
|
if not file1 or not file2:
|
|
return
|
|
|
|
p1 = Path(file1).expanduser()
|
|
p2 = Path(file2).expanduser()
|
|
|
|
if not p1.exists() or not p2.exists():
|
|
self.print_status("One or both files not found", "error")
|
|
return
|
|
|
|
print(f"\n{Colors.CYAN}Comparing files...{Colors.RESET}\n")
|
|
|
|
# Size comparison
|
|
s1, s2 = p1.stat().st_size, p2.stat().st_size
|
|
print(f"File 1 size: {s1:,} bytes")
|
|
print(f"File 2 size: {s2:,} bytes")
|
|
print(f"Difference: {abs(s1-s2):,} bytes")
|
|
|
|
# Hash comparison
|
|
h1 = self.get_file_hashes(str(p1))
|
|
h2 = self.get_file_hashes(str(p2))
|
|
|
|
print(f"\n{Colors.CYAN}Hash Comparison:{Colors.RESET}")
|
|
for algo in ['md5', 'sha256']:
|
|
match = h1.get(algo) == h2.get(algo)
|
|
status = f"{Colors.GREEN}MATCH{Colors.RESET}" if match else f"{Colors.RED}DIFFERENT{Colors.RESET}"
|
|
print(f" {algo.upper()}: {status}")
|
|
|
|
if h1.get('sha256') != h2.get('sha256'):
|
|
# Show diff if text files
|
|
success, output = self.run_cmd(f"diff '{p1}' '{p2}' 2>/dev/null | head -30")
|
|
if success and output:
|
|
print(f"\n{Colors.CYAN}Differences (first 30 lines):{Colors.RESET}")
|
|
print(output)
|
|
|
|
def show_menu(self):
|
|
clear_screen()
|
|
display_banner()
|
|
|
|
print(f"{Colors.CYAN}{Colors.BOLD} Analysis & Forensics{Colors.RESET}")
|
|
print(f"{Colors.DIM} File analysis and forensics tools{Colors.RESET}")
|
|
print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}")
|
|
print()
|
|
print(f" {Colors.CYAN}[1]{Colors.RESET} Analyze File")
|
|
print(f" {Colors.CYAN}[2]{Colors.RESET} Extract Strings")
|
|
print(f" {Colors.CYAN}[3]{Colors.RESET} Hash Lookup")
|
|
print(f" {Colors.CYAN}[4]{Colors.RESET} Analyze Log")
|
|
print(f" {Colors.CYAN}[5]{Colors.RESET} Hex Dump")
|
|
print(f" {Colors.CYAN}[6]{Colors.RESET} Compare Files")
|
|
print()
|
|
print(f" {Colors.DIM}[0]{Colors.RESET} Back")
|
|
print()
|
|
|
|
def run(self):
|
|
while True:
|
|
self.show_menu()
|
|
try:
|
|
choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip()
|
|
|
|
if choice == "0":
|
|
break
|
|
elif choice == "1":
|
|
self.analyze_file()
|
|
elif choice == "2":
|
|
self.extract_strings()
|
|
elif choice == "3":
|
|
self.hash_lookup()
|
|
elif choice == "4":
|
|
self.analyze_log()
|
|
elif choice == "5":
|
|
self.hex_dump()
|
|
elif choice == "6":
|
|
self.compare_files()
|
|
|
|
if choice in ["1", "2", "3", "4", "5", "6"]:
|
|
input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
|
|
|
|
except (EOFError, KeyboardInterrupt):
|
|
break
|
|
|
|
|
|
def run():
|
|
Analyzer().run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|