#!/usr/bin/env python3 """Packed malware analysis agent for UPX and generic packer detection and unpacking.""" import subprocess import os import sys import hashlib import math from collections import Counter try: import pefile HAS_PEFILE = True except ImportError: HAS_PEFILE = False def compute_hashes(filepath): """Compute file hashes.""" md5 = hashlib.md5() sha256 = hashlib.sha256() with open(filepath, "rb") as f: for chunk in iter(lambda: f.read(65536), b""): md5.update(chunk) sha256.update(chunk) return {"md5": md5.hexdigest(), "sha256": sha256.hexdigest()} def calculate_entropy(data): """Calculate Shannon entropy of binary data.""" if not data: return 0.0 counter = Counter(data) length = len(data) return round(-sum((c / length) * math.log2(c / length) for c in counter.values()), 4) def detect_upx(filepath): """Check for UPX packing signatures in the binary.""" indicators = [] with open(filepath, "rb") as f: data = f.read() if b"UPX!" in data: indicators.append("UPX! magic string found in binary") if b"UPX0" in data: indicators.append("UPX0 section name found") if b"UPX1" in data: indicators.append("UPX1 section name found") if b"UPX2" in data: indicators.append("UPX2 section name found") # Check for corrupted/modified UPX headers upx_pos = data.find(b"UPX!") if upx_pos != -1: # UPX version info follows the magic if upx_pos + 24 <= len(data): version_byte = data[upx_pos + 4] indicators.append(f"UPX version byte: 0x{version_byte:02X}") return indicators def detect_generic_packing(filepath): """Detect generic packing indicators using PE section analysis.""" if not HAS_PEFILE: return {"error": "pefile not installed: pip install pefile"} try: pe = pefile.PE(filepath) except pefile.PEFormatError: return {"error": "Not a valid PE file"} indicators = [] sections = [] high_entropy_count = 0 for section in pe.sections: name = section.Name.rstrip(b"\x00").decode("utf-8", errors="replace") entropy = section.get_entropy() raw_size = section.SizeOfRawData virtual_size = section.Misc_VirtualSize sections.append({ "name": name, "entropy": round(entropy, 4), "raw_size": raw_size, "virtual_size": virtual_size, "ratio": round(virtual_size / raw_size, 2) if raw_size > 0 else 0, }) if entropy > 7.0: high_entropy_count += 1 indicators.append(f"High entropy section: {name} ({entropy:.2f})") if virtual_size > raw_size * 5 and raw_size > 0: indicators.append(f"Suspicious size ratio in {name}: virtual/raw = {virtual_size/raw_size:.1f}x") imports = [] if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"): for entry in pe.DIRECTORY_ENTRY_IMPORT: dll_name = entry.dll.decode("utf-8", errors="replace") func_count = len(entry.imports) imports.append({"dll": dll_name, "functions": func_count}) total_imports = sum(i["functions"] for i in imports) if total_imports < 10: indicators.append(f"Very few imports ({total_imports}) - typical of packed binaries") # Check for LoadLibrary/GetProcAddress (runtime import resolution) import_names = [] if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"): for entry in pe.DIRECTORY_ENTRY_IMPORT: for imp in entry.imports: if imp.name: import_names.append(imp.name.decode("utf-8", errors="replace")) if "LoadLibraryA" in import_names and "GetProcAddress" in import_names: indicators.append("LoadLibraryA + GetProcAddress present (runtime import resolution)") pe.close() return { "sections": sections, "imports": imports, "total_imports": total_imports, "high_entropy_sections": high_entropy_count, "indicators": indicators, "likely_packed": high_entropy_count > 0 or total_imports < 10, } def unpack_upx(filepath, output_path=None): """Attempt to unpack a UPX-packed binary.""" if output_path is None: output_path = filepath + ".unpacked" # First try standard UPX decompression cmd = ["upx", "-d", "-o", output_path, filepath] result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: return True, "Standard UPX unpack succeeded", output_path # If standard fails, try fixing UPX headers return False, result.stderr.strip(), None def fix_upx_headers(filepath, output_path): """Attempt to fix corrupted UPX magic bytes for unpacking.""" with open(filepath, "rb") as f: data = bytearray(f.read()) # Look for known UPX section names that might be renamed modified = False # Common modifications: UPX0/UPX1 renamed to something else for i in range(len(data) - 3): # Look for section header pattern near typical PE section table location if data[i:i+3] in [b"UP0", b"UP1", b"UX0", b"UX1"]: # Might be modified UPX section name pass # Fix UPX! magic if corrupted for i in range(len(data) - 4): if data[i:i+2] == b"UX" and data[i+2:i+4] == b"!\x00": data[i:i+3] = b"UPX" modified = True if modified: with open(output_path, "wb") as f: f.write(data) return True return False def compare_packed_unpacked(packed_path, unpacked_path): """Compare packed vs unpacked binary properties.""" if not HAS_PEFILE: return {} comparison = {} for label, path in [("packed", packed_path), ("unpacked", unpacked_path)]: try: pe = pefile.PE(path) imports = 0 if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"): for entry in pe.DIRECTORY_ENTRY_IMPORT: imports += len(entry.imports) sections = len(pe.sections) pe.close() comparison[label] = { "size": os.path.getsize(path), "sections": sections, "imports": imports, "sha256": compute_hashes(path)["sha256"], } except Exception as e: comparison[label] = {"error": str(e)} return comparison if __name__ == "__main__": print("=" * 60) print("Packed Malware Analysis Agent") print("UPX detection, packer identification, automated unpacking") print("=" * 60) target = sys.argv[1] if len(sys.argv) > 1 else None if target and os.path.exists(target): print(f"\n[*] Analyzing: {target}") hashes = compute_hashes(target) print(f"[*] SHA-256: {hashes['sha256']}") print(f"[*] Size: {os.path.getsize(target)} bytes") print("\n--- UPX Signature Check ---") upx_indicators = detect_upx(target) for ind in upx_indicators: print(f" [!] {ind}") print("\n--- Generic Packing Analysis ---") packing = detect_generic_packing(target) if "error" not in packing: print(f" Likely packed: {packing['likely_packed']}") print(f" Total imports: {packing['total_imports']}") print(f" High entropy sections: {packing['high_entropy_sections']}") for ind in packing.get("indicators", []): print(f" [!] {ind}") print("\n Sections:") for s in packing.get("sections", []): flag = " [HIGH]" if s["entropy"] > 7.0 else "" print(f" {s['name']:10s} entropy={s['entropy']:.2f} " f"raw={s['raw_size']} virt={s['virtual_size']}{flag}") if upx_indicators: print("\n--- UPX Unpacking ---") success, msg, output = unpack_upx(target) if success: print(f" [OK] {msg}") print(f" [*] Unpacked file: {output}") print("\n--- Comparison ---") comp = compare_packed_unpacked(target, output) for label, data in comp.items(): if "error" not in data: print(f" {label}: size={data['size']}, " f"sections={data['sections']}, imports={data['imports']}") else: print(f" [FAIL] {msg}") print(" [*] Try fixing UPX headers or use dynamic unpacking with a debugger") else: print(f"\n[DEMO] Usage: python agent.py ")