Autarch/modules/analyze.py

418 lines
14 KiB
Python
Raw Normal View History

"""
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()