ARM Assembly: Fundamentos para Malware Movil e IoT
Guia de arquitectura ARM para analistas de malware. Diferencias con x86, registros, modos de ejecucion, instrucciones Thumb, y como se manifiesta el malware en plataformas moviles Android/iOS y dispositivos IoT.
Por que ARM importa para el analisis de malware
Cuando pensamos en malware, la mente va directamente a binarios PE de Windows corriendo en x86/x64. Pero el panorama ha cambiado. Android domina el mercado movil con procesadores ARM. Los dispositivos IoT, desde routers domesticos hasta infraestructura industrial, ejecutan variantes de ARM. Y Apple ha migrado sus Mac a ARM con Apple Silicon.
El resultado: una porcion creciente del malware que llega a los equipos de respuesta son binarios ELF para ARM. Sin conocer esta arquitectura, el analista queda ciego ante botnets IoT, troyanos bancarios Android y amenazas emergentes contra Apple Silicon.
Este articulo cubre los fundamentos que necesitas para leer ensamblador ARM con contexto defensivo.
ARM vs x86: diferencias fundamentales
Filosofia de diseno
x86 es CISC (Complex Instruction Set Computing). Una sola instruccion puede acceder a memoria, operar sobre el dato y almacenar el resultado. Las instrucciones tienen longitud variable (1 a 15 bytes).
ARM es RISC (Reduced Instruction Set Computing). Las instrucciones son de longitud fija (4 bytes en modo ARM, 2 bytes en Thumb). Solo las instrucciones LDR y STR acceden a memoria. Las operaciones aritmeticas y logicas trabajan exclusivamente con registros.
; x86: una instruccion accede a memoria y opera
add eax, [ebx+0x10] ; lee memoria y suma en un paso
; ARM: se necesitan dos instrucciones
LDR R0, [R1, #0x10] ; paso 1: cargar de memoria a registro
ADD R0, R0, R2 ; paso 2: sumar registros
Ejecucion condicional
Una caracteristica unica de ARM es la ejecucion condicional. Casi cualquier instruccion puede llevar un sufijo de condicion que determina si se ejecuta segun los flags del CPSR (Current Program Status Register).
; x86: necesita un salto condicional
cmp eax, 0
je skip
add ebx, 1
skip:
; ARM: ejecucion condicional sin salto
CMP R0, #0
ADDNE R1, R1, #1 ; ADD solo si Not Equal (Z=0)
Esto hace que el codigo ARM tenga menos saltos condicionales, lo que complica ligeramente el analisis de flujo de control pero produce grafos mas lineales.
Registros ARM
ARMv7 (32 bits)
ARM tiene 16 registros de 32 bits accesibles en modo usuario:
| Registro | Nombre | Funcion |
|---|---|---|
| R0-R3 | Argumentos | Parametros de funcion y valor de retorno (R0) |
| R4-R11 | Variables | Registros preservados por el callee |
| R12 | IP | Intra-Procedure scratch register |
| R13 | SP | Stack Pointer |
| R14 | LR | Link Register (direccion de retorno) |
| R15 | PC | Program Counter |
El CPSR (Current Program Status Register) contiene los flags de condicion: N (negativo), Z (zero), C (carry), V (overflow).
La diferencia mas notable respecto a x86: el Program Counter (PC) es un registro accesible. Puedes leerlo y escribirlo directamente. Esto tiene implicaciones para el analisis de malware porque permite saltos calculados que no usan instrucciones de branch explicitas.
; Salto calculado modificando PC directamente
LDR PC, [R0, R1, LSL #2] ; PC = memoria[R0 + R1*4]
; Equivale a un jump table sin instruccion de branch
AArch64 (64 bits)
ARMv8 en modo 64 bits amplia significativamente:
| Registro | Funcion |
|---|---|
| X0-X7 | Argumentos y retorno |
| X8 | Registro de retorno indirecto |
| X9-X15 | Temporales (caller-saved) |
| X16-X17 | IP0, IP1 (intra-procedure) |
| X18 | Platform register (reservado en algunos OS) |
| X19-X28 | Callee-saved |
| X29 | Frame Pointer (FP) |
| X30 | Link Register (LR) |
| SP | Stack Pointer (no es un registro general) |
| PC | Program Counter (no accesible directamente) |
En AArch64 el PC ya no es directamente accesible como registro general. Esto elimina los saltos mediante escritura a PC que veias en ARMv7, simplificando el analisis estatico.
Thumb y Thumb-2
Por que existe Thumb
El modo Thumb reduce las instrucciones a 16 bits. Esto significa que el mismo programa ocupa aproximadamente la mitad de espacio en memoria. Para dispositivos con poca flash o cache, esto es critico.
El malware movil se beneficia: binarios mas pequenos se descargan mas rapido y levantan menos sospechas por tamano.
Thumb-2
Thumb-2 es una extension que mezcla instrucciones de 16 y 32 bits. Es el modo por defecto en procesadores Cortex-A modernos (los que usan Android e iOS). El compilador puede elegir la instruccion de 16 o 32 bits segun la complejidad de la operacion.
; Thumb-2: mezcla de anchos
MOVS R0, #1 ; 16 bits
MOVW R0, #0x1234 ; 32 bits (valor mayor que 255)
ADD R0, R1, R2 ; 16 bits
ADDW R0, R1, #0x800 ; 32 bits (inmediato mayor que 7 bits)
Implicacion para el analisis
Al abrir un binario ARM en Ghidra o IDA, el desensamblador necesita saber si una region de codigo esta en modo ARM o Thumb. Si seleccionas el modo equivocado, veras instrucciones incoherentes.
Indicadores del modo:
- El bit 0 de la direccion de destino en un branch: si es 1, el destino esta en Thumb mode. Si es 0, en ARM mode.
- El registro CPSR flag T indica el modo actual.
- En binarios Android NDK, el codigo nativo suele estar en Thumb-2 por defecto.
En Ghidra, puedes cambiar el modo de una region: click derecho sobre la direccion, "Set Register Values", y cambiar el flag TMode.
Instrucciones ARM esenciales para analisis de malware
Carga y almacenamiento
LDR R0, [R1] ; R0 = memoria[R1]
LDR R0, [R1, #0x10] ; R0 = memoria[R1 + 16]
LDR R0, [R1, R2] ; R0 = memoria[R1 + R2]
LDR R0, [R1, R2, LSL #2] ; R0 = memoria[R1 + R2*4]
STR R0, [R1] ; memoria[R1] = R0
LDRB R0, [R1] ; cargar byte
LDRH R0, [R1] ; cargar halfword (16 bits)
; Post-indexado (actualiza R1 despues)
LDR R0, [R1], #4 ; R0 = memoria[R1]; R1 = R1 + 4
; Pre-indexado con writeback
LDR R0, [R1, #4]! ; R1 = R1 + 4; R0 = memoria[R1]
El post-indexado y pre-indexado son comunes en bucles de copia de memoria, algo que veras frecuentemente en shellcode ARM.
Aritmetica y logica
ADD R0, R1, R2 ; R0 = R1 + R2
SUB R0, R1, #10 ; R0 = R1 - 10
MUL R0, R1, R2 ; R0 = R1 * R2
AND R0, R1, R2 ; R0 = R1 AND R2
ORR R0, R1, R2 ; R0 = R1 OR R2
EOR R0, R1, R2 ; R0 = R1 XOR R2 (EOR = Exclusive OR)
LSL R0, R1, #3 ; R0 = R1 shift left 3
LSR R0, R1, #3 ; R0 = R1 shift right 3 (logico)
Nota: XOR en ARM se llama EOR (Exclusive OR). Cuando busques patrones de cifrado XOR en malware ARM, busca EOR, no XOR.
Branches (saltos)
B label ; salto incondicional
BL funcion ; branch with link (call): guarda retorno en LR
BX R0 ; branch and exchange: salta a R0, cambia modo ARM/Thumb
BLX funcion ; branch, link and exchange
BEQ label ; branch if equal (Z=1)
BNE label ; branch if not equal (Z=0)
BGT label ; branch if greater than
BLT label ; branch if less than
BX y BLX son criticos: permiten cambiar entre modo ARM y Thumb segun el bit 0 de la direccion.
System calls (syscalls)
En Linux ARM, las syscalls se invocan con SVC (Supervisor Call, antes SWI):
; Syscall write en ARM Linux
MOV R7, #4 ; syscall number: write
MOV R0, #1 ; fd: stdout
LDR R1, =msg ; buffer
MOV R2, #13 ; length
SVC #0 ; invocar syscall
; AArch64 usa registros X
MOV X8, #64 ; syscall number: write (diferente en aarch64)
MOV X0, #1 ; fd
LDR X1, =msg ; buffer
MOV X2, #13 ; length
SVC #0
El shellcode ARM para Linux usa SVC extensivamente. Los numeros de syscall difieren entre ARMv7 y AArch64.
Malware Android: ARM en la practica
Estructura de un APK con codigo nativo
El malware Android sofisticado incluye librerias nativas (archivos .so) dentro del APK:
malware.apk
lib/
armeabi-v7a/ (ARM 32-bit)
libpayload.so
arm64-v8a/ (ARM 64-bit)
libpayload.so
x86/ (emulador)
libpayload.so
Estas librerias .so son binarios ELF que contienen el codigo critico del malware: descifrado de payloads, anti-analisis, comunicacion con C2. El codigo Java/Kotlin actua como wrapper que invoca las funciones nativas via JNI (Java Native Interface).
Patron JNI en malware
// El malware expone funciones JNI que Java invoca
JNIEXPORT jstring JNICALL
Java_com_app_Utils_decrypt(JNIEnv *env, jobject obj, jbyteArray data)
En ensamblador ARM, estas funciones siguen la convencion JNI:
; R0 = JNIEnv* (puntero a tabla de funciones JNI)
; R1 = jobject (this)
; R2 = jbyteArray (primer argumento real)
; El malware accede a funciones JNI a traves de la vtable
LDR R3, [R0] ; R3 = puntero a tabla JNI
LDR R3, [R3, #0x2C0] ; R3 = GetByteArrayElements (offset en tabla)
BLX R3 ; llamar a la funcion JNI
Tecnicas anti-analisis en ARM Android
El malware movil usa varias tecnicas especificas de ARM:
Deteccion de emulador: lee registros del coprocesador o comprueba instrucciones especificas de hardware real.
; Leer MIDR_EL1 (Main ID Register) en AArch64
MRS X0, MIDR_EL1
; En emulador QEMU, devuelve valores diferentes al hardware real
Codigo auto-modificable: escribe instrucciones en memoria ejecutable en runtime.
; Escribir una instruccion NOP sobre un check de seguridad
MOV R0, #0xE1A00000 ; NOP en ARM (MOV R0, R0)
STR R0, [R1] ; sobrescribir instruccion en R1
; Flush de cache necesario en ARM (no coherente como x86)
MCR p15, 0, R0, c7, c5, 0 ; invalidar I-cache
A diferencia de x86, ARM tiene caches de instrucciones y datos separadas. El malware que se auto-modifica necesita invalidar la cache de instrucciones explicitamente, lo que es una senal detectable.
Malware IoT: Mirai y derivados
Compilacion cruzada
El malware IoT compila el mismo fuente para multiples arquitecturas. Un servidor de build tipico de Mirai genera binarios para:
- armv4l, armv5l, armv6l, armv7l (ARM 32-bit, diferentes versiones)
- aarch64 (ARM 64-bit)
- mipsel, mipseb (MIPS little/big endian)
- x86, x86_64
- powerpc, sparc, sh4
Patrones comunes en botnets ARM
Eliminacion de competidores: el bot mata otros procesos de malware para monopolizar el dispositivo.
; Patron tipico: iterar /proc buscando otros bots
; Leer /proc/[pid]/cmdline y comparar con nombres conocidos
MOV R7, #5 ; syscall: open
LDR R0, =procpath ; "/proc/123/cmdline"
MOV R1, #0 ; O_RDONLY
SVC #0 ; abrir archivo
MOV R7, #3 ; syscall: read
MOV R1, R4 ; buffer
MOV R2, #256 ; bytes a leer
SVC #0
Persistencia limitada: muchos dispositivos IoT usan sistemas de archivos en RAM. El malware solo necesita sobrevivir hasta el siguiente reinicio, ya que escanea y reinfecta dispositivos constantemente.
Escaneo de red: funcionalidad central de botnets como Mirai. Genera IPs aleatorias y prueba credenciales por defecto via Telnet/SSH.
Herramientas para analisis ARM
Ghidra
Ghidra soporta ARM y AArch64 nativamente. Al importar un binario ELF ARM, selecciona el procesador correcto:
- ARM:LE:32:v7 para ARMv7 (la mayoria de malware IoT y Android 32-bit)
- AARCH64:LE:64:v8A para AArch64 (Android moderno, Apple Silicon)
Recuerda configurar el modo Thumb si es necesario (TMode register).
IDA Pro
IDA detecta automaticamente ARM vs Thumb en la mayoria de los casos. Para regiones mal detectadas, usa Alt+G para cambiar el registro T (0 = ARM, 1 = Thumb).
QEMU para ejecucion dinamica
QEMU permite ejecutar binarios ARM en una maquina x86:
# Ejecucion de un binario ARM estatico
qemu-arm ./malware_sample
# Con strace para ver syscalls
qemu-arm -strace ./malware_sample
# AArch64
qemu-aarch64 ./malware_sample_64
Para analisis dinamico mas profundo, QEMU en modo sistema emula un sistema ARM completo donde puedes ejecutar el malware con las herramientas de depuracion habituales.
radare2 / rizin
# Abrir binario ARM
r2 -a arm -b 32 malware.elf
# Analisis automatico
aaa
# Ver funciones
afl
# Cambiar a modo Thumb
e asm.bits=16
Ejercicio defensivo: identificar arquitectura de un binario
Cuando recibes una muestra desconocida, el primer paso es identificar la arquitectura:
# file identifica arquitectura y endianness
file sample.elf
# ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)
# readelf para mas detalles
readelf -h sample.elf
# Machine: ARM
# Flags: 0x5000400, Version5 EABI, hard-float ABI
# Para binarios Android .so
unzip -l malware.apk | grep lib/
# lib/armeabi-v7a/libhook.so
# lib/arm64-v8a/libhook.so
Conclusiones para el analista
ARM no es mas dificil que x86, es diferente. Las instrucciones son mas regulares y predecibles. La ejecucion condicional reduce los saltos. Los registros tienen nombres mas intuitivos.
Lo que cambia es el contexto: en lugar de APIs de Windows, trabajas con syscalls de Linux, JNI de Android, o frameworks de iOS. En lugar de PE, analizas ELF o Mach-O.
Con el crecimiento del malware movil y las botnets IoT, ARM ya no es una habilidad opcional para el analista de malware. Es una necesidad operativa.
En el siguiente articulo de la serie, llevaremos estos conocimientos a la practica con Ghidra, aprendiendo a navegar el desensamblado y el decompilador para analizar binarios reales.
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.