
مصحح أخطاء x86 قابل للحقن في الوضع الحقيقي للهندسة العكسية للـ BIOS وتصحيح أخطاء كود الوضع الحقيقي التعسفي عبر كابل تسلسلي، مع تكامل GDB ودعم نقاط التوقف/نقاط المراقبة العتادية.
BREAD (BIOS Reverse Engineering & Advanced Debugger) هو مصحح أخطاء x86 حقني يعمل في الوضع الحقيقي، ويمكنه تصحيح أي كود في الوضع الحقيقي (على أجهزة حقيقية) من جهاز كمبيوتر آخر عبر كابل تسلسلي.
نشأ BREAD من العديد من المحاولات الفاشلة لهندسة BIOS العكسية القديمة. نظرًا لأن الغالبية العظمى - إن لم يكن كل - تحليل BIOS يتم بشكل ثابت باستخدام مفككات (disassemblers)، يصبح فهم BIOS صعبًا للغاية، إذ لا توجد طريقة لمعرفة قيمة السجلات أو الذاكرة في جزء معين من الكود.
على الرغم من ذلك، يمكن لـ BREAD أيضًا تصحيح أي كود عشوائي في الوضع الحقيقي، مثل الكود القابل للإقلاع أو برامج DOS أيضًا.
عرض سريع:
https://user-images.githubusercontent.com/8294550/217709970-9007a1e3-7352-470d-a22f-cbb5219d5547.mp4
ينقسم هذا المصحح إلى جزئين: المصحح (مكتوب بالكامل بلغة التجميع ويعمل على الجهاز الذي يتم تصحيحه) والجسر (bridge)، مكتوب بلغة C ويعمل على Linux.
المصحح هو الكود القابل للحقن، مكتوب في وضع حقيقي 16 بت، ويمكن وضعه داخل ROM BIOS أو أي كود وضع حقيقي آخر. عند تنفيذه، يقوم بإعداد معالجات المقاطعة المناسبة، ويضع المعالج في وضع الخطوة الواحدة (single-step)، وينتظر الأوامر على المنفذ التسلسلي.
أما الجسر، فهو الرابط بين المصحح و GDB. يتصل الجسر مع GDB عبر TCP ويقوم بتوجيه الطلبات/الاستجابات إلى المصحح عبر المنفذ التسلسلي. الفكرة من وراء الجسر هي إزالة تعقيد حزم GDB وإنشاء بروتوكول أبسط للاتصال بالجهاز. بالإضافة إلى ذلك، يسمح البروتوكول الأبسط بأن يكون حجم الكود النهائي أصغر، مما يسهل حقن المصحح في بيئات مختلفة.
كما هو موضح في المخطط التالي:
+---------+ حزم بسيطة +----------+ حزم GDB +---------+
| |--------------->| |--------------->| |
| dbg | | bridge | | gdb |
|(جهاز حقيقي)|<---------------| (Linux) |<---------------| (Linux) |
+---------+ تسلسلي +----------+ TCP +---------+
من خلال تنفيذ كعب GDB، يوفر BREAD العديد من الميزات مباشرة. الأوامر التالية مدعومة:
هندسة ثنائية خام، مثل BIOS، في GDB تعني تلقائيًا عدم وجود رموزها الأصلية. ومع ذلك، مع تقدم عملية الهندسة العكسية، يكتسب المستخدم/المبرمج/المخترق فهمًا أفضل لأجزاء معينة من الكود، وتسمح أدوات التحليل الثابتة مثل IDA و Cutter و Ghidra وغيرها بإضافة تعليقات توضيحية وتعليقات وتعريفات دالة والمزيد. هذه التحسينات تعزز بشكل كبير إنتاجية المستخدم.
مع أخذ هذا في الاعتبار، يوجد نص برمجي مساعد بلغة Python في المشروع يسمى symbolify.py. بالنظر إلى قائمة الرموز (عنوان تسمية)، يقوم بإنشاء ملف ELF صغير مع إضافة هذه الرموز. يمكن بعد ذلك تحميل هذا ELF في GDB لاحقًا واستخدامه لتبسيط عملية التصحيح بشكل كبير.
يمكن أن يتضمن ملف الرموز مسافات بيضاء، أسطر فارغة، تعليقات (#)، وتعليقات على سطر العنوان. يمكن أن تكون العناوين بتنسيق عشري أو ست عشري، ويمكن أن تكون التسميات/الرموز (مفصولة بحرف مسافة بيضاء واحد أو أكثر) على الشكل [a-z0-9_]+، كما في (يمكن العثور على مثال حقيقي في symbols/ami_ipm41d3.txt):
#
# هذا تعليق
#
0xdeadbeef my_symbol1
0x123 othersymbol # هذه الدالة تقوم بـ xyz
# مثال مع عنوان عشري
456 anotherone
على سبيل المثال، بالنظر إلى ملف الرموز المتاح في symbols/ami_ipm41d3.txt، يمكن للمستخدم القيام بشيء مثل:
$ ./simbolify.py symbols/ami_ipm41d3.txt ip41symbols.elf
ثم تحميله في GDB كما يلي:
(gdb) add-symbol-file ip41symbols.elf 0
add symbol table from file "ip41symbols.elf" at
.text_addr = 0x0
(y or n) y
Reading symbols from ip41symbols.elf...
(No debugging symbols found in ip41symbols.elf)
(gdb) p cseg_
cseg_change_video_mode_logo cseg_get_cpuname
(gdb) p cseg_
لاحظ أنه حتى الإكمال التلقائي لـ GDB يعمل كما هو متوقع، أليس مذهلاً؟
كم عددها؟ نعم. نظرًا لأن الكود الذي يتم تصحيحه لا يدرك أنه يتم تصحيحه، يمكن أن يتداخل مع المصحح بعدة طرق، على سبيل المثال لا الحصر:
القفز إلى الوضع المحمي: إذا تحول الكود الذي يتم تصحيحه إلى الوضع المحمي، فإن هياكل معالجات المقاطعة وما إلى ذلك تتغير ولن يتم استدعاء المصحح بعد الآن في تلك النقطة من الكود. ومع ذلك، من الممكن أن تسمح القفزة العائدة إلى الوضع الحقيقي (مع استعادة الحالة السابقة بالكامل) للمصحح بالعمل مرة أخرى.
تغييرات IDT: إذا قام الكود الذي يتم تصحيحه بتغيير IDT أو عنوان قاعدته لأي سبب، فلن يتم استدعاء معالجات المصحح بشكل صحيح.
المكدس: يستخدم BREAD مكدسًا ويفترض أنه موجود! لا ينبغي إدراجه في مواقع لم يتم فيها تكوين المكدس بعد.
لتصحيح BIOS، هناك قيود أخرى مثل: لا يمكن تصحيح كود BIOS من البداية (bootblock)، حيث أن إعدادًا أدنى (مثل RAM) مطلوب لكي يعمل BREAD بشكل صحيح. ومع ذلك، من الممكن إجراء "إعادة تشغيل دافئة" عن طريق تعيين CS:EIP إلى F000:FFF0. في هذا السيناريو، يمكن متابعة تهيئة BIOS مرة أخرى، حيث أن BREAD قد تم تحميله بشكل صحيح. يرجى ملاحظة أن "مسار الكود" لتهيئة BIOS أثناء إعادة التشغيل الدافئة قد يكون مختلفًا عن إعادة التشغيل الباردة وقد لا يكون تدفق التنفيذ هو نفسه تمامًا.
البناء يتطلب فقط GNU Make، ومترجم C (مثل GCC أو Clang أو TCC)، و NASM، وجهاز Linux.
للمصحح وضعان للتشغيل: وضع الاستقصاء (الافتراضي) ووضع قائم على المقاطعة:
وضع الاستقصاء هو النهج الأبسط ويجب أن يعمل بشكل جيد في مجموعة متنوعة من البيئات. ومع ذلك، نظرًا لطبيعة الاستقصاء، هناك استخدام مرتفع لوحدة المعالجة المركزية:
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make
الوضع القائم على المقاطعة يحسن استخدام وحدة المعالجة المركزية عن طريق استخدام مقاطعات UART لاستقبال بيانات جديدة، بدلاً من الاستقصاء المستمر عنها. يؤدي ذلك إلى بقاء وحدة المعالجة المركزية في حالة "توقف" حتى تتلقى أوامر من المصحح، وبالتالي منعها من استهلاك 100% من موارد وحدة المعالجة المركزية. ومع ذلك، نظرًا لأن المقاطعات ليست ممكّنة دائمًا، لا يتم تعيين هذا الوضع كخيار افتراضي:
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make UART_POLLING=no
استخدام BREAD يتطلب فقط كابلًا تسلسليًا (ونعم، اللوحة الأم لديك تحتوي على رأس COM، تحقق من الدليل) وحقن الكود في الموقع المناسب.
للحقن، يجب إجراء تغييرات طفيفة في dbg.asm (مصدر المصحح). يجب تغيير 'ORG' للكود وكذلك كيفية عودة الكود (ابحث عن ">> CHANGE_HERE <<" في الكود للأماكن التي تحتاج إلى تغيير).
باستخدام AMI legacy كمثال، حيث سيتم وضع وحدة المصحح في مكان شعار BIOS (0x108200 أو FFFF:8210) وتم استبدال التعليمات التالية في ROM باستدعاء بعيد للوحدة:
...
00017EF2 06 push es
00017EF3 1E push ds
00017EF4 07 pop es
00017EF5 8BD8 mov bx,ax -┐ مستبدلة بـ: call 0xFFFF:0x8210 (dbg.bin)
00017EF7 B8024F mov ax,0x4f02 -┘
00017EFA CD10 int 0x10
00017EFC 07 pop es
00017EFD C3 ret
...
يكفي التعديل التالي:
diff --git a/dbg.asm b/dbg.asm
index caedb70..88024d3 100644
--- a/dbg.asm
+++ b/dbg.asm
@@ -21,7 +21,7 @@
; SOFTWARE.
[BITS 16]
-[ORG 0x0000] ; >> CHANGE_HERE <<
+[ORG 0x8210] ; >> CHANGE_HERE <<
%include "constants.inc"
@@ -140,8 +140,8 @@ _start:
; >> CHANGE_HERE <<
; تعليمات BIOS المستبدلة أدناه (إن وجدت)
- nop
- nop
+ mov ax, 0x4F02
+ int 0x10
nop
nop
من المهم ملاحظة أنه إذا قمت بتعديل بعض التعليمات داخل ROM الخاص بك لاستدعاء كود المصحح، فيجب استعادتها قبل العودة من المصحح.
سبب استبدال هاتين التعليمتين هو أنهما يتم تنفيذهما قبل عرض BIOS للشعار على الشاشة مباشرة، والذي أصبح الآن المصحح، مما يضمن بعض النقاط الرئيسية:
يمكن أن يكون العثور على موقع جيد لاستدعاء المصحح (حيث يكون BIOS قد قام بالتهيئة الكافية، ولكن ليس بعد فوات الأوان) أمرًا صعبًا، لكنه ممكن.
بعد ذلك، يصبح dbg.bin جاهزًا للإدراج في الموضع الصحيح في ROM.
تصحيح برامج DOS باستخدام BREAD أمر صعب بعض الشيء، لكنه ممكن:
dbg.asm بحيث يفهمه DOS كبرنامج DOS صالح:times)int 0x20)التعديل التالي يعالج هذا:
diff --git a/dbg.asm b/dbg.asm
index caedb70..b042d35 100644
--- a/dbg.asm
+++ b/dbg.asm
@@ -21,7 +21,10 @@
; SOFTWARE.
[BITS 16]
-[ORG 0x0000] ; >> CHANGE_HERE <<
+[ORG 0x100]
+
+times 40*1024 db 0x90 ; احتفظ بمسافة،
+ ; 40 كيلوبايفت تكفي
%include "constants.inc"
@@ -140,7 +143,7 @@ _start:
; >> CHANGE_HERE <<
; تعليمات BIOS المستبدلة أدناه (إن وجدت)
- nop
+ int 0x20 ; مقاطعة DOS لخروج العملية
nop
إنشاء صورة قرص مرن قابلة للإقلاع لـ FreeDOS (أو DOS) تحتوي فقط على النواة والطرفية: KERNEL.SYS و COMMAND.COM. أضف أيضًا إلى صورة القرص المرن هذه البرنامج المراد تصحيحه و DBG.COM (dbg.bin).
يجب اتخاذ الخطوات التالية بعد إنشاء الصورة:
bridge مفتوح بالفعل (راجع القسم التالي للتعليمات).DBG.COM.DBG.COM بالاستمرار حتى تنتهي.من المهم ملاحظة أن DOS لا يمحو صورة العملية بعد خروجها. نتيجة لذلك، يمكن تكوين المصحح مثل أي برنامج DOS آخر ويمكن تعيين نقاط التوقف المناسبة. بداية المصحح مليئة بـ NOPs، لذلك من المتوقع ألا تقوم العملية الجديدة بالكتابة فوق ذاكرة المصحح، مما يسمح له بمواصلة العمل حتى بعد أن يبدو "منتهيًا". هذا يسمح لـ BREAD بتصحيح البرامج الأخرى، بما في ذلك DOS نفسه.
الجسر هو الصمغ بين المصحح و GDB ويمكن استخدامه بطرق مختلفة، سواء على أجهزة حقيقية أو جهاز افتراضي.
معلماته هي:
Usage: ./bridge [options]
Options:
-s تفعيل التسلسلي عبر مأخذ توصيل، بدلاً من الجهاز
-d <path> استبدال مسار الجهاز الافتراضي (/dev/ttyUSB0)
(لا يعمل إذا كان -s مفعلاً)
-p <port> المنفذ التسلسلي (كمأخذ توصيل)، الافتراضي: 2345
-g <port> منفذ GDB، الافتراضي: 1234
-h هذه المساعدة
إذا لم يتم تمرير أي خيارات، يكون السلوك الافتراضي هو:
./bridge -d /dev/ttyUSB0 -g 1234
الاستخدامات الأدنى الموصى بها:
./bridge -s (وضع المأخذ، تسلسلي على 2345 و GDB على 1234)
./bridge (وضع الجهاز، تسلسلي على /dev/ttyUSB0 و GDB على 1234)
لاستخدامه على جهاز حقيقي، فقط قم باستدعائه بدون معلمات. اختيارياً، يمكنك تغيير مسار الجهاز باستخدام المعلمة -d:
./bridge أو ./bridge -d /path/to/device)Single-stepped, you can now connect GDB! ثم قم بتشغيل GDB: gdb.للاستخدام في جهاز افتراضي، يتغير ترتيب التنفيذ قليلاً:
./bridge أو ./bridge -d /path/to/device)make bochs أو make qemu)Single-stepped, you can now connect GDB! ثم قم بتشغيل GDB: gdb.في كلتا الحالتين، تأكد من تشغيل GDB داخل المجلد الجذر لـ BRIDGE، حيث توجد ملفات مساعدة في هذا المجلد لكي يعمل GDB بشكل صحيح في 16 بت.
BREAD دائمًا مفتوح للمجتمع ومستعد لقبول المساهمات، سواء كانت مع مشكلات، وثائق، اختبارات، ميزات جديدة، إصلاحات أخطاء، أخطاء إملائية، وما إلى ذلك. مرحبًا بكم على متن السفينة.
BREAD مرخص بموجب رخصة MIT. كتبه Davidson Francis و (نأمل) مساهمون آخرون contributors.
نقاط التوقف مطبقة كنقاط توقف عتادية، وبالتالي هناك عدد محدود من نقاط التوقف المتاحة. في التنفيذ الحالي، نقطة توقف نشطة واحدة فقط في كل مرة! ↩
نقاط المراقبة العتادية (مثل نقاط التوقف) مدعومة أيضًا مرة واحدة فقط. ↩
يرجى ملاحظة أن سجلات التصحيح لا تعمل بشكل افتراضي على VMs. بالنسبة لـ bochs، يجب أن يتم تجميعه مع العلم --enable-x86-debugger=yes. بالنسبة لـ Qemu، يجب أن يعمل مع KVM ممكّن: --enable-kvm (make qemu يقوم بذلك بالفعل). ↩