IntermedioPEWindowsIATAPIsanalisis estaticoevasion

Import Address Table: APIs Sospechosas y Resolucion Dinamica

Analisis de la Import Address Table (IAT) en archivos PE. APIs sospechosas de Windows agrupadas por comportamiento, resolucion dinamica con GetProcAddress y LoadLibrary, API hashing y tecnicas de evasion.

MalwareIntel Research··12 min lectura
Serie: Análisis de Binarios — Parte 3

La IAT como declaracion de intenciones

Si las secciones son la radiografia de un PE, la Import Address Table es su declaracion de intenciones. Cada funcion importada revela una capacidad: leer archivos, conectarse a la red, manipular procesos, modificar el registro. Un binario que importa CreateRemoteThread, VirtualAllocEx y WriteProcessMemory esta practicamente confesando que inyecta codigo.

El problema es que el malware lo sabe. Por eso las familias modernas minimizan sus imports estaticos y resuelven el resto en runtime. Entender tanto la IAT como las tecnicas de resolucion dinamica es fundamental para el analisis.

Estructura de la Import Table

La Import Table del PE se compone de varias estructuras interrelacionadas:

Import Directory Table: Un array de estructuras IMAGE_IMPORT_DESCRIPTOR, una por cada DLL importada. El array termina con una entrada llena de ceros.

Cada descriptor contiene:

CampoDescripcion
OriginalFirstThunkRVA al Import Lookup Table (ILT)
TimeDateStampTimestamp de binding (0 si no bound)
ForwarderChainIndice de forwarded imports (-1 si ninguno)
NameRVA al string con nombre de la DLL
FirstThunkRVA a la Import Address Table (IAT)

Import Lookup Table (ILT): Array de entradas que identifican cada funcion importada, por nombre o por ordinal.

Import Address Table (IAT): Inicialmente identica a la ILT, pero cuando el PE se carga en memoria, el loader de Windows sustituye cada entrada con la direccion real de la funcion.

import pefile

pe = pefile.PE("sample.exe")

for entry in pe.DIRECTORY_ENTRY_IMPORT:
    dll_name = entry.dll.decode()
    print("\n[" + dll_name + "]")
    for imp in entry.imports:
        if imp.name:
            print("  " + imp.name.decode())
        else:
            print("  Ordinal:", imp.ordinal)

Catalogo de APIs sospechosas por categoria

Inyeccion de procesos

La inyeccion de codigo en procesos remotos es una de las tecnicas mas comunes del malware. Las APIs clave forman patrones reconocibles:

APIDLLFuncion
OpenProcesskernel32Obtener handle a proceso objetivo
VirtualAllocExkernel32Reservar memoria en proceso remoto
WriteProcessMemorykernel32Escribir codigo/datos en el proceso
CreateRemoteThreadkernel32Ejecutar el codigo inyectado
NtCreateThreadExntdllAlternativa a CreateRemoteThread
RtlCreateUserThreadntdllOtra alternativa para crear threads
QueueUserAPCkernel32Inyeccion via APC (asincrona)
NtQueueApcThreadntdllVersion nativa de QueueUserAPC
SetWindowsHookExuser32Inyeccion via hooks de ventana

Patron clasico de inyeccion:

  1. OpenProcess para obtener handle al proceso objetivo
  2. VirtualAllocEx para reservar memoria con permisos PAGE_EXECUTE_READWRITE
  3. WriteProcessMemory para copiar el shellcode
  4. CreateRemoteThread para ejecutar

Manipulacion de memoria

APIDLLIndicador
VirtualAllockernel32Reservar memoria ejecutable
VirtualProtectkernel32Cambiar permisos de memoria (RW a RX)
HeapCreatekernel32Crear heap privado
NtAllocateVirtualMemoryntdllAlternativa nativa a VirtualAlloc
NtProtectVirtualMemoryntdllAlternativa nativa a VirtualProtect

La combinacion VirtualAlloc + VirtualProtect es clasica del desempaquetado: el malware reserva memoria con permisos RW, escribe el codigo desempaquetado, y luego cambia los permisos a RX para ejecutarlo.

Operaciones de archivo y persistencia

APIDLLIndicador
CreateFilekernel32Crear o abrir archivos
WriteFilekernel32Escribir contenido
CopyFilekernel32Copiar el propio ejecutable
MoveFilekernel32Mover archivos
DeleteFilekernel32Eliminar archivos (cubrir rastros)
GetTempPathkernel32Obtener directorio temporal
CreateDirectorykernel32Crear directorios

Registro de Windows (persistencia)

APIDLLIndicador
RegOpenKeyExadvapi32Abrir clave de registro
RegSetValueExadvapi32Escribir valor (persistencia)
RegCreateKeyExadvapi32Crear nueva clave
RegDeleteKeyadvapi32Eliminar clave (limpieza)

Las claves de Run y RunOnce son los objetivos clasicos para persistencia:

  • HKCU\Software\Microsoft\Windows\CurrentVersion\Run
  • HKLM\Software\Microsoft\Windows\CurrentVersion\Run

Red y comunicacion C2

APIDLLIndicador
WSAStartupws2_32Inicializar Winsock
socketws2_32Crear socket de red
connectws2_32Conectar a servidor remoto
send / recvws2_32Enviar/recibir datos
InternetOpenwininetInicializar sesion HTTP
InternetOpenUrlwininetAbrir URL
HttpOpenRequestwininetCrear request HTTP
HttpSendRequestwininetEnviar request HTTP
URLDownloadToFileurlmonDescargar archivo de URL
WinHttpOpenwinhttpAlternativa a WinINet

URLDownloadToFile es especialmente sospechosa porque descarga y escribe un archivo en una sola llamada. Es la API favorita de droppers simples.

Criptografia

APIDLLIndicador
CryptAcquireContextadvapi32Obtener proveedor criptografico
CryptEncryptadvapi32Cifrar datos
CryptDecryptadvapi32Descifrar datos
CryptGenKeyadvapi32Generar clave
CryptImportKeyadvapi32Importar clave publica
BCryptEncryptbcryptAPI criptografica moderna

En ransomware, la presencia de CryptEncrypt o BCryptEncrypt junto con CryptImportKey (para importar la clave publica RSA del atacante) es una senal definitiva.

Enumeracion del sistema

APIDLLIndicador
GetSystemInfokernel32Informacion del sistema
GetComputerNamekernel32Nombre del equipo
GetUserNameadvapi32Nombre del usuario
GetVersionExkernel32Version de Windows
CreateToolhelp32Snapshotkernel32Enumerar procesos
Process32First / Nextkernel32Iterar procesos
IsDebuggerPresentkernel32Detectar debugger
CheckRemoteDebuggerPresentkernel32Detectar debugger remoto
GetTickCountkernel32Anti-sandbox (timing)

Ejecucion y servicios

APIDLLIndicador
CreateProcesskernel32Ejecutar otro proceso
ShellExecuteshell32Ejecutar con shell
WinExeckernel32Ejecutar comando (legacy)
CreateServiceadvapi32Crear servicio Windows
StartServiceadvapi32Iniciar servicio
CreateScheduledTasktaskschdCrear tarea programada

Resolucion dinamica: LoadLibrary y GetProcAddress

Cuando un PE importa estaticamente una funcion, esta aparece en la IAT y es visible con cualquier herramienta. El malware evita esto resolviendo funciones en runtime:

// Pseudocodigo de resolucion dinamica
HMODULE hKernel32 = LoadLibraryA("kernel32.dll")
FARPROC pVirtualAlloc = GetProcAddress(hKernel32, "VirtualAllocEx")
FARPROC pWriteMemory = GetProcAddress(hKernel32, "WriteProcessMemory")
FARPROC pCreateThread = GetProcAddress(hKernel32, "CreateRemoteThread")

Deteccion en analisis estatico:

Si la IAT de un PE solo contiene unas pocas funciones y entre ellas estan LoadLibraryA/W y GetProcAddress, es casi seguro que resuelve el resto dinamicamente.

import pefile

pe = pefile.PE("sample.exe")

total_imports = 0
has_loadlibrary = False
has_getprocaddress = False

for entry in pe.DIRECTORY_ENTRY_IMPORT:
    for imp in entry.imports:
        total_imports += 1
        if imp.name:
            name = imp.name.decode()
            if "LoadLibrary" in name:
                has_loadlibrary = True
            if "GetProcAddress" in name:
                has_getprocaddress = True

print("Total imports:", total_imports)
print("Tiene LoadLibrary:", has_loadlibrary)
print("Tiene GetProcAddress:", has_getprocaddress)

if total_imports < 20 and has_loadlibrary and has_getprocaddress:
    print("[!] Probable resolucion dinamica de APIs")

API Hashing: el siguiente nivel de ocultacion

La resolucion dinamica con GetProcAddress requiere strings con los nombres de las APIs en el binario, que pueden encontrarse con herramientas como strings o FLOSS. El API hashing elimina estos strings.

Como funciona:

  1. El malware calcula un hash (CRC32, djb2, ROR13, custom) de cada nombre de API que necesita.
  2. Almacena solo los hashes en su codigo (valores numericos de 32 bits).
  3. En runtime, itera sobre las exports de la DLL, calcula el hash de cada nombre y compara.
  4. Cuando encuentra coincidencia, obtiene la direccion de la funcion.

Algoritmos de hash comunes:

// ROR13 (usado por Metasploit y muchos frameworks)
hash = 0
for each char in function_name:
    hash = rotate_right(hash, 13) + char

// djb2 (usado por multiples familias)
hash = 5381
for each char in function_name:
    hash = hash * 33 + char

// CRC32 (usado por varios RATs)
hash = crc32(function_name)

Deteccion de API hashing:

  • Buscar constantes conocidas: 5381 (djb2), 0xEDB88320 (CRC32 polynomial)
  • Buscar patrones de rotacion (ROR, ROL) en loops
  • Usar HashDB de OALabs para identificar hashes conocidos
  • Buscar acceso al PEB (Process Environment Block) para resolver modulos
// Acceso al PEB en x86 para obtener lista de modulos cargados
mov eax, fs:[0x30]       // PEB
mov eax, [eax + 0x0C]    // PEB_LDR_DATA
mov eax, [eax + 0x14]    // InMemoryOrderModuleList

Imports por ordinal: funciones sin nombre

Algunas DLLs exportan funciones solo por numero ordinal (sin nombre). El malware puede importar por ordinal para dificultar el analisis:

for entry in pe.DIRECTORY_ENTRY_IMPORT:
    for imp in entry.imports:
        if imp.name is None and imp.ordinal is not None:
            print(
                "Import por ordinal:",
                entry.dll.decode(),
                "#" + str(imp.ordinal)
            )

Para resolver que funcion corresponde a cada ordinal, se necesita la Export Table de la DLL correspondiente o bases de datos de ordinales conocidos.

Bound imports y delayed imports

Bound imports

Los bound imports son una optimizacion donde el linker pre-resuelve las direcciones de las funciones importadas basandose en la version de la DLL disponible en tiempo de compilacion. Si la DLL no ha cambiado, el loader no necesita resolver las direcciones.

En el contexto de malware, los bound imports pueden revelar la version exacta del sistema operativo donde se compilo el binario.

Delayed imports

Los delayed imports son funciones que no se resuelven al cargar el PE, sino la primera vez que se llaman. Windows usa un stub de delayed loading que llama a LoadLibrary y GetProcAddress automaticamente.

El malware usa delayed imports para:

  • Evitar errores si una DLL no existe en la version de Windows del objetivo
  • Cargar DLLs sospechosas solo cuando realmente las necesita
  • Dificultar el analisis estatico (las imports no aparecen en la tabla principal)
if hasattr(pe, "DIRECTORY_ENTRY_DELAY_IMPORT"):
    for entry in pe.DIRECTORY_ENTRY_DELAY_IMPORT:
        print("[Delayed]", entry.dll.decode())
        for imp in entry.imports:
            if imp.name:
                print("  ", imp.name.decode())

Forwarded exports: la cadena de redireccion

Una DLL puede "forwardear" un export a otra DLL. Por ejemplo, kernel32.dll forwarda muchas funciones a ntdll.dll internamente. El malware puede abusar de este mecanismo:

  • Crear una DLL que forwarda la mayoria de funciones a la DLL original pero intercepta algunas (DLL proxying/hijacking).
  • Las funciones forwardeadas no contienen codigo en la DLL que las exporta, solo un string como "ntdll.RtlAllocateHeap".

Perfiles de imports por tipo de malware

Diferentes categorias de malware tienen perfiles de imports caracteristicos:

Dropper

kernel32.dll:
  CreateFile, WriteFile, GetTempPath
  CreateProcess, WinExec
  LoadLibrary, GetProcAddress
urlmon.dll:
  URLDownloadToFile

Patron: descarga un archivo, lo guarda en %TEMP% y lo ejecuta.

RAT (Remote Access Trojan)

kernel32.dll:
  CreateProcess, CreatePipe, ReadFile, WriteFile
  GetComputerName, GetUserName
ws2_32.dll:
  WSAStartup, socket, connect, send, recv
advapi32.dll:
  RegSetValueEx (persistencia)
shell32.dll:
  ShellExecute
user32.dll:
  GetDesktopWindow, GetForegroundWindow
  keybd_event (keylogger)

Patron: comunicacion de red + ejecucion de comandos + captura de informacion.

Ransomware

kernel32.dll:
  FindFirstFile, FindNextFile (enumerar archivos)
  CreateFile, ReadFile, WriteFile
  MoveFile, DeleteFile
advapi32.dll:
  CryptAcquireContext, CryptImportKey
  CryptEncrypt, CryptGenKey
  RegSetValueEx
shell32.dll:
  ShellExecute (ejecutar nota de rescate)

Patron: enumerar archivos + cifrar + persistencia + mostrar nota.

Infostealer

kernel32.dll:
  CreateToolhelp32Snapshot, Process32First
  ReadProcessMemory
crypt32.dll:
  CryptUnprotectData (descifrar credenciales Chrome)
advapi32.dll:
  CredEnumerate (credenciales Windows)
wininet.dll:
  InternetOpen, HttpSendRequest (exfiltracion)

Patron: enumerar procesos + robar credenciales + exfiltrar via HTTP.

Script de analisis de imports con scoring

import pefile
import sys

SUSPICIOUS_APIS = dict(
    # Inyeccion de procesos
    VirtualAllocEx=("injection", 8),
    WriteProcessMemory=("injection", 9),
    CreateRemoteThread=("injection", 9),
    NtCreateThreadEx=("injection", 9),
    QueueUserAPC=("injection", 7),
    SetWindowsHookEx=("injection", 6),

    # Manipulacion de memoria
    VirtualAlloc=("memory", 3),
    VirtualProtect=("memory", 5),

    # Red
    URLDownloadToFile=("network", 8),
    InternetOpen=("network", 4),
    HttpSendRequest=("network", 5),
    WSAStartup=("network", 3),

    # Persistencia
    RegSetValueEx=("persistence", 6),
    CreateService=("persistence", 7),

    # Criptografia
    CryptEncrypt=("crypto", 6),
    CryptImportKey=("crypto", 7),

    # Anti-analisis
    IsDebuggerPresent=("anti_analysis", 5),
    CheckRemoteDebuggerPresent=("anti_analysis", 6),

    # Ejecucion
    CreateProcess=("execution", 4),
    ShellExecute=("execution", 4),
    WinExec=("execution", 7),

    # Resolucion dinamica
    GetProcAddress=("dynamic_resolution", 4),
    LoadLibraryA=("dynamic_resolution", 4),
    LoadLibraryW=("dynamic_resolution", 4),
)

def analyze_imports(filepath):
    pe = pefile.PE(filepath)

    findings = []
    categories = dict()
    total_score = 0

    for entry in pe.DIRECTORY_ENTRY_IMPORT:
        dll = entry.dll.decode()
        for imp in entry.imports:
            if imp.name:
                name = imp.name.decode()
                # Buscar en nombre base (sin sufijo A/W)
                base_name = name.rstrip("AW") if name.endswith(("A", "W")) else name
                for api_name, info in SUSPICIOUS_APIS.items():
                    if api_name in name or api_name in base_name:
                        cat, score = info
                        findings.append((dll, name, cat, score))
                        categories[cat] = categories.get(cat, 0) + score
                        total_score += score

    print("=== Import Analysis Report ===")
    print("File:", filepath)
    print()

    if findings:
        print("APIs sospechosas encontradas:")
        for dll, api, cat, score in sorted(findings, key=lambda x: -x[3]):
            print("  [" + str(score) + "] " + dll + " -> " + api + " (" + cat + ")")

        print()
        print("Score por categoria:")
        for cat, score in sorted(categories.items(), key=lambda x: -x[1]):
            print("  " + cat + ": " + str(score))

        print()
        print("Score total:", total_score)

        if total_score > 30:
            print("VEREDICTO: Altamente sospechoso")
        elif total_score > 15:
            print("VEREDICTO: Sospechoso, requiere analisis dinamico")
        else:
            print("VEREDICTO: Bajo riesgo, pero verificar contexto")
    else:
        print("No se encontraron APIs sospechosas en imports estaticos.")
        print("[!] Verificar resolucion dinamica")

if __name__ == "__main__":
    analyze_imports(sys.argv[1])

Herramientas especializadas

PEStudio: Resalta automaticamente los imports sospechosos con un sistema de puntuacion integrado.

Dependency Walker (depends.exe): Herramienta clasica para visualizar el arbol de dependencias de un PE. Muestra imports y exports de forma jerarquica.

CFF Explorer: Permite navegar la Import Directory en detalle, incluyendo bound imports y delayed imports.

IDA Pro / Ghidra: Resuelven automaticamente la mayoria de imports y muestran las llamadas a APIs en el codigo desensamblado.

x64dbg: En analisis dinamico, permite poner breakpoints en cualquier API para observar parametros y contexto de llamada.

HashDB (OALabs): Plugin para IDA y Ghidra que identifica API hashes conocidos y los resuelve automaticamente.

Conclusion

La Import Address Table es la ventana mas directa a las capacidades de un binario. Cada API importada cuenta parte de la historia: inyeccion, persistencia, comunicacion de red, cifrado, evasion. Pero el malware moderno oculta sus imports reales con resolucion dinamica, API hashing y delayed loading.

El analista efectivo combina la revision de la IAT con la busqueda de patrones de GetProcAddress, constantes de hashing y accesos al PEB. Cuando el analisis estatico llega a su limite, el analisis dinamico con breakpoints en APIs clave completa el cuadro.

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.