Was macht Analyse verpackter Malware mit UPX-Entpacker?
Wann anwenden
- Die statische Analyse zeigt Abschnitte mit hoher Entropie und minimale Importe, was auf eine Verpackung der Binärdatei hindeutet.
- PEiD, Detect It Easy oder PEStudio identifizieren UPX oder einen anderen bekannten Packer.
- Die Importtabelle enthält nur LoadLibrary und GetProcAddress (Lauzeit-Importauflösung, typisch für verpackte Binärdateien).
- Sie müssen die ursprüngliche Binärdatei für eine ordnungsgemäße Disassemblierung und Dekompilierung in Ghidra oder IDA wiederherstellen.
- Die automatische UPX-Dekompression schlägt fehl, weil der Malware-Autor die UPX-Magiebytes oder -Header modifiziert hat.
Nicht anwenden bei benutzerdefinierten Packern, VM-basierten Schutzprogrammen (Themida, VMProtect) oder Proben, bei denen dynamisches Entpacken per Debugging besser geeignet ist.
Voraussetzungen
- UPX (Ultimate Packer for eXecutables) installiert (
apt install upx-ucloder Download von https://upx.github.io/) - Detect It Easy (DIE) zur Packer-Identifizierung
- Python 3.8+ mit der Bibliothek
pefilefür manuelle Header-Reparatur - x64dbg oder x32dbg für manuelles Entpacken, wenn automatisierte Tools fehlschlagen
- PE-bear oder CFF Explorer zur PE-Header-Inspektion und -Reparatur
- Isolierte Analyse-VM ohne Netzwerkanbindung
Arbeitsablauf
Schritt 1: Packer identifizieren
Bestimmen Sie, ob die Probe verpackt ist, und identifizieren Sie den Packer:
# Prüfung mit Detect It Easy
diec suspect.exe
# Prüfung mit UPX (Test ohne Entpacken)
upx -t suspect.exe
# Python-basierte Entropie- und Packererkennung
python3 << 'PYEOF'
import pefile
import math
pe = pefile.PE("suspect.exe")
print("Abschnittsanalyse:")
for section in pe.sections:
name = section.Name.decode().rstrip('\x00')
entropy = section.get_entropy()
raw = section.SizeOfRawData
virtual = section.Misc_VirtualSize
print(f" {name:8s} Entropie: {entropy:.2f} Raw: {raw:>8} Virtual: {virtual:>8}")
# Prüfung auf UPX-Abschnittsnamen
section_names = [s.Name.decode().rstrip('\x00') for s in pe.sections]
if 'UPX0' in section_names or 'UPX1' in section_names:
print("\n[!] UPX-Abschnittsnamen erkannt")
elif '.upx' in [s.lower() for s in section_names]:
print("\n[!] UPX-Varianten-Abschnittsnamen erkannt")
# Prüfung der Importanzahl (verpackte Binärdateien haben sehr wenige)
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
total_imports = sum(len(e.imports) for e in pe.DIRECTORY_ENTRY_IMPORT)
print(f"\nGesamtimporte: {total_imports}")
if total_imports < 10:
print("[!] Sehr wenige Importe - wahrscheinlich verpackt")
else:
print("\n[!] Kein Importverzeichnis - stark verpackt")
PYEOF
Schritt 2: Standard-UPX-Dekompression versuchen
Versuchen Sie die integrierte UPX-Dekompression:
# Standard-UPX-Dekomprimieren
upx -d suspect.exe -o unpacked.exe
# Falls UPX mit Fehler "not packed by UPX" fehlschlägt, sind die Header möglicherweise modifiziert
# Ausführliche Ausgabe zum Debuggen
upx -d suspect.exe -o unpacked.exe -v 2>&1
# Überprüfen der entpackten Datei
file unpacked.exe
diec unpacked.exe
Schritt 3: Modifizierte UPX-Header reparieren
Falls die Standard-Dekompression fehlschlägt, reparieren Sie manipulierte Magiebytes:
# Modifizierte UPX-Header reparieren
import struct
with open("suspect.exe", "rb") as f:
data = bytearray(f.read())
# UPX-Magiebytes: "UPX!" (0x55505821)
# Malware-Autoren modifizieren diese häufig, um automatisches Entpacken zu verhindern
# Suche nach modifizierten UPX-Signaturen
upx_magic = b"UPX!"
modified_patterns = [b"UPX0", b"UPX\x00", b"\x00PX!", b"UPx!"]
# Finden und Wiederherstellen von Abschnittsnamen
pe_offset = struct.unpack_from("<I", data, 0x3C)[0]
num_sections = struct.unpack_from("<H", data, pe_offset + 6)[0]
section_table_offset = pe_offset + 0x18 + struct.unpack_from("<H", data, pe_offset + 0x14)[0]
print(f"PE-Offset: 0x{pe_offset:X}")
print(f"Anzahl der Abschnitte: {num_sections}")
print(f"Abschnittstabellen-Offset: 0x{section_table_offset:X}")
for i in range(num_sections):
offset = section_table_offset + (i * 40)
name = data[offset:offset+8]
print(f"Abschnitt {i}: {name}")
# UPX-Magiebytes in der Binärdatei wiederherstellen
# Suche nach dem UPX-Header-Signaturort (normalerweise in der Nähe des Endes der verpackten Daten)
for i in range(len(data) - 4):
if data[i:i+3] == b"UPX" and data[i+3] != ord("!"):
print(f"Modifiziertes UPX-Magie bei Offset 0x{i:X} gefunden: {data[i:i+4]}")
data[i:i+4] = b"UPX!"
print(f"Wiederhergestellt zu: UPX!")
# Abschnittsnamen ebenfalls wiederherstellen, falls modifiziert
for i in range(num_sections):
offset = section_table_offset + (i * 40)
name = data[offset:offset+8].rstrip(b'\x00')
if name in [b"UPX0", b"UPX1", b"UPX2"]:
continue # Bereits korrekt
# Prüfung auf häufige Modifikationen
if name.startswith(b"UP") or name.startswith(b"ux"):
original = f"UPX{i}".encode().ljust(8, b'\x00')
data[offset:offset+8] = original
print(f"Abschnittsnamen bei Offset 0x{offset:X} wiederhergestellt zu {original}")
with open("suspect_fixed.exe", "wb") as f:
f.write(data)
print("\nReparierte Datei geschrieben. Erneuter Versuch: upx -d suspect_fixed.exe -o unpacked.exe")
Schritt 4: Manuelles Entpacken mit Debugger
Wenn das automatisierte Entpacken vollständig fehlschlägt, verwenden Sie dynamisches Entpacken:
Manuelles UPX-Entpacken mit x64dbg:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. Verpackte Probe in x64dbg laden
2. Bis zum Einstiegspunkt ausführen (System-Breakpoint, dann F9)
3. UPX-Entpackungs-Stub-Muster:
a. PUSHAD (speichert alle Register)
b. Dekompressionsschleife (verarbeitet verpackte Abschnitte)
c. Importauflösung (LoadLibrary/GetProcAddress-Aufrufe)
d. POPAD (stellt Register wieder her)
e. JMP zum OEP (Original Entry Point)
4. Hardware-Breakpoint auf ESP nach PUSHAD setzen:
- Nach PUSHAD: Rechtsklick auf ESP in den Registern -> Follow in Dump
- Hardware-Breakpoint auf Zugriff an [ESP]-Adresse setzen
- Ausführen (F9) - unterbricht bei POPAD vor dem JMP zum OEP
5. Schrittweise vorwärts gehen (F7/F8), bis Sie den JMP zum OEP erreichen
6. Beim OEP: Scylla-Plugin verwenden zum Dumpen und Reparieren der Importe:
- Plugins -> Scylla -> OEP = aktueller EIP
- "IAT Autosearch" anklicken -> "Get Imports"
- "Dump" anklicken, um entpackte Binärdatei zu speichern
- "Fix Dump" anklicken, um die Importtabelle zu reparieren
Schritt 5: Entpackte Binärdatei validieren
Überprüfen Sie, ob die entpackte Probe gültig und vollständig ist:
# Überprüfen, ob die entpackte PE gültig ist
python3 << 'PYEOF'
import pefile
pe = pefile.PE("unpacked.exe")
# Prüfung, ob Abschnitte normal sind
print("Entpackte Abschnittsanalyse:")
for section in pe.sections:
name = section.Name.decode().rstrip('\x00')
entropy = section.get_entropy()
print(f" {name:8s} Entropie: {entropy:.2f}")
# Überprüfen, ob Importe aufgelöst wurden
print(f"\nImportanzahl:")
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
for entry in pe.DIRECTORY_ENTRY_IMPORT:
dll = entry.dll.decode()
count = len(entry.imports)
print(f" {dll}: {count} Funktionen")
total = sum(len(e.imports) for e in pe.DIRECTORY_ENTRY_IMPORT)
print(f" Gesamt: {total} Importe")
# Dateigrößen vergleichen
import os
packed_size = os.path.getsize("suspect.exe")
unpacked_size = os.path.getsize("unpacked.exe")
print(f"\nVerpackt: {packed_size:>10} Bytes")
print(f"Entpackt: {unpacked_size:>10} Bytes")
print(f"Verhältnis: {unpacked_size/packed_size:.1f}x")
PYEOF
Schlüsselkonzepte
| Begriff | Definition |
|---|---|
| Packen | Komprimieren oder Verschlüsseln von ausführbarem Code, um die Dateigröße zu reduzieren und die statische Analyse zu erschweren; die Binärdatei enthält einen Entpackungs-Stub, der den Code zur Laufzeit wiederherstellt |
| UPX | Ultimate Packer for eXecutables; Open-Source-Programmpacker, der von Malware-Autoren häufig missbraucht wird, da er kostenlos und effektiv ist |
| Original Entry Point (OEP) | Die tatsächliche Startadresse des Malware-Codes vor dem Packen; der Entpackungs-Stub dekomprimiert den Code und springt dann zum OEP |
| Import-Rekonstruktion | Prozess des Wiederaufbaus der Import-Adresstabelle (IAT) nach dem Dumpen eines entpackten Prozesses aus dem Speicher mit Tools wie Scylla oder ImpRec |
| PUSHAD/POPAD | x86-Anweisungen, die alle Allzweckregister speichern/wiederherstellen; UPX verwendet dieses Muster, um den Registerzustand während des Entpackens zu bewahren |
| Abschnittsentropie | Zufälligkeitsmaß der Daten eines PE-Abschnitts; verpackte Abschnitte zeigen eine Entropie > 7.0, während normale Codeabschnitte durchschnittlich 5.0-6.5 aufweisen |
| Magiebytes | Signaturbytes in einer Datei, die ihr Format identifizieren; UPX verwendet "UPX!", das Malware-Autoren modifizieren, um die automatische Dekompression zu verhindern |
Werkzeuge und Systeme
- UPX: Open-Source-Programmpacker mit integrierter Dekompressionsfähigkeit für ordnungsgemäß verpackte Dateien
- Detect It Easy (DIE): Werkzeug zur Erkennung von Packern, Compilern und Linkern, das den Schutz von PE-, ELF- und MachO-Dateien identifiziert
- x64dbg/x32dbg: Open-Source-Windows-Debugger, der zum manuellen Entpacken durch dynamische Ausführung und breakpointbasierte OEP-Findung verwendet wird
- Scylla: Import-Rekonstruktionswerkzeug, das in x64dbg integriert ist und die IAT nach dem Speicher-Dumping wiederherstellt
- PE-bear: PE-Datei-Viewer und -Editor zur Inspektion und Reparatur von PE-Headern nach dem Entpacken
Häufige Szenarien
Szenario: Entpacken von Malware mit modifizierten UPX-Headern
Kontext: Eine Malware-Probe wird anhand der Abschnittsnamen (UPX0, UPX1) als UPX-verpackt identifiziert, aber upx -d schlägt mit "CantUnpackException: header corrupted" fehl. Der Malware-Autor hat die UPX-Magiebytes modifiziert, um die automatisierte Dekompression zu verhindern.
Vorgehen:
- Öffnen Sie die Binärdatei in einem Hex-Editor und suchen Sie nach dem UPX-Header-Bereich (normalerweise am Ende der verpackten Daten)
- Identifizieren Sie die modifizierten Magiebytes (z. B. "UPX!" geändert zu "UPX\x00" oder vollständig genullt)
- Verwenden Sie das Python-Reparaturskript, um die "UPX!"-Magie und korrekte Abschnittsnamen wiederherzustellen
- Wiederholen Sie
upx -dmit der reparierten Binärdatei - Falls die Reparatur fehlschlägt, greifen Sie auf manuelles Entpacken mit x64dbg zurück (PUSHAD -> Hardware-BP auf ESP -> POPAD -> JMP OEP)
- Validieren Sie, dass die entpackte Binärdatei ordnungsgemäße Importe und vernünftige Entropiewerte aufweist
- Importieren Sie in Ghidra oder IDA für die vollständige statische Analyse
Fallstricke:
- Annahme, dass UPX der einzige Packer ist; die Binärdatei kann doppelt verpackt sein (UPX + benutzerdefinierte Schicht)
- Modifikation der ursprünglichen verpackten Probe anstatt einer Kopie
- Unterlassene Importrekonstruktion nach manuellem Speicher-Dump (die gedumpte Binärdatei stürzt ohne IAT-Fix ab)
- Vergessen, auf Overlay-Daten zu prüfen, die nach den UPX-verpackten PE-Abschnitten angefügt sein können
Ausgabeformat
ENTPACKUNGS-ANALYSEBERICHT
===========================
Probe: suspect.exe
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
Packer: UPX 3.96 (modifizierte Header)
VERPACKTE BINÄRDATEI
Abschnitte: UPX0 (Entropie: 0.00) UPX1 (Entropie: 7.89) .rsrc (Entropie: 3.45)
Importe: 2 (kernel32.dll: LoadLibraryA, GetProcAddress)
Dateigröße: 98.304 Bytes
ENTPACKUNGSMETHODE
Methode: Header-Reparatur + UPX -d
Header-Fix: UPX!-Magie bei Offset 0x1F000 wiederhergestellt
Befehl: upx -d suspect_fixed.exe -o unpacked.exe
Ergebnis: ERFOLG
ENTPACKTE BINÄRDATEI
Abschnitte: .text (Entropie: 6.21) .rdata (Entropie: 4.56) .data (Entropie: 3.12) .rsrc (Entropie: 3.45)
Importe: 147 (kernel32, user32, advapi32, wininet, ws2_32)
Dateigröße: 245.760 Bytes (2.5-fache Expansion)
OEP: 0x00401000
VALIDIERUNG
PE gültig: Ja
Importe aufgelöst: Ja (147 Funktionen in 8 DLLs)
Ausführbar: Ja (läuft ohne Absturz in der Sandbox)
NÄCHSTE SCHRITTE
- unpacked.exe in Ghidra importieren für vollständige Disassemblierung
- YARA-Regeln gegen die entpackte Binärdatei ausführen
- Entpackte Binärdatei an VirusTotal senden für verbesserte Erkennung