Na sequência do Industrial Ninja: como o PLC foi hackeado no Positive Hack Days 9



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:

  1. Petrovich me disse ontem que do PlcSim você pode baixar blocos no Step7.
  2. No estande, foram utilizados CLPs da série Siemens Simatic S7-300.
  3. 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:

#!/usr/bin/env python2 import struct from scapy.all import * packets = rdpcap('block_upload_traffic.pcap') s7_hdr_struct = '>BBHHHHBB' s7_hdr_sz = struct.calcsize(s7_hdr_struct) tpkt_cotp_sz = 7 names = iter(['OB1.bin', 'FC1.bin', 'FC2.bin', 'FC3.bin']) buf = '' for packet in packets: if packet.getlayer(IP).src == '10.0.102.11': tpkt_cotp_s7 = str(packet.getlayer(TCP).payload) if len(tpkt_cotp_s7) < tpkt_cotp_sz + s7_hdr_sz: continue s7 = tpkt_cotp_s7[tpkt_cotp_sz:] s7_hdr = s7[:s7_hdr_sz] param_sz = struct.unpack(s7_hdr_struct, s7_hdr)[4] s7_param = s7[12:12+param_sz] s7_data = s7[12+param_sz:] if s7_param in ('\x1e\x00', '\x1e\x01'): # upload buf += s7_data[4:] elif s7_param == '\x1f': with open(next(names), 'wb') as f: f.write(buf) buf = '' 

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:

  1. 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).
  2. Salve o PLC em um arquivo.
  3. 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.
  4. 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:
  LB#16#0 T #TEMP13 T #TEMP15 LP#DBX 0.0 T #TEMP4 CLR = #TEMP14 M015: L #TEMP4 LAR1 OPN DB 100 L DBLG TAR1 <=D JC M016 L DW#16#0 T #TEMP0 L #TEMP6 LW#16#0 <>I JC M00d LP#DBX 0.0 LAR1 M00d: LB [AR1,P#0.0] T #TEMP5 LW#16#1 ==I JC M007 L #TEMP5 LW#16#2 ==I JC M008 L #TEMP5 LW#16#3 ==I JC M00f L #TEMP5 LW#16#4 ==I JC M00e L #TEMP5 LW#16#5 ==I JC M011 L #TEMP5 LW#16#6 ==I JC M012 JU M010 M007: +AR1 P#1.0 LP#DBX 0.0 LAR2 LB [AR1,P#0.0] LC#8 *I +AR2 +AR1 P#1.0 LB [AR1,P#0.0] JL M003 JU M001 JU M002 JU M004 M003: JU M005 M001: OPN DB 101 LB [AR2,P#0.0] T #TEMP0 JU M006 M002: OPN DB 101 LB [AR2,P#0.0] T #TEMP1 JU M006 M004: OPN DB 101 LB [AR2,P#0.0] T #TEMP2 JU M006 M00f: +AR1 P#1.0 LB [AR1,P#0.0] LC#8 *IT #TEMP11 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 TAR1 #TEMP4 OPN DB 101 LP#DBX 0.0 LAR1 L #TEMP11 +AR1 LAR2 #TEMP9 LB [AR2,P#0.0] TB [AR1,P#0.0] L #TEMP4 LAR1 JU M006 M008: +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP3 +AR1 P#1.0 LB [AR1,P#0.0] JL M009 JU M00b JU M00a JU M00c M009: JU M005 M00b: L #TEMP3 T #TEMP0 JU M006 M00a: L #TEMP3 T #TEMP1 JU M006 M00c: L #TEMP3 T #TEMP2 JU M006 M00e: +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 TAR1 #TEMP4 LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] AW INVI T #TEMP12 LB [AR1,P#0.0] LB [AR2,P#0.0] OW L #TEMP12 AW TB [AR1,P#0.0] L DW#16#0 T #TEMP0 L MB 101 T #TEMP1 L MB 102 T #TEMP2 L #TEMP4 LAR1 JU M006 M011: +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 TAR1 #TEMP4 LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] -ITB [AR1,P#0.0] L DW#16#0 T #TEMP0 L MB 101 T #TEMP1 L MB 102 T #TEMP2 L #TEMP4 LAR1 JU M006 M012: L #TEMP15 INC 1 T #TEMP15 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 TAR1 #TEMP4 LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] ==I JCN M013 JU M014 M013: LP#DBX 0.0 LAR1 T #TEMP4 LB#16#0 T #TEMP6 JU M006 M014: L #TEMP4 LAR1 L #TEMP13 LL#1 +IT #TEMP13 JU M006 M006: L #TEMP0 T MB 100 L #TEMP1 T MB 101 L #TEMP2 T MB 102 +AR1 P#1.0 L #TEMP6 + 1 T #TEMP6 JU M005 M010: LP#DBX 0.0 LAR1 L 0 T #TEMP6 TAR1 #TEMP4 M005: TAR1 #TEMP4 CLR = #TEMP16 L #TEMP13 LL#20 ==IS #TEMP16 L #TEMP15 ==IA #TEMP16 JC M017 L #TEMP13 LL#20 <IS #TEMP16 L #TEMP15 ==IA #TEMP16 JC M018 JU M019 M017: SET = #TEMP14 JU M016 M018: CLR = #TEMP14 JU M016 M019: CLR O #TEMP14 = #RET_VAL JU M015 M016: CLR O #TEMP14 = #RET_VAL 

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
]
 #    LB#16#0 T #CHECK_N #     T #COUNTER_N #     LP#DBX 0.0 T #POINTER #     CLR = #PRE_RET_VAL #     - LOOP: L #POINTER LAR1 OPN DB 100 L DBLG TAR1 <=D #       JC FINISH L DW#16#0 T #REG0 L #TEMP6 LW#16#0 <>I JC M00d LP#DBX 0.0 LAR1 #  switch - case     M00d: LB [AR1,P#0.0] T #OPCODE LW#16#1 ==I JC OPCODE_1 L #OPCODE LW#16#2 ==I JC OPCODE_2 L #OPCODE LW#16#3 ==I JC OPCODE_3 L #OPCODE LW#16#4 ==I JC OPCODE_4 L #OPCODE LW#16#5 ==I JC OPCODE_5 L #OPCODE LW#16#6 ==I JC OPCODE_6 JU OPCODE_OTHER #   01:    DB101[X]   Y # OP01(X, Y): REG[Y] = DB101[X] OPCODE_1: +AR1 P#1.0 LP#DBX 0.0 LAR2 LB [AR1,P#0.0] #   X (  DB101) LC#8 *I +AR2 +AR1 P#1.0 LB [AR1,P#0.0] #   Y ( ) JL M003 #  switch - case    Y JU M001 #      . JU M002 #       JU M004 #      M003: JU LOOPEND M001: OPN DB 101 LB [AR2,P#0.0] T #REG0 #   DB101[X]  REG[0] JU PRE_LOOPEND M002: OPN DB 101 LB [AR2,P#0.0] T #REG1 #   DB101[X]  REG[1] JU PRE_LOOPEND M004: OPN DB 101 LB [AR2,P#0.0] T #REG2 #   DB101[X]  REG[2] JU PRE_LOOPEND #   02:   X   Y # OP02(X, Y): REG[Y] = X OPCODE_2: +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP3 +AR1 P#1.0 LB [AR1,P#0.0] JL M009 JU M00b JU M00a JU M00c M009: JU LOOPEND M00b: L #TEMP3 T #REG0 JU PRE_LOOPEND M00a: L #TEMP3 T #REG1 JU PRE_LOOPEND M00c: L #TEMP3 T #REG2 JU PRE_LOOPEND #  03    ,    ... #   04:   X  Y # OP04(X, Y): REG[0] = 0; REG[X] = (REG[X] == REG[Y]) OPCODE_4: +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP7 #   - X LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 # REG[X] +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 # REG[Y] TAR1 #POINTER LAR1 #TEMP9 # REG[X] LAR2 #TEMP10 # REG[Y] LB [AR1,P#0.0] LB [AR2,P#0.0] AW INVI T #TEMP12 # ~(REG[Y] & REG[X]) LB [AR1,P#0.0] LB [AR2,P#0.0] OW L #TEMP12 AW # (~(REG[Y] & REG[X])) & (REG[Y] | REG[X]) -     TB [AR1,P#0.0] L DW#16#0 T #REG0 L MB 101 T #REG1 L MB 102 T #REG2 L #POINTER LAR1 JU PRE_LOOPEND #   05:   Y  X # OP05(X, Y): REG[0] = 0; REG[X] = REG[X] - REG[Y] OPCODE_5: +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 # REG[X] +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 # REG[Y] TAR1 #POINTER LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] -I # ACCU1 = ACCU2 - ACCU1, REG[X] - REG[Y] TB [AR1,P#0.0] L DW#16#0 T #REG0 L MB 101 T #REG1 L MB 102 T #REG2 L #POINTER LAR1 JU PRE_LOOPEND #   06:  #CHECK_N    X  Y # OP06(X, Y): #CHECK_N += (1 if REG[X] == REG[Y] else 0) OPCODE_6: L #COUNTER_N INC 1 T #COUNTER_N +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP7 # REG[X] LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 # REG[X] +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 # REG[Y] TAR1 #POINTER LAR1 #TEMP9 # REG[Y] LAR2 #TEMP10 # REG[X] LB [AR1,P#0.0] LB [AR2,P#0.0] ==I JCN M013 JU M014 M013: LP#DBX 0.0 LAR1 T #POINTER LB#16#0 T #TEMP6 JU PRE_LOOPEND M014: L #POINTER LAR1 #   #CHECK_N L #CHECK_N LL#1 +IT #CHECK_N JU PRE_LOOPEND PRE_LOOPEND: L #REG0 T MB 100 L #REG1 T MB 101 L #REG2 T MB 102 +AR1 P#1.0 L #TEMP6 + 1 T #TEMP6 JU LOOPEND OPCODE_OTHER: LP#DBX 0.0 LAR1 L 0 T #TEMP6 TAR1 #POINTER LOOPEND: TAR1 #POINTER CLR = #TEMP16 L #CHECK_N LL#20 ==IS #TEMP16 L #COUNTER_N ==IA #TEMP16 #   ,  #CHECK_N == #COUNTER_N == 20 JC GOOD L #CHECK_N LL#20 <IS #TEMP16 L #COUNTER_N ==IA #TEMP16 JC FAIL JU M019 GOOD: SET = #PRE_RET_VAL JU FINISH FAIL: CLR = #PRE_RET_VAL JU FINISH M019: CLR O #PRE_RET_VAL = #RET_VAL JU LOOP FINISH: CLR O #PRE_RET_VAL = #RET_VAL 

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!

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


All Articles