Intermedioensambladorarmmalware-moviliotreverse-engineering

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.

MalwareIntel Research··12 min lectura
Serie: Lenguaje Ensamblador — Parte 9

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:

RegistroNombreFuncion
R0-R3ArgumentosParametros de funcion y valor de retorno (R0)
R4-R11VariablesRegistros preservados por el callee
R12IPIntra-Procedure scratch register
R13SPStack Pointer
R14LRLink Register (direccion de retorno)
R15PCProgram 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:

RegistroFuncion
X0-X7Argumentos y retorno
X8Registro de retorno indirecto
X9-X15Temporales (caller-saved)
X16-X17IP0, IP1 (intra-procedure)
X18Platform register (reservado en algunos OS)
X19-X28Callee-saved
X29Frame Pointer (FP)
X30Link Register (LR)
SPStack Pointer (no es un registro general)
PCProgram 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.