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

PLC ، اعتمادًا على قراءات الضغط ووفقًا لبرنامجها ، اتخذت قرارًا بشأن نفخ أو نفخ الكرة (فتح وإغلاق الصمامات المقابلة). ومع ذلك ، تم توفير وضع التحكم اليدوي في جميع المدرجات ، مما مكن من التحكم في حالات الصمام دون أي قيود.
تميزت المدرجات بتعقيد تمكين هذا الوضع: على الحامل غير المحمي ، كان من الأسهل القيام به ، ولكن في الحامل العالي الأمان ، كان الأمر أكثر صعوبة.
خلال يومين ، تم حل خمس من المشكلات الست ؛ حصل الفائز بالمركز الأول على 233 نقطة (أمضى أسبوعًا في التحضير للمسابقة). ثلاثة فائزين: أضع - a1exdandy ، II - Rubikoid ، III - Ze.
ومع ذلك ، خلال PHDays ، لم يتمكن أي من المشاركين من التغلب على المواقف الثلاثة ، لذلك قررنا إجراء مسابقة عبر الإنترنت ونشرنا أصعب مهمة في أوائل يونيو. كان على المشاركين إتمام المهمة في غضون شهر ، والعثور على العلم ، ووصف الحل بالتفصيل وبشكل مثير للاهتمام.
تحت الخفض ، ننشر تحليلًا لأفضل حل للمهمة المرسلة على مدار الشهر ، تم العثور عليه بواسطة Alexey Kovrizhnykh (a1exdandy) من شركة Digital Security ، التي احتلت المركز الأول في المسابقة خلال PHDays. أدناه نحن نقدم نصها مع تعليقاتنا.
التحليل الأولي
لذلك ، في المهمة كان هناك أرشيف مع الملفات:
- block_upload_traffic.pcapng
- DB100.bin
- hints.txt
يحتوي ملف hints.txt على المعلومات والنصائح الضرورية لحل المهمة. وهنا محتوياته:
- أخبرني بتروفيتش أمس أنه من PlcSim يمكنك تنزيل القطع في Step7.
- في المنصة ، تم استخدام PLCs من سلسلة Siemens Simatic S7-300.
- 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 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 .......
استنادا إلى الاسم ، فإن ملف block_upload_traffic.pcapng يحتوي على تفريغ لحركة تحميل الكتلة إلى PLC.
تجدر الإشارة إلى أن تفريغ حركة المرور على موقع المنافسة خلال المؤتمر كان أكثر صعوبة قليلاً. للقيام بذلك ، كان من الضروري فهم البرنامج النصي من ملف المشروع لـ TeslaSCADA2. من خلاله ، كان من الممكن فهم موقع التفريغ المشفر مع RC4 والمفتاح الذي يجب استخدامه لفك تشفيره. يمكن الحصول على مقالب كتل البيانات في الموقع باستخدام عميل بروتوكول S7. لقد استخدمت العميل التجريبي من حزمة Snap7 لهذا الغرض.
استخراج وحدات معالجة الإشارات من تفريغ حركة المرور
بالنظر إلى محتويات التفريغ ، يمكنك أن تفهم أن كتل معالجة الإشارات OB1 و FC1 و FC2 و FC3 تنتقل فيها:

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

بعد تلقينا معلومات حول بنية ملفات plc ، ظهرت خطة العمل التالية لقراءة برامج PLC S7:
- باستخدام Simatic Manager ، نقوم بإنشاء بنية كتلة في S7-PlcSim مماثلة لتلك التي حصلنا عليها من التفريغ. يجب أن تتوافق أحجام الكتلة (يتم تحقيقها عن طريق ملء الكتل بالعدد الصحيح من التعليمات) ومعرفاتها (OB1 ، FC1 ، FC2 ، FC3).
- احفظ PLC في ملف.
- نستبدل محتويات الكتل في الملف المستلم بالكتل من تفريغ حركة المرور. يتم تحديد بداية الكتل بالتوقيع.
- يتم تحميل الملف الناتج إلى 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.

لحل المشكلة ، من الضروري تحليل وظيفة FC3 وفهم ما يجب القيام به حتى تقوم بإرجاع وحدة منطقية.
تم ترتيب كتل معالجة الإشارات PLC في حامل الأمان المنخفض في موقع المسابقة بنفس الطريقة ، ولكن لتعيين قيمة المتغير # TEMP0 ، كان يكفي كتابة خط طريقي النينجا إلى كتلة DB1. تم التحقق من القيمة في الكتلة بشكل واضح ولا يتطلب معرفة متعمقة بلغة برمجة الكتلة. من الواضح ، على مستوى الأمان العالي ، سيكون من الصعب للغاية تحقيق التحكم اليدوي وتحتاج إلى فهم تعقيدات لغة STL (إحدى طرق برمجة S7 PLC).
عكس كتلة FC3
محتويات كتلة FC3 في تمثيل STL: الرمز ضخم للغاية ولشخص غير مألوف لدى المحكمة الخاصة بلبنان ، فقد يبدو معقدًا. ليس من المنطقي تفكيك كل تعليمات في إطار هذه المقالة ، للحصول على إرشادات مفصلة وميزات لغة STL ، راجع الدليل المقابل:
بيان قائمة (STL) ل S7-300 و S7-400 البرمجة . هنا سأقدم الرمز نفسه بعد المعالجة - إعادة تسمية الملصقات والمتغيرات وإضافة تعليقات تصف خوارزمية العمل وبعض التركيبات للغة STL. ألاحظ على الفور أنه في الكتلة قيد النظر ، يتم تنفيذ جهاز افتراضي ينفذ بعض الكود الثنائي الموجود في كتلة DB100 ، التي نعرف محتوياتها. إرشادات الجهاز الظاهري هي 1 بايت من رمز التشغيل وبايتات الوسائط ، بايت واحد لكل وسيطة. كل التعليمات التي تمت مراجعتها لها حجة ، قمت بتحديد قيمها في التعليقات كـ X و Y.
بعد أن حصلت على فكرة عن إرشادات الجهاز الظاهري ، سنقوم بكتابة أداة تفكيك صغيرة لتحليل الرمز الفرعي في كتلة 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 وسيكون من الممكن تفجير البالون أو تفريغه.
هذا كل شئ! أظهر أليكسي مستوى عالًا من المعرفة يستحق النينجا الصناعي :) لقد أرسلنا جوائز لا تنسى للفائز. شكرا جزيلا لجميع المشاركين!