Antecedentes
La pasantía es el proceso de adquirir conocimiento y experiencia. Nuestro equipo de Raccoon Security cree que es imposible mejorar la seguridad de la información de los dispositivos y el software que nos rodea sin transmitir este conocimiento y experiencia a las futuras generaciones de especialistas. Es por eso que hemos estado organizando pasantías individuales para estudiantes talentosos y graduados durante muchos años.
La investigación de seguridad es una habilidad que no se enseña en la universidad. Puede aprenderlo de ejemplos concretos y bajo la guía de mentores experimentados. Cada año, nuestros pasantes resuelven problemas técnicos complejos, logran sus objetivos y siguen adelante, ampliando sus horizontes profesionales y haciendo que el mundo sea un poco más seguro. Cada uno de ellos tiene su propia historia de convertirse en un especialista, y bajo el corte: el comienzo de uno de ellos.
Introduccion
En octubre del año pasado, vine para una pasantía técnica en la empresa NTC Vulkan. Mi interés se dirigió al campo de la ingeniería inversa. Sabía lo que era, ya había intentado investigar independientemente crackme bajo x86, pero entendí que lo más interesante radica precisamente en la unión del software y el hardware. No tenía experiencia en esta área, pero tenía el deseo de probar suerte.
No tenía ninguna expectativa específica de este evento: amigos y conocidos a menudo hablan de pasantías técnicas en varias empresas conocidas. Y cuando me ofrecieron probar suerte en la búsqueda de un adaptador USB-SATA, me alegré de la nueva oportunidad de aprender algo. La experiencia adquirida y el resultado que obtuve hicieron posible verificar la exactitud de la elección del lugar de la pasantía y la futura profesión.
Y el estudio comenzó con la disponibilidad de un adaptador USB-SATA normal. Esto es lo que hice a continuación.
Análisis de circuito visual.
Primero debe inspeccionar la placa del adaptador y determinar los elementos básicos del dispositivo. En las siguientes figuras, se marcan los bloques principales de componentes que son importantes para el funcionamiento del dispositivo. Fotos tomadas después de la investigación:

Adaptador USB a SATA. Vista superior

Adaptador USB a SATA. Vista inferior
Después de pasar un tiempo en Google, descubrí que hay dos convertidores de voltaje en la placa: uno a 3.3 V, el otro a 1.2 V. También es muy fácil determinar la memoria flash instalada en la placa. La ROM funciona en la interfaz SPI y la capacidad de memoria es de 512 Kbps.
Parece que la etapa de inteligencia de circuito está casi completa, pero una búsqueda rápida en Internet no arrojó ningún resultado a petición de "ASM1051". No se encontraron documentos para el chip instalado en el tablero. Es cierto, aún logró encontrar software que le permite actualizarlo. Además, hay una pequeña hoja de datos para el modelo anterior ASM1053 .
USB
Cuando se conecta a una computadora, el adaptador aparece como un dispositivo de almacenamiento USB. Decidí que un conocimiento más profundo de USB probablemente sería útil para mi investigación, por lo que pasé las siguientes horas estudiando la interfaz.
En general, los dispositivos USB pueden ser de diferentes clases, dependiendo de su funcionalidad. Por ejemplo, las unidades flash son el dispositivo de almacenamiento masivo, y los teclados y ratones son el dispositivo de interfaz humana (HID) . Y dado que mi adaptador es visible en el administrador de dispositivos como un dispositivo de almacenamiento, significa que está definido como Almacenamiento masivo y debería funcionar con comandos SCSI.
Literatura USB básica que fue útil Leer memoria de ROM
Como no se sabe nada sobre el ASM1051 instalado en la placa, la memoria de la ROM se consideró la acción más obvia. Me mudé al laboratorio. Separó el chip de memoria flash con un secador de cabello para soldar, lo conectó al programador USB ChipProg-48. No hubo problemas para leer, y tenía un archivo binario en mis manos. En ese momento, no podía decir qué había en la unidad flash y comencé a analizar los datos.
Análisis de archivo binario
En primer lugar, abrí un volcado de memoria desde la ROM usando WinHex, pero puedes usar cualquier editor HEX. Comenzó a mirar los bytes:

Inicio de un volcado de memoria leído desde la ROM
La captura de pantalla de arriba es una captura de pantalla del editor. La línea ASMT1051
, que comienza con la dirección 0x44, es inmediatamente evidente. También puede ver la línea asmedia
desde la dirección 0x18. Para el análisis de datos inicial, utilicé la herramienta de análisis de frecuencia, que está disponible en WinHex.

Histograma de análisis de frecuencia de memoria ROM
El histograma muestra los bytes que están más en el archivo. Además del montón 0x00 y 0xFF (las columnas extremas en el histograma), los siguientes bytes a menudo se encuentran en la memoria:
- 0x02;
- 0x74;
- 0x90;
- 0xA3;
- 0xE0;
- 0xF0.
Sería posible confirmar mi suposición de que hay firmware en la ROM. Una manera fácil de hacer esto es intentar comparar los códigos de operación de diferentes arquitecturas adecuadas para microcontroladores (en adelante, MC) con bytes que a menudo se encuentran en la memoria.
Si se estima de forma aproximada, muy a menudo en cualquier código del ensamblador debe cumplir comandos tales como:
Por supuesto, en diferentes arquitecturas estos comandos pueden tener diferentes variaciones, pero hay un sentido común.
Tuve que pasar por varios conjuntos de instrucciones para diferentes núcleos antes de encontrar los correctos. La comparación con la arquitectura de Intel 8051 dio un resultado muy plausible. Los códigos de operación de algunos comandos coinciden con bytes populares de un archivo, por ejemplo:
- 0x02 - LJMP addr16;
- 0x74 - MOV A, #immed;
- 0x90 - MOV DPTR, #immed;
- 0xA3 - INC DPTR;
- 0xE0 - MOVX A, @DPTR;
- 0xF0 - MOVX @DPTR, A.
Realmente parece que hay firmware para MK en la ROM. Uno podría cargar inmediatamente el binario en el desensamblador IDA Pro , pero en el almuerzo uno de los colegas preguntó:
"¿Estás seguro de que el código en la memoria comienza exactamente desde la dirección cero?"
Y realmente, debe tener en cuenta que algunos "basura" o datos de la dirección 0x00 pueden estar en la memoria.
En general, me enfrenté a la tarea de determinar la dirección inicial del código. Para lograr este objetivo, era mejor usar el emulador EM100 SPI. El emulador reemplaza el chip de memoria en la placa, con él no hay necesidad de soldar la ROM cada vez con firmware, además, el EM100 puede grabar un registro de acceso a la memoria. Dado que el firmware de la ROM ya se ha leído, ahora puede descargarlo al emulador SPI. A continuación, debe soldar el emulador a la placa del adaptador y grabar un registro al conectar el adaptador a través de USB a una PC.

El emulador SPI está soldado a la placa del adaptador USB-SATA
Solde el cableado del emulador a los pads de la memoria flash y flasheé el emulador con algunos firmware. Ahora queda por ver si MK direcciona memoria, y si es así, en qué direcciones.

ROM de acceso a la memoria ROM (obtenida utilizando el software emulador SPI)
La figura anterior muestra que cuando se conecta la alimentación al adaptador, el controlador ASM1051 instalado en la placa envía varios comandos 0x03 (Leer datos).
Primero, el ASM1051 lee 0x80 bytes, comenzando en 0x0000. Los siguientes son dos bytes, comenzando en la dirección 0x0080, luego dos bytes más desde la dirección 0x8082. Luego lee la mayor parte de la memoria de la ROM, comenzando en la dirección 0x0082.
Podemos suponer que una gran cantidad de bytes que se leen desde la ROM al final, comenzando con la dirección 0x0082, es probablemente el código. Qué y por qué se solicita antes de esto aún no está claro. Solo se sabe que en respuesta a la primera solicitud, el ASM1051 recibirá líneas de la memoria flash que están marcadas en la figura anterior. Solo se ubicaron en los primeros 0x80 bytes.
Es hora de comprobar si la memoria externa en la placa contiene firmware para MK con el núcleo 8051, y el código en sí se encuentra en la dirección 0x0082. Abra el volcado de memoria en IDA Pro, especifique el tipo de procesador Intel 8051 y desplace el código 0x0082.

Archivo binario abierto en IDA Pro con desplazamiento 0x82
No hubo problemas al abrir el binario en el desensamblador.
Conclusiones:
- MK ASM1051 tiene una arquitectura 8051.
- En ROM hay un código que comienza con la dirección 0x82. Hay algo más además del código.
- Los primeros 0x80 bytes atraen la atención.
Análisis de código
Ahora que me he asegurado de que el código en la IDA se carga correctamente, puede comenzar a analizarlo y comentarlo en paralelo.
Durante el estudio del código, se encontraron funciones simples, como restar números de 32 bits, se encontraron varios controladores, similares a switch ()
en S. Melkali, y funciones muy simples, como almacenar un valor del registro R7 en la memoria en alguna dirección. Los hallazgos más significativos que describiré a continuación.
Encuentra el n. ° 1
Curiosamente, en respuesta a mi solicitud INQUIRY ( comando SCSI ), recibí una respuesta que contenía dos líneas que vimos al comienzo de la memoria ROM. Por supuesto, inmediatamente cambié estas líneas en la memoria del emulador, esperando una solicitud de consulta para ver lo que escribí. Un sueño tan ingenuo se derrumbó rápidamente. Ahora, en respuesta al comando, vi otra línea, el ASM1051 no solicitó la mayor parte de la memoria de la ROM. MK leyó solo los primeros 0x80 bytes y todo. En la arquitectura de 8051, se puede usar el firmware de máscara (hardware), aparentemente, ASM1051 comenzó a arrancar desde él.
Entonces quedó claro que los primeros 0x80 bytes son realmente importantes, y cambiarlos simplemente no funcionará. Decidí estudiar con más detalle las solicitudes que MK hace en SPI antes de descargar el código.

Solicitud de datos SPI en ROM
Dos solicitudes de dos bytes parecían interesantes. La búsqueda en IDA 0x00, 0x80 y 0xEB arrojó una gran cantidad de resultados que no analicé, pero el byte 0x5A se encontró con menos frecuencia.

Comparación con el byte 0x5A. Contando suma de comprobación-8
Literalmente, el sexto clic me llevó a la sección de código que se muestra en la figura anterior. Se puede ver que el valor del registro con la dirección 0x80 7E se compara con 0x5A. Luego, la suma de comprobación-8 se lee para los valores ubicados desde la dirección 0x80 04 a 0x80 7E . Luego, el valor a 0x80 7F se compara con la cantidad recibida previamente.

El comienzo de la memoria en ROM
Tales compensaciones se parecían al comienzo de un volcado de memoria desde la ROM. La figura anterior muestra que la dirección 0x7E contiene el byte 0x5A. Y si cuenta la suma de comprobación 8 para bytes desde la posición 0x04 a 0x7E, entonces obtenemos 0xA7, y este valor solo se encuentra en la dirección 0x7F.
De manera similar, logramos encontrar el cálculo de la suma de verificación para bytes de la dirección 0x0082 a 0x807F (aparentemente este es el código completo), que se verifica con el byte en la dirección 0x8083. Y en 0x8082 nuevamente se encuentra el valor 0x5A.
Sí, esto es un poco más complicado que simplemente cambiar las líneas en la memoria. También los cambié, pero también calculé y anoté las sumas de verificación para el nuevo archivo en los lugares correctos. Después de eso, en respuesta al comando SCSI INQUIRY, vi mis líneas.
Conclusiones:
- Durante el arranque, el ASM1051 intenta descargar el código de la ROM.
- Primero, el ASM1051 compara el byte de suma de comprobación-8 de la dirección 0x04 a 0x7E con el valor en 0x7F.
- Si la comparación de la suma de verificación para el preámbulo es exitosa, entonces podemos considerarla para el "código" (direcciones de 0x0082 a 0x807F). ASM1051 compara esta cantidad con el valor en la dirección 0x8083 y verifica que el byte 0x5A se encuentra en la dirección 0x8082.
- Si todas las comprobaciones son correctas, el ASM1051 se carga desde la ROM; de lo contrario, utiliza el firmware de máscara.
Encuentra el número 2
Mientras revisaba y comentaba las funciones, descubrí que muy a menudo la función PRINTF se usa en el código (lo llamé así). Lo interesante de esto es que antes de que se llame, se escribe un carácter impreso en el registro R7.

Función PRINTF en IDA Pro
La función en sí se presentó en la figura anterior. Tratemos con ella. En primer lugar, debe mover el valor del registro con la dirección 0x7F6 a la batería. Si hay cero, salga de la función. Lo más interesante sucede si no hay cero. Luego, el valor del registro R7 se mueve al registro con la dirección 0xC001 y, como recordamos, antes de llamar a esta función, se escribe un carácter impreso en R7. Luego, verifique si el valor en R7 es igual al código de carácter "." O "-", si no, salga de la función. Pero si la comparación resultó ser exitosa, entonces la función toma el valor del registro con la dirección 0x16A y lo mueve a 0xC001, pero lo hace complicado. Por ejemplo, en lugar del byte 0x41 (carácter "A" en ASCII), la función se moverá a 0xC001 byte 0x34 (carácter "4" en ASCII) y luego 0x31 (carácter "1" en ASCII). Salga de la función nuevamente.
Descubrí que la verificación al comienzo de la función no se puede pasar, ya que el registro con la dirección 0x7F6 se inicializa a cero, luego no cambia en el código. Es decir, el programador deshabilita esta función, aunque permanece compilada. El hecho de que los bytes solo se escriban en el registro 0xC001 (y, a veces, dos seguidos) sugiere que es muy probable que sea un registro de hardware.
Todo esto se parece a UART. Para saber si esto es así, debe hacer lo siguiente:
- Identifique las patas en el ASM1051 donde se emite el UART.
- Defina los parámetros UART (velocidad, paridad, número de bits de parada).
- Sería bueno habilitar UART en el código (aparentemente, está desactivado).
Todo parece bastante simple: puede turnarse para tocar las piernas con un analizador lógico y buscar aquel en el que sea visible el momento de enviar el UART. En presencia de una señal, la velocidad puede determinarse por el tiempo de los pulsos. Con el resto de los parámetros, todo también está claro, solo vea el momento de enviar un byte en el analizador.
Para "habilitar" esta función, puede escribir ceros en lugar de las primeras tres líneas, donde el valor se verifica en el registro con la dirección 0x7F6. Para hacer esto, nuevamente abro el firmware en WinHex.

Se asignan seis bytes para restablecer.
En el editor, cambio los seis bytes deseados a ceros. Ahora el firmware está listo y se puede descargar al emulador de ROM. Si suponemos que la función para generar bytes en el UART está activada y su llamada se encuentra muy a menudo en todo el código, entonces podemos esperar que los bytes "vuelen" desde el UART cuando el adaptador se está ejecutando. Espero ver un marcador que señale en bytes en el UART cuánto del código se está ejecutando.
Como escribí anteriormente, para encontrar las patas Rx y Tx necesarias, puede mirar el analizador lógico uno por uno. Sin embargo, supuse que el Rx y el Tx en el ASM1051 están en el mismo lugar que el ASM1053: las patas 40 y 41, respectivamente. Puse la sonda del analizador en el pin 41 (supuesto Tx) y veo algo similar a la señal deseada:

Diagrama de tiempos con la pierna 41 - Tx
Para conectar el convertidor USB-UART y observar los caracteres impresos entrantes en el terminal, tuve que soldar dos cables delgados directamente a la placa del adaptador y fijarlo con pegamento caliente.

Dos cables soldados a RX y TX
Estudié un poco el diagrama de la figura "Diagrama de temporización de la etapa 41 - Tx": el tiempo de un pulso, aparentemente, es de 1 μs, y para seis bits: 6.3 μs. Habiendo recalculado el valor en baudios, recibí unos 950,000 baudios, la velocidad UART estándar más cercana es 921600 baudios. Creo que esta discrepancia se obtiene debido al error de medición del analizador lógico, no tomé el dispositivo más valioso, sino el "bebé" chino. Después de configurar los parámetros en la ventana del programa Terminal 1.9b, pude observar los bytes entrantes del ASM1051 MK durante su funcionamiento.

Ventana de programa del terminal 1.9b durante la operación del adaptador
Conclusión
El ASM1051 MK tiene un módulo de hardware UART. El registro para enviar datos tiene la dirección 0xC001. La velocidad de datos es de 921600 baudios. Hay un poco de parada. La pierna 41 es Tx y la pierna 40 es Rx (aunque esto no es exacto).
Encuentra el número 3
Desplazándose por el código en el desensamblador, agregando comentarios, puede encontrar construcciones más difíciles que escribir un número en un registro. Así que me encontré con un controlador interesante, parte del cual en C, parecía un switch ()
.

El controlador de comandos del registro con la dirección 0x800F
Comprendiendo que en algún lugar en algún lugar se deben procesar los comandos SCSI , comencé a buscar entre ellos bytes con los que se compara el contenido del registro con la dirección 0x800F en la figura anterior. Resultó que las primeras cuatro ramas verifican los comandos Leer (10), Escribir (10), Leer (16), Escribir (16). No hay duda de que este es un controlador de comandos SCSI. Luego, miré una función que se llama si el comando que entró no es de lectura / escritura (u_Switch). Dependiendo del byte en el registro con la dirección 0x16A (el valor se toma de 0x800F), lee la dirección a la que llegaremos cuando salgan de esta función. Esto es similar a switch ()
.

Cambiar comandos SCSI
Como ya determiné el byte con el que comparo el comando SCSI que entró en el adaptador, rápidamente arreglé la correspondencia de direcciones por comandos. Entonces, por ejemplo, en la figura anterior se puede ver que si el byte 0x1A estuviera en el registro con la dirección 0x16A, luego de salir de la función u_Switch iríamos a la dirección 0x1B85. Curiosamente, no todos los bytes en comparación con u_Switch están definidos en el estándar SCSI. Es decir, el adaptador puede procesar los bytes 0xE6 o 0xDF, pero el estándar no los fija.
Como puede ver, el adaptador puede ejecutar comandos personalizados y hay funciones de controlador para ellos.

Página 13 de la clase de almacenamiento masivo de bus serie universal
Preste atención al desplazamiento 0x0F relativo a la dirección 0x8000. Antes del procesador, es desde el registro con la dirección 0x800F donde se lee el comando SCSI. Si lees detenidamente la tabla de la figura anterior, puedes ver que en el Command Block Wrapper (CBW), el campo CBWCB también tiene un desplazamiento de 0x0F . Resulta que las direcciones de la memoria RAM ASM1051, que comienzan con 0x8000, pueden ser un búfer USB, como se muestra en la tabla a continuación.
La figura siguiente muestra la sección de código donde se produce la comparación con la cadena USBC (como debería ser la firma dCBWSignature) y la firma propuesta se encuentra en la dirección 0x8000. Creo que esto es suficiente para asegurarse de que el búfer USB esté ubicado en la memoria RAM a partir de 0x8000.

Compruebe el campo dCBWSignature para que coincida con la cadena USBC
Conclusiones:
- MK ASM1051 puede manejar no solo los comandos SCSI, que se describen en el estándar.
- La dirección inicial del búfer USB es 0x8000. El comando SCSI se encuentra en el registro con la dirección 0x800F, lo que significa que habrá más datos / argumentos de los comandos.
Encuentra el número 4
Sabiendo que MK puede procesar equipos no estándar, quería saber qué estaban haciendo. La mayoría de ellos rápidamente me obedecieron. No citaré el estudio del código de estos comandos, ya que esto puede llevar mucho tiempo y puede ser material para un artículo separado titulado "El ensamblador es simple". Describiré los resultados en la tabla a continuación.
Admito que no descubrí todos los comandos leyendo funciones en la IDA. Cuando me topé con la pared durante la investigación, recordé que vi software y muchos firmware para el ASM1051 cuando buscaba documentación sobre él. Con el software encontrado, puede actualizar el firmware y reiniciar el dispositivo. Por lo tanto, decidí que era hora de usar Device Monitoring Studio y ver qué envía la PC al adaptador durante la actualización.
Por lo tanto, fue posible entender cómo ocurre el proceso de actualización del firmware: primero se envía el preámbulo (con el comando 0xE1), luego el código se escribe con el comando 0xE3, luego todo esto se pule reiniciando (el comando 0xE8). Para una actualización rápida y conveniente, escribí un script de Python que inserta las líneas necesarias en el preámbulo, luego lee las sumas de verificación y actualiza el dispositivo. Ahora ya no necesito un emulador, tuve la oportunidad de cargar firmware al ASM1051 a través de USB, puede devolver la ROM nativa a la placa.
Conclusiones
Para actualizar el firmware, se deben ejecutar tres comandos SCSI secuencialmente: 0xE1, 0xE3 y 0xE8.
Encuentra el número 5
Además de los comandos no documentados, fue interesante observar los controladores de los comandos estándar.

Mover el tercer bit del registro 0xC884 al séptimo bit del registro 0x8002
Hay una prueba interesante en el controlador del comando SCSI MODE SENSE (10). La figura anterior muestra parte del código de función. Se puede ver que el tercer bit se lee desde el registro 0xC884 . Luego, el valor de este bit se establece en el registro en 0x8002.
Lo interesante aquí es que el registro 0xC884 no se inicializa en ninguna parte del código, lo que significa que lo más probable es que sea hardware.

Tabla 362 del Manual de referencia de comandos SCSI
Además, si mira la documentación para el comando 0x5A SCSI (MODE SENSE), queda claro que el adaptador USB-SATA debe responder a la solicitud MODE SENSE. El tercer byte de la respuesta contiene el séptimo bit de WP (protección de escritura - protección de escritura). Por cierto, ya vi el séptimo bit en 0x8002, y el desplazamiento desde el comienzo del búfer USB (0x8000) es exactamente 3 aquí .
Conclusión
El adaptador USB-SATA probado lee el tercer bit del registro de hardware en 0xC884 y lo envía al host USB como un bit WP.
Encuentra el número 6
El registro de hardware encontrado durante la investigación del controlador de comando MODE SENSE SCSI es muy similar al GPIO. Para confirmar esto, decidí tocar las patas ASM1051 con una resistencia activa y leer el valor de registro (comando SCSI 0xE4) con la dirección 0xC884 . Para hacer esto, escribí un script de Python usando comandos SCSI personalizados que monitorean el valor en el registro 0xC884 y lo muestran en la PC.
Después de realizar tal experimento, compilé una tabla en la que mostraba qué bits en el registro 0xC884 cambiaron cuando la resistencia ASM1051 tocó las patas. Resulta que el registro en estudio está estrechamente relacionado con el GPIO, pero el intento de escribir en él (con el comando SCSI 0xE5) no tuvo éxito: el valor no cambió.
Entonces decidí que este registro es de solo lectura o que en algún lugar está prohibido escribir en él a nivel de hardware. Si, por ejemplo, las patas MK se configuraron inicialmente solo para lectura, entonces, probablemente, la escritura en el registro 0xC884 podría no estar disponible.
En general, para encontrar los registros asociados con el GPIO, revisé el código de inicialización MK. Escribí todos los registros cuyas direcciones están cerca de 0xC884 . Tengo alrededor de 10 de ellos. Les recuerdo que el décimo tramo del MK está conectado al LED en el tablero, corresponde al segundo bit en el registro 0xC884 . – 0880 , (, ). , , 0880 (/), 0884 , - .
0880 , 0884 . 0884 . ASM1051.
:
GPIO ASM1051. 0880 / I/O. 0884 I/O.
№ 5.
GPIO- , 45- 0884 . WP , USB. 45- , HDD, , .

HDD, 45-
. GND 45- , HDD. .
45- ASM1051 HDD.
USB-SATA-. ASM1051. , - , . , GPIO. – ASM1051 , HDD. , , (« »), , , USB-SATA- ASM1051.
, footprint ASM1051, datasheet ASM1053. , ASM1051 .

ASM1051
, 3D- , .

3D-
WP . GPIO ASM1051 , UART. , SATA, HDD. USB 3.0 Micro-B Type-C. HDD USB, HDD 3.5" +12 , 12 21 . .

Conclusión
, .
-, , . « «», . , .
, (, ) embedded-. , , . , , , , .
, datasheets, , . , !

Raccoon Security – «» , , , .
, , . .