Bibliotecas nativas para Android / Sudo Null IT News

En este artículo veremos cómo trabajar con bibliotecas nativas que pueden estar contenidas en aplicaciones de Android. Las bibliotecas nativas son códigos que un desarrollador ha escrito y luego compilado para una arquitectura informática específica. La mayoría de las veces este código está escrito en C o C++. Las razones más comunes por las que un desarrollador podría hacer esto son operaciones matemáticamente complejas o que requieren mucho tiempo, como trabajar con bibliotecas de gráficos.

Vale la pena señalar que los desarrolladores de malware también han comenzado a migrar al código nativo, ya que la ingeniería inversa de los binarios compilados es generalmente menos común que el análisis de códigos de bytes DEX. Esto se debe en gran medida al hecho de que el código de bytes DEX se puede descompilar en Java, mientras que el código compilado nativo a menudo debe analizarse como lenguaje ensamblador.

En este artículo hablaremos sobre el uso de librerías nativas para ARM y x86, para que cada usuario pueda elegir la arquitectura con la que le conviene más trabajar.

Introducción a la interfaz nativa de Java

JNI (Java Native Interface) es una interfaz de programación en el lenguaje de programación Java que le permite interactuar con aplicaciones y bibliotecas escritas en otros lenguajes de programación, como C, C++ o ensamblador. Esta interfaz permite a los desarrolladores declarar métodos Java que se implementan en código nativo (normalmente también compilados en C/C++). La interfaz JNI no es específica de Android, pero generalmente está disponible para aplicaciones Java que se ejecutan en varias plataformas.

El kit de desarrollo nativo de Android (NDK) es un conjunto de herramientas de Android diseñadas específicamente para JNI. Este conjunto de herramientas permite a los desarrolladores escribir código en C y C++ para sus aplicaciones de Android.

Juntos, JNI y NDK permiten a los desarrolladores de Android implementar algunas de las funciones de sus aplicaciones en código nativo. El código Java (o Kotlin) llamará a un método nativo declarado en Java, que se implementa en una biblioteca nativa compilada.

La ventaja más importante de JNI es que no impone restricciones a la implementación de la máquina virtual Java subyacente. Por lo tanto, los proveedores de Java VM pueden agregar soporte JNI sin afectar otras partes de la máquina virtual. Los programadores pueden escribir una versión de su propia aplicación o biblioteca y esperar que funcione con todas las máquinas virtuales Java habilitadas para JNI.

Analizando bibliotecas nativas de Android

A continuación, nos centraremos en cómo aplicar ingeniería inversa a la funcionalidad de la aplicación que se implementó en las bibliotecas nativas de Android. En primer lugar, comprendamos a qué nos referimos cuando decimos “bibliotecas nativas de Android”.

Si observamos la estructura de una aplicación de Android, las bibliotecas nativas de Android se incluyen en archivos APK como .so, bibliotecas de objetos compartidos, en formato de archivo ELF. Si ha analizado archivos binarios de Linux antes, este es el mismo formato.

Estas bibliotecas se incluyen de forma predeterminada en el archivo APK en la ruta del archivo /lib//lib.so. Esta es la ruta predeterminada, pero los desarrolladores también pueden incluir la biblioteca integrada en /assets/ si así lo desean.

Debido a que el código nativo se compila para procesadores específicos, si un desarrollador quiere que su aplicación se ejecute en más de un tipo de hardware, debe incluir cada una de esas versiones de la biblioteca nativa compilada en la aplicación. La ruta predeterminada anterior incluye un directorio para cada tipo de procesador admitido oficialmente por Android.

A continuación se muestran las rutas de directorio predeterminadas para diferentes procesadores:

  • CPU: ruta de biblioteca nativa

  • “generic” 32-bit ARM — lib/armeabi/libcalc.so

  • x86 — lib/x86/libcalc.so

  • x64 — lib/x86_64/libcalc.so

  • ARMv7 — lib/armeabi-v7a/libcalc.so

  • ARM64 — lib/arm64-v8a/libcalc.so

Ejecutando el código

Antes de que una aplicación de Android pueda llamar y ejecutar cualquier código implementado en su biblioteca nativa, la aplicación (código Java) debe cargar la biblioteca en la memoria. Hay dos llamadas API diferentes para esto:

System.loadLibrary("calc")

o

System.load("lib/armeabi/libcalc.so")

La diferencia entre las dos llamadas API presentadas es que LoadLibrary solo toma el nombre corto de la biblioteca como argumento (es decir, libcalc.so = “calc” & libinit.so = “init”) y el sistema determinará correctamente la arquitectura en la que se está ejecutando en este momento y, por lo tanto, es el archivo correcto a utilizar. Por otro lado, para la descarga se requiere la ruta completa a la biblioteca. Esto significa que depende del desarrollador de la aplicación determinar la arquitectura y, por lo tanto, el archivo de biblioteca correcto a cargar.

Cuando el código Java llama a una de estas dos API (LoadLibrary o load), la biblioteca nativa pasada como argumento ejecuta su JNI_OnLoad, si tuviera uno implementado.

Como recordatorio, antes de ejecutar cualquier método nativo, debe cargar su biblioteca llamando a System.LoadLibrary o System.load en su código Java. Cuando se ejecuta cualquiera de estas dos API, también se ejecuta la función JNI_OnLoad en la biblioteca nativa.

Conectando Java y código nativo

Ahora veamos las interacciones entre Java y las bibliotecas nativas. Para ejecutar una función desde una biblioteca nativa, debe haber un método declarado en Java que pueda llamarse en código Java. Cuando se llama a este método, se ejecuta la función “par” de la biblioteca nativa (ELF/.so).

El método nativo de Java declarado aparece en el código, como se muestra a continuación. Se parece a cualquier otro método Java, excepto que incluye la palabra clave nativa y no contiene código en su implementación, ya que su código en realidad está en la biblioteca nativa compilada.

public native String doThingsInNativeLibrary(int var0);

En teoría, para llamar a este método nativo, el código Java debe llamarlo de la misma manera que cualquier otro método Java. Pero en el backend de JNI y NDK, debes ejecutar la función correspondiente en tu propia biblioteca. Para ello, debe conocer el mapeo entre un método nativo declarado en Java y una función en la biblioteca nativa.

Hay 2 formas diferentes de realizar este emparejamiento o vinculación:

  • Enlace dinámico utilizando la resolución de nombres de métodos nativos JNI

  • Enlace estático mediante la llamada API de RegisterNatives

Consideremos estos métodos con más detalle.

Enlace dinámico

Para vincular dinámicamente un método nativo declarado en Java y una función en una biblioteca nativa, el desarrollador nombra el método y la función de acuerdo con las especificaciones para que el sistema JNI pueda realizar el enlace dinámicamente.

Según la especificación, el desarrollador debe nombrar la función de la siguiente manera para que el sistema pueda asociar dinámicamente su propio método y función. El nombre de su propio método se compone de los siguientes componentes:

  • prefijo java_

  • nombre completo de la clase

  • delimitador de guión bajo (“_”)

  • nombre del método.

  • Los métodos nativos anulados utilizan dos guiones bajos (“__”) seguidos de la firma del argumento.

Para realizar enlaces dinámicos para el siguiente método nativo declarado en Java y asumir que está en la clase com.android.interesting.Stuff

 public native String doThingsInNativeLibrary(int var0);

Una función en una biblioteca nativa se puede llamar de la siguiente manera

Java_com_android_interesting_Stuff_doThingsInNativeLibrary

Si la biblioteca nativa no tiene una función con ese nombre, esto significa que la aplicación debe realizar un enlace estático, lo cual se analiza a continuación.

Enlace estático

Si un desarrollador no quiere o no puede nombrar funciones nativas de acuerdo con la especificación (por ejemplo, quiere eliminar símbolos de depuración), entonces debe usar enlaces estáticos con la API de RegisterNatives para hacer interfaz entre un método nativo declarado en Java y una función en el biblioteca nativa. La función RegisterNatives se llama desde código nativo en lugar de código Java, y se llama con mayor frecuencia en la función JNI_OnLoad porque RegisterNatives debe ejecutarse antes de llamar al método nativo declarado en Java.

 jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);

 

typedef struct { 

    char *name; 

    char *signature; 

    void *fnPtr; 

} JNINativeMethod;

Por lo tanto, en Java se utilizan enlaces estáticos y dinámicos para determinar qué método se llamará durante la ejecución del programa. El enlace estático se produce en el momento de la compilación del código según el tipo de variable o referencia, mientras que el enlace dinámico se produce en el momento de la ejecución del programa.

Conclusión

Los métodos presentados en el artículo le permiten ampliar la funcionalidad de las aplicaciones de Android utilizando código nativo. Como resultado, las aplicaciones pueden realizar tareas más complejas o que consumen muchos recursos.

Puede actualizar sus habilidades de desarrollo de aplicaciones de Android al nivel medio/senior en Curso online «Desarrollador Android. Profesional” bajo la dirección de expertos de la industria.

Publicaciones Similares

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *