Ingeniería inversa del firmware del dispositivo utilizando el ejemplo de un "rinoceronte" intermitente. Parte 2


Presentamos a su atención la segunda parte del artículo sobre ingeniería inversa del firmware del dispositivo Flashing Rhino basado en un taller en la conferencia SMARTRHINO-2018 .

En la primera parte del artículo, el firmware del dispositivo se cargó en el desensamblador IDA y se realizó un análisis inicial de los comandos de protocolo del dispositivo. Los comandos individuales se probaron en un dispositivo que funciona.

En la segunda parte, se realizará un análisis de las tareas de firmware restantes.

Permítame recordarle que, después de analizar la tarea de Bluetooth en términos de control de LED, se decidió cambiar a la tarea de LED, ya que la tarea inicial era crear una aplicación para controlar los LED, y para esto es necesaria una comprensión detallada de la operación del firmware.

El archivo de firmware está disponible para estudio independiente.

Toda la información se proporciona solo con fines educativos.

Debajo del gato hay muchos rinocerontes intermitentes.

Tarea LED


Brevemente: un análisis completo de la tarea responsable de cambiar los LED. Análisis de tipos de datos y variables globales.

La tarea de LED está representada por la función x_leds_task , ubicada en 0x08005A08 .

Además de las líneas extrañas "Tengo una superpotencia ..." en la función principal de la tarea de LED, puede prestar atención a la línea "hue> max: change shine \ r \ n" .



Al mismo tiempo, vemos una situación familiar: (PALABRA *) (v26 + 4). En el menú contextual de la variable v26, seleccione el elemento "Convertir a estructura *", luego indique la estructura creada anteriormente:



Dado que v5 = v26 , repetimos la operación "Convertir a estructura *" para la variable v5.

Continuamos estructurando el código y los datos. Establecer la representación hexadecimal en todas partes. Renombrar:

  • v5 - led ;
  • v6 - idx ;
  • v8 - hue_1 ;
  • v9 - hue_2 ;
  • v26 - _led ;

El código está mejorando. Pero algunas variables todavía duelen la vista, por ejemplo, la variable v23:





Aparentemente, v23 es una matriz de 4 bytes.
idx es el índice del LED; este índice se agrega a la dirección base; de esta manera, se hace acceso a elementos en los mismos desplazamientos; así es como se comportan las matrices.

Asignamos el tipo char v23[4] y le cambiamos el nombre a leds_smth , el código se vuelve más bonito:



También puede observar que el resultado de la función x_queue_recv se devuelve a la variable v25:

 x_queue_recv(&v25, leds_queue, 1000); 

Pero puede que no esté claro cómo están los datos que necesita en la estructura _led . El hecho es que las variables v25 y _led están ubicadas cerca de la pila ; esto se puede entender por el hecho de que en el descompilado se escriben en líneas adyacentes. La ubicación de las variables en la pila se puede ver en una ventana separada si hace doble clic en la variable:



Probablemente son una estructura, o el compilador ha realizado alguna optimización. Por lo tanto, se puede argumentar que los datos de la tarea Bluetooth se transmiten a la tarea LED. Para averiguarlo con mayor precisión, comprobaré el dispositivo; para el LED cero a través de Bluetooth, enviaré los valores 0x208 , 0x2D0 , 0x398 , 0x3E9 , que podrían observarse en el código:



Los resultados de verificar el valor de tono en el dispositivo:

  • 0x208: los LED dejaron de cambiar sin problemas y se configuraron en los colores: rojo, verde, azul, púrpura;
  • 0x2D0: los LED comenzaron a cambiar nuevamente;
  • 0x398: nada ha cambiado;
  • 0x3E9: nada ha cambiado.

Si vuelve a mirar el código, puede ver que el valor 0x398 se puede asociar lógicamente con un valor menor que 0x167 (se establecen valores diferentes para el elemento de matriz leds_smth ). Por lo tanto, realizaré esta verificación: primero, estableceré el primer LED en verde (tono = 0x78, LED 010078FF20 ), mientras que los otros tres LED continúan LED 010078FF20 sus colores.


Ahora LED 010398FFFF protocolo Bluetooth LED 010398FFFF - después de esto, el primer LED ha cambiado al modo de cambio de color general.

Por lo tanto, el valor de tono de 0x398 restablece el valor de color estático, lo que significa que la matriz leds_smth contiene banderas (0 o 1) para que los LED estén ocupados:

  • 0: el LED no está ocupado, participa en un cambio de color suave ( tono = 0x398 );
  • 1 - el LED está ocupado, el usuario establece un color estático ( tono <= 0x167 ).

Cambie el nombre de leds_smth a leds_busy .

Ahora el propósito del siguiente bloque de código debería quedar claro:



El ciclo en las líneas 83-101 realiza un mosaico de color suave con un paso de cambio de color de 5: v12 += 5 . Si el LED tiene un color estático encendido, entonces este LED no participa en el mosaico. Después del ciclo hay líneas de inclusión a corto plazo de todos los LED.

Renombrar:

  • sub_800678A - x_led_set_hsv ;
  • v12 - hue_step ;
  • v13, v17, v18, v19 - led0_busy , led1_busy , led2_busy , led3_busy ;
  • v11, v20, v21, v22 - hue0 , hue1 , hue2 , hue3 ;
  • dword_200004C4 - led_control .

La función sub_80039FE presumiblemente realiza un tiempo de espera (de lo contrario, los LED no cambiaron sin problemas, pero al instante), llamémoslo x_sleep , y la variable v16 es led_timeout .

El propósito de la función sub_8006934 aún no es obvio, pero se usa en todas partes después de configurar el color en los LED; puede llamarlo x_led_fix_color .

Después de estos cambios de nombre, es fácil entender la función sub_8006944 (llamada en la rama hue <= 0x167):



Simplemente realiza una verificación adicional para determinar el color del LED. Cambie el nombre de la función sub_8006944 a x_led_set_hsv_wrap (sufijo _wrap - una explicación de que esto es un "contenedor" sobre otra función) y configure el siguiente prototipo para ella:

 signed int __fastcall x_led_set_hsv_wrap(int led_control, signed int idx, int hue, char sat, char val) 

Volvamos un nivel a la función x_leds_task. Una vez más, mirando el código, puede encontrar que la rama "hue> 0x3E8" comenzó a verse así:



Es decir, un valor de tono mayor que 0x3E8 debería cambiar el tiempo de espera del mosaico coloreado. Lo comprobaré enviando algunos valores al dispositivo:

  • hue = 0x3E9: los LED comenzaron a cambiar rápidamente:


  • hue = 0xFFFF - los LED comenzaron a cambiar muy lentamente:



Al salir del ciclo principal de la tarea LED, se usa la función sub_8003C44 , que también se usa en la función sub_8005070:



Renombrar:

  • sub_8005070 - x_freeMsg ;
  • sub_8003C44 - x_free_queue .

Además en la tarea de LED, la siguiente rama no puede dejar de llamar la atención:



Puede intentar ejecutar el comando LED B816D8D90000FFFF . Pero si recuerda que solo se toman 2 caracteres como índice LED, un intento de alcanzar este código obviamente no tendrá éxito. Deja este hilo para más tarde. Cambie el nombre de la función sub_8004AE8 a x_mad_blinking , y es hora de arreglar la firma de la función x_printf (la última vez que escribí la firma incorrecta):

 void x_printf(const char *format, ...) 

El ciclo principal de la tarea LED se desmonta, pero todavía hay un código al comienzo de la tarea.

Miremos el código:



En la línea 49, lo más probable es que se verifique la disponibilidad de los LED y, en caso de error, se realice una llamada a la función sub_8004BBC, que apaga las interrupciones e inicia un bucle infinito en el que se usa la línea "../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_gpio.c". Lo más probable es que sea una afirmación o función similar.

Renombrar:

  • sub_8004BBC - x_gpio_assert ;
  • sub_800698C - x_check_gpio .

El propósito de la función sub_8006968 quedará claro si observa cuidadosamente el dispositivo cuando está encendido:


Los cuatro LED juntos se encienden primero en rojo, luego en verde y luego en azul. Después de eso, se establecen por color: 0-rojo, 1-verde, 2-azul, 3-violeta. Y solo entonces comienzan a cambiar de mosaico.

Dado que el mosaico comienza en el ciclo de tareas principal, es lógico que las líneas 58-61 antes del ciclo principal sean responsables de la inclusión a corto plazo de diferentes colores en los LED, y las líneas 52-56 son responsables de configurar el rojo, el verde y el azul en todos los LED a la vez. Cambie el nombre de la función sub_8006968 a x_led_all_set_rgb (RGB, simplemente en una corazonada, de acuerdo con los argumentos pasados).

Rarezas en la tarea de LED


Brevemente: definir la funcionalidad del código que contiene líneas extrañas. Generando una contraseña para el dispositivo.

Ahora pasemos al comienzo de la función x_leds_task:



"Eraze" , "gen" , "flash" , "reset" - ¿por qué es todo esto?

Tratemos de resolverlo.

Deje que sub_80066BC sea x_leds_task_init .

Veamos sub_8006B38:



Memset de agua pura, ¿de acuerdo?



Volver a x_leds_task. Algo está mal con el tipo de variable v24:



La IDA cometió un pequeño error con el tipo, pero un comentario con una marca de pila nos ayuda. Entre las variables v24 y v25 hasta 12 bytes (0x44 - 0x38). Por lo tanto, cambiamos el nombre de v24 a buf y asignamos el tipo unsigned __int8 buf[12] (Ida advertirá que el nuevo tipo de datos es más grande que el anterior; estamos de acuerdo).

Siguiente Función sub_8004CE4:



Cambie el nombre de a1 a buf , v1 a _buf .

Función sub_8006B26:



¿La reconociste?

¿Y si sin maquillaje?

Por supuesto memcpy . Renombrar

Entonces, el propósito de la función sub_8004CE4 es obtener algunos datos en la dirección 0x08007C00 . Por cierto, esta dirección está en el rango de direcciones de la memoria flash del microcontrolador (y el firmware, en particular). Cambie el nombre de sub_8004CE4 a x_read_data_0x08007C00 .

X_leds_task Function Line 36:

 if ( (unsigned int)buf[0] - 65 > 0x19 ) 

Cambie la visualización de datos (tecla R en el número 65, tecla H en el número 0x19):

 if ( (unsigned int)buf[0] - 'A' > 25 ) 

Después de una pequeña reflexión, puede comprender que esta es una prueba del rango del alfabeto latino AZ.

A continuación, utilizando las indicaciones en forma de cadenas de formato, cambie el nombre de:

  • sub_8004C10 - x_erase ;
  • sub_80059C8 - x_gen ;
  • sub_8004C84 - x_flash .

La función sub_8003C66 no hace nada notable: solo aumenta algunas variables globales: cambie el nombre de sub_8003C66 a x_smth_inc .

La función x_erase en realidad no acepta ningún argumento; esto se puede verificar en el desensamblador:



Dentro de x_erase, se usa la dirección familiar 0x08007C00 y se accede a tres funciones desconocidas:



Un vistazo rápido a estas tres funciones, vemos que están accediendo a direcciones en el rango 0x40022000 - 0x400223FF . La documentación para el microcontrolador establece claramente que este es el rango de "Interfaz FLASH" . Es decir, la función x_erase borra un trozo de memoria flash, ¡genial!

Aparentemente, la función x_flash escribe en la memoria flash, después de verificar la longitud de la línea para escribir (por cierto, los argumentos a2 y a3 son superfluos aquí; ayudaremos a Idea):



¿Y todo esto sucede en el "dispositivo de iluminación"?

Bueno, ¿qué pasa con la función x_gen ? Después de un vistazo rápido y cambiar el nombre de las variables, se verá así:



La función sub_8006CB4 se ve así:



Y sub_8006D10 , así:



No contenga el deseo de buscar en Internet estas constantes indecentemente bellas: 0xABCD , 0x1234 , 0xE66D , 0xDEEC , 0x4C957F2D y 0x5851F42D . Si Internet aún no está completamente prohibido, probablemente encontrará estas constantes en la fuente de funciones aleatorias . No es de extrañar que la función principal se llame x_gen.

Esta también es una situación muy típica: llame a srand () antes del ciclo y llame al azar () en el ciclo, así que cámbiele el nombre:

  • sub_8006D10 - x_rand;
  • sub_8006CB4 - x_srand.

Un lector curioso, al examinar la función sub_8005098 , puede averiguar de dónde proviene la semilla para la función srand.

Por lo tanto, la función x_gen genera una cadena aleatoria del tamaño especificado .

Después de que la línea generada se escribe en la memoria flash, el dispositivo se reinicia:



Parece una especie de reinicio extraño. Pero si miramos la lista de tareas de este dispositivo, encontraremos "watchdogTask" entre ellas. Obviamente, si hay una "tarea atascada", el perro guardián se reinicia.

La tarea LED, excepto el modo MadBlinking, puede considerarse analizada.

Veamos a través de las líneas qué otras tareas hay en el sistema:



Después de restaurar los enlaces a las cadenas en el código, puede ver esta imagen:



Primero, hay un enlace a una cadena con la tarea de nombre, luego un enlace a la función de tarea principal. Y se utilizan en la función principal , donde se inician estas tareas:



Ejecutemos los cambios de nombre que faltan:

  • sub_80050FC - x_sensor_task ;
  • sub_8004AAC - x_watchdogTask ;
  • sub_8005440 - x_uartRxTask .

Tarea de vigilancia


El watchdog de tareas no hace nada particularmente interesante:



Renombrar:

  • dword_200003F8 - wd_variable ;
  • sub_8001050 - x_update_wd_var .

Tarea UART


Brevemente: busque datos y funciones que tengan enlaces desde diferentes funciones. Determinación de su finalidad.

Un vistazo rápido a la tarea UART le permite detectar el envío de datos a una cola desconocida definida por la variable unk_200003EC :



Una vez restaurados los enlaces a esta variable a través de la búsqueda binaria, veremos que además de x_uartRxTask se usa en main (allí, la cola se crea, aparentemente) y en la función desconocida hasta ahora sub_80051EC :



Renombrar:

  • sub_80051EC - x_recvMsg_uart_queue ;
  • unk_200003EC - uart_queue .

Ver referencias cruzadas a x_recvMsg_uart_queue:

  • sub_8005250;
  • x_bluetooth_task.

Primero, vea la función sub_8005250 :



Después de pensar, renombra:

  • unk_2000034C - cmd_count ;
  • a1 - cmd ;
  • v4 - _cmd ;
  • v6 es rsp ;
  • sub_8005250 - x_bluetooth_cmd .

Veamos ahora dónde todavía se usa x_bluetooth_cmd. Todos los enlaces adicionales solo de la tarea Bluetooth, es hora de volver a ella.

Volver a la tarea de bluetooth


Brevemente: el análisis final de la tarea de Bluetooth. Buscar autorización sin contraseña.



Si observa los lugares donde se utiliza la función sub_8006A84 , y no es demasiado vago y mira sus entrañas, no habrá duda: esto es calloc . Es lógico: para recibir datos en el búfer, primero debe crear este búfer.

Ahora sub_8006DBC . Veámoslo (las variables ya han sido renombradas):



Recordando las funciones de la biblioteca estándar de C para trabajar con cadenas, veremos aquí strstr (buscar una subcadena) y renómbrala audazmente.

Repasemos el código de la función x_bluetooth_task; tal vez algo ha cambiado aquí desde la última visita . En el proceso, nombramos las variables:

  • v2 - _state ;
  • v3 - data_len .

Hay una función sub_80052E2 justo al lado. Por analogía con funciones que extraen números de un comando de Bluetooth, extrae una cadena de una longitud especificada, llamémosla x_get_str .

Continuamos:

  • v26 - isEcho ;
  • v6 - meow_str ;
  • v10 - uart_cmd_byte ;
  • v11 - uart_cmd_str ;
  • v12 - str_0 ;
  • v13 - str_1 ;
  • v14 - format_str ;
  • sub_8000F5C - x_blink_small_led .

Termine con un cambio de nombre rápido:

  • v19 - contraseña ; (ya que hay líneas sobre autorización y contraseña al lado)
  • sub_8004CC0 - x_check_password ;
  • sub_8006AF4 - x_free (dado que la contraseña, cmd y bt_args son punteros a objetos dinámicos (¡verifique esto!), la memoria debe liberarse después de usarlos);
  • sub_8006DAC - x_strcpy (¡compruébalo!).

Ahora explore las ramas READ , WRIT , AUTP , SETP .

Como mostró una prueba en un dispositivo en ejecución, se requiere autorización para los comandos READ, WRIT, SETP. Un intento de autorización con el comando AUTP nos lleva a la función x_check_password para verificar la contraseña:



Resulta que la longitud de la contraseña debe ser de 8 caracteres y la contraseña se compara (en la función sub_8006B08) con bytes en la dirección 0x08007C00 , donde se almacena la cadena generada de caracteres aleatorios AZ.

Resulta que, sin conocer la contraseña, no podemos iniciar sesión en el dispositivo. Bueno, o casi no puedo ...

Presta atención a dónde se usa la variable auth_flag :



Resulta que se usa no solo en tareas Bluetooth. Y aquí todavía no buscamos en la tarea Sensor. Vamos alli

Tarea del sensor


Brevemente: ¿qué hace el botón táctil?

De acuerdo con las mejores prácticas de programación, toda la tarea del Sensor cabe en una pantalla IDA. Y esto no puede sino alegrarse:



Línea a línea ...

  • "TSC% d \ r \ n": esta línea debería hacerle pensar en el controlador de detección táctil para microcontroladores STM32;
  • "AUTH BTN \ r \ n" - botón de autorización ???
  • "SET AUTH% d \ r \ n": ¿establecer el indicador de autorización?

Veamos cómo se comportará el dispositivo si presiona el botón táctil (¿se dio cuenta de que el rinoceronte en la pata tiene un botón táctil?):



Cuando se presiona brevemente, se enciende el pequeño LED rojo. Con una pulsación larga, este LED se enciende durante mucho tiempo.

Si correlacionamos esto con el código, podemos suponer que la función sub_8000708 es una función para obtener la hora actual. Luego, si la diferencia entre la hora actual y el comienzo de tocar el sensor es superior a 1000 (1 segundo), el LED se enciende durante 0xEA60 milisegundos (1 minuto). Pero la variable auth_flag es de gran interés, que se establece en 1 con una pulsación larga del botón táctil, lo que le da al atacante el acceso al administrador del acceso de "dispositivo de iluminación" a funciones privilegiadas.

Por lo tanto, después de la autorización "por botón", puede leer la contraseña almacenada en el dispositivo (comando READ), escribir en la RAM (función WRIT) o establecer una nueva contraseña (SETP).

Parpadeo loco


Brevemente: ¿se puede ejecutar una extraña rama de código de Parpadeo loco?

Volvamos a la tarea de Bluetooth y renuevemos un poco más.

  • v21 - vip_smth (aún no está claro qué hay allí);
  • v22 - vip_str (cadena de tamaño desconocido, extraída de los argumentos);
  • v23 - mad_led - asigne "Convertir a struct *" y especifique struct_LED .

Y aquí vemos el número 0xB816D8D9 (se encontró en la primera parte del artículo en la tarea de Bluetooth) como el índice del LED. Este código se ejecutará si se realiza la verificación:

 if ( sub_8005520(vip_str, vip_smth) == 0x46F70674 ) 

Cambie el nombre de sub_8005520 a x_vip_check y échele un vistazo:



Dado que el primer argumento es una cadena (al menos la cadena se pasa a esta función), este código muestra que el segundo argumento es la longitud de esta cadena (o la longitud que debe procesarse). Renombrar:

  • a1 - str ;
  • a2 - len .

Veamos la función sub_8000254 :



Ahora mire sub_8000148 . Aquí está su comienzo:



Esto es solo un tercio de la función ... Mmmm ... ¡Qué rico! Un cavador experimentado verá fácilmente aquí ...

Que?
operación de división entera.

¿Cómo se puede desenterrar?
Si hace un esfuerzo, desde la función sub_8000254 puede acceder a x_printf (a través de varias otras funciones). Un punto importante a destacar en este punto es que generalmente todas las funciones estándar son bastante estándar . Esto significa que puede intentar encontrar en el dominio público al menos algún código fuente de la función que se está investigando, para que el estudio sea más productivo.

Entonces, tomamos la fuente de printf, luego miramos vfprintf , comparándolo con el código del firmware estudiado. Usando el código fuente, salimos a la función itoa y concluimos que la función sub_8000254 es el operador operador% (tomando el resto de la división), y esta terrible función larga no es más que tomar la parte entera de la división (operación div).

Puede surgir una pregunta legítima: ¿por qué? El hecho es que no puede haber operaciones DIV, MOD en un microcontrolador particular, por lo tanto, el compilador sustituye la llamada de funciones individuales en lugar de estos operadores. Por cierto, aquí hay algunas otras funciones matemáticas .

No olvide cambiar el nombre mientras excava.

Por lo tanto, la función x_vip_check , calcula ... Y esta será su tarea .

Por cierto, si ejecuta el comando VIP correcto, obtenemos un "rinoceronte en la discoteca":



Breve informe sobre firmware


El firmware del dispositivo se basa en el sistema operativo en tiempo real FreeRTOS. El sistema tiene las siguientes tareas:

  1. Tarea de Bluetooth . Procesa comandos que vienen en forma de texto a través de Bluetooth.
  2. Tarea LED . Controla los LED de color según los comandos de Bluetooth.
  3. Tarea del sensor . Enciende el LED rojo, permite la autorización a corto plazo sin una contraseña en el dispositivo.
  4. Tarea UART . Le permite interactuar con el módulo Bluetooth a través del puerto UART interno (utilizado para inicializar Bluetooth).
  5. Tarea de vigilancia . Realiza un seguimiento de las heladas.

El estudio no tuvo en cuenta la capacidad de leer datos del puerto UART (contactos Tx / GND).

Resumen


Durante la clase magistral en la conferencia, solo se desmontó la funcionalidad principal de control de LED. Los participantes más activos recibieron sus "rinocerontes" experimentales.

En mi opinión, el "rinoceronte" produjo un diseño decente para un curso de capacitación en ingeniería inversa y búsqueda de vulnerabilidades. Una característica del diseño puede ser la capacidad de cambiar el firmware tantas veces como desee, cada curso tiene su propio firmware. A diferencia del análisis de un archivo ejecutable, el firmware inverso le permite comprender mejor:

  • cómo trabajar con IDA;
  • principios de interacción entre el firmware y el dispositivo;
  • Principios operativos RTOS.

¡Muchas gracias a todos los que leyeron hasta el final!

Source: https://habr.com/ru/post/es412561/


All Articles