على خطى Industrial Ninja: كيف تم اختراق PLC في Positive Hack Days 9

على خطى Industrial Ninja: كيف تم اختراق PLC في Positive Hack Days 9

في PHDays 9 الأخير، أجرينا مسابقة لاختراق محطة ضخ الغاز - مسابقة النينجا الصناعية. كانت هناك ثلاث منصات في الموقع بمعايير أمنية مختلفة (لا توجد أمان، إجراءات أمنية منخفضة، إجراءات أمنية مشددة)، تحاكي نفس العملية الصناعية: تم ضخ الهواء تحت الضغط إلى بالون (ثم إطلاقه).

على الرغم من معايير السلامة المختلفة، كان تكوين الأجهزة للحوامل هو نفسه: سلسلة Siemens Simatic PLC S7-300؛ زر الانكماش في حالات الطوارئ وجهاز قياس الضغط (متصل بمدخلات PLC الرقمية (DI))؛ صمامات تعمل على نفخ وتفريغ الهواء (متصلة بالمخرجات الرقمية للـ PLC (DO)) - أنظر الشكل أدناه.

على خطى Industrial Ninja: كيف تم اختراق PLC في Positive Hack Days 9

اتخذ PLC، اعتمادًا على قراءات الضغط ووفقًا لبرنامجه، قرارًا بتفريغ الكرة أو تضخيمها (فتح وإغلاق الصمامات المقابلة). ومع ذلك، كان لدى جميع المدرجات وضع التحكم اليدوي، مما جعل من الممكن التحكم في حالات الصمامات دون أي قيود.

اختلفت المدرجات في مدى تعقيد تمكين هذا الوضع: في المدرجات غير المحمية كان من الأسهل القيام بذلك، وفي المدرجات الأمنية المشددة كان الأمر أكثر صعوبة في المقابل.

تم حل خمس من المشاكل الستة في يومين؛ حصل المشارك بالمركز الأول على 233 نقطة (أمضى أسبوعًا في التحضير للمسابقة). ثلاثة فائزين: المركز الأول - a1exdandy، II - Rubikoid، III - Ze.

ومع ذلك، خلال أيام الدكتوراه، لم يتمكن أي من المشاركين من التغلب على المواقف الثلاثة، لذلك قررنا إجراء مسابقة عبر الإنترنت ونشرنا المهمة الأكثر صعوبة في أوائل يونيو. كان على المشاركين إكمال المهمة في غضون شهر، والعثور على العلم، ووصف الحل بالتفصيل وبطريقة مثيرة للاهتمام.

ننشر أدناه تحليلاً لأفضل الحلول للمهمة التي تم إرسالها خلال الشهر، والتي عثر عليها Alexey Kovrizhnykh (a1exdandy) من شركة Digital Security، الذي حصل على المركز الأول في المنافسة خلال PHDays. أدناه نقدم نصها مع تعليقاتنا.

التحليل الاولي

لذلك، تحتوي المهمة على أرشيف يحتوي على الملفات التالية:

  • block_upload_traffic.pcapng
  • DB100.bin
  • تلميحات.txt

يحتوي ملفتلميحات.txt على المعلومات والتلميحات اللازمة لحل المهمة. وهنا محتوياته:

  1. أخبرني بتروفيتش بالأمس أنه يمكنك تحميل الكتل من PlcSim إلى Step7.
  2. تم استخدام سلسلة Siemens Simatic S7-300 PLC في المنصة.
  3. PlcSim هو محاكي PLC يسمح لك بتشغيل وتصحيح البرامج لـ Siemens S7 PLCs.

يبدو أن الملف DB100.bin يحتوي على كتلة بيانات 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 0 02a00000030 ..w............. 0501: 0202 1602 0501 0206 0100 0104 0102 00000040 ................ 7502: 0401 0206 0100 0105 0102 0 02a0501 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 ل.........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 ......م .... .... 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 ....ل......

كما يوحي الاسم، يحتوي الملف block_upload_traffic.pcapng على تفريغ لحركة تحميل الكتل إلى PLC.

تجدر الإشارة إلى أن الحصول على تفريغ حركة المرور هذا في موقع المنافسة أثناء المؤتمر كان أكثر صعوبة بعض الشيء. للقيام بذلك، كان من الضروري فهم البرنامج النصي من ملف المشروع لـ TeslaSCADA2. من خلاله كان من الممكن فهم مكان وجود التفريغ المشفر باستخدام RC4 والمفتاح الذي يجب استخدامه لفك تشفيره. يمكن الحصول على مقالب كتل البيانات في الموقع باستخدام عميل بروتوكول S7. لهذا استخدمت العميل التجريبي من حزمة Snap7.

استخراج كتل معالجة الإشارة من تفريغ حركة المرور

بالنظر إلى محتويات التفريغ، يمكنك أن تفهم أنه يحتوي على كتل معالجة الإشارات OB1 وFC1 وFC2 وFC3:

على خطى Industrial Ninja: كيف تم اختراق PLC في Positive Hack Days 9

يجب إزالة هذه الكتل. يمكن القيام بذلك، على سبيل المثال، باستخدام البرنامج النصي التالي، بعد أن قمت مسبقًا بتحويل حركة المرور من تنسيق pcapng إلى 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 = ''

بعد فحص الكتل الناتجة، ستلاحظ أنها تبدأ دائمًا بالبايت 70 70 (ص). الآن عليك أن تتعلم كيفية تحليلها. يشير تلميح المهمة إلى أنك بحاجة إلى استخدام PlcSim لهذا الغرض.

الحصول على تعليمات يمكن قراءتها من قبل الإنسان من الكتل

أولاً، دعونا نحاول برمجة S7-PlcSim عن طريق تحميل عدة كتل مع تعليمات متكررة (= Q 0.0) إليها باستخدام برنامج Simatic Manager، وحفظ PLC الذي تم الحصول عليه في المحاكي في ملف example.plc. من خلال النظر إلى محتويات الملف، يمكنك بسهولة تحديد بداية الكتل التي تم تنزيلها من خلال التوقيع 70 70، الذي اكتشفناه سابقًا. قبل الكتل، على ما يبدو، يتم كتابة حجم الكتلة كقيمة صغيرة ذات 4 بايت.

على خطى Industrial Ninja: كيف تم اختراق PLC في Positive Hack Days 9

وبعد حصولنا على معلومات حول بنية ملفات plc، ظهرت خطة العمل التالية لقراءة برامج PLC S7:

  1. باستخدام Simatic Manager، نقوم بإنشاء بنية كتلة في S7-PlcSim مشابهة لتلك التي تلقيناها من التفريغ. يجب أن تتطابق أحجام الكتل (يتم تحقيق ذلك عن طريق ملء الكتل بالعدد المطلوب من التعليمات) ومعرفاتها (OB1، FC1، FC2، FC3).
  2. احفظ PLC في ملف.
  3. نقوم باستبدال محتويات الكتل في الملف الناتج بالكتل من تفريغ حركة المرور. يتم تحديد بداية الكتل من خلال التوقيع.
  4. نقوم بتحميل الملف الناتج في S7-PlcSim وننظر إلى محتويات الكتل في Simatic Manager.

يمكن استبدال الكتل، على سبيل المثال، بالكود التالي:

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)

ربما اتخذ أليكسي طريقًا أكثر صعوبة، ولكنه لا يزال صحيحًا. لقد افترضنا أن المشاركين سيستخدمون برنامج NetToPlcSim حتى يتمكن PlcSim من الاتصال عبر الشبكة، وتحميل الكتل إلى PlcSim عبر Snap7، ثم تنزيل هذه الكتل كمشروع من PlcSim باستخدام بيئة التطوير.

من خلال فتح الملف الناتج في S7-PlcSim، يمكنك قراءة الكتل المكتوبة باستخدام Simatic Manager. يتم تسجيل وظائف التحكم الرئيسية في الجهاز في الكتلة FC1. تجدر الإشارة بشكل خاص إلى المتغير #TEMP0، والذي عند تشغيله يبدو أنه يضبط التحكم PLC على الوضع اليدوي استنادًا إلى قيم ذاكرة البت M2.2 وM2.3. يتم تعيين قيمة #TEMP0 بواسطة الدالة FC3.

على خطى Industrial Ninja: كيف تم اختراق PLC في Positive Hack Days 9

لحل المشكلة، تحتاج إلى تحليل الدالة FC3 وفهم ما يجب القيام به حتى تقوم بإرجاع دالة منطقية.

تم ترتيب كتل معالجة إشارات PLC في منصة Low Security في موقع المنافسة بطريقة مماثلة، ولكن لتعيين قيمة المتغير #TEMP0، كان يكفي كتابة السطر بطريقتي النينجا في كتلة DB1. كان التحقق من القيمة في الكتلة أمرًا سهلاً ولم يتطلب معرفة عميقة بلغة برمجة الكتلة. من الواضح أنه على مستوى الأمان العالي، سيكون تحقيق التحكم اليدوي أكثر صعوبة ومن الضروري فهم تعقيدات لغة STL (إحدى طرق برمجة S7 PLC).

الكتلة العكسية FC3

محتويات كتلة FC3 في تمثيل 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

الكود طويل جدًا وقد يبدو معقدًا لشخص غير معتاد على STL. لا فائدة من تحليل كل تعليمات في إطار هذه المقالة، ويمكن العثور على التعليمات التفصيلية وقدرات لغة STL في الدليل المقابل: قائمة البيانات (STL) لبرمجة S7-300 وS7-400. سأقدم هنا نفس الكود بعد المعالجة - إعادة تسمية التسميات والمتغيرات وإضافة تعليقات تصف خوارزمية التشغيل وبعض بنيات لغة STL. اسمحوا لي أن أشير على الفور إلى أن الكتلة المعنية تحتوي على جهاز افتراضي ينفذ بعض الرموز الثانوية الموجودة في كتلة DB100، والتي نعرف محتوياتها. تتكون تعليمات الآلة الافتراضية من بايت واحد من كود التشغيل وبايتات من الوسائط، بايت واحد لكل وسيطة. تحتوي جميع التعليمات المدروسة على وسيطتين، وقد حددت قيمهما في التعليقات بـ X وY.

الكود بعد المعالجة]

# Инициализация различных переменных
      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

بعد أن حصلت على فكرة عن تعليمات الجهاز الظاهري، دعنا نكتب مفككًا صغيرًا لتحليل الكود الثانوي في كتلة 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

ونتيجة لذلك، نحصل على رمز الجهاز الظاهري التالي:

رمز الآلة الافتراضية

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)

كما ترون، يقوم هذا البرنامج ببساطة بفحص كل حرف من DB101 للتأكد من تساويه مع قيمة معينة. السطر الأخير لاجتياز جميع الاختبارات هو: n0w u 4r3 7h3 m4573r. إذا تم وضع هذا الخط في الكتلة DB101، فسيتم تنشيط التحكم اليدوي PLC وسيكون من الممكن تفجير البالون أو تفريغه.


هذا كل شئ! أظهر أليكسي مستوى عالٍ من المعرفة يستحقه النينجا الصناعي :) أرسلنا جوائز لا تُنسى للفائز. شكرا جزيلا لجميع المشاركين!

المصدر: www.habr.com

إضافة تعليق