Registros en ARM
La arquitectura ARM utiliza 16 registros, entre los que podemos encontrar argumentos a funciones, el contador del programa, puntero a la pila, etc. A continuación enumeramos cada uno de ellos junto con su descripción:
- R0 a R3: argumentos de la función, también podemos encontrarlos como a0-a3.
- R4 a R9: para las variables locales.
- R7: número de llamada al sistema.
- R10 (sl): límite de la pila.
- R11 (fp): Fame Pointer
- R12 (IP): Intra Procedure.
- R13 (sp): Stack Pointer como RSP en x86_64.
- R14 (lr): Link Register.
- R15 (PC): Contador de Programa (como RIP en x86_64 y EIP en x86).
Instrucciones de salto en ARM
Las instrucciones de salto o ramificación, branching en inglés, se utilizan para cuando tenemos bucles, llamadas a procedimientos o funciones. En ARM encontramos algunas de las siguientes:
- B: saltamos a una subrutina pero sin almacenar donde estábamos, similar a la instrucción de salto JMP de x86.
- BL: saltamos a una subrutina pero se guarda el PC-4 en LR con la posición de retorno para la vuelta de la subrutina. Para restaurar PC desde LR tan sólo tenemos que hacer: MOV PC, LR
- También existen otras instrucciones algo más completas como BX y BLX, las cuales se utilizan en THUMB MODE, un modo utilizado para reducir los requisitos de memoria. Si el registro T está a 0 usa modo ARM, en caso contrario usar modo THUMB.
Otras instrucciones en ARM
La arquitectura ARM contiene cuatro tipos de clases de instrucciones, vemos aquí algunos de los ejemplos que más nos podemos encontrar:
- Operaciones aritméticas
- ADD r0,r1,r2
- SUB R1,R2,#1
- Operaciones de comparación
- CMP R0,R1
- Operaciones lógicas
- AND R1,R2
- Movimiento de datos entre registros
- MOV r0,r1
Android Debugging
Una vez que hemos visto información sobre la arquitectura ARM vamos a llevar a cabo el proceso de debuguear la App con nombre com.isi.testapp , la cual ya tenemos cargada a la espera de darle a ejecutar/play para iniciar el debug. Pero antes de ello, si nos fijamos en la interfaz de usuario veíamos como se nos pedía una password. Por tanto el objetivo que tenemos es intentar bypasear esta pantalla de login inicial.
Buscamos todas las ocurrencias de la palabra password pulsando Alt+T o Search - Text, para ello marcar la opción Find all occurences.
Vamos a intentar localizar las instrucciones donde se lleva a cabo la comparación de la contraseña insertada y la que tiene almacenada la App para poner un breakpoint y bypasearla.
Si nos fijamos en la columna de las funciones tenemos varias ocurrencias que se dan en la función onClick del MainActivity, si nos vamos a esta zona de código nos encontramos con lo siguiente
Vamos a ir analizando los diferentes opcodes (operation code o código de operación: porción de una instrucción de lenguaje máquina que especifica la operación a ser realizada) que nos aparecen poco a poco.
En las siguientes líneas lo que tenemos es que se almacena el valor introducido por la interfaz en la
variable v1 que posteriormente se asignará al String pass, ahí es donde tenemos almacenado el valor introducido por el usuario.
0004641C invoke-interface {v2}, <ref Editable.toString() imp. @_def_Editable_toString@L>
00046422 move-result-object v1
00046424 .local name:'pass' type:'Ljava/lang/String;'
00046424 pass = v1
El siguiente punto sirve para almacenar el valor de la String aPassword_0 (que es “password”), para después llevar a cabo una comparación para ver si son iguales la variable pass y el valor de v2, es decir, lo que ha introducido el usuario y la clave almacenada. En la siguiente línea con el opcode move-result, lo que se hace es almacenar el resultado de la función llamada (0 si no son iguales y 1 si son iguales). El siguiente opcode if-eqz lo que hace es evaluar si el valor de v2 es 0, caso de serlo salta hasta la posición de memoria marcada como loc_46482 donde nos da Login Failed, en caso de valer 1 sigue ejecutando en la siguiente posición de memoria 00046434.
00046424 const-string v2, aPassword_0 # "password"
00046428 invoke-virtual {pass, v2},<boolean String.contentEquals(ref) imp. @ _def_String_contentEquals@ZL>
CODE:0004642E move-result v2
CODE:00046430 if-eqz v2, loc_46482
Llegados a este punto lo tenemos bastante fácil para llevar a cabo el bypass, ya que sólo tenemos que hacer que o bien se salte la comparación, o compare la misma variable, o incluso añadir un NOP (Not Operation, que no haga nada el programa) en la posición donde se realiza la comparación.
Os dejo este enlace por si queréis tener más información sobre opcodes de la máquina virtual Dalvik de Android http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
Bypaseando una App
Para llevar a cabo el bypass y cambiar la lógica de la aplicación hacemos lo siguiente. Con el breakpoint situado en el opcode de comparación le damos a play e introducimos como clave la palabra “pepe” como en la imagen:
Se nos para la ejecución de la App justo en el breakpoint como vemos a continuación:
Además para asegurarnos de lo que está pasando vamos a ver el valor de v2 y de pass en el visor de variables. Para ello nos vamos a Debugger - Debugger Windows - Watch view y en esta ventana añadimos las variables (botón derecho Add watch) haciéndole un casting del valor almacenado.
Vemos como la comparación entre la palabra “password” y “pepe” ha dado como resultado 0, es decir, no son iguales y por tanto el valor de v2 es 0 y nos dará contraseña incorrecta. Pero ahora lo que vamos a hacer es modificar el valor de v2 por un 1 y bypasearemos el login de la App.
A partir de aquí ya podemos hacer lo que se nos ocurra, desde añadir opcodes para devolver una shell o saltarse la autenticación de una App, en este último caso sólo hay que modificar el código hexadecimal y recompilar la APK la cual firmaremos posteriormente.
Espero que os haya gustado esta serie de artículos y que perdáis el miedo a debuguear cualquier aplicación, tan sólo tenéis que conocer la arquitectura sobre la cual vais a trabajar a bajo nivel y poneros con IDA Pro u otro debugger.
Un handshake
"La inspiración existe, pero tiene que encontrarte trabajando"
0 comentarios:
Publicar un comentario