Packing y Unpacking: UPX, Themida, VMProtect y Tecnicas
Guia completa sobre packing y unpacking de malware. UPX, Themida, VMProtect, ASPack, custom packers. Tecnicas de unpacking manual, busqueda del OEP, tail jump y herramientas especializadas.
Por que el malware se empaqueta
Mas del 80% del malware en circulacion usa algun tipo de packing. Las razones son claras:
- Evasion de antivirus: Los motores de firmas no pueden detectar patrones en codigo cifrado o comprimido.
- Dificultar el analisis: El codigo real no es visible hasta que se ejecuta y desempaqueta en memoria.
- Reducir tamano: Packers como UPX reducen el tamano del ejecutable significativamente.
- Proteccion de IP: Protectores comerciales como Themida se usan para proteger software legitimo de pirateria, pero el malware abusa de la misma tecnologia.
Entender packing y unpacking es una habilidad fundamental. Sin desempaquetar, el analisis estatico es practicamente inutil: no se pueden ver las imports reales, los strings son ilegibles y el codigo esta ofuscado.
Como funciona un packer
El proceso de packing sigue un patron general:
Fase de empaquetado (offline):
- El packer lee el PE original completo.
- Comprime o cifra el codigo y datos.
- Genera un nuevo PE con un stub de desempaquetado.
- El contenido cifrado se almacena en una o mas secciones del nuevo PE.
- El entry point del nuevo PE apunta al stub.
Fase de desempaquetado (runtime):
- El stub se ejecuta primero.
- Descomprime o descifra el codigo original en memoria.
- Reconstruye la Import Address Table.
- Salta al OEP (Original Entry Point) del programa original.
Packers comunes y sus caracteristicas
UPX (Ultimate Packer for eXecutables)
El packer mas usado, open source. Solo comprime (no cifra) usando algoritmos como LZMA, NRV y UCL.
Deteccion:
- Secciones nombradas UPX0, UPX1, UPX2
- UPX0 tiene SizeOfRawData = 0 (se expande en runtime)
- UPX1 tiene entropia alta (7.0+)
- String "UPX!" al inicio de la seccion comprimida
Unpacking trivial:
upx -d packed_sample.exe -o unpacked_sample.exe
Nota: El malware a veces modifica los headers de UPX para que el comando upx -d falle. En ese caso, hay que reparar los magic bytes de UPX o usar unpacking manual.
ASPack
Packer comercial popular. Compresion razonable con poco overhead.
Deteccion:
- Seccion .aspack
- Entry point en seccion .aspack
- Patron de codigo: PUSHAD al inicio del stub
Unpacking: Manual. Buscar el tail jump (JMP al OEP) despues del POPAD que restaura los registros.
Themida / WinLicense
Protector comercial avanzado de Oreans Technologies. Usa multiples capas de proteccion:
| Capa | Tecnica |
|---|---|
| 1 | Cifrado del codigo original |
| 2 | Virtualizacion de codigo (VM interna) |
| 3 | Anti-debugging (multiples tecnicas) |
| 4 | Anti-VM y anti-sandbox |
| 5 | Mutacion de codigo (cada copia es diferente) |
| 6 | Integridad de memoria (detecta breakpoints) |
Deteccion:
- Seccion .themida
- Tamano de la seccion .themida desproporcionado
- Multiples secciones RWX
- Imports minimas
Unpacking: Extremadamente dificil. Requiere scripts especializados para x64dbg, parcheado de anti-debug y mucha paciencia. Puede llevar horas o dias.
VMProtect
Virtualiza el codigo convirtiendolo en bytecode para una VM interna. El codigo original nunca existe como instrucciones x86/x64 en memoria.
Deteccion:
- Secciones .vmp0, .vmp1
- Entry point en seccion .vmp
- Tamano del binario significativamente mayor que el original
Unpacking: No se puede "desempaquetar" en el sentido tradicional. El codigo esta transformado a un ISA (Instruction Set Architecture) custom. Se necesitan herramientas de devirtualizacion como:
- Oreans UnVirtualizer (plugin IDA)
- NoVMP (open source, soporte parcial)
- Analisis manual de la VM handler table
Otros packers relevantes
| Packer | Tipo | Deteccion | Dificultad |
|---|---|---|---|
| PECompact | Compresion | Seccion .pec, PECompact2 | Baja |
| Petite | Compresion | Seccion .petite | Baja |
| MPRESS | Compresion | Secciones .MPRESS1, .MPRESS2 | Baja |
| Enigma | Proteccion | Seccion .enigma1 | Alta |
| Obsidium | Proteccion | Anti-debug extensivo | Alta |
| ConfuserEx | .NET protector | Metadata ofuscada | Media |
| .NET Reactor | .NET protector | Codigo IL cifrado | Media |
Custom packers: el desafio real
Las familias de malware sofisticadas usan packers propios que no tienen firma en ningun detector. Estos packers se identifican por comportamiento, no por nombre:
- Entry point en seccion sin nombre o con nombre aleatorio
- Entropia alta sin match de packer conocido
- Imports minimas (solo GetProcAddress, VirtualAlloc, LoadLibrary)
- Patron de PUSHAD/POPAD (guardar/restaurar registros) seguido de jump largo
Tecnicas de unpacking manual
Paso 1: Deteccion del packer
# Con Detect It Easy (die)
diec sample.exe
# Con PEiD (si disponible)
peid sample.exe
# Manual: verificar secciones y entropia
python3 -c "
import pefile
pe = pefile.PE('sample.exe')
for s in pe.sections:
name = s.Name.decode().rstrip(chr(0))
print(name, round(s.get_entropy(), 2), hex(s.SizeOfRawData), hex(s.Misc_VirtualSize))
"
Paso 2: Buscar el OEP
El OEP (Original Entry Point) es donde el codigo original comienza a ejecutarse despues de que el stub del packer termina su trabajo. Encontrarlo es el objetivo principal.
Tecnica 1: Tail Jump
El stub del packer siempre termina con un salto al OEP. Este salto final (tail jump) suele ser:
- Un JMP largo a una direccion que esta en otra seccion
- Un PUSH + RET (push de la direccion del OEP seguido de return)
- Un CALL + JMP indirecto
En x64dbg, se puede buscar este patron poniendo un hardware breakpoint en ejecucion en la primera seccion (donde estara el codigo original) y dejando que el stub se ejecute.
Tecnica 2: PUSHAD/POPAD
Muchos packers empiezan con PUSHAD (guardar todos los registros) y terminan con POPAD (restaurarlos) antes del tail jump:
Entry Point (stub):
PUSHAD ; guarda registros originales
... (codigo del stub, desempaquetado)
POPAD ; restaura registros
JMP OEP ; salta al programa original
En x64dbg: poner breakpoint en la instruccion PUSHAD, ejecutar, anotar el valor de ESP, poner hardware breakpoint on access en esa direccion de stack. Cuando para, el proximo JMP o RET lleva al OEP.
Tecnica 3: Breakpoints en APIs de memoria
El stub necesita desempaquetar codigo en memoria. Poner breakpoints en:
- VirtualAlloc / VirtualAllocEx (reservar memoria)
- VirtualProtect (cambiar permisos a ejecutable)
- WriteProcessMemory (si se inyecta en otro proceso)
Cuando VirtualProtect cambia una region a ejecutable (PAGE_EXECUTE_READ), el codigo desempaquetado esta listo. La direccion de esa region suele contener el OEP.
Tecnica 4: Breakpoint en API conocida del programa original
Si se sabe que funcion llama el programa original (por ejemplo MessageBox si es una aplicacion GUI), poner breakpoint en MessageBoxA/W y ejecutar. Cuando para, examinar el call stack para encontrar el caller, que estara en el codigo desempaquetado.
Paso 3: Dump de memoria
Una vez localizado el OEP, hacer un dump de la memoria del proceso. Las herramientas comunes:
Scylla (x64dbg plugin):
- Con el proceso parado en el OEP, abrir Scylla.
- Verificar que el OEP detectado es correcto.
- Click en "IAT Autosearch" para localizar la Import Address Table.
- Click en "Get Imports" para reconstruir las imports.
- "Dump" para volcar la memoria a un archivo.
- "Fix Dump" para aplicar las imports reconstruidas al dump.
OllyDumpEx / OllyDump (OllyDbg plugin): Funcionalidad similar a Scylla pero para OllyDbg.
Paso 4: Reconstruccion de la IAT
El dump contiene el codigo desempaquetado, pero la IAT apunta a direcciones de memoria que ya no son validas fuera del proceso. Hay que reconstruirla:
- Scylla automatiza este proceso en la mayoria de los casos.
- Import REConstructor (ImpREC): Herramienta legacy pero todavia util.
- Manual: Identificar las direcciones en la IAT, determinar a que funcion corresponden y reconstruir la Import Directory.
Unpacking de UPX modificado
Cuando el malware modifica los headers de UPX para que upx -d falle:
upx -d sample.exe
# Error: CantUnpackException: file is possibly modified/hacked/protected
# Solucion 1: reparar el magic
# Buscar y restaurar los bytes "UPX!" en el header de la seccion
# Solucion 2: unpacking manual
# En x64dbg:
# 1. Breakpoint en el entry point
# 2. F9 para ejecutar el stub
# 3. El tail jump de UPX es siempre un JMP largo a la seccion UPX0
# 4. Dump con Scylla
Unpacking automatizado
Herramientas de unpacking automatico
| Herramienta | Descripcion | Tipo |
|---|---|---|
| Unipacker | Unpacker automatico basado en emulacion | Open source |
| PE-sieve | Detecta y dumpa modulos modificados en memoria | Open source (hasherezade) |
| Mal-Unpack | Unpacking via ejecucion controlada | Open source |
| ANY.RUN | Sandbox que permite dump post-ejecucion | Servicio online |
| Unpac.me | Servicio de unpacking automatizado | Servicio online |
Script de deteccion de packing
import pefile
import math
def detect_packing(filepath):
pe = pefile.PE(filepath)
indicators = []
score = 0
# Verificar entropia de secciones
for section in pe.sections:
name = section.Name.decode().rstrip("\x00")
entropy = section.get_entropy()
if entropy > 7.0:
indicators.append(
"Entropia alta en " + name + ": " + str(round(entropy, 2))
)
score += 3
# Verificar imports minimas
import_count = 0
has_loadlibrary = False
has_getprocaddress = False
if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"):
for entry in pe.DIRECTORY_ENTRY_IMPORT:
for imp in entry.imports:
import_count += 1
if imp.name:
name = imp.name.decode()
if "LoadLibrary" in name:
has_loadlibrary = True
if "GetProcAddress" in name:
has_getprocaddress = True
if import_count < 10:
indicators.append(
"Solo " + str(import_count) + " imports"
)
score += 2
if has_loadlibrary and has_getprocaddress and import_count < 15:
indicators.append("Solo LoadLibrary + GetProcAddress (resolucion dinamica)")
score += 3
# Verificar secciones RWX
for section in pe.sections:
chars = section.Characteristics
if chars & 0xE0000000 == 0xE0000000:
name = section.Name.decode().rstrip("\x00")
indicators.append("Seccion " + name + " tiene permisos RWX")
score += 2
# Verificar nombres de secciones de packers conocidos
known_packer_sections = [
"UPX", ".aspack", ".adata", ".vmp", ".themida",
".enigma", ".petite", ".MPRESS", "pec", ".packed"
]
for section in pe.sections:
name = section.Name.decode().rstrip("\x00")
for packer_name in known_packer_sections:
if packer_name.lower() in name.lower():
indicators.append(
"Seccion de packer conocido: " + name
)
score += 4
# Verificar ratio VirtualSize / SizeOfRawData
for section in pe.sections:
name = section.Name.decode().rstrip("\x00")
vs = section.Misc_VirtualSize
raw = section.SizeOfRawData
if raw > 0 and vs > raw * 5:
indicators.append(
name + " se expande " + str(round(vs/raw, 1))
+ "x en memoria"
)
score += 2
if vs > 0 and raw == 0:
indicators.append(
name + " tiene tamano en disco = 0 pero VirtualSize = "
+ hex(vs)
)
score += 3
# Resultado
print("=== Packing Detection Report ===")
print("File:", filepath)
print()
if score >= 6:
print("VEREDICTO: Probablemente empaquetado (score: " + str(score) + ")")
elif score >= 3:
print("VEREDICTO: Posiblemente empaquetado (score: " + str(score) + ")")
else:
print("VEREDICTO: Probablemente no empaquetado (score: " + str(score) + ")")
if indicators:
print("\nIndicadores:")
for ind in indicators:
print(" [!] " + ind)
return score, indicators
if __name__ == "__main__":
import sys
detect_packing(sys.argv[1])
Estrategias ante protectores avanzados
Para protectores como Themida y VMProtect, el unpacking manual tradicional no funciona. Alternativas:
Analisis dinamico directo: En lugar de desempaquetar, ejecutar en sandbox y observar el comportamiento. Las llamadas a APIs, conexiones de red y archivos creados revelan la funcionalidad sin necesidad de ver el codigo.
Hooking de APIs: Instrumentar las APIs del sistema para capturar parametros y valores de retorno. Herramientas como API Monitor, Frida o DBI (Dynamic Binary Instrumentation) con Intel PIN.
Memory forensics: Ejecutar el malware, hacer un dump de toda la memoria del proceso y buscar el codigo desempaquetado con herramientas como pe-sieve o hollows-hunter.
Snapshots de VM: Tomar snapshots antes y despues de la ejecucion del stub, comparar la memoria para encontrar el codigo desempaquetado.
Conclusion
El packing es la primera barrera que un analista debe superar. Para packers simples como UPX, el proceso es trivial. Para packers comerciales como ASPack o PECompact, el unpacking manual con x64dbg y Scylla es el camino estandar. Para protectores avanzados como Themida y VMProtect, el analisis dinamico y el memory forensics son alternativas mas practicas que intentar un unpacking completo.
La clave es saber clasificar rapidamente el nivel de proteccion del binario para elegir la estrategia correcta: no tiene sentido pasar horas intentando desempaquetar Themida si un analisis dinamico en sandbox puede responder las preguntas del caso en minutos.
Preguntas frecuentes
Libros recomendados
Artículos relacionados
Secciones PE: .text, .data, .rsrc, .reloc y Anomalias
Entropia: Detectar Empaquetado y Cifrado en Binarios
Ofuscacion de Codigo: Tecnicas y Estrategias de Deobfuscacion
Tecnicas Anti-Analisis: Anti-Debug, Anti-VM y Anti-Sandbox
Este contenido tiene fines exclusivamente educativos y de investigación en ciberseguridad defensiva. No se proporcionan binarios maliciosos ni payloads ejecutables. El uso indebido de esta información es responsabilidad exclusiva del usuario. Leer disclaimer completo.