Adaptador USB-SATA reverso (histórico de um estagiário)

Antecedentes


Estágio é o processo de obtenção de conhecimento e experiência. Nossa equipe de segurança da Raccoon acredita que é impossível melhorar a segurança das informações de dispositivos e software ao nosso redor sem transmitir esse conhecimento e experiência para as gerações futuras de especialistas. É por isso que organizamos estágios individuais para estudantes e graduados talentosos há muitos anos.


Pesquisa de segurança é uma habilidade que não é ensinada na universidade. Você pode aprender com exemplos concretos e sob a orientação de mentores experientes. A cada ano, nossos estagiários resolvem problemas técnicos complexos, alcançam seus objetivos e seguem em frente, expandindo seus horizontes profissionais e tornando o mundo um pouco mais seguro. Cada um deles tem sua própria história de se tornar um especialista, e sob o corte - o começo de um deles.



1. Introdução


Em outubro do ano passado, participei de um estágio técnico na empresa NTC Vulkan. Meu interesse foi direcionado para o campo da engenharia reversa. Eu sabia o que era, já havia tentado pesquisar independentemente o crackme sob o x86, mas entendi que a coisa mais interessante está precisamente na junção de software e hardware. Eu não tinha experiência nessa área, mas queria experimentar minha mão.


Eu não tinha nenhuma expectativa específica em relação a esse evento - amigos e conhecidos costumam falar sobre estágios técnicos em várias empresas conhecidas. E quando me ofereceram a oportunidade de pesquisar um adaptador USB-SATA, fiquei satisfeito com a nova oportunidade de aprender algo. A experiência adquirida e o resultado que obtive possibilitaram verificar a exatidão da escolha do local de estágio e futura profissão.


E o estudo começou com a aquisição de um adaptador USB-SATA comum. Aqui está o que eu fiz a seguir.


Análise visual de circuitos


Primeiro, você precisa inspecionar a placa adaptadora e determinar os elementos básicos do dispositivo. As figuras abaixo indicam os principais blocos de componentes que são importantes para a operação do dispositivo. Fotos tiradas após pesquisa:



Adaptador USB para SATA. Vista superior



Adaptador USB para SATA. Vista inferior


Depois de passar algum tempo no Google, descobri que havia dois conversores de voltagem na placa: um a 3,3 V e o outro a 1,2 V. Também é muito fácil determinar a memória flash instalada na placa. A ROM funciona na interface SPI e a capacidade da memória é 512 Kbps.


Parece que o estágio de reconhecimento do circuito está quase completo, mas uma rápida pesquisa na Internet não produziu nenhum resultado para a consulta "ASM1051". Não foram encontrados documentos para o chip instalado no painel. É verdade, ainda conseguiu encontrar um software que permite atualizá-lo. Além disso, há uma pequena folha de dados para o modelo mais antigo ASM1053 .


USB


Quando conectado a um computador, o adaptador aparece como um dispositivo de armazenamento USB. Decidi que um conhecimento mais profundo sobre USB provavelmente seria útil para minha pesquisa, então, nas próximas horas que passei estudando a interface.
Em geral, os dispositivos USB podem ser de diferentes classes, dependendo de sua funcionalidade. Por exemplo, as unidades flash são o dispositivo de armazenamento em massa e os teclados e mouses são o dispositivo de interface humana (HID) . E como meu adaptador é visível no gerenciador de dispositivos como um dispositivo de armazenamento, significa que ele é definido como Armazenamento em Massa e deve funcionar com comandos SCSI.



Ler memória da ROM


Como nada se sabe sobre o ASM1051 instalado na placa, a memória da ROM foi considerada a ação mais óbvia. Eu me mudei para o laboratório. Separe o chip de memória flash com um secador de cabelo de solda e conecte-o ao programador USB ChipProg-48. Não houve problemas para ler e eu tinha um arquivo binário em minhas mãos. Naquele momento, eu não sabia dizer o que havia na unidade flash e comecei a analisar os dados.


Análise de arquivo binário


Antes de tudo, abri um despejo de memória da ROM usando o WinHex, mas você pode usar qualquer editor HEX. Começou a olhar para os bytes:



Início de um despejo de memória lido na ROM


A captura de tela acima é uma captura de tela do editor. A linha ASMT1051 , que começa com o endereço 0x44, é imediatamente evidente. Você também pode ver a linha asmedia no endereço 0x18. Para a análise inicial dos dados, usei a ferramenta de análise de frequência, disponível no WinHex.



Histograma de análise de frequência da memória ROM


O histograma mostra os bytes que estão mais no arquivo. Além do heap 0x00 e 0xFF (as colunas extremas no histograma), os seguintes bytes são frequentemente encontrados na memória:


  • 0x02;
  • 0x74;
  • 0x90;
  • 0xA3;
  • 0xE0;
  • 0xF0.

Seria possível confirmar minha suposição de que há firmware na ROM. Uma maneira fácil de fazer isso é tentar comparar os opcodes de diferentes arquiteturas adequadas para microcontroladores (daqui em diante - MC) com bytes que são freqüentemente encontrados na memória.


Se estimado aproximadamente, muitas vezes em qualquer código no assembler deve atender a comandos como:


  • mov;
  • jmp;
  • ligar;
  • ret.

Obviamente, em diferentes arquiteturas, esses comandos podem ter variações diferentes, mas há um senso comum.


Eu tive que passar por vários conjuntos de instruções para diferentes núcleos antes de encontrar os corretos. A comparação com a arquitetura do Intel 8051 deu um resultado muito plausível. Os códigos de operação de alguns comandos coincidem com os bytes populares de um arquivo, por exemplo:


  • 0x02 - LJMP addr16;
  • 0x74 - MOV A, #immed;
  • 0x90 - DPV MOV, #immed;
  • 0xA3 - INC DPTR;
  • 0xE0 - MOVX A, @DPTR;
  • 0xF0 - MOVX @DPTR, A.

Realmente parece que há firmware para MK na ROM. Pode-se carregar imediatamente o binário no desmontador do IDA Pro , mas no almoço um dos colegas perguntou:


"Você tem certeza de que o código na memória começa exatamente a partir do endereço zero?"

E realmente, você precisa levar em consideração que alguns “lixo” ou dados do endereço 0x00 podem estar na memória.


Em geral, enfrentei a tarefa de determinar o endereço inicial do código. Para atingir esse objetivo, era melhor usar o emulador EM100 SPI. O emulador substitui o chip de memória na placa, não sendo necessário soldar a ROM todas as vezes com firmware; além disso, o EM100 pode gravar um log de acesso à memória. Dado que o firmware da ROM já foi lido, agora você pode baixá-lo para o emulador SPI. Em seguida, você precisa soldar o emulador na placa adaptadora e registrar um log ao conectar o adaptador via USB a um PC.



O emulador SPI é soldado à placa adaptadora USB-SATA


Soldei a fiação do emulador para os blocos da memória flash e atualizei o emulador com alguns firmware. Agora resta ver se o MK endereça a memória e, em caso afirmativo, em quais endereços.



ROM de acesso à memória ROM (obtida usando o software emulador SPI)


A figura acima mostra que, quando a energia está conectada ao adaptador, o controlador ASM1051 instalado na placa envia vários comandos 0x03 (Read Data).


Primeiro, o ASM1051 lê 0x80 bytes, começando em 0x0000. A seguir, estão dois bytes, começando no endereço 0x0080 e mais dois bytes do endereço 0x8082. Em seguida, ele lê a maior parte da memória da ROM, iniciando no endereço 0x0082.


Podemos supor que um grande número de bytes lidos da ROM pela última vez, começando com o endereço 0x0082, seja provavelmente o código. O que e por que é solicitado antes disso ainda não está claro. Sabe-se apenas que, em resposta à primeira solicitação, o ASM1051 receberá linhas da memória flash marcadas na figura acima. Eles estavam localizados nos primeiros 0x80 bytes.


É hora de verificar que a memória externa na placa contém firmware para MK com o kernel 8051, e o próprio código está localizado no endereço 0x0082. Abra o despejo de memória no IDA Pro, especifique o tipo de processador Intel 8051 e faça o deslocamento para o código 0x0082.



Arquivo binário aberto no IDA Pro com deslocamento 0x82


Não houve problemas ao abrir o binário no desmontador.


Conclusões:


  1. O MK ASM1051 possui uma arquitetura 8051.
  2. Na ROM, existe um código que começa com o endereço 0x82. Há algo mais além do código.
  3. Os primeiros 0x80 bytes atraem atenção.

Análise de código


Agora que eu verifiquei se o código no IDA está carregado corretamente, você pode começar a analisá-lo e comentar em paralelo.


Durante o estudo do código, foram encontradas funções simples, como subtrair números de 32 bits, vários manipuladores, semelhantes ao switch () em S. Melkali, e funções muito simples, como armazenar o valor do registro R7 na memória em algum endereço. As descobertas mais significativas que descreverei abaixo.


Encontre o número 1


Curiosamente, em resposta à minha solicitação INQUIRY ( comando SCSI ), recebi uma resposta contendo duas linhas que vimos no início da memória ROM. Obviamente, mudei imediatamente essas linhas na memória do emulador, aguardando uma solicitação do INQUIRY para ver o que escrevi. Um sonho tão ingênuo desmoronou rapidamente. Agora, em resposta ao comando, vi outra linha, o ASM1051 não solicitou a maior parte da memória da ROM. O MK leu apenas os primeiros 0x80 bytes e tudo. Na arquitetura do 8051, o firmware da máscara (hardware) pode ser usado, aparentemente, o ASM1051 começou a inicializá-lo.


Então ficou claro que os primeiros bytes 0x80 são realmente importantes, e alterá-los simplesmente não funcionará. Decidi estudar com mais detalhes os pedidos que o MK faz no SPI antes de baixar o código.



Solicitação de dados SPI na ROM


Dois pedidos de dois bytes pareciam interessantes. As pesquisas no IDA 0x00, 0x80 e 0xEB produziram um grande número de resultados que eu não analisei, mas o byte 0x5A foi encontrado com menos frequência.



Comparação com o byte 0x5A. Contando a soma de verificação-8


Literalmente, o sexto clique me levou à seção de código mostrada na figura acima. Pode-se observar que o valor do registro com o endereço 0x80 7E é comparado com 0x5A. A soma de verificação-8 é lida para valores localizados do endereço 0x80 04 a 0x80 7E . Em seguida, o valor em 0x80 7F é comparado com o valor recebido anteriormente.



O começo da memória na ROM


Essas compensações se assemelhavam ao início de um despejo de memória da ROM. A figura acima mostra que o endereço 0x7E contém o byte 0x5A. E se você contar a soma de verificação 8 para bytes da posição 0x04 a 0x7E, obteremos 0xA7, e esse valor estará no endereço 0x7F.


De maneira semelhante, conseguimos encontrar o cálculo da soma de verificação para bytes do endereço 0x0082 a 0x807F (aparentemente esse é o código inteiro), que é verificado com o byte no endereço 0x8083. E em 0x8082 novamente está o valor 0x5A.


Sim, isso é um pouco mais complicado do que apenas alterar as linhas na memória. Também os mudei, mas também calculei e escrevi as somas de verificação para o novo arquivo nos lugares certos. Depois disso, em resposta ao comando SCSI INQUIRY, vi minhas linhas.


Conclusões:


  1. Durante a inicialização, o ASM1051 tenta baixar o código da ROM.
  2. Primeiro, o ASM1051 compara o checksum-8 byte do endereço 0x04 a 0x7E com o valor em 0x7F.
  3. Se a comparação da soma de verificação do preâmbulo for bem-sucedida, podemos considerá-la como "código" (endereços de 0x0082 a 0x807F). O ASM1051 compara esse valor com o valor no endereço 0x8083 e verifica se o byte 0x5A está localizado no endereço 0x8082.
  4. Se todas as verificações estiverem corretas, o ASM1051 será carregado da ROM, caso contrário, ele usará o firmware da máscara.

Encontre o número 2


Ao revisar e comentar as funções, descobri que muitas vezes a função PRINTF é usada no código (eu a chamei assim). O interessante é que, antes de ser chamado, um caractere impresso é gravado no registro R7.



Função PRINTF no IDA Pro


A função em si foi apresentada na figura acima. Vamos lidar com ela. Primeiro, você precisa mover o valor do registro com o endereço 0x7F6 para a bateria. Se houver zero, saia da função. A coisa mais interessante acontece se não houver zero. Em seguida, o valor do registro R7 é movido para o registro com o endereço 0xC001 e, como lembramos, antes de chamar esta função, um caractere impresso é gravado em R7. Em seguida, verifique se o valor em R7 é igual ao código de caractere “.” Ou “-”, se não, saia da função. Mas se a comparação tiver sido bem-sucedida, a função pega o valor do registrador com o endereço 0x16A e o move para 0xC001, mas é complicado. Por exemplo, em vez do byte 0x41 (caractere "A" em ASCII), a função passará para 0xC001 byte 0x34 (caractere "4" em ASCII) e, em seguida, 0x31 (caractere "1" em ASCII). Saia da função novamente.


Eu descobri que a verificação no início da função não pode ser aprovada, uma vez que o registro com o endereço 0x7F6 é inicializado em zero, então não muda no código. Ou seja, essa função é desativada pelo programador, embora permaneça compilada. O fato de os bytes serem gravados apenas no registro 0xC001 (e às vezes dois em uma linha) sugere que esse é provavelmente um registro de hardware.


Tudo isso se parece com o UART. Para descobrir se é assim, você deve fazer o seguinte:


  1. Identifique as pernas no ASM1051 onde o UART é emitido.
  2. Defina os parâmetros UART (velocidade, paridade, número de bits de parada).
  3. Seria bom habilitar o UART no código (aparentemente, está desativado).

Tudo parece bem simples: você pode se revezar tocando as pernas com um analisador lógico e procurar aquele em que o momento do envio do UART estará visível. Na presença de um sinal, a velocidade pode ser determinada pelo tempo dos pulsos. Com o restante dos parâmetros, tudo fica claro, basta ver o momento de enviar um byte no analisador.


Para "ativar" esta função, você pode escrever zeros em vez das três primeiras linhas, onde o valor é verificado no registro com o endereço 0x7F6. Para fazer isso, abro novamente o firmware no WinHex.



Seis bytes a serem redefinidos são alocados.


No editor, altero os seis bytes desejados para zeros. Agora o firmware está pronto e pode ser baixado no emulador de ROM. Se assumirmos que a função de saída de bytes no UART está ativada e sua chamada está localizada com muita freqüência em todo o código, podemos esperar que os bytes "voem" do UART quando o adaptador estiver em execução. Espero ver um rastreador que sinalize em bytes no UART quanto do código está sendo executado.


Como escrevi acima, para encontrar as pernas Rx e Tx necessárias, você pode examinar o analisador lógico um a um. No entanto, eu assumi que o Rx e o Tx no ASM1051 estão no mesmo local que o ASM1053 - pernas 40 e 41, respectivamente. Coloquei a sonda do analisador no pino 41 (presumido Tx) e vejo algo semelhante ao sinal desejado:



Diagrama de tempo com a perna 41 - Tx


Para conectar o conversor USB-UART e observar os caracteres impressos recebidos no terminal, tive que soldar duas cablagens finas diretamente na placa adaptadora e fixá-lo com cola quente.



Duas ligações soldadas para RX e TX


Estudei o diagrama da figura “Diagrama de tempo da perna 41 - Tx” um pouco: o tempo de um pulso, aparentemente, é de 1 µs, e para seis bits - 6,3 µs. Depois de recalcular o valor em bauds, recebi cerca de 950.000 bauds, a velocidade UART padrão mais próxima é 921600 baud. Eu acho que essa discrepância é obtida devido ao erro de medição pelo analisador lógico, eu não peguei o dispositivo mais digno, mas o chinês "bebê". Depois de definir os parâmetros na janela do programa Terminal 1.9b, pude observar os bytes recebidos do ASM1051 MK durante sua operação.



Janela do programa do terminal 1.9b durante a operação do adaptador


Conclusão:


O ASM1051 MK possui um módulo de hardware UART. O registro para o envio de dados tem o endereço 0xC001. A taxa de dados é 921600 baud. Há um bit de parada. A perna 41 é Tx e a perna 40 é Rx (embora isso não seja preciso).


Encontre o número 3


Percorrendo o código no desmontador, adicionando comentários, você pode encontrar construções mais difíceis do que escrever um número em um registro. Então, me deparei com um manipulador interessante, parte do qual em C, parecia switch () .



O manipulador de comando do registro com o endereço 0x800F


Entendendo que em algum lugar em algum lugar os comandos SCSI devem ser processados, comecei a procurar entre eles bytes com os quais o conteúdo do registro com o endereço 0x800F é comparado na figura acima. Os quatro primeiros ramos verificaram os comandos Read (10), Write (10), Read (16), Write (16). Não há dúvida de que este é um manipulador de comandos SCSI. Em seguida, observei uma função que é chamada se o comando que entrou não for Read / Write (u_Switch). Ele, dependendo do byte no registrador com o endereço 0x16A (o valor é retirado de 0x800F), lê o endereço para o qual obteremos quando eles saírem dessa função. Isso é semelhante ao switch () .



Comandos do switch SCSI


Como já determinei o byte com o qual comparo o comando SCSI que entrou no adaptador, organizei rapidamente a correspondência de endereços por comandos. Assim, por exemplo, na figura acima, pode ser visto que se o byte 0x1A estivesse no registrador com o endereço 0x16A, depois de sair da função u_Switch, iríamos para o endereço 0x1B85. Curiosamente, nem todos os bytes comparados com o u_Switch são definidos no padrão SCSI. Ou seja, o adaptador pode processar os bytes 0xE6 ou 0xDF, mas eles não são corrigidos pelo padrão.


Como você pode ver, o adaptador pode executar comandos customizados e existem funções de manipulador para eles.



Página 13 da Classe de armazenamento em massa de barramento serial universal


Preste atenção no deslocamento 0x0F em relação ao endereço 0x8000. Antes do processador, é do registro com o endereço 0x800F que o comando SCSI é lido. Se você ler atentamente a tabela na figura acima, poderá ver que no CBW (Command Block Wrapper), o campo CBWCB também possui um deslocamento de 0x0F . Acontece que os endereços da memória RAM do ASM1051, começando com 0x8000, podem ser um buffer USB, conforme mostrado na tabela abaixo.


Endereço de memóriaDescrição do produto
0x8000-0x8003dCBWSignature (USBC - no caso de receber um pacote)
0x8004-0x8007dCBWTag
0x8008-0x800BdCBWDataTransferLength
0x800CbmdCBWFlag
0x800DbCBWLUN
0x800EbCBWCBLength
0x800F-0x801FCBWCB - Comando SCSI e seus parâmetros

A figura abaixo mostra a seção de código em que ocorre a comparação com a cadeia USBC (deve ser a assinatura do dCBWSignature) e a assinatura proposta está localizada no endereço 0x8000. Eu acho que isso é suficiente para garantir que o buffer USB esteja localizado na memória RAM a partir de 0x8000.



Verifique o campo dCBWSignature para corresponder à string USBC


Conclusões:


  1. O MK ASM1051 pode lidar não apenas com comandos SCSI, descritos na norma.
  2. O endereço inicial do buffer USB é 0x8000. O comando SCSI está localizado no registrador com o endereço 0x800F, o que significa que haverá mais dados / argumentos dos comandos.

Encontre o número 4


Sabendo que o MK pode processar equipes não padronizadas, eu queria saber o que estavam fazendo. A maioria deles rapidamente me obedeceu. Não citarei o estudo do código desses comandos, pois isso pode levar muito tempo e pode ser material para um artigo separado intitulado “Assembler is simple”. Descreverei os resultados na tabela abaixo.


Comando SCSIDescrição da equipe
0xE0Permite que você leia os primeiros 0x80 bytes da ROM. No futuro, chamarei essa parte da memória de preâmbulo (sim, os mesmos 0x80 bytes nos quais existem linhas asmedia e ASM1051 )
0xE1Grava os primeiros 0x80 bytes na ROM
0xE3Grava na memória ROM de 0x80 endereços qualquer número de bytes. O argumento (como se viu) é o tamanho do pacote
0xE4Lê o bloco de bytes da RAM do ASM1051. Como argumento, pega o endereço inicial e o número de bytes que lemos
0xE5Grava um byte na RAM em
0xE7Lê o último pacote recebido no buffer ATA.
0xE8Reinicia o dispositivo

Admito que não descobri todos os comandos lendo as funções na IDA. Tendo esbarrado na parede durante a pesquisa, lembrei-me de ter visto software e muitos firmware para o ASM1051 quando estava procurando documentação sobre ele. Usando o software encontrado, você pode atualizar o firmware e reiniciar o dispositivo. Portanto, decidi que era hora de usar o Device Monitoring Studio e ver o que envia o PC ao adaptador durante a atualização.


Assim, foi possível entender como ocorre o processo de atualização do firmware: primeiro, o preâmbulo é enviado (com o comando 0xE1), depois o código é escrito com o comando 0xE3 e, em seguida, tudo isso é polido pela reinicialização (o comando 0xE8). Para uma atualização rápida e conveniente, escrevi um script Python que insere as linhas necessárias no preâmbulo, depois lê as somas de verificação e atualiza o dispositivo. Agora não preciso mais de um emulador, tive a oportunidade de fazer upload de firmware para o ASM1051 via USB, você pode devolver a ROM nativa à placa.


Conclusões


Para atualizar o firmware, três comandos SCSI devem ser executados sequencialmente: 0xE1, 0xE3 e 0xE8.


Encontre o número 5


Além dos comandos não documentados, era interessante observar os manipuladores dos comandos padrão.



Movendo o terceiro bit do registro 0xC884 para o sétimo bit do registro 0x8002


Há um teste interessante no manipulador do comando MODE SENSE (10) SCSI. A figura acima mostra parte do código da função. Pode-se ver que o terceiro bit está sendo lido no registro 0xC884 . Em seguida, o valor desse bit é definido no registro em 0x8002.


O interessante aqui é que o registro 0xC884 não foi inicializado em nenhum lugar do código, o que significa que é o hardware mais provável.



Tabela 362 do Manual de referência de comandos SCSI


Além disso, se você consultar a documentação do comando SCSI 0x5A (MODE SENSE), fica claro que o adaptador USB-SATA deve responder à solicitação MODE SENSE. O terceiro byte da resposta contém o sétimo bit do WP (proteção contra gravação - proteção contra gravação). A propósito, eu já vi o sétimo bit em 0x8002, e o deslocamento desde o início do buffer USB (0x8000) é exatamente 3 aqui .


Conclusão:


O adaptador USB-SATA testado lê o terceiro bit no registro de hardware em 0xC884 e o envia ao host USB como um bit WP.


Encontre o número 6


O registro de hardware encontrado durante a investigação do manipulador de comando MODE SENSE SCSI é muito semelhante ao GPIO. Para confirmar isso, decidi tocar as pernas do ASM1051 com um resistor ativo e ler o valor do registro (comando SCSI 0xE4) com o endereço 0xC884 . Para fazer isso, escrevi um script Python usando comandos SCSI personalizados que monitoram o valor no registro 0xC884 e o exibem no PC.


Bits 0xC88476543210 0
Perna ASM1051--37.-9104544

Após realizar tal experimento, compilei uma tabela na qual exibi quais bits no registro 0xC884 foram alterados quando o resistor ASM1051 tocou as pernas. Acontece que o registro em estudo está intimamente conectado ao GPIO, mas a tentativa de gravá-lo (com o comando SCSI 0xE5) não teve êxito - o valor não foi alterado.


Decidi que esse registro é somente leitura ou que é proibido escrever em algum lugar no nível do hardware. Se, por exemplo, as pernas MK foram configuradas inicialmente apenas para leitura, provavelmente a gravação no registro 0xC884 poderia estar indisponível.


Em geral, para encontrar os registros associados ao GPIO, repassei o código de inicialização MK. Anotei todos os registros cujos endereços estão próximos de 0xC884 . Eu tenho cerca de 10. Lembramos que a décima perna do MK está conectada ao LED na placa, corresponde ao segundo bit no registro 0xC884 . – 0880 , (, ). , , 0880 (/), 0884 , - .


0880 , 0884 . 0884 . ASM1051.


:


GPIO ASM1051. 0880 / I/O. 0884 I/O.


№ 5.


GPIO- , 45- 0884 . WP , USB. 45- , HDD, , .



HDD, 45-


. GND 45- , HDD. .



45- ASM1051 HDD.



USB-SATA-. ASM1051. , - , . , GPIO. – ASM1051 , HDD. , , (« »), , , USB-SATA- ASM1051.


, footprint ASM1051, datasheet ASM1053. , ASM1051 .



ASM1051


, 3D- , .



3D-


WP . GPIO ASM1051 , UART. , SATA, HDD. USB 3.0 Micro-B Type-C. HDD USB, HDD 3.5" +12 , 12 21 . .




Conclusão


, .


-, , . « «», . , .


, (, ) embedded-. , , . , , , , .


, datasheets, , . , !



Raccoon Security – «» , , , .
, , . .

Source: https://habr.com/ru/post/pt485966/


All Articles