¿Qué hace Análisis de malware empaquetado con desempaquetador UPX?
Cuándo usarlo
- El análisis estático revela secciones de alta entropía e importaciones mínimas, indicando que el binario está empaquetado.
- PEiD, Detect It Easy o PEStudio identifican UPX u otro empaquetador conocido.
- La tabla de importaciones contiene solo LoadLibrary y GetProcAddress (resolución de importaciones en tiempo de ejecución, típica de binarios empaquetados).
- Necesitas recuperar el binario original para un desensamblado y descompilación adecuados en Ghidra o IDA.
- La descompresión automatizada de UPX falla porque el autor del malware modificó los bytes mágicos o las cabeceras de UPX.
No lo uses cuando trabajes con empaquetadores personalizados, protectores basados en VM (Themida, VMProtect), o muestras donde el desempaquetado dinámico mediante depuración sea más apropiado.
Prerrequisitos
- UPX (Ultimate Packer for eXecutables) instalado (
apt install upx-uclo descargar de https://upx.github.io/) - Detect It Easy (DIE) para identificación de empaquetadores
- Python 3.8+ con la librería
pefilepara reparación manual de cabeceras - x64dbg o x32dbg para desempaquetado manual cuando fallen las herramientas automatizadas
- PE-bear o CFF Explorer para inspección y reparación de cabeceras PE
- Máquina virtual de análisis aislada sin conectividad de red
Flujo de trabajo
Paso 1: Identificar el empaquetador
Determina si la muestra está empaquetada e identifica el empaquetador:
# Verificar con Detect It Easy
diec suspect.exe
# Verificar con UPX (prueba sin desempaquetar)
upx -t suspect.exe
# Detección de entropía y empaquetador basada en Python
python3 << 'PYEOF'
import pefile
import math
pe = pefile.PE("suspect.exe")
print("Análisis de secciones:")
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} Entropía: {entropy:.2f} Raw: {raw:>8} Virtual: {virtual:>8}")
# Verificar nombres de secciones UPX
section_names = [s.Name.decode().rstrip('\x00') for s in pe.sections]
if 'UPX0' in section_names or 'UPX1' in section_names:
print("\n[!] Nombres de secciones UPX detectados")
elif '.upx' in [s.lower() for s in section_names]:
print("\n[!] Nombres de secciones variantes de UPX detectados")
# Verificar cantidad de importaciones (los binarios empaquetados tienen muy pocas)
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
total_imports = sum(len(e.imports) for e in pe.DIRECTORY_ENTRY_IMPORT)
print(f"\nTotal de importaciones: {total_imports}")
if total_imports < 10:
print("[!] Muy pocas importaciones - probablemente empaquetado")
else:
print("\n[!] Sin directorio de importaciones - fuertemente empaquetado")
PYEOF
Paso 2: Intentar descompresión estándar de UPX
Prueba la descompresión integrada de UPX:
# Descompresión estándar de UPX
upx -d suspect.exe -o unpacked.exe
# Si UPX falla con el error "not packed by UPX", las cabeceras pueden estar modificadas
# Salida detallada para depuración
upx -d suspect.exe -o unpacked.exe -v 2>&1
# Verificar el archivo desempaquetado
file unpacked.exe
diec unpacked.exe
Paso 3: Reparar cabeceras UPX modificadas
Si la descompresión estándar falla, repara los bytes mágicos alterados:
# Reparar cabeceras UPX modificadas
import struct
with open("suspect.exe", "rb") as f:
data = bytearray(f.read())
# Bytes mágicos de UPX: "UPX!" (0x55505821)
# Los autores de malware suelen modificarlos para evitar el desempaquetado automático
# Buscar firmas UPX modificadas
upx_magic = b"UPX!"
modified_patterns = [b"UPX0", b"UPX\x00", b"\x00PX!", b"UPx!"]
# Encontrar y restaurar nombres de secciones
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"Desplazamiento PE: 0x{pe_offset:X}")
print(f"Número de secciones: {num_sections}")
print(f"Desplazamiento de tabla de secciones: 0x{section_table_offset:X}")
for i in range(num_sections):
offset = section_table_offset + (i * 40)
name = data[offset:offset+8]
print(f"Sección {i}: {name}")
# Restaurar bytes mágicos UPX en el binario
# Buscar la ubicación de la firma de cabecera UPX (típicamente cerca del final de los datos empaquetados)
for i in range(len(data) - 4):
if data[i:i+3] == b"UPX" and data[i+3] != ord("!"):
print(f"Bytes mágicos UPX modificados encontrados en desplazamiento 0x{i:X}: {data[i:i+4]}")
data[i:i+4] = b"UPX!"
print(f"Restaurado a: UPX!")
# También restaurar nombres de secciones si están modificados
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 # Ya están correctos
# Verificar modificaciones comunes
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"Nombre de sección restaurado en 0x{offset:X} a {original}")
with open("suspect_fixed.exe", "wb") as f:
f.write(data)
print("\nArchivo corregido escrito. Reintentar: upx -d suspect_fixed.exe -o unpacked.exe")
Paso 4: Desempaquetado manual con depurador
Cuando el desempaquetado automatizado falla por completo, utiliza el desempaquetado dinámico:
Desempaquetado manual UPX con x64dbg:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. Cargar la muestra empaquetada en x64dbg
2. Ejecutar hasta el punto de entrada (punto de interrupción del sistema, luego F9)
3. Patrón del stub de desempaquetado UPX:
a. PUSHAD (guarda todos los registros)
b. Bucle de descompresión (procesa las secciones empaquetadas)
c. Resuelve importaciones (llamadas a LoadLibrary/GetProcAddress)
d. POPAD (restaura los registros)
e. JMP al OEP (punto de entrada original)
4. Establecer punto de interrupción por hardware en ESP después de PUSHAD:
- Después de PUSHAD, haz clic derecho en ESP en registros -> Seguir en volcado
- Establecer punto de interrupción por hardware en acceso a la dirección [ESP]
- Ejecutar (F9) - se detiene en POPAD antes de JMP al OEP
5. Avanzar (F7/F8) hasta llegar al JMP al OEP
6. En OEP: Usar el plugin Scylla para volcar y corregir importaciones:
- Plugins -> Scylla -> OEP = EIP actual
- Clic en "IAT Autosearch" -> "Get Imports"
- Clic en "Dump" para guardar el binario desempaquetado
- Clic en "Fix Dump" para reparar la tabla de importaciones
Paso 5: Validar el binario desempaquetado
Verifica que la muestra desempaquetada sea válida y completa:
# Verificar que el PE desempaquetado sea válido
python3 << 'PYEOF'
import pefile
pe = pefile.PE("unpacked.exe")
# Verificar que las secciones sean normales
print("Análisis de secciones desempaquetadas:")
for section in pe.sections:
name = section.Name.decode().rstrip('\x00')
entropy = section.get_entropy()
print(f" {name:8s} Entropía: {entropy:.2f}")
# Verificar que las importaciones estén resueltas
print(f"\nCantidad de importaciones:")
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} funciones")
total = sum(len(e.imports) for e in pe.DIRECTORY_ENTRY_IMPORT)
print(f" Total: {total} importaciones")
# Comparar tamaños de archivo
import os
packed_size = os.path.getsize("suspect.exe")
unpacked_size = os.path.getsize("unpacked.exe")
print(f"\nEmpaquetado: {packed_size:>10} bytes")
print(f"Desempaquetado: {unpacked_size:>10} bytes")
print(f"Proporción: {unpacked_size/packed_size:.1f}x")
PYEOF
Conceptos clave
| Término | Definición |
|---|---|
| Empaquetado | Compresión o cifrado de código ejecutable para reducir el tamaño del archivo y dificultar el análisis estático; el binario contiene un stub de desempaquetado que restaura el código en tiempo de ejecución |
| UPX | Ultimate Packer for eXecutables; empaquetador de ejecutables de código abierto comúnmente abusado por autores de malware porque es gratuito y efectivo |
| Punto de entrada original (OEP) | Dirección real de inicio del código malicioso antes del empaquetado; el stub de desempaquetado descomprime el código y luego salta al OEP |
| Reconstrucción de importaciones | Proceso de reconstruir la tabla de direcciones de importación después de volcar un proceso desempaquetado de la memoria usando herramientas como Scylla o ImpRec |
| PUSHAD/POPAD | Instrucciones x86 que guardan/restauran todos los registros de propósito general; UPX usa este patrón para preservar el estado de los registros durante el desempaquetado |
| Entropía de sección | Medida de aleatoriedad de los datos de una sección PE; las secciones empaquetadas muestran entropía > 7.0 mientras que las secciones de código normales promedian 5.0-6.5 |
| Bytes mágicos | Bytes de firma dentro de un archivo que identifican su formato; UPX usa "UPX!" que los autores de malware modifican para evitar la descompresión automatizada |
Herramientas y sistemas
- UPX: Empaquetador de ejecutables de código abierto con capacidad de descompresión integrada para archivos correctamente empaquetados
- Detect It Easy (DIE): Herramienta de detección de empaquetadores, compiladores y enlazadores que identifica protección en archivos PE, ELF y Mach-O
- x64dbg/x32dbg: Depurador de Windows de código abierto utilizado para desempaquetado manual mediante ejecución dinámica y búsqueda del OEP basada en puntos de interrupción
- Scylla: Herramienta de reconstrucción de importaciones integrada con x64dbg para reconstruir la IAT después del volcado de memoria
- PE-bear: Visor y editor de archivos PE para inspeccionar y reparar cabeceras PE después del desempaquetado
Escenarios comunes
Escenario: Desempaquetado de malware con cabeceras UPX modificadas
Contexto: Una muestra de malware es identificada como empaquetada con UPX por los nombres de sección (UPX0, UPX1) pero upx -d falla con "CantUnpackException: header corrupted". El autor del malware modificó los bytes mágicos de UPX para evitar la descompresión automatizada.
Enfoque:
- Abre el binario en un editor hexadecimal y busca el área de la cabecera UPX (típicamente al final de los datos empaquetados)
- Identifica los bytes mágicos modificados (por ejemplo, "UPX!" cambiado a "UPX\x00" o completamente puesto a cero)
- Usa el script de reparación en Python para restaurar la magia "UPX!" y corregir los nombres de sección
- Reintenta
upx -den el binario reparado - Si la reparación falla, recurre al desempaquetado manual con x64dbg (PUSHAD -> BP por hardware en ESP -> POPAD -> JMP OEP)
- Valida que el binario desempaquetado tenga importaciones adecuadas y valores de entropía razonables
- Importa en Ghidra o IDA para un análisis estático completo
Errores comunes:
- Asumir que UPX es el único empaquetador; el binario puede estar doblemente empaquetado (UPX + capa personalizada)
- Modificar la muestra empaquetada original en lugar de trabajar sobre una copia
- No reconstruir las importaciones después del volcado de memoria manual (el binario volcado fallará sin la corrección de la IAT)
- Olvidar verificar la presencia de datos superpuestos añadidos después de las secciones PE empaquetadas con UPX
Formato de salida
INFORME DE ANÁLISIS DE DESEMPAQUETADO
=======================================
Muestra: suspect.exe
SHA-256: e3b0c44298fc1c149afbf4c8996fb924...
Empaquetador: UPX 3.96 (cabeceras modificadas)
BINARIO EMPAQUETADO
Secciones: UPX0 (entropía: 0.00) UPX1 (entropía: 7.89) .rsrc (entropía: 3.45)
Importaciones: 2 (kernel32.dll: LoadLibraryA, GetProcAddress)
Tamaño archivo: 98,304 bytes
MÉTODO DE DESEMPAQUETADO
Método: Reparación de cabecera + UPX -d
Corrección cab.: Bytes mágicos UPX! restaurados en desplazamiento 0x1F000
Comando: upx -d suspect_fixed.exe -o unpacked.exe
Resultado: ÉXITO
BINARIO DESEMPAQUETADO
Secciones: .text (entropía: 6.21) .rdata (entropía: 4.56) .data (entropía: 3.12) .rsrc (entropía: 3.45)
Importaciones: 147 (kernel32, user32, advapi32, wininet, ws2_32)
Tamaño archivo: 245,760 bytes (expansión 2.5x)
OEP: 0x00401000
VALIDACIÓN
PE Válido: Sí
Importaciones resueltas: Sí (147 funciones en 8 DLL)
Ejecutable: Sí (se ejecuta sin fallo en sandbox)
PRÓXIMOS PASOS
- Importar unpacked.exe en Ghidra para desensamblado completo
- Ejecutar reglas YARA contra el binario desempaquetado
- Enviar binario desempaquetado a VirusTotal para una detección mejorada