x64 vs x86: Diferencias Clave para Analisis de Malware
Diferencias fundamentales entre x86 y x64 que afectan al analisis de malware: registros extendidos, convencion de llamada Windows x64, RIP-relative addressing, WoW64, y como adaptar el analisis cuando el sample es de 64 bits.
La transicion a 64 bits en el mundo del malware
La mayoria del malware moderno, especialmente ransomware y APTs, se compila para 64 bits. Las ventajas son claras: acceso a mas de 4 GB de memoria, mas registros disponibles, y el hecho de que muchos EDRs y sandboxes manejan mejor (o solo manejan) binarios de 64 bits. Pero una cantidad significativa de malware, particularmente commodity malware y shellcode, sigue siendo de 32 bits por compatibilidad.
Este articulo cubre las diferencias entre x86 y x64 que afectan directamente al analisis de malware. Si vienes de analizar malware de 32 bits, estas son las adaptaciones que necesitas.
Registros extendidos
De 8 a 16 registros de proposito general
x64 extiende los 8 registros de x86 a 64 bits y anade 8 nuevos:
| x86 | x64 | Nuevos en x64 |
|---|---|---|
| EAX | RAX | R8 |
| EBX | RBX | R9 |
| ECX | RCX | R10 |
| EDX | RDX | R11 |
| ESI | RSI | R12 |
| EDI | RDI | R13 |
| EBP | RBP | R14 |
| ESP | RSP | R15 |
Cada registro nuevo tiene subregistros: R8D (32 bits), R8W (16 bits), R8B (8 bits).
Ademas, x64 permite acceder al byte bajo de RSI, RDI, RSP y RBP (SIL, DIL, SPL, BPL), cosa que no era posible en x86.
Impacto en el analisis
Mas registros significa que el compilador puede mantener mas valores en registros sin recurrir al stack. El codigo de 64 bits tiene menos accesos a memoria para variables locales, lo que hace que el analisis sea ligeramente mas rapido (menos operaciones de stack que seguir) pero requiere prestar atencion a mas registros simultaneamente.
Regla de zero-extension
Cuando escribes en un subregistro de 32 bits (EAX, R8D), los 32 bits superiores del registro de 64 bits se ponen a cero automaticamente. Esto no ocurre con subregistros de 16 u 8 bits:
mov eax, 0x12345678 ; RAX = 0x0000000012345678 (bits 63-32 a cero)
mov ax, 0x1234 ; RAX = 0x0000000012341234 (bits 63-16 NO cambian)
mov al, 0x12 ; RAX = 0x0000000012341212 (bits 63-8 NO cambian)
En malware, esto puede causar bugs sutiles que a veces son intencionales (para esconder valores en los bits altos de un registro).
Convencion de llamada Windows x64
La diferencia mas impactante para el analisis. En x86 hay multiples convenciones (cdecl, stdcall, fastcall). En Windows x64 hay una sola.
Parametros en registros
Los primeros 4 parametros enteros/puntero van en: RCX, RDX, R8, R9 (en ese orden). Los parametros de punto flotante van en XMM0-XMM3. Los demas parametros van en el stack.
; x86 (stdcall): CreateFile con 7 PUSHes
push 0 ; hTemplateFile
push 0x80 ; dwFlagsAndAttributes
push 3 ; dwCreationDisposition
push 0 ; lpSecurityAttributes
push 1 ; dwShareMode
push 0x80000000 ; dwDesiredAccess
push rcx ; lpFileName
call CreateFileA
; x64: CreateFile con MOVs a registros + stack
mov [rsp+0x30], 0 ; hTemplateFile (7mo param, stack)
mov [rsp+0x28], 0x80 ; dwFlagsAndAttributes (6to param, stack)
mov [rsp+0x20], 3 ; dwCreationDisposition (5to param, stack)
xor r9d, r9d ; lpSecurityAttributes = NULL (4to param)
mov r8d, 1 ; dwShareMode (3er param)
mov edx, 0x80000000 ; dwDesiredAccess (2do param)
lea rcx, [rsp+0x40] ; lpFileName (1er param)
call CreateFileW
Shadow space
El caller siempre reserva 32 bytes (4 registros * 8 bytes) en el stack antes de cada CALL, independientemente del numero de parametros. Este espacio se llama "shadow space" o "home space" y el callee puede usarlo para guardar los registros de parametros.
; Prologo tipico de funcion en x64
sub rsp, 0x28 ; 0x20 shadow space + 0x08 para alineacion a 16 bytes
; ... cuerpo de la funcion ...
add rsp, 0x28
ret
El shadow space explica por que ves sub rsp, 0x28 (o 0x38, 0x48) al inicio de casi todas las funciones en 64 bits. El primer 0x20 es shadow space, el resto es para variables locales, y el total se redondea para mantener RSP alineado a 16 bytes.
Alineacion del stack a 16 bytes
En x64, RSP debe estar alineado a 16 bytes antes de un CALL. Despues del CALL (que empuja 8 bytes de direccion de retorno), RSP queda desalineado. El prologo de la funcion corrige esto. Si ves un push rbp (8 bytes) seguido de sub rsp, N donde N es multiplo de 16, o directamente sub rsp, N donde N es impar en multiplos de 8, es por la alineacion.
Registros volatiles y no volatiles
Volatiles (caller-saved): RAX, RCX, RDX, R8, R9, R10, R11. Despues de un CALL, asume que estos registros contienen basura.
No volatiles (callee-saved): RBX, RBP, RDI, RSI, R12, R13, R14, R15. Si una funcion los usa, debe guardarlos y restaurarlos.
En la practica, si ves PUSHes de RBX, RSI, RDI al inicio de una funcion en x64, es porque la funcion los usa y los preserva segun la convencion.
RIP-relative addressing
En x86, las direcciones de datos globales son absolutas:
; x86: direccion absoluta
mov eax, [0x00403000] ; lee de la direccion fija 0x00403000
En x64, el modo por defecto es relativo a RIP:
; x64: relativo a RIP
mov eax, [rip+0x1234] ; lee de la direccion RIP + 0x1234
; El desensamblador muestra: mov eax, [0x00403000] (direccion calculada)
RIP-relative addressing tiene dos consecuencias importantes para el analisis de malware:
El codigo es position independent por defecto. No necesita relocaciones para funcionar en diferentes direcciones base. Esto facilita la escritura de shellcode de 64 bits.
Los desensambladores calculan la direccion efectiva y la muestran directamente. No necesitas hacer la aritmetica mentalmente. Pero si estas leyendo un dump hexadecimal crudo, los offsets en el encoding de la instruccion son relativos, no absolutos.
System V AMD64 ABI (Linux/macOS x64)
Si analizas malware de Linux o macOS en 64 bits, la convencion de llamada es diferente:
| Parametro | Windows x64 | System V AMD64 (Linux) |
|---|---|---|
| 1ro | RCX | RDI |
| 2do | RDX | RSI |
| 3ro | R8 | RDX |
| 4to | R9 | RCX |
| 5to | Stack | R8 |
| 6to | Stack | R9 |
| 7mo+ | Stack | Stack |
| Shadow space | 32 bytes obligatorio | No existe |
| Red zone | No existe | 128 bytes bajo RSP |
La "red zone" de System V es una zona de 128 bytes por debajo de RSP que las funciones leaf (que no llaman a otras funciones) pueden usar sin decrementar RSP. El malware de Linux a veces almacena datos en la red zone.
WoW64: el puente entre 32 y 64 bits
Que es WoW64
Windows on Windows 64 (WoW64) permite ejecutar binarios de 32 bits en Windows de 64 bits. WoW64 proporciona:
- Emulacion de registros de 32 bits
- Traduccion de llamadas al sistema (32 bits a 64 bits)
- Redireccion de System32 (los binarios de 32 bits ven SysWOW64 como System32)
- Redireccion del registro (HKLM\SOFTWARE\WOW6432Node)
Heaven's Gate
Heaven's Gate es una tecnica donde malware de 32 bits ejecuta codigo de 64 bits directamente, saltando la capa WoW64. Esto permite evadir hooks de seguridad colocados en las DLLs de 32 bits:
; Desde proceso de 32 bits
; Cambiar CS (Code Segment) a segmento de 64 bits
push 0x33 ; selector de segmento de 64 bits
push offset code_64bit ; direccion del codigo de 64 bits
retf ; far return: cambia CS y EIP/RIP
; A partir de aqui, el codigo se ejecuta en modo 64 bits
; incluso dentro de un proceso "de 32 bits"
code_64bit:
; Instrucciones de 64 bits (registros RAX, syscalls directos, etc.)
Heaven's Gate dificulta el analisis porque los desensambladores interpretan todo el binario como 32 bits (el PE header dice x86), pero hay secciones que deben leerse como codigo de 64 bits.
Para detectar Heaven's Gate en analisis estatico, busca:
- PUSH 0x33 seguido de RETF
- JMP FAR con selector 0x33
- Segmentos de bytes que no desensamblan correctamente en modo 32 bits
En x64dbg, puedes cambiar el modo de desensamblado de una seccion a 64 bits para verla correctamente.
Syscalls directos (direct syscalls)
En x64, el malware avanzado evita las APIs de Windows completamente y ejecuta syscalls directos al kernel:
; Syscall directo en x64 (equivale a NtAllocateVirtualMemory)
mov r10, rcx ; primer parametro
mov eax, 0x18 ; numero de syscall para NtAllocateVirtualMemory
syscall ; invoca al kernel directamente
Los numeros de syscall cambian entre versiones de Windows (Windows 10 build 1903 vs 21H2 vs 11). El malware que usa syscalls directos necesita detectar la version del SO y usar la tabla de numeros correcta.
Los syscalls directos evaden cualquier hook en ntdll.dll (donde las funciones Nt* residen). Los EDRs que dependen de hooks en user-mode no pueden interceptar syscalls directos. Esta tecnica se ha vuelto muy popular en herramientas como SysWhispers, HellsGate y TartarusGate.
Para detectar syscalls directos en analisis estatico:
- Busca la instruccion SYSCALL (opcode 0F 05)
- Busca MOV EAX, constante seguido de SYSCALL
- Si los numeros de syscall estan hardcodeados, el malware no es portable entre versiones de Windows
- Si hay una tabla o se leen dinamicamente de ntdll.dll, el malware detecta la version en runtime
Diferencias en anti-debug
PEB en x64
Los offsets del PEB cambian:
| Campo | Offset x86 | Offset x64 |
|---|---|---|
| BeingDebugged | PEB+0x02 | PEB+0x02 |
| NtGlobalFlag | PEB+0x68 | PEB+0xBC |
| ProcessHeap | PEB+0x18 | PEB+0x30 |
| Ldr | PEB+0x0C | PEB+0x18 |
Acceso al PEB:
; x86
mov eax, fs:[0x30] ; PEB via TEB
; x64
mov rax, gs:[0x60] ; PEB via TEB (GS en lugar de FS)
Modulos cargados en x64
La lista InMemoryOrderModuleList tambien cambia de offsets:
; x64: encontrar kernel32.dll
mov rax, gs:[0x60] ; PEB
mov rax, [rax+0x18] ; PEB->Ldr
mov rax, [rax+0x20] ; InMemoryOrderModuleList.Flink
mov rax, [rax] ; segundo modulo (ntdll.dll en x64)
mov rax, [rax] ; tercer modulo (kernel32.dll)
mov rax, [rax+0x20] ; DllBase de kernel32.dll
Diferencias en shellcode x64
El shellcode de 64 bits difiere del de 32 bits en varios aspectos:
No necesita GetPC (CALL/POP) porque puede usar LEA con RIP-relative addressing para conocer su propia posicion.
Usa GS en lugar de FS para acceder al TEB/PEB.
Los parametros se pasan en registros, lo que reduce el tamano del shellcode (menos PUSHes).
Debe mantener RSP alineado a 16 bytes antes de cualquier CALL, lo que requiere ajustes de alineacion.
Las direcciones son de 8 bytes, lo que afecta al tamano de buffers y offsets.
Adaptaciones para el analisis
Al pasar de analizar malware de 32 bits a 64 bits:
Busca parametros en RCX, RDX, R8, R9 antes de los CALLs en lugar de PUSHes en el stack.
Observa los registros R8-R15: a menudo contienen valores importantes que el malware usa como almacenamiento intermedio.
Presta atencion a SUB RSP / ADD RSP: el prologo y epilogo en x64 son diferentes al PUSH EBP / MOV EBP, ESP clasico.
Los punteros son de 8 bytes: las estructuras tienen offsets diferentes (por ejemplo, los entries de la IAT son de 8 bytes en lugar de 4).
GS:[0x60] reemplaza a FS:[0x30] para acceder al PEB.
Los desensambladores hacen la mayor parte del trabajo automaticamente, pero conocer estas diferencias te permite detectar errores del desensamblador y entender malware que mezcla codigo de 32 y 64 bits (Heaven's Gate) o que usa syscalls directos.
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.