Files
T
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

242 lines
8.5 KiB
Python

#!/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 <packed_binary.exe>")