Ingeniería inversa de JNI, o cómo Facebook bloquea su propia aplicación
Al parecer, Facebook provoca el cierre inesperado de sus aplicaciones de forma intencionada para probar la reacción de los usuarios y evaluar su apego al servicio de Facebook. Sin embargo, esta publicación no trata del análisis del comportamiento de los usuarios, sino de los aspectos técnicos de cómo se hace, o simplemente de una excusa para adentrarse en la ingeniería inversa de JNI.
Al parecer, Facebook provoca el cierre inesperado de sus aplicaciones de forma intencionada para probar la reacción de los usuarios y evaluar su apego al servicio de Facebook. Sin embargo, esta publicación no trata del análisis del comportamiento de los usuarios, sino de los aspectos técnicos de cómo se hace, o simplemente de una excusa para adentrarse en la ingeniería inversa de JNI.
En primer lugar, ¿qué nos llevó a hablar de JNI a propósito de la función de bloqueo de la aplicación de Facebook? Pues bien, al usar el Ostorlab Mobile Application Security Scanner (es totalmente gratuito y ya puede probarlo) descubrimos los siguientes métodos nativos:

Un método nativo indica que se implementa en código nativo mediante JNI (Java Native Interface).
JNI es, básicamente, la forma de escribir en C/C++ código al que se puede acceder desde Java. Esto suele hacerse por diversas razones, y el rendimiento puede ser una de ellas. Las ventajas de escribir código nativo son principalmente el rendimiento, una mejor ofuscación del código y la posibilidad de acceder a funcionalidades de bajo nivel. Las desventajas son la falta de portabilidad y un mayor riesgo de seguridad.
Por tanto, los métodos nativos son excelentes candidatos desde la perspectiva de un atacante si procesan datos introducidos por el usuario. En el ejemplo de la aplicación de Instagram, los métodos nativos se utilizan para procesar imágenes y vídeos y, por lo tanto, son potencialmente vulnerables a errores de corrupción de memoria.
La ingeniería inversa y el fuzzing de JNI parecen ser un tema poco investigado, a pesar de su importante papel en las aplicaciones móviles para Android.
Volvamos al método crashThisProcess de Facebook: ¿cómo podemos encontrar la implementación del método?
Si consultamos la especificación de Java JNI sobre cómo la JVM resuelve los métodos, esto es lo que dice:

Tras comprobar los símbolos de las bibliotecas buscando la cadena Java_, no encontramos ningún método llamado
crashThisProcess.

La inspección de la VM de OpenJDK confirma la especificación, pero no ofrece más pistas:
Sin embargo, al buscar la cadena crashThisProcess, la encontramos en libbreakpad.so:
Hagamos ingeniería inversa del binario e intentemos entender cómo se hace referencia a esta cadena. Al abrir el binario en IDA y buscar la cadena, la encontramos en la sección .rodata:
Esta cadena es referenciada por la siguiente estructura:
Tras investigar más, descubrimos que la estructura coincide con la estructura JNINativeMethod:
Y este es un código de ejemplo que demuestra cómo podría utilizarse:
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "simplejni native.cpp"
#include <utils/Log.h>
#include <stdio.h>
#include "jni.h"
static jint
add(JNIEnv *env, jobject thiz, jint a, jint b) {
int result = a + b;
ALOGI("%d + %d = %d", a, b, result);
return result;
}
static const char *classPathName = "com/example/android/simplejni/Native";
static JNINativeMethod methods[] = {
{"add", "(II)I", (void*)add },
};
/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
ALOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
ALOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* Register native methods for all classes we know about.
*
* returns JNI_TRUE on success.
*/
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, classPathName,
methods, sizeof(methods) / sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
// ----------------------------------------------------------------------------
/*
* This is called by the VM when the shared library is first loaded.
*/
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
jint result = -1;
JNIEnv* env = NULL;
ALOGI("JNI_OnLoad");
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed");
goto bail;
}
env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
ALOGE("ERROR: registerNatives failed");
goto bail;
}
result = JNI_VERSION_1_4;
bail:
return result;
}
Los métodos parecen registrarse dinámicamente durante la carga, en la función JNI_OnLoad, mediante la función registerNativeMethods. El último elemento apunta al cuerpo del método JNI. Por ejemplo, en el caso del método crashThisProcess, este es el desensamblado del cuerpo del método (el nombre de la subrutina se añadió manualmente):
Podemos ver que la función escribe 0x2A en la dirección de memoria 0x000000, lo que provocará un fallo de segmentación del proceso.
Al experimentar con la identificación automatizada de estas estructuras, usando el formato único de firma de los métodos Java
y comprobando después el resto de la estructura, pudimos escribir un script (disponible en Github) que encuentra los métodos JNI
exportados dinámicamente mediante JNI_OnLoad o estáticamente en el símbolo exportado:
Ostorlab Mobile Application Security Scanner listará estos métodos automáticamente en las próximas versiones.