Intermedioensambladorx64x86reverse-engineeringwindows

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.

MalwareIntel Research··10 min lectura
Serie: Lenguaje Ensamblador — Parte 8

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:

x86x64Nuevos en x64
EAXRAXR8
EBXRBXR9
ECXRCXR10
EDXRDXR11
ESIRSIR12
EDIRDIR13
EBPRBPR14
ESPRSPR15

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:

ParametroWindows x64System V AMD64 (Linux)
1roRCXRDI
2doRDXRSI
3roR8RDX
4toR9RCX
5toStackR8
6toStackR9
7mo+StackStack
Shadow space32 bytes obligatorioNo existe
Red zoneNo existe128 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:

CampoOffset x86Offset x64
BeingDebuggedPEB+0x02PEB+0x02
NtGlobalFlagPEB+0x68PEB+0xBC
ProcessHeapPEB+0x18PEB+0x30
LdrPEB+0x0CPEB+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

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.