Conclusiones y condiciones de decisión.

  • Registre dependencias directas y transitivas, puntos de entrada de carga, momento de inicialización, APIs objetivo mínimas y llamadores para cada SO.
  • El descubrimiento estático de nombres JNI y el registro dinámico mediante RegisterNatives tienen restricciones distintas; verifique el método de vinculación real antes de procesar los nombres.
  • arm64-v8a, armeabi-v7a y x86_64 representan artefactos y matrices de compatibilidad distintos; el éxito en una arquitectura no se extrapola a las demás.
  • La aceptación de la fuerza de protección y la estabilidad en tiempo de ejecución deben vincularse al mismo APK final, firma y conjunto de SO.

Mapee primero el grafo de dependencias ELF y la cronología de carga real

Una aplicación Android puede comprender bibliotecas principales, bibliotecas de lógica de negocio, bibliotecas de algoritmos, SDK de terceros y bibliotecas del sistema. El código Java o Kotlin puede invocar directamente System.loadLibrary, mientras que las bibliotecas principales pueden cargar otras bibliotecas mediante dependencias ELF o carga en tiempo de ejecución. El punto de bloqueo final puede residir fuera de la biblioteca protegida, ya que la resolución de símbolos y el orden de inicialización los determina toda la cadena de dependencias.

El grafo de dependencias debe registrar archivos de biblioteca por ABI, relaciones DT_NEEDED, niveles mínimos de API del sistema, puntos de entrada de carga, funciones de inicialización, interfaces exportadas y llamadores. Además, marque si las bibliotecas son de desarrollo propio, dependencias de código abierto o SDK de código cerrado, ya que esto define los límites para la modificación y la responsabilidad de las pruebas de regresión.

La documentación de Android NDK indica que los símbolos nativos suelen resolverse durante la carga de la biblioteca. Si el código hace referencia a una API ausente en el sistema objetivo, dlopen puede fallar inmediatamente, incluso si la ramificación en tiempo de ejecución sugiere que esa ruta de código no se ejecutaría. Por tanto, la versión mínima de NDK API y la versión del sistema del dispositivo deben incluirse en el grafo de dependencias.

Campos mínimos para un grafo de dependencias nativas
CampoQué registrarImpacto en el endurecimientoMétodo de verificación
Biblioteca y origenBiblioteca principal, dependencias transitivas, terceros y bibliotecas del sistemaDetermina el alcance modificable y la responsabilidad de regresiónDesempaquete y verifique frente a cada ABI en el APK final
Punto de entrada de cargaDependencias estáticas, System.loadLibrary o dlopen en tiempo de ejecuciónDetermina la etapa de fallo más temprana y la ubicación del registroRegistre la cronología de inicio y los resultados de carga
Versión mínima de APIAPP_PLATFORM de compilación y símbolos del sistema utilizadosPueden producirse símbolos faltantes en tiempo de carga si superan la API del dispositivoInicio en dispositivo real en sistemas objetivo y verificación de símbolos
Orden de inicializaciónJNI_OnLoad, constructores e inicialización de negocioLos cambios en la disposición del código o el timing pueden amplificar dependencias implícitasCompare cronologías entre compilaciones sin proteger y candidatas a lanzamiento
Excepciones e hilosExcepciones C++, propiedad de hilos, uso de JNIEnvLos errores entre bibliotecas y entre hilos suelen causar bloqueos inmediatosCheckJNI, pilas con símbolos y pruebas de regresión de negocio

El registro estático frente al dinámico define los límites de procesamiento de símbolos

JNI puede descubrir métodos nativos mediante convenciones de nomenclatura o establecer mapeos entre métodos Java y direcciones de función usando RegisterNatives durante la inicialización. El registro estático depende de nombres exportados predecibles; el registro dinámico normalmente requiere exponer solo unos pocos puntos de entrada de inicialización, pero depende de la búsqueda de clases, firmas de métodos, momento de registro y el hilo de carga.

Si el endurecimiento o el procesamiento de símbolos altera los nombres de registro estático, el sistema podría no localizar la implementación nativa. De igual forma, cambios en las firmas de métodos, reglas de retención de nombres de clase u orden de inicialización en la tabla de registro dinámico pueden causar fallos antes de la primera invocación. Simplemente verificar la cantidad de símbolos exportados no prueba que los enlaces JNI sigan siendo correctos.

Las guías oficiales de JNI para Android también advierten que JNIEnv tiene restricciones de hilo; excepciones pendientes, referencias inválidas y uso entre hilos pueden provocar cierres inesperados. Las pruebas de regresión posteriores al endurecimiento deben cubrir hilos reales y rutas de excepción, no solo invocar un método de demostración sin parámetros.

Puntos de control clave para dos métodos de registro JNI
Método de registroDependenciasPuntos sensibles al endurecimientoVerificación mínima
Descubrimiento de nombres estáticosNombre de clase Java, nombre de método, firma y símbolos exportadosCambio de nombres, símbolos ocultos y discrepancias de firmaInvocar cada punto de entrada crítico y verificar errores de enlace
RegisterNativesBúsqueda de clase, firmas de métodos, tabla de registro y momento de inicializaciónProcesamiento de nombres de clase, orden de registro y cambios en direcciones de funciónConfirmar la finalización del registro y ejecutar escenarios reales de entrada/salida
Envoltorios de tercerosReglas internas del SDK e implementaciones de código cerradoAutovalidación, exportaciones implícitas y diferencias de versiónVerificar frente a matrices de soporte del proveedor y escenarios del mundo real
  • Listar mapeos clave de Java a nativo
  • Confirmar método de registro estático o dinámico
  • Mantener reglas necesarias de nombre de clase y firma
  • Verificar excepciones, hilos y ciclos de vida de referencias

La ABI debe aceptarse como artefacto de lanzamiento independiente

La ABI es más que una etiqueta de directorio. La documentación oficial del NDK de Android especifica que la ABI define el conjunto de instrucciones, endianness, convenciones de llamada, uso de pila y registros, formato de archivo ejecutable y manipulación de nombres en C++. arm64-v8a y armeabi-v7a difieren en código máquina y dependencias; aprobar en un emulador x86_64 no representa éxito en dispositivos ARM reales.

Para cada ABI, confirme que existan todas las dependencias directas y transitivas, que el nivel mínimo de API de la biblioteca sea compatible con el sistema objetivo y que las herramientas de empaquetado no hayan eliminado erróneamente archivos para una arquitectura específica. Si solo la biblioteca principal incluye arm64-v8a mientras una dependencia de terceros carece del artefacto correspondiente, la aplicación fallará durante la carga.

Si el producto planea soportar solo un subconjunto de ABIs, declárelo explícitamente en las notas de lanzamiento, configuraciones de tiendas de aplicaciones y alcances de prueba. Las arquitecturas no compilables, no instalables o donde no se ejecutaron rutas JNI críticas deben marcarse como no cubiertas; el éxito de compilación no puede sustituir la evidencia en tiempo de ejecución.

Ejemplo de matriz de aceptación de ABI
Elemento de verificaciónarm64-v8aarmeabi-v7ax86_64Regla de decisión
Todas las dependencias presentesVerificar por bibliotecaVerificar por alcance de lanzamientoVerificar solo si es requeridoBloquear si falta alguna dependencia requerida
Cargable en sistema mínimoDispositivo real con API objetivoDispositivo real con API objetivoEmulador o dispositivoNo validar únicamente en el sistema más reciente
Rutas JNI críticasEntrada/salida realEntrada/salida realNo puede sustituir resultados de ARMRegistrar independientemente para cada ABI lanzada
Atribuibilidad de cierres inesperadosConservar símbolos coincidentesConservar símbolos coincidentesConservar símbolos coincidentesLos símbolos deben corresponder a la misma candidata de lanzamiento

¿Qué condiciones de ejecución nativa afecta la protección?

El ocultamiento de símbolos reduce pistas estáticas, el procesamiento de cadenas disminuye la exposición en texto plano y la virtualización del flujo de control o de funciones altera la forma de ejecución del código seleccionado. Estas técnicas impactan de manera diferente los símbolos exportados, límites de función, desenrollado de excepciones, llamadas indirectas, alineación y rendimiento. Menos símbolos tras el procesamiento no representa una protección nativa completa, ni la capacidad de leer la biblioteca anula todos los efectos de la protección.

La inicialización de arranque, el manejo de señales, las excepciones de C++, los callbacks, el estado local del hilo, la búsqueda reflexiva de símbolos y la autovalidación de SDK de terceros son áreas de alta sensibilidad. Al seleccionar el alcance, priorice las funciones de negocio con entrada/salida clara, frecuencia de llamadas medible y fallos aislables. Evite procesar toda la cadena de inicialización en la primera prueba de concepto (PoC).

Las configuraciones de protección deben registrar la justificación, la fase de llamada, la ABI, las dependencias, el presupuesto de rendimiento y el grupo de reversión para cada función o grupo de funciones. Lo siguiente demuestra únicamente el formato de una lista de verificación pública y segura, excluyendo símbolos internos o sintaxis de configuración del producto.

Formato de registro público seguro para grupos de protección nativa
native_group: protocol-core-v1
abis: [arm64-v8a, armeabi-v7a]
load_phase: post-authentication
jni_registration: dynamic
execution:
  frequency: bounded
  main_thread: false
dependencies:
  - business-runtime
  - system-crypto
acceptance:
  - dependency-resolution
  - jni-registration
  - exception-path
  - output-parity
  - latency-budget
rollback: native-group-v0

Localización de cierres inesperados tras el endurecimiento de SO utilizando la primera diferencia detectada

Al solucionar problemas, primero fije la identidad del archivo, la firma, el dispositivo, el sistema, el método de instalación y la entrada de negocio tanto para la línea base sin protección como para la candidata de lanzamiento. Luego, busque en la cronología la primera diferencia: creación del proceso, inicio de la aplicación, carga de bibliotecas, JNI_OnLoad, finalización del registro, primera invocación nativa o retorno clave de negocio. La última entrada del registro de cierre puede ser simplemente una reacción en cadena.

UnsatisfiedLinkError suele indicar bibliotecas faltantes, incompatibilidades de ABI, problemas de dependencias o errores de resolución de símbolos; los errores de registro pueden manifestarse como métodos nativos faltantes; un JNIEnv incorrecto, referencias inválidas o excepciones pendientes pueden provocar cierres durante invocaciones de negocio. Conserve los símbolos nativos que coincidan con la candidata de lanzamiento para la atribución interna, pero no exponga símbolos reales ni direcciones en informes públicos.

CheckJNI puede ayudar a detectar algunos usos incorrectos de JNI, pero es una herramienta de diagnóstico, no una representación del estado de ejecución en producción, y no puede cubrir todos los problemas de memoria nativa y concurrencia. Tras las correcciones, debe volver a ejecutar las rutas críticas bajo la configuración de lanzamiento objetivo.

Síntomas comunes y siguientes pasos para obtener evidencia
Primer síntomaVerificaciones prioritariasEvidencia requeridaQué no hacer primero
Fallo en dlopenABI, DT_NEEDED, nivel mínimo de API y símbolosLista de bibliotecas y errores de carga que coincidan con la candidata de lanzamientoActivar y desactivar repetidamente interruptores de protección no relacionados
Método nativo no encontradoMétodo de registro, nombre de clase, firma del método y temporizaciónTabla de registro, reglas de retención y puntos de entrada de llamadaObservar únicamente el recuento de símbolos exportados
Cierre en la primera invocaciónParámetros, referencias, hilos, excepciones y procesamiento de funcionesPila simbolizada, entradas y comparación con la línea baseExplicar usando archivos de símbolos de otra versión
Fallo en algunos dispositivosAPI del sistema, diferencias de proveedor, ABI y bibliotecas de tercerosMatriz de sistemas de dispositivos y primeras diferenciasSustituir conclusiones de dispositivos reales por éxito en emuladores

Las conclusiones de lanzamiento deben cubrir robustez, compatibilidad y mantenibilidad

La observación de robustez estática puede registrar cambios en símbolos, cadenas, estructura de código y la superficie de exposición de puntos de entrada clave; la aceptación en tiempo de ejecución verifica la carga, JNI, lógica de negocio crítica, excepciones y ABIs objetivo. Estos dos tipos de evidencia se complementan; ninguno puede reemplazar al otro.

La candidata de lanzamiento final también debe completar actualizaciones de instalación, verificación de firmas, comprobaciones de canal, pruebas de arranque, monitoreo de cierres, archivado de símbolos nativos y simulacros de reversión. Los archivos de símbolos deben permitir encontrar la versión coincidente mediante la identidad del archivo; de lo contrario, los cierres nativos en línea no podrán simbolizarse correctamente.

Este artículo proporciona un marco de inspección de ingeniería, no conclusiones de prueba para un SO o configuración de protección específicos. Sin un paquete objetivo, una ABI objetivo y rutas de llamada reales, solo se puede evaluar si el trabajo de preparación está completo, sin poder prometer compatibilidad o rendimiento.

  • Cada ABI lanzada cuenta con evidencia de tiempo de ejecución
  • Los puntos de entrada críticos de JNI cubren rutas normales y de excepción
  • Los símbolos nativos coinciden con la identidad de la candidata final
  • Los resultados de carga, negocio y reversión son reproducibles
  • Los dispositivos y sistemas no cubiertos están restringidos explícitamente

Límites de evidencia y aplicabilidad

Esta sección separa los datos documentados de la plataforma, los juicios de ingeniería y los límites que no se pueden generalizar en afirmaciones de productos no verificadas.

Sentencia del artículoBase de hecho o ingenieríaLímite de aplicabilidad
La ABI debe aceptarse como una dimensión independiente de los artefactos.El NDK oficial de Android define la ABI abarcando conjuntos de instrucciones, convenciones de llamada, registros, pila, formato ELF y manipulación de nombres.Las definiciones de la documentación no prueban que la aplicación incluya dependencias completas o se ejecute correctamente en los dispositivos objetivo.
Las referencias a API nativas superiores al sistema del dispositivo pueden fallar durante la carga.La documentación de problemas comunes del NDK indica que los símbolos suelen resolverse al cargar la biblioteca; hacer referencia a API inexistentes no puede evitarse mediante bifurcación en tiempo de ejecución.Los fallos específicos deben confirmarse mediante parámetros de compilación, dependencias y registros del dispositivo.
Los errores de JNI requieren validación a nivel de hilo, referencia y excepción.Las directrices de JNI de Android enumeran problemas como JNIEnv inválido, referencias incorrectas, excepciones pendientes y registro de métodos que pueden provocar bloqueos.CheckJNI solo ayuda a detectar un subconjunto de problemas y no puede sustituir las pruebas exhaustivas de lógica de negocio y seguridad de memoria.
Los resultados de un único emulador x86_64 no pueden extrapolarse a dispositivos reales ARM.Diferentes ABIs utilizan distintos conjuntos de instrucciones y convenciones de llamada; las combinaciones finales de bibliotecas y dependencias también pueden variar.Si el producto explícitamente no publica una determinada ABI, puede marcarse como no aplicable en lugar de someterla a pruebas forzadas.
La fortaleza de la protección y la compatibilidad deben cerrarse en la misma candidata de lanzamiento.Recompilar o reemplazar archivos SO modifica el código, los símbolos, las dependencias y el comportamiento potencial en tiempo de ejecución.Este es un principio de gobernanza de artefactos y no implica que se haya aprobado ningún nivel específico de protección.

Preguntas de ingeniería

Si solo hay un SO principal, ¿sigue siendo necesario un gráfico de dependencias?

Sí. El SO principal puede seguir dependiendo de bibliotecas del sistema, tiempos de ejecución de C++ o bibliotecas de terceros, e interactuar con la capa Java mediante JNI. El gráfico de dependencias confirma el punto de carga más temprano y los límites de responsabilidad.

Si arm64-v8a pasa las pruebas, ¿todavía necesito probar armeabi-v7a?

Si el paquete de lanzamiento incluye y soporta armeabi-v7a, se requiere verificación independiente. Las instrucciones, convenciones de llamada y artefactos de dependencia de las dos ABIs difieren y no pueden sustituirse entre sí.

¿Ocultar todos los símbolos exportados es más seguro?

No necesariamente. Los puntos de entrada estáticos de JNI necesarios, las llamadas de terceros y las convenciones del sistema pueden depender de símbolos exportados. Confirme primero los métodos de registro y llamada, luego minimice la exposición.

¿Puedo lanzar si CheckJNI no reporta errores?

No, esto por sí solo es insuficiente para el lanzamiento. También debe verificar la configuración real del objetivo, las entradas de negocio, las rutas de excepción, la ABI, el alcance del sistema, las actualizaciones de instalación y las capacidades de reversión.

¿Debo desactivar toda la protección inmediatamente después de un bloqueo en el endurecimiento de un SO?

Primero fije la identidad de la candidata y encuentre la diferencia más temprana. Puede realizar una búsqueda binaria en grupos de protección o revertir, pero cambie solo una variable registrable a la vez para evitar generar nuevos artefactos no atribuibles.

¿Quieres probar esto en tu propia aplicación?

Envíe la versión candidata, los sistemas de destino y las rutas comerciales críticas para una prueba de concepto de Yudun y una evaluación de compatibilidad.

Continuar con: Lista de verificación de compatibilidad nativa y endurecimiento de SO