Theo bước chân của Ninja công nghiệp: PLC đã bị hack như thế nào tại Ngày Hack tích cực 9

Theo bước chân của Ninja công nghiệp: PLC đã bị hack như thế nào tại Ngày Hack tích cực 9

Vào PHDays 9 vừa qua, chúng tôi đã tổ chức cuộc thi hack nhà máy bơm gas - cuộc thi Ninja công nghiệp. Có ba khán đài trên địa điểm với các thông số bảo mật khác nhau (Không bảo mật, Bảo mật thấp, Bảo mật cao), mô phỏng cùng một quy trình công nghiệp: không khí dưới áp suất được bơm vào khinh khí cầu (và sau đó được thả ra).

Mặc dù có các thông số an toàn khác nhau nhưng thành phần phần cứng của khán đài đều giống nhau: dòng Siemens Simatic PLC S7-300; nút giảm phát khẩn cấp và thiết bị đo áp suất (kết nối với đầu vào kỹ thuật số PLC (DI)); van hoạt động để bơm phồng và xả khí (được kết nối với đầu ra kỹ thuật số của PLC (DO)) - xem hình bên dưới.

Theo bước chân của Ninja công nghiệp: PLC đã bị hack như thế nào tại Ngày Hack tích cực 9

PLC, tùy thuộc vào chỉ số áp suất và phù hợp với chương trình của nó, đưa ra quyết định xả hơi hoặc bơm phồng quả bóng (mở và đóng các van tương ứng). Tuy nhiên, tất cả các giá đỡ đều có chế độ điều khiển thủ công, giúp kiểm soát trạng thái của các van mà không có bất kỳ hạn chế nào.

Các khán đài khác nhau về mức độ phức tạp của việc kích hoạt chế độ này: ở khán đài không được bảo vệ, việc này dễ thực hiện nhất và ở khán đài Bảo mật cao, việc này tương ứng khó khăn hơn.

Năm trong số sáu vấn đề đã được giải quyết trong hai ngày; Người đứng thứ nhất đạt được 233 điểm (anh ấy đã dành một tuần để chuẩn bị cho cuộc thi). Ba người chiến thắng: I place - a1exdandy, II - Rubikoid, III - Ze.

Tuy nhiên, trong PHDays, không ai trong số những người tham gia có thể vượt qua cả ba khán đài nên chúng tôi quyết định thực hiện một cuộc thi trực tuyến và công bố nhiệm vụ khó khăn nhất vào đầu tháng 6. Những người tham gia phải hoàn thành nhiệm vụ trong vòng một tháng, tìm lá cờ và mô tả giải pháp một cách chi tiết và thú vị.

Bên dưới phần cắt, chúng tôi công bố bản phân tích về giải pháp tốt nhất cho nhiệm vụ từ những người đã gửi trong tháng, được tìm thấy bởi Alexey Kovrizhnykh (a1exdandy) từ công ty Digital Security, người đã đứng đầu cuộc thi trong PHDays. Dưới đây chúng tôi trình bày văn bản của nó với ý kiến ​​​​của chúng tôi.

Phân tích ban đầu

Vì vậy, tác vụ chứa một kho lưu trữ với các tệp sau:

  • block_upload_traffic.pcapng
  • DB100.bin
  • gợi ý.txt

Tệp Hints.txt chứa thông tin và gợi ý cần thiết để giải quyết tác vụ. Đây là nội dung của nó:

  1. Hôm qua Petrovich đã nói với tôi rằng bạn có thể tải các khối từ PlcSim vào Step7.
  2. PLC dòng Simatic S7-300 của Siemens đã được sử dụng tại gian hàng.
  3. PlcSim là trình giả lập PLC cho phép bạn chạy và gỡ lỗi các chương trình cho PLC Siemens S7.

Tệp DB100.bin dường như chứa khối dữ liệu PLC DB100: 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 0 02a00000030 ..w............ 0501: 0202 1602 0501 0206 0100 0104 0102 00000040 ................... 7502: 0401 0206 0100 0105 0102 0 02a0501 00000050 u............ 0202: 1602 0501 0206 0100 0106 0102 3402 4............00000060. 0401: 0206 0100 0107 0102 2602 0501 0202 00000070 ........&..... 4: 02c0501 0206 0100 0108 0102 3302 0401 3 L.........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......

Như tên cho thấy, tệp block_upload_traffic.pcapng chứa một tập hợp lưu lượng tải khối lên PLC.

Điều đáng chú ý là lượng truy cập này tại địa điểm thi đấu trong thời gian diễn ra hội nghị khó có được hơn một chút. Để làm được điều này, cần phải hiểu tập lệnh từ tệp dự án cho TeslaSCADA2. Từ đó có thể hiểu được kết xuất được mã hóa bằng RC4 nằm ở đâu và cần sử dụng khóa nào để giải mã nó. Có thể lấy được các khối dữ liệu trên trang web bằng cách sử dụng ứng dụng khách giao thức S7. Để làm điều này, tôi đã sử dụng ứng dụng khách demo từ gói Snap7.

Trích xuất các khối xử lý tín hiệu từ kết xuất lưu lượng

Nhìn vào nội dung của dump có thể hiểu nó chứa các khối xử lý tín hiệu OB1, FC1, FC2 và FC3:

Theo bước chân của Ninja công nghiệp: PLC đã bị hack như thế nào tại Ngày Hack tích cực 9

Những khối này phải được loại bỏ. Ví dụ, điều này có thể được thực hiện với tập lệnh sau, trước đó đã chuyển đổi lưu lượng truy cập từ định dạng pcapng sang 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 ('x1ex00', 'x1ex01'):  # upload
            buf += s7_data[4:]
        elif s7_param == 'x1f':
            with open(next(names), 'wb') as f:
                f.write(buf)
            buf = ''

Sau khi kiểm tra các khối kết quả, bạn sẽ nhận thấy rằng chúng luôn bắt đầu bằng byte 70 70 (pp). Bây giờ bạn cần học cách phân tích chúng. Gợi ý bài tập gợi ý rằng bạn cần sử dụng PlcSim cho việc này.

Nhận hướng dẫn mà con người có thể đọc được từ các khối

Trước tiên, chúng ta hãy thử lập trình S7-PlcSim bằng cách tải một số khối có lệnh lặp lại (= Q 0.0) vào nó bằng phần mềm Simatic Manager và lưu PLC thu được trong trình mô phỏng vào tệp example.plc. Bằng cách xem nội dung của tệp, bạn có thể dễ dàng xác định phần đầu của các khối đã tải xuống bằng chữ ký 70 70 mà chúng tôi đã phát hiện ra trước đó. Rõ ràng, trước các khối, kích thước khối được viết dưới dạng giá trị endian nhỏ 4 byte.

Theo bước chân của Ninja công nghiệp: PLC đã bị hack như thế nào tại Ngày Hack tích cực 9

Sau khi chúng tôi nhận được thông tin về cấu trúc của tệp plc, kế hoạch hành động sau đây xuất hiện để đọc các chương trình PLC S7:

  1. Bằng cách sử dụng Simatic Manager, chúng tôi tạo cấu trúc khối trong S7-PlcSim tương tự như cấu trúc chúng tôi nhận được từ kết xuất. Kích thước khối phải khớp (điều này đạt được bằng cách điền vào các khối với số lượng hướng dẫn cần thiết) và mã định danh của chúng (OB1, FC1, FC2, FC3).
  2. Lưu PLC vào một tập tin.
  3. Chúng tôi thay thế nội dung của các khối trong tệp kết quả bằng các khối từ kết xuất lưu lượng. Sự bắt đầu của các khối được xác định bởi chữ ký.
  4. Chúng tôi tải tệp kết quả vào S7-PlcSim và xem nội dung của các khối trong Trình quản lý Simatic.

Các khối có thể được thay thế, ví dụ, bằng mã sau:

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)

Alexey đã đi một con đường có lẽ khó khăn hơn nhưng vẫn đúng. Chúng tôi giả định rằng những người tham gia sẽ sử dụng chương trình NetToPlcSim để PlcSim có thể giao tiếp qua mạng, tải các khối lên PlcSim thông qua Snap7, sau đó tải xuống các khối này dưới dạng dự án từ PlcSim bằng môi trường phát triển.

Bằng cách mở tệp kết quả trong S7-PlcSim, bạn có thể đọc các khối được ghi đè bằng Trình quản lý Simatic. Các chức năng điều khiển thiết bị chính được ghi vào khối FC1. Đặc biệt lưu ý là biến #TEMP0, khi được bật sẽ xuất hiện để đặt điều khiển PLC sang chế độ thủ công dựa trên các giá trị bộ nhớ bit M2.2 và M2.3. Giá trị #TEMP0 được thiết lập bởi hàm FC3.

Theo bước chân của Ninja công nghiệp: PLC đã bị hack như thế nào tại Ngày Hack tích cực 9

Để giải quyết vấn đề, bạn cần phân tích hàm FC3 và hiểu những gì cần phải làm để nó trả về một hàm logic.

Các khối xử lý tín hiệu PLC tại khán đài Low Security tại địa điểm thi đấu cũng được sắp xếp theo cách tương tự, nhưng để đặt giá trị của biến #TEMP0, chỉ cần viết dòng ninja của tôi vào khối DB1 là đủ. Việc kiểm tra giá trị trong một khối rất dễ hiểu và không yêu cầu kiến ​​thức sâu về ngôn ngữ lập trình khối. Rõ ràng, ở cấp độ Bảo mật cao, việc đạt được điều khiển thủ công sẽ khó khăn hơn nhiều và cần phải hiểu được sự phức tạp của ngôn ngữ STL (một trong những cách lập trình PLC S7).

Khối đảo ngược FC3

Nội dung của khối FC3 trong biểu diễn STL:

      L     B#16#0
      T     #TEMP13
      T     #TEMP15
      L     P#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
      L     W#16#0
      <>I   
      JC    M00d
      L     P#DBX 0.0
      LAR1  
M00d: L     B [AR1,P#0.0]
      T     #TEMP5
      L     W#16#1
      ==I   
      JC    M007
      L     #TEMP5
      L     W#16#2
      ==I   
      JC    M008
      L     #TEMP5
      L     W#16#3
      ==I   
      JC    M00f
      L     #TEMP5
      L     W#16#4
      ==I   
      JC    M00e
      L     #TEMP5
      L     W#16#5
      ==I   
      JC    M011
      L     #TEMP5
      L     W#16#6
      ==I   
      JC    M012
      JU    M010
M007: +AR1  P#1.0
      L     P#DBX 0.0
      LAR2  
      L     B [AR1,P#0.0]
      L     C#8
      *I    
      +AR2  
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      JL    M003
      JU    M001
      JU    M002
      JU    M004
M003: JU    M005
M001: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #TEMP0
      JU    M006
M002: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #TEMP1
      JU    M006
M004: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #TEMP2
      JU    M006
M00f: +AR1  P#1.0
      L     B [AR1,P#0.0]
      L     C#8
      *I    
      T     #TEMP11
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP7
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9
      TAR1  #TEMP4
      OPN   DB   101
      L     P#DBX 0.0
      LAR1  
      L     #TEMP11
      +AR1  
      LAR2  #TEMP9
      L     B [AR2,P#0.0]
      T     B [AR1,P#0.0]
      L     #TEMP4
      LAR1  
      JU    M006
M008: +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP3
      +AR1  P#1.0
      L     B [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
      L     B [AR1,P#0.0]
      T     #TEMP7
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10
      TAR1  #TEMP4
      LAR1  #TEMP9
      LAR2  #TEMP10
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      AW    
      INVI  
      T     #TEMP12
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      OW    
      L     #TEMP12
      AW    
      T     B [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
      L     B [AR1,P#0.0]
      T     #TEMP7
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10
      TAR1  #TEMP4
      LAR1  #TEMP9
      LAR2  #TEMP10
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      -I    
      T     B [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
      L     B [AR1,P#0.0]
      T     #TEMP7
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10
      TAR1  #TEMP4
      LAR1  #TEMP9
      LAR2  #TEMP10
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      ==I   
      JCN   M013
      JU    M014
M013: L     P#DBX 0.0
      LAR1  
      T     #TEMP4
      L     B#16#0
      T     #TEMP6
      JU    M006
M014: L     #TEMP4
      LAR1  
      L     #TEMP13
      L     L#1
      +I    
      T     #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: L     P#DBX 0.0
      LAR1  
      L     0
      T     #TEMP6
      TAR1  #TEMP4
M005: TAR1  #TEMP4
      CLR   
      =     #TEMP16
      L     #TEMP13
      L     L#20
      ==I   
      S     #TEMP16
      L     #TEMP15
      ==I   
      A     #TEMP16
      JC    M017
      L     #TEMP13
      L     L#20
      <I    
      S     #TEMP16
      L     #TEMP15
      ==I   
      A     #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

Mã này khá dài và có vẻ phức tạp đối với người không quen với STL. Không có ích gì khi phân tích từng hướng dẫn trong khuôn khổ bài viết này; bạn có thể tìm thấy các hướng dẫn chi tiết và khả năng của ngôn ngữ STL trong sách hướng dẫn tương ứng: Danh sách câu lệnh (STL) cho lập trình S7-300 và S7-400. Ở đây tôi sẽ trình bày cùng một mã sau khi xử lý - đổi tên nhãn và biến và thêm nhận xét mô tả thuật toán hoạt động và một số cấu trúc ngôn ngữ STL. Hãy để tôi lưu ý ngay rằng khối được đề cập có chứa một máy ảo thực thi một số mã byte nằm trong khối DB100, nội dung mà chúng tôi biết. Hướng dẫn máy ảo bao gồm 1 byte mã vận hành và byte đối số, một byte cho mỗi đối số. Tất cả các hướng dẫn được xem xét đều có hai đối số; tôi đã chỉ định giá trị của chúng trong phần nhận xét là X và Y.

Mã sau khi xử lý]

# Инициализация различных переменных
      L     B#16#0
      T     #CHECK_N        # Счетчик успешно пройденных проверок
      T     #COUNTER_N      # Счетчик общего количества проверок
      L     P#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
      L     W#16#0
      <>I   
      JC    M00d
      L     P#DBX 0.0
      LAR1  

# Конструкция switch - case для обработки различных опкодов
M00d: L     B [AR1,P#0.0]
      T     #OPCODE
      L     W#16#1
      ==I   
      JC    OPCODE_1
      L     #OPCODE
      L     W#16#2
      ==I   
      JC    OPCODE_2
      L     #OPCODE
      L     W#16#3
      ==I   
      JC    OPCODE_3
      L     #OPCODE
      L     W#16#4
      ==I   
      JC    OPCODE_4
      L     #OPCODE
      L     W#16#5
      ==I   
      JC    OPCODE_5
      L     #OPCODE
      L     W#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
      L     P#DBX 0.0
      LAR2  
      L     B [AR1,P#0.0]   # Загрузка аргумента X (индекс в DB101)
      L     C#8
      *I    
      +AR2  
      +AR1  P#1.0
      L     B [AR1,P#0.0]   # Загрузка аргумента Y (индекс регистра)
      JL    M003            # Аналог switch - case на основе значения Y
      JU    M001            # для выбора необходимого регистра для записи.
      JU    M002            # Подобные конструкции используются и в других
      JU    M004            # операциях ниже для аналогичных целей
M003: JU    LOOPEND
M001: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #REG0           # Запись значения DB101[X] в REG[0]
      JU    PRE_LOOPEND
M002: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #REG1           # Запись значения DB101[X] в REG[1]
      JU    PRE_LOOPEND
M004: OPN   DB   101
      L     B [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
      L     B [AR1,P#0.0]
      T     #TEMP3
      +AR1  P#1.0
      L     B [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
      L     B [AR1,P#0.0]
      T     #TEMP7          # первый аргумент - X
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9          # REG[X]
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10         # REG[Y]
      TAR1  #POINTER
      LAR1  #TEMP9          # REG[X]
      LAR2  #TEMP10         # REG[Y]
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      AW    
      INVI  
      T     #TEMP12         # ~(REG[Y] & REG[X])
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      OW    
      L     #TEMP12
      AW                    # (~(REG[Y] & REG[X])) & (REG[Y] | REG[X]) - аналог проверки на равенство
      T     B [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
      L     B [AR1,P#0.0]
      T     #TEMP7
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9          # REG[X]
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10         # REG[Y]
      TAR1  #POINTER
      LAR1  #TEMP9
      LAR2  #TEMP10
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      -I                    # ACCU1 = ACCU2 - ACCU1, REG[X] - REG[Y]
      T     B [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
      L     B [AR1,P#0.0]
      T     #TEMP7          #  REG[X]     
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9          #  REG[X]  
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10         # REG[Y]
      TAR1  #POINTER
      LAR1  #TEMP9          # REG[Y]
      LAR2  #TEMP10         # REG[X]
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      ==I   
      JCN   M013
      JU    M014
M013: L     P#DBX 0.0
      LAR1  
      T     #POINTER
      L     B#16#0
      T     #TEMP6
      JU    PRE_LOOPEND
M014: L     #POINTER
      LAR1  
# Инкремент значения #CHECK_N
      L     #CHECK_N
      L     L#1
      +I    
      T     #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: L     P#DBX 0.0
      LAR1  
      L     0
      T     #TEMP6
      TAR1  #POINTER

LOOPEND: TAR1  #POINTER
      CLR   
      =     #TEMP16
      L     #CHECK_N
      L     L#20
      ==I   
      S     #TEMP16
      L     #COUNTER_N
      ==I   
      A     #TEMP16
# Все проверки пройдены, если #CHECK_N == #COUNTER_N == 20
      JC    GOOD
      L     #CHECK_N
      L     L#20
      <I    
      S     #TEMP16
      L     #COUNTER_N
      ==I   
      A     #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

Khi đã có ý tưởng về các hướng dẫn của máy ảo, hãy viết một trình dịch ngược nhỏ để phân tích mã byte trong khối 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

Kết quả chúng ta nhận được mã máy ảo sau:

Mã máy ảo

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)

Như bạn có thể thấy, chương trình này chỉ cần kiểm tra từng ký tự từ DB101 xem có bằng nhau ở một giá trị nhất định hay không. Dòng cuối cùng để vượt qua tất cả các bước kiểm tra là: n0w u 4r3 7h3 m4573r. Nếu đường này được đặt trong khối DB101 thì điều khiển PLC thủ công sẽ được kích hoạt và có thể làm nổ hoặc xì hơi quả bóng.


Đó là tất cả! Alexey đã thể hiện trình độ hiểu biết cao xứng đáng là một ninja công nghiệp :) Chúng tôi đã gửi những giải thưởng đáng nhớ cho người chiến thắng. Rất cám ơn tất cả những người tham gia!

Nguồn: www.habr.com

Thêm một lời nhận xét