Skip to content

Latest commit

 

History

History
210 lines (167 loc) · 17 KB

File metadata and controls

210 lines (167 loc) · 17 KB

اللغات: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية

← واجهة NeverC الثنائية للإضافات

إضافات DynCode

يُصرِّف -fdyncode وحدة ترجمة واحدة إلى صورة مسطّحة مستقلة عن الموضع (.bin) لا تحمل شيفرتها أي انتقالات عنونة ولا تملك مقطع بيانات. وهي تستهدف arm64/x86_64 على macOS وLinux وAndroid وWindows، عند مستوى تنفيذ المستخدم أو النواة. تراقب الإضافات المراحل المُصنَّفة التي تحوّل C إلى تلك الصورة، أو تعترضها، أو تستبدلها، عبر واجهة C الخالصة نفسها التي تستعملها بقية المجالات: بلا كائنات C++ من LLVM، ولا أنواع STL، ولا استثناءات، ولا مؤشرات مضيف لا تُصرّح جداول الواجهات بمدة حياتها.

الواجهات

#include "neverc/Plugin/PluginDynCode.h"
الواجهة الجدول الفتحات الغرض
NEVERC_INTERFACE_DYNCODE_{HIGH,LOW} NevercDynCodeAPI 16 قراءة الطلب والصورة والتقرير وخرائط المقاطع والرموز والانتقالات والمراجع الخارجية
NEVERC_INTERFACE_DYNCODE_REGISTRAR_{HIGH,LOW} NevercDynCodeRegistrarAPI 5 RegisterTarget وRegisterImportProvider وRegisterExtractor وRegisterCharsetEncoder وRegisterBinaryVerifier
NEVERC_INTERFACE_DYNCODE_PHASE_{HIGH,LOW} NevercDynCodePhaseAPI 4 GetPhaseInfo وGetRequest وGetImage وGetReport

الثلاث جميعها NEVERC_INTERFACE_STABLE عند الإصدار الرئيسي 1. ومن داخل رد نداء مرحلة، يكون NevercDynCodePhaseAPI هو نقطة الدخول — فهو يحوّل الإطار إلى المقابض التي يستهلكها الجدول الآخر:

NevercDynCodeRequestHandle Request;
Phase->GetRequest(Phase->Context, Frame, Frame->Input, &Request);

NevercDynCodeRequestInfo Info = {0};
Info.Header = (NevercABITableHeader){sizeof(Info), NEVERC_DYNCODE_API_MAJOR,
                                     NEVERC_DYNCODE_API_MINOR, 0};
DynCode->GetRequestInfo(DynCode->Context, Task, Request, &Info);

وعائلات الخرائط الأربع — خرائط المقاطع، وخرائط الرموز، والانتقالات، والمراجع الخارجية — تُجتاز كلها بالثلاثية نفسها first/next/info، مثل GetFirstRelocation وGetNextRelocation وGetRelocationInfo. وبهذا تقرأ الإضافة ما قرّره الاستخراج دون تحليل تقرير JSON.

DynCode ناتج تصريف، لا خطوة لاحقة على main()

-fdyncode هو إجراء/مهمة عادية في مخطط المُشغِّل الموجَّه اللادوري. تنشر مهمة التصريف رسم ObjectGraph مُدقَّقًا في الذاكرة؛ ثم تستهلك مهمة -dyncode-extract ذلك الرسم وتكتب صورة -o التي طلبها المستخدم. ويُظهر -### وطباعة المراحل ومخطط المهام جميعها مهمة الاستخراج، فلا تحتاج إضافة أبدًا إلى إعادة بناء argv معاد كتابته لتكتشف الوضع. ويُشارَك الطلب المُجمَّد محليًا على مستوى المهمة مع توليد الشيفرة داخل العملية؛ فلا وجود لـgetCurrentDynCodeOptions()، ولا لراية وضع عامة على مستوى العملية، ولا لرحلة ذهاب وإياب عبر كائن مؤقت.

وتُخفَّض وحدة ترجمة واحدة بالضبط إلى صورة واحدة. أما المدخلات المتعددة و -c/-S/-E والثلاثيات غير المدعومة فتُرفض مقدمًا بتشخيصات مستقرة.

مستويات التوافق

معرِّفات المراحل ومعرِّفات القطع الأثرية وحاويات الطلب والتقرير والصورة وعقود ردود النداء كلها واجهة ثنائية STABLE منذ الإصدار الأول. أما أنواع إعادة التموضع الخاصة بالهدف ومخططات المقاطع والرموز لصيغة الكائن فهي LOCKSTEP: قارن معرِّف مخطط الهدف وبصمته قبل استهلاكها. وترفض NeverC المخطط غير المتطابق قبل استدعاء أي مزوِّد.

الطلب المُجمَّد

عند بدء المهمة يُطبِّع المُشغِّل سطر الأوامر في DynCodeRequest غير قابل للتغيير ثم يُجمِّده. وتستعير المهام الفرعية اللقطة ولا تعدّلها أبدًا. ويحمل الطلب مفتاح الهدف وصيغة الكائن، ومستوى التنفيذ (مستخدم/نواة)، وسياسة نقطة الدخول (رمز صريح، أو قائمة مرشحين افتراضية، أو اشتراط أن تكون نقطة الدخول عند الصفر)، وسياسة PIC/المقاطع، وسياسة المراجع الخارجية، ومجموعة البايتات السيئة أو ملفها التعريفي وراية إعادة الكتابة، ومعرِّف مزوِّد مجموعة المحارف، والطول الأقصى والمحاذاة وبايت الحشو.

رسم المراحل المُصنَّف

DynCode رسم ثابت من 34 مرحلة. ثلاثون منها انتقالات عادية OBSERVABLE | INTERCEPTABLE | REPLACEABLE؛ وأربع منها OBSERVABLE | SEALED_HOST_GATE. والبوابات المختومة هي التدقيق النهائي لـIR، والتدقيق النهائي لـMIR، وتدقيق الصورة، والإيداع. وتستطيع الإضافة مراقبة أي مرحلة، أو تغليف انتقال قابل للاستبدال بمُعترِض، أو استبدال مزوِّده كليًا؛ لكنها لا تستطيع أبدًا استبدال بوابة مختومة أو تخطّيها أو الالتفاف عليها، ولا تستطيع التعبير عن تحويل مُعطَّل على هيئة رد نداء متروك — فالتحويل المُعطَّل يُشغِّل مزوِّدًا صريحًا لا يفعل شيئًا، ومع ذلك يظل مُدقِّق المُضيف يُثبت تكافؤ ناتجه.

والمراحل، بالترتيب، هي:

  1. تجميد الطلب؛
  2. تحويلات IR — التحضير، وخفض التفرّع غير المباشر، وخفض التعليمات الجوهرية للذاكرة (قبل الكومة وبعدها)، وخفض زمن تشغيل السلاسل، وساحة الكومة، وثلاثة مواضع لـcompiler_rt (قبل/بعد/نهائي)، وخفض استيراد النداء النظامي وPEB والنواة، وموضعان لـdata_to_text (قبل/بعد)، والتحسين المُضمَّن، وإنهاء السلاسل، والتكديس، وتحويل كل شيء إلى blr، ثم التدقيق النهائي المختوم لـIR؛
  3. تحويل تحضير MIR والتدقيق النهائي المختوم لـMIR؛
  4. استيراد الكائن — ربط ObjectGraph المُدقَّق بالمهمة؛
  5. الاستخراج — التخطيط، والتصفيف، وإعادة التموضع، وبناء الصورة المرشَّحة؛
  6. المراحل الثنائية المحدودة — ما بعد الاستخراج، وإعادة كتابة البايتات السيئة، وترميز مجموعة المحارف، والحجم/المحاذاة/الحشو، وما قبل التدقيق؛
  7. تدقيق الصورة المختوم؛
  8. الإيداع المختوم.

والمصدر المِعياري للمعرِّفات والسياسات ومستويات الاستقرار والبوابات هو Schema/PhaseSchema.json؛ وعقد التغطية القابل للتنفيذ هو coverage.json.

التحويلات المدمجة مزوِّدات أيضًا

كل مرور IR/MIR مدمج مُغلَّف بوصفه مزوِّدًا مُصنَّفًا؛ ولا يُكشف كائن مرور LLVM أبدًا عبر واجهة C الثنائية. واستبدال مرحلة يعني أن المزوِّد المدمج لا يعمل — والاختبار الناجح يُثبت السلوك أو الأثر، لا مجرد نجاح التسجيل. وتظهر مراحل mem_intrin وcompiler_rt وdata_to_text في أكثر من موضع؛ وكل موضع مرحلة مستقلة بمعرِّفها وإثباتها، فتصير إعادة التشغيل عديمة الأثر الجانبي ولا تعتمد أبدًا على حالة مرور خفيّة.

ObjectGraph هو المُدخل الكائني العادي الوحيد

يستهلك الاستخراج ObjectGraph مُدقَّقًا واحدًا بالضبط، من إنتاج مسار توليد الشيفرة الخاص بالهدف. وتربط dyncode.object.import ذلك الرسم وتفحص مفتاح الهدف ومصدره؛ ولا تعيد أبدًا قراءة بايتات من القرص ولا تُجري تحليلًا كائنيًا ثانيًا. وتدخل صيغة كائن مخصّصة إلى DynCode بمجرد أن تصير قابلة للقراءة داخل ObjectGraph وتتوافر لها مزوِّدات إعادة تموضع وهدف مطابقة. أما الكائنات المتعددة ومجموعات رسوم LTO فتُرفض عند التجميد بـCAPABILITY_UNAVAILABLE مستقرة.

المراجع الخارجية وخفض الاستيراد

مجموعة الخارجيات المسموح بها في الطلب تعني فقط «قد يتولّى مزوِّد هذا»؛ وهي لا تسمح أبدًا بأن ينجو انتقال عنونة غير محلول إلى داخل الصورة المسطّحة. فكل مرجع خارجي يجب أن ينتهي إلى إحدى الحالات: مُزال في IR/MIR، أو محلول إلى رمز داخل الصورة، أو محوَّل إلى عقد محلِّل زمن تشغيل مُعلَن ومقبول لدى المُدقِّق، أو خطأ صريح. وكعب النداء النظامي، واستيراد PEB، واستيراد النواة، هي مزوِّدات الاستيراد المدمجة الثلاثة؛ ويُعلن كلٌّ منها مُطابِق الهدف/المستوى/الرمز الخاص به والعقد الثنائي الذي يُنتجه. ويجوز للإضافة أن تضيف ImportProvider، لكن عليها أن تعيد مصدر الاستبدال، وتغيّر واجهة الدخول الثنائية، ومعاملات المحلِّل، والمراجع المتبقية.

الصورة والتقرير وتحرير البايتات المحدود

يُنتج الاستخراج DynCodeImage وDynCodeReport. والصورة بانٍ بايتات محدود، مع إزاحة نقطة الدخول ورمزها، وخرائط إخراج المقاطع والرموز المصدرية، ومآلات إعادة التموضع، وسجلات العقود الخارجية وعقود زمن التشغيل. وكل تحرير بايت يمرّ عبر واجهة القراءة/الكتابة/الإدراج/الإلحاق/تغيير الحجم المفحوصة في الباني؛ ولا وجود لأي uint8_t **. والتحرير يُحدِّث جيل الصورة ويُبطل أي إثبات إعادة تموضع أو PIC أو نقطة دخول يتقاطع مع المدى المتغيّر.

والتقرير ناتج تدقيق حتمي غير قابل للتغيير: بصمات الطلب والمسار والمُدخل والمُخرَج، وسجل المزوِّدين لكل مرحلة، والمقاطع المختارة والمرفوضة وسببها، واختيار نقطة الدخول، وعمليات إعادة التموضع المُرقَّعة والمرفوضة وتلك ذات عقود زمن التشغيل، والخارجيات المتبقية، والحجم والمحاذاة والحشو، ومسح البايتات السيئة، وقائمة تحقّق المُدقِّق. ويكتب -fdyncode-report=<path> صيغته القياسية بـJSON؛ وتُصيَّر التشخيصات المُسهبة من التقرير نفسه بدل مجموعة عدّادات ثانية.

وتعمل سلسلة إعادة كتابة البايتات السيئة بترتيب طوبولوجي مُجمَّد، وتعيد كل خطوة سجل تغيير. ويُختار مرمِّز مجموعة المحارف بمعرِّف مستقر مطابق تمامًا، ويعيد كعب مفكِّك وحمولة مُرمَّزة وتحديثًا لنقطة الدخول وإثبات هدف؛ والمعرِّف المجهول أو الملتبس خطأ صريح. وتعطيل إعادة الكتابة يختار خطوة صريحة لا تفعل شيئًا — ومع ذلك يظل التدقيق النهائي يعمل.

المُدقِّق النهائي وتوقيت ما بعد الإنهاء

تنتهي كل المراحل القابلة للكتابة قبل المُدقِّق النهائي المختوم. ويفحص المُدقِّق ألّا يتبقّى انتقال عنونة أو مرجع خارجي غير مُعالَج، وألّا يوجد مقطع بيانات أو TLS أو فك كدسة أو تنقيح أو بيانات وصفية محظور، وأن نقطة الدخول موجودة ومحاذاة بشكل صحيح وعند الإزاحة صفر متى لزم ذلك، وأن كل موقع إعادة تموضع يقع ضمن المدى بإثبات PIC مطابق لبايتات الصورة الحالية، وألّا تتداخل خرائط المقاطع والرموز، وأن قواعد الطول والمحاذاة والحشو محفوظة، وأن البايتات النهائية — بما فيها المفكِّك والترويسة والحشو — لا تحوي أي بايت محظور. وأي إخفاق يعيد تشخيصًا مُهيكلًا ويطرح حزمة الإخراج كاملة.

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

خيارات المُشغِّل

-fdyncode يُفعِّل الوضع. و-fdyncode-entry= يختار رمز نقطة الدخول. و -fdyncode-bad-bytes= / -fdyncode-bad-byte-profile= يضبطان البايتات المحظورة، و-fdyncode-bad-byte-rewrite (مُفعَّل افتراضيًا) يختار سلسلة إعادة الكتابة، و-fdyncode-charset= يختار مرمِّزًا مُسجَّلًا. و -fdyncode-max-length= و-fdyncode-align= و-fdyncode-pad= تحدّ الحجم النهائي. و-fdyncode-keep-obj= يحفظ نسخة من الكائن القابل لإعادة التموضع الوسيط، و-fdyncode-report= يكتب تقرير التدقيق. و-mdyncode-context=user|kernel يختار مستوى التنفيذ.

قواعد التزامن والإخفاق

  • احفظ الحالة القابلة للتغيير في نطاقات العملية/الجلسة/المهمة التي يوفّرها المضيف؛ لا تستعمل أبدًا مفردة «الإضافة الحالية» أو «الخيارات الحالية».
  • لا تخزّن مؤقتًا مقابض المهمة أو الرؤى المستعارة بعد عودة ردّ النداء.
  • استدعِ متابعة المُعترِض مرة واحدة على الأكثر، وعلى خيط ردّ النداء.
  • أعِد NevercStatus الأصلي؛ فإخفاق REPLACE المُعلَن لا يتراجع بصمت إلى المزوِّد المدمج.
  • أعلِن أضيق نموذجَي تزامن وإعادة دخول صادقين.

للتصريحات المعيارية راجع PluginDynCode.h، ولمتتبِّع مراحل للقراءة فقط راجع pluginsdk/examples/DynCodeTracePlugin.c، ولمرمِّز طقم محارف راجع pluginsdk/examples/DynCodeEncoderPlugin.c.