Aplicaciones de Android de ingeniería inversa para eludir las capacidades de detección de raíz

Los fabricantes de teléfonos inteligentes envían dispositivos Android con un conjunto estricto de permisos y sistemas de control de acceso para proteger a los usuarios de los riesgos de seguridad y evitar que dañen accidentalmente sus dispositivos. Sin embargo, estos sistemas pueden parecer restrictivos para los usuarios que deseen personalizar su dispositivo de una manera no prevista por el fabricante.

Para obtener acceso completo a un dispositivo Android, debe estar rooteado. Tener acceso root a un dispositivo proporciona los siguientes beneficios, entre otros:

  • Instalación de aplicaciones móviles que se encuentran en tiendas de aplicaciones de terceros
  • Aplicaciones de prueba
  • Aplicación de temas/máscaras personalizados para aplicaciones y la pantalla de inicio
  • Mejorar la duración de la batería
  • Mejora del rendimiento
  • Modificación del comportamiento de las aplicaciones móviles en el dispositivo

Tener acceso raíz a los dispositivos Android es un componente necesario de las pruebas de seguridad de aplicaciones móviles en NowSecure. Sin él, los investigadores de seguridad no podrían obtener información sobre el funcionamiento interno de las aplicaciones móviles tan fácilmente.

¿Qué es la detección de raíz?

Cuando los usuarios tienen acceso de raíz, pueden manipular cada parte del dispositivo. Si bien no todos los usuarios de un dispositivo rooteado pueden tener intenciones maliciosas, algunos desarrolladores no quieren permitir que los dispositivos rooteados usen sus aplicaciones móviles. Permitir que una aplicación móvil se ejecute en un dispositivo rooteado la abre a una amplia variedad de vulnerabilidades. Las aplicaciones móviles confidenciales, como las bancarias, médicas, comerciales y gubernamentales, a menudo implementan comprobaciones que determinan si la aplicación se ejecuta en un dispositivo con privilegios de root. En la mayoría de los casos, estas comprobaciones impedirán que la aplicación móvil funcione correctamente. A veces, los desarrolladores implementan capacidades estrictas de detección de raíces que evitan que las aplicaciones se ejecuten en dispositivos rooteados.

A menudo es posible omitir la detección de raíz, sin embargo, la cantidad de habilidad que se requiere puede variar mucho según la aplicación móvil. Hay muchas técnicas de detección de raíz que se pueden implementar. Algunas aplicaciones usan controles básicos que se pueden encontrar fácilmente en línea, mientras que otras pueden usar métodos de detección personalizados que nunca antes se habían visto. Debido a que la mayoría de la lógica de detección de raíz se ejecuta directamente en un dispositivo, estas técnicas a menudo se pueden descubrir mediante ingeniería inversa.

A través de una combinación de análisis estático y dinámico, puede descubrir qué está haciendo la detección raíz, cuándo se llama y cómo eludir las comprobaciones. Los pasos no serán exactamente los mismos para todas las aplicaciones, sin embargo, el proceso para eludir la detección de raíz es similar en la mayoría de los casos.

Tutorial para aplicaciones de Android de ingeniería inversa

Ser capaz de eludir la detección de raíz es una habilidad invaluable que todos los investigadores de seguridad móvil y los evaluadores de penetración móviles deben tener en su conjunto de herramientas.

Este tutorial cubre los pasos para aplicar ingeniería inversa a las aplicaciones de Android y omitir tres técnicas comunes de detección de raíces usando Frida. Este tutorial utiliza una aplicación de prueba rudimentaria, pero las mismas técnicas se aplican a las aplicaciones móviles del mundo real. Puedes descargar la aplicación de prueba aquí:

requisitos previos

  • Un dispositivo Android rooteado
  • Una aplicación con detección de raíz
  • JADX-GUI
  • Paquetes Frida Python (frida, frida-tools)
  • Frida Server ejecutándose en el dispositivo de destino (cómo)
  • Conocimientos básicos de Java y Javascript

Encontrar el código de detección con análisis estático

Aplicaciones de Android de ingenieria inversa para eludir las capacidades

Al iniciar la aplicación móvil de muestra, podemos ver los tres métodos de detección de raíces que utiliza la aplicación. Ya que los tres son true, se detectó el acceso a la raíz y el mensaje «¡Parece que esta aplicación se está ejecutando en un dispositivo pirateado!» brindis aparece en la parte inferior de la pantalla. Dado que este brindis aparece cada vez que falla la verificación de raíz, tenemos una idea de dónde comenzar a buscar en el código descompilado.

Para descompilar el código, inicie JADX-GUI, seleccione «Abrir archivo» y seleccione el APK que desea descompilar. Una vez que se descompile la aplicación de Android, comience a buscar palabras o frases que puedan estar relacionadas con la detección de raíces. Algunas frases útiles podrían ser: raíz detectada, su, magisk, verificación de raíz, está enraizado, jailbreak, etc. Dado que tenemos un aviso de advertencia que se muestra cuando se detecta la raíz, podemos buscar el contenido del brindis.

1678465405 621 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

Los resultados de la búsqueda nos muestran la ubicación en el código descompilado donde se crea este brindis de advertencia. Al hacer clic en el resultado de la búsqueda, podemos navegar hasta el código que se encarga de realizar la verificación y decidir si se debe mostrar este brindis.

Aplicaciones de Android de ingenieria inversa para eludir las capacidades

El código fuente muestra que una función llamada checkForRoot se llama. Si la función devuelve true, entonces se ha detectado el acceso raíz. Según esta información, el código que necesitamos omitir debe residir dentro de esta función.

Comprender y omitir el código de detección

1678465406 563 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

Dentro de checkForRoot función, vemos que se realizan tres comprobaciones. Estos cheques regresan boolean valores y si alguno de ellos es true, luego se detectó la raíz. Estos tres controles corresponden a los tres valores que se muestran en la pantalla de inicio de la aplicación, por lo que los revisaremos uno por uno. Nuestro objetivo será manipular el comportamiento de la aplicación, por lo que todos estos controles regresan false. Para manipular el comportamiento de la aplicación móvil, nos basaremos en una herramienta de análisis dinámico llamada Frida. Frida utiliza varios métodos para conectarse al tiempo de ejecución de una aplicación y proporciona una interfaz para que los investigadores vean o manipulen cómo funciona un programa mientras se ejecuta. En este tutorial, usaremos la API de JavaScript de Frida para implementar nuestros ganchos.

su Comprobación binaria

Entendiendo la comprobación binaria su

La primera comprobación que realiza la aplicación está contenida en doesSuBinaryExist.

1678465406 539 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

Esta función crea una matriz de rutas de archivos donde un su el binario podría ser potencialmente localizado. Luego, el código inicializa un File class para cada una de estas rutas y verifica si el archivo se puede encontrar usando un exists llamada. Esta verificación se usa comúnmente en la detección de raíz, porque los usuarios o las aplicaciones usan un binario su para obtener sudo o root acceso a un dispositivo. Para eludir esta verificación, debemos engañar a la aplicación para que piense que el su el binario no existe en el dispositivo. Como sabemos que utiliza exists para encontrar el binario, ese debería ser nuestro objetivo.

Omitir la verificación binaria su

Como queremos modificar el exists método, primero debemos conectarnos a la clase que llama al método. En este caso, exists se llama desde dentro de la clase File. Mirando la lista de clases importadas a la clase MainActivity en la parte superior de la filepodemos obtener la File nombre de la clase que nos interesa.

1678465406 382 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

Para comenzar a escribir el gancho, necesitamos abrir un archivo JavaScript vacío. Podemos empezar a enganchar el File clase con:

const File = Java.use('java.io.File');

A continuación, debemos indicar qué método queremos modificar. Podemos hacer esto para el exists método como este:

File.exists.implementation = function ()

Esta línea de código nos permite sobrescribir el comportamiento de lo que debería suceder cuando se realiza una llamada a exists. Cualquier código que queramos ejecutar debe colocarse dentro de la función que acabamos de definir.

Para entender cómo implementar el bypass, necesitamos entender cómo funciona este método. Podemos hacer esto echando un vistazo a la Documentación de Android. La documentación explica que el exists el método devuelve true si el camino existe y false si no es así En este caso, todo lo que tenemos que hacer es verificar si la ruta del archivo que se está verificando termina con suy si lo hace, obligamos al método a devolver false. Si no está comprobando suentonces podemos continuar con la llamada al método normalmente usando this.exists.

Para determinar qué camino exists se está llamando, nos referiremos una vez más a la documentación de Android y veremos si el File class puede proporcionar acceso a esa información. De acuerdo con la documentosel File la clase tiene un getPath método que devuelve la ruta del archivo como una cadena. Podemos llamar manualmente a este método y usar la salida para verificar si la llamada debe omitirse o no.

El gancho completo se ve así:

Java.perform(function())

Nota: El anzuelo está envuelto en Java.perform(), porque esto asegura que la máquina virtual de Java se inicialice antes de que comencemos a cargar nuestros ganchos. Si esta llamada no está incluida, es posible que experimente un comportamiento inesperado.

Guarde el archivo JavaScript y genere su aplicación con Frida usando el siguiente comando:

frida -U -f com.example.rootbypass -l root_bypass.js

El comportamiento de cada argumento es el siguiente:

  • -U = utilizar el dispositivo conectado a través de USB
  • -f = el nombre del paquete de la aplicación que está probando
  • -l = el archivo JavaScript que se va a cargar

Si el enlace se escribió correctamente, la aplicación de destino debería generarse y la terminal debería mostrar lo siguiente:

1678465406 189 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

La salida de la consola nos muestra que las comprobaciones su se omitieron con éxito y todas las demás exists las llamadas continuaron con normalidad. Podemos también verifique que el bypass funcionó mirando la pantalla de la aplicación.

1678465407 404 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

¡Eso es uno abajo y quedan dos!

que su comprobar

Comprender el control de cuál su

Otra forma en que las aplicaciones pueden verificar si el su binario existe en un dispositivo es mediante el uso de la which dominio. En nuestro código descompilado, podemos ver que se ejecuta which su para tratar de descubrir la ruta del archivo para el binario.

1678465407 347 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

Este código ejecuta un which su usando el Runtime clase. Si se encuentra el binario, el comando envía la ruta del archivo a la salida estándar, pero si no encuentra nada, no se imprimirá nada en la pantalla. El método de detección de raíz luego regresa true si which su envía cualquier cosa a stdout, de lo contrario regresa false.

Debido a que la mayoría de la lógica de detección de raíz se ejecuta directamente en un dispositivo, estas técnicas a menudo se pueden descubrir mediante ingeniería inversa.

Pasando por alto el control what su

Una vez más, debemos consultar la documentación de Android, pero esta vez debemos ver cómo funciona el exec opera el método. De acuerdo con la documentosparece que hay varias versiones de exec que se puede llamar, por lo que debemos asegurarnos de que estamos conectando la versión que usa nuestra aplicación de destino. En este caso, el which su El comando está representado por una matriz de cadenas, por lo que debemos crear un gancho utilizando la documentación para la sobrecarga que toma una única matriz de cadenas como parámetro.

Debido a que también necesitamos acceso al argumento en nuestro gancho, podemos enganchar el argumento agregándolo a la firma de la función que creamos. La configuración para el nuevo gancho se ve así:

const Runtime = Java.use('java.lang.Runtime');  Runtime.exec.overload('[Ljava.lang.String;').implementation = function(commandArray)

Now that the skeleton for our hook has been created, we need to give it functionality. Because we want to bypass commands that reference the su binary, we should loop over all the words in the command and see if we find su. The command is stored as an array of strings, so this can be done with a simple for loop. If su is found, we need to swap that word for a string that does not exist on the device and exec the substituted command. The code to should look like this:

Java.perform(function())

Este nuevo código se puede colocar dentro del Java.perform() función del otro gancho. Cuando la aplicación se inicia con frida, la aplicación ahora debería mostrar dos controles omitidos:

1678465407 548 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

Comprobación de la aplicación raíz

Comprender la verificación de la aplicación raíz

Algunas aplicaciones como Magisk se utilizan comúnmente para ayudar en el proceso de enraizamiento. Tener cualquiera de estas aplicaciones instaladas en un dispositivo indica que probablemente se haya rooteado.

1678465407 72 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

Este método comprueba si alguno de los tres nombres de paquetes de aplicaciones raíz proporcionados está instalado en el dispositivo y, si se detecta un paquete, devuelve true.

Omitir la verificación de la aplicación raíz

A primera vista, parece que debería ser un gancho fácil de escribir. Todo lo que uno debe hacer es enganchar la llamada a getPackageInfo desde el PackageManager clase y vuelta false. Sin embargo, si escribes un gancho para el PackageManager clase como esta, su enlace nunca se ejecutará:

const PackageManager = Java.use("android.content.pm.PackageManager");
PackageManager.getPackageInfo.overload('java.lang.String', 'int').implementation = function (packageName, flags) 

Esto se debe a que PackageManager es una clase abstracta. Esto significa que debe buscar una clase que amplíe el PackageManger clase y escribir un gancho para eso. Afortunadamente, Android es de código abierto, por lo que podemos ver fácilmente todas las clases que amplían el PackageManager ¡clase! Si levantamos el código fuente de Androidhaga clic en PackageManagery luego haga clic en «Referencias» en la parte inferior, ¡podemos ver una lista de clases que lo amplían!

1678465408 931 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

La primera clase que se extiende PackageManager se llama ApplicationPackageManager. Usando el conocimiento obtenido de las omisiones anteriores, necesitamos buscar los argumentos y devolver el valor para getPackageInfo en los documentos de Android. Una vez que entendemos cómo funciona el método y hemos escrito el esqueleto, el bypass debe verificar si el paquete de destino coincide con lo que hemos instalado en nuestro dispositivo e insertar un nombre de paquete falso. El gancho terminado debería verse así:

Java.perform(function())

Asegúrese de que el gancho tenga en cuenta la sobrecarga correcta de getPackageInfo y la firma de la función acepta ambos parámetros. Cuando este gancho se combina con los dos anteriores, deberíamos ver que la terminal debería verse así:

1678465408 764 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

Y si todas las omisiones funcionan como se esperaba, ¡nuestra aplicación no podrá detectar que se está ejecutando en un dispositivo rooteado!

1678465408 555 Aplicaciones de Android de ingenieria inversa para eludir las capacidades

Conclusión

Si bien este tutorial solo muestra tres métodos de detección de raíz, esta técnica de combinar análisis estático con instrumentación dinámica se puede aplicar a todos los métodos. A medida que encuentre más técnicas de detección de raíz y las agregue a su secuencia de comandos de omisión de Frida, puede crear una herramienta confiable para omitir las técnicas de detección de raíz en una variedad de aplicaciones móviles.

Para ahorrar tiempo en el análisis de aplicaciones móviles, los evaluadores de penetración y otros pueden aprovechar el hardware y el software preconfigurados de NowSecure Workstation que comprime las evaluaciones de vulnerabilidad de las aplicaciones móviles en solo unas horas y permite realizar pruebas repetibles. También puede conocer a los expertos de NowSecure en nuestra serie de Tech Talks: registro hoy.



Fuente del artículo

Deja un comentario