
No PHDays 9, realizamos um concurso para invadir uma usina de bombeamento de gás - o concurso
Ninja Industrial . Havia três estandes no local com parâmetros de segurança diferentes (sem segurança, baixa segurança, alta segurança) emulando o mesmo processo industrial: o ar era bombeado para dentro do balão (e depois descia) sob pressão.
Apesar dos vários parâmetros de segurança, o hardware dos estandes era o mesmo: Siemens Simatic S7-300 PLC; botão de sopro de emergência e dispositivo de medição de pressão (conectado às entradas digitais do CLP (DI)); válvulas para bombeamento e sangramento (conectadas às saídas digitais do PLC (DO)) - veja a figura abaixo.

O PLC, dependendo das leituras de pressão e de acordo com seu programa, decidiu soprar ou inflar a bola (abriu e fechou as válvulas correspondentes). No entanto, foi fornecido um modo de controle manual em todas as bancadas, o que possibilitou o controle dos estados das válvulas sem restrições.
Os estandes foram distinguidos pela complexidade de ativar esse modo: em um estande desprotegido, era o mais fácil de fazer e, no estande de alta segurança, consequentemente, era mais difícil.
Em dois dias, cinco dos seis problemas foram resolvidos; o vencedor do primeiro lugar ganhou 233 pontos (ele passou uma semana se preparando para a competição). Três vencedores: Coloco - a1exdandy, II - Rubikoid, III - Ze.
No entanto, durante o PHDays, nenhum dos participantes conseguiu superar os três estandes, por isso decidimos fazer um concurso online e publicamos a tarefa mais difícil no início de junho. Os participantes tiveram que concluir a tarefa em um mês, encontrar a bandeira, descrever a solução em detalhes e de forma interessante.
Publicamos uma análise da melhor solução para a tarefa enviada ao longo do mês, que foi encontrada por Alexey Kovrizhnykh (a1exdandy) da empresa de Segurança Digital, que ficou em primeiro lugar na competição durante os PHDays. Abaixo, fornecemos seu texto com nossos comentários.
Análise inicial
Portanto, na tarefa havia um arquivo com arquivos:
- block_upload_traffic.pcapng
- DB100.bin
- hints.txt
O arquivo hints.txt contém as informações e dicas necessárias para resolver a tarefa. Aqui está o seu conteúdo:
- Petrovich me disse ontem que do PlcSim você pode baixar blocos no Step7.
- No estande, foram utilizados CLPs da série Siemens Simatic S7-300.
- O PlcSim é um emulador de PLC que permite executar e depurar programas para PLCs Siemens S7.
O arquivo DB100.bin, aparentemente, contém um bloco de dados do DB100 PLC:
00000000: 0100 0102 6e02 0401 0206 0100 0101 0102 .... n ...........
00000010: 1002 0501 0202 2002 0501 0206 0100 0102 ...... .........
00000020: 0102 7702 0401 0206 0100 0103 0102 0a02 ..w .............
00000030: 0501 0202 1602 0501 0206 0100 0104 0102 ................
00000040: 7502 0401 0206 0100 0105 0102 0a02 0501 u ...............
00000050: 0202 1602 0501 0206 0100 0106 0102 3402 ........... 4
00000060: 0401 0206 0100 0107 0102 2602 0501 0202 .......... & .....
00000070: 4c02 0501 0206 0100 0108 0102 3302 0401 L ........... 3 ...
00000080: 0206 0100 0109 0102 0a02 0501 0202 1602 ................
00000090: 0501 0206 0100 010a 0102 3702 0401 0206 .......... 7 .....
000000a0: 0100 010b 0102 2202 0501 0202 4602 0501 ...... "..... F ...
000000b0: 0206 0100 010c 0102 3302 0401 0206 0100 ........ 3 .......
000000c0: 010d 0102 0a02 0501 0202 1602 0501 0206 ................
000000d0: 0100 010e 0102 6d02 0401 0206 0100 010f ...... m .........
000000e0: 0102 1102 0501 0202 2302 0501 0206 0100 ........ # .......
000000f0: 0110 0102 3502 0401 0206 0100 0111 0102 .... 5 ...........
00000100: 1202 0501 0202 2502 0501 0206 0100 0112 ......% .........
00000110: 0102 3302 0401 0206 0100 0113 0102 2602 ..3 ........... &.
00000120: 0501 0202 4c02 0501 0206 0100 .... L .......
A julgar pelo nome, o arquivo block_upload_traffic.pcapng contém um despejo de tráfego de carregamento de bloco para o PLC.
Vale ressaltar que esse despejo de tráfego no local da competição durante a conferência foi um pouco mais difícil de obter. Para isso, foi necessário entender o script do arquivo de projeto do TeslaSCADA2. A partir dele, foi possível entender onde o dump criptografado com o RC4 está localizado e qual chave deve ser usada para descriptografá-lo. Despejos de blocos de dados no site podem ser obtidos usando o cliente do protocolo S7. Eu usei o cliente de demonstração do pacote Snap7 para isso.
Extraindo unidades de processamento de sinal de um despejo de tráfego
Observando o conteúdo do despejo, podemos entender que os blocos de processamento de sinal OB1, FC1, FC2 e FC3 são transmitidos nele:

É necessário extrair esses blocos. Isso pode ser feito, por exemplo, com o seguinte script, depois de converter o tráfego do formato pcapng para pcap:
Depois de estudar os blocos recebidos, você pode notar que eles sempre começam com os bytes 70 70 (pp). Agora você precisa aprender como analisá-los. Uma dica para a tarefa sugere que você precisa usar o PlcSim para isso.
Obtendo instruções legíveis por humanos a partir de blocos
Primeiro, vamos tentar programar o S7-PlcSim carregando nele vários blocos com instruções repetidas (= Q 0.0) usando o software Simatic Manager e salve o resultado no emulador de PLC no arquivo example.plc. Observando o conteúdo do arquivo, é possível determinar facilmente o início dos blocos carregados pela assinatura 70 70, que descobrimos anteriormente. Antes dos blocos, aparentemente, o tamanho do bloco é escrito na forma de um valor little-endian de 4 bytes.

Depois de recebermos informações sobre a estrutura dos arquivos plc, apareceu o seguinte plano de ação para a leitura dos programas PLC S7:
- Usando o Simatic Manager, criamos uma estrutura de blocos no S7-PlcSim semelhante à que recebemos do dump. Os tamanhos dos blocos devem corresponder (alcançados preenchendo os blocos com o número certo de instruções) e seus identificadores (OB1, FC1, FC2, FC3).
- Salve o PLC em um arquivo.
- Substituímos o conteúdo dos blocos no arquivo recebido pelos blocos do despejo de tráfego. O início dos blocos é determinado pela assinatura.
- O arquivo resultante é carregado no S7-PlcSim e examinamos o conteúdo dos blocos no Simatic Manager.
Os blocos podem ser substituídos, por exemplo, pelo seguinte código:
with open('original.plc', 'rb') as f: plc = f.read() blocks = [] for fname in ['OB1.bin', 'FC1.bin', 'FC2.bin', 'FC3.bin']: with open(fname, 'rb') as f: blocks.append(f.read()) i = plc.find(b'pp') for block in blocks: plc = plc[:i] + block + plc[i+len(block):] i = plc.find(b'pp', i + 1) with open('target.plc', 'wb') as f: f.write(plc)
Aleksey foi possivelmente mais complicado, mas ainda assim. Assumimos que os participantes usariam o NetToPlcSim para que o PlcSim pudesse se comunicar pela rede, carregasse blocos no PlcSim via Snap7 e depois baixasse esses blocos como um projeto do PlcSim usando o ambiente de desenvolvimento.
Ao abrir o arquivo resultante no S7-PlcSim, você pode ler os blocos substituídos usando o Simatic Manager. As principais funções de gerenciamento de dispositivos são registradas no bloco FC1. A variável # TEMP0 atrai atenção especial, quando está ligada, parece que o controle do PLC foi alternado para o modo manual com base nos valores da memória de bits M2.2 e M2.3. # TEMP0 é definido por FC3.

Para resolver o problema, é necessário analisar a função FC3 e entender o que precisa ser feito para que ela retorne uma unidade lógica.
Os blocos de processamento de sinal do PLC no estande de baixa segurança no local da competição foram organizados da mesma maneira, mas para definir o valor da variável # TEMP0, bastava escrever a linha do meu caminho ninja no bloco DB1. A verificação do valor no bloco foi organizada de forma clara e não exigia um conhecimento aprofundado da linguagem de programação do bloco. Obviamente, no nível de alta segurança, será muito mais difícil obter o controle manual e é necessário entender os meandros da linguagem STL (uma das maneiras de programar o S7 PLC).
Bloco reverso FC3
O conteúdo do bloco FC3 na representação STL: O código é bastante volumoso e, para uma pessoa não familiarizada com STL, pode parecer complicado. Não faz sentido desmontar cada instrução na estrutura deste artigo; para obter instruções detalhadas e recursos da linguagem STL, consulte o manual correspondente:
STL (Lista de instruções
) para a programação S7-300 e S7-400 . Aqui darei o mesmo código após o processamento - renomeando rótulos e variáveis e adicionando comentários descrevendo o algoritmo de trabalho e algumas construções da linguagem STL. Percebo imediatamente que no bloco em consideração é implementada uma máquina virtual que executa algum bytecode localizado no bloco DB100, cujo conteúdo conhecemos. As instruções da máquina virtual são 1 byte de código operacional e bytes de argumentos, um byte para cada argumento. Todas as instruções revisadas têm dois argumentos, designei seus valores nos comentários como X e Y.
Código de pós-processamento Tendo uma idéia das instruções da máquina virtual, escreveremos um pequeno desmontador para analisar o bytecode no bloco DB100:
import string alph = string.ascii_letters + string.digits with open('DB100.bin', 'rb') as f: m = f.read() pc = 0 while pc < len(m): op = m[pc] if op == 1: print('R{} = DB101[{}]'.format(m[pc + 2], m[pc + 1])) pc += 3 elif op == 2: c = chr(m[pc + 1]) c = c if c in alph else '?' print('R{} = {:02x} ({})'.format(m[pc + 2], m[pc + 1], c)) pc += 3 elif op == 4: print('R0 = 0; R{} = (R{} == R{})'.format( m[pc + 1], m[pc + 1], m[pc + 2])) pc += 3 elif op == 5: print('R0 = 0; R{} = R{} - R{}'.format( m[pc + 1], m[pc + 1], m[pc + 2])) pc += 3 elif op == 6: print('CHECK (R{} == R{})\n'.format( m[pc + 1], m[pc + 2])) pc += 3 else: print('unk opcode {}'.format(op)) break
Como resultado, obtemos o seguinte código de máquina virtual:
Código da máquina virtual R1 = DB101[0] R2 = 6e (n) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[1] R2 = 10 (?) R0 = 0; R1 = R1 - R2 R2 = 20 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[2] R2 = 77 (w) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[3] R2 = 0a (?) R0 = 0; R1 = R1 - R2 R2 = 16 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[4] R2 = 75 (u) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[5] R2 = 0a (?) R0 = 0; R1 = R1 - R2 R2 = 16 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[6] R2 = 34 (4) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[7] R2 = 26 (?) R0 = 0; R1 = R1 - R2 R2 = 4c (L) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[8] R2 = 33 (3) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[9] R2 = 0a (?) R0 = 0; R1 = R1 - R2 R2 = 16 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[10] R2 = 37 (7) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[11] R2 = 22 (?) R0 = 0; R1 = R1 - R2 R2 = 46 (F) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[12] R2 = 33 (3) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[13] R2 = 0a (?) R0 = 0; R1 = R1 - R2 R2 = 16 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[14] R2 = 6d (m) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[15] R2 = 11 (?) R0 = 0; R1 = R1 - R2 R2 = 23 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[16] R2 = 35 (5) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[17] R2 = 12 (?) R0 = 0; R1 = R1 - R2 R2 = 25 (?) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0) R1 = DB101[18] R2 = 33 (3) R0 = 0; R1 = (R1 == R2) CHECK (R1 == R0) R1 = DB101[19] R2 = 26 (?) R0 = 0; R1 = R1 - R2 R2 = 4c (L) R0 = 0; R1 = R1 - R2 CHECK (R1 == R0)
Como você pode ver, este programa simplesmente verifica cada símbolo do DB101 quanto à igualdade em relação a um determinado valor. A linha final para passar todas as verificações: n0w u 4r3 7h3 m4573r. Se esta linha for colocada no bloco DB101, o controle manual do PLC será ativado e será possível explodir ou desinflar o balão.
Isso é tudo! Alexey demonstrou um alto nível de conhecimento digno de um ninja industrial :) Enviamos prêmios memoráveis ao vencedor. Muito obrigado a todos os participantes!