Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
أدوات/GitHubGitHub/noahware/binprotect
التحليل الثابتالتحليل الديناميكي (عزل)الهندسة العكسيةتحليل البرمجيات الخبيثةتحليل الملفات الثنائية
GitHubnoahware/binprotect

binprotect

أداة تعقيد bin2bin لملفات PE بمعمارية x64، لا تضيف قسمًا إلى الملف الثنائي.

عرض المستودع
304364منذ 22 أيامتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

تعليمات البناء

انظر 4. البناء لتعليمات البناء.

المحتويات

  1. مقدمة
  2. إعادة كتابة الثنائيات
    • 2.1. تتبع العناوين النسبية
    • 2.2. التفكيك
      • 2.2.1. تقسيم الكتل الأساسية
      • 2.2.2. التدفق غير المباشر للتحكم
        • 2.2.2.1. جداول القفز
          • 2.2.2.1.1. جداول القفز المحدودة
          • 2.2.2.1.2. جداول القفز غير المحدودة
          • 2.2.2.1.3. أنواع مختلفة من جداول القفز
        • 2.2.2.2. الحالات الحدّية
      • 2.2.3. الدوال
      • 2.2.4. التعامل مع استدعاءات 'Noreturn'
    • 2.3. دعم الاستثناءات
      • 2.3.1. دعم التراجع
        • 2.3.1.1. التنفيذ
      • 2.3.2. تحليل معلومات الاستثناءات
        • 2.3.2.1. SEH/C_SCOPE_TABLE
        • 2.3.2.2. FuncInfo3 وFuncInfo4
      • 2.3.3. تحليل RTTI وThrowInfo
        • 2.3.3.1. RTTI
        • 2.3.3.2. ThrowInfo
  3. التعتيم
    • 3.1. الآلة الافتراضية
      • 3.1.1. دعم التراجع
    • 3.2. كتل المسندات المعتمة
    • 3.3. تسطيح تدفق التحكم
    • 3.4. الاستبدال الخطي
    • 3.5. الحساب المختلط للقيم المنطقية
  4. البناء
  5. الاستخدام
  6. الاختصارات
  7. الاعتمادات

1. مقدمة

أداة التعتيم هذه هي bin2bin، مما يعني أنها تأخذ ملفًا ثنائيًا قابلاً للتنفيذ تم تجميعه مسبقًا وتعيد إنتاجه مع تمريرات التعتيم المطبّقة. يمكن استخدام ذلك لحماية تطبيق دون الحاجة إلى الوصول إلى الكود المصدري الأصلي. يتم دعم ملفات x64 PE (الملفات القابلة للتنفيذ المحمولة) فقط حاليًا، لكن هناك خططًا لإضافة دعم لصيغ ثنائية أخرى (مثل ELF) في المستقبل.

حاليًا، تُدرج جميع أدوات التعتيم المعروفة من نوع bin2bin قسمًا في نهاية الملف الثنائي لوضع الكود أو البيانات المعتمة داخله. وذلك للحفاظ على التخطيط الأصلي للملف الثنائي دون الحاجة إلى تغيير محتويات الأقسام الموجودة مسبقًا. هذا أسهل بكثير في الإدارة لأنه يُبقي معظم RVAs (العناوين النسبية) صالحة.

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

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

ستصف هذه الوثيقة كلاً من إعادة كتابة الملف الثنائي القابل للتنفيذ وتقنيات التعتيم المطبقة. تم تنفيذ تقنيات التعتيم التالية:

  • آلة افتراضية.
  • مسندات معتمة.
  • تسطيح تدفق التحكم.
  • استبدال خطي.
  • حساب مختلط للقيم المنطقية.

علاوة على ذلك، يدعم هذا المشروع الاستثناءات (استثناءات C++ وSEH) ويمكنه تعتيم الدوال التي تحتوي على معالجة استثناءات.

للمساعدة في التفكيك واكتشاف الكود في الملف الثنائي، يتم قبول ملفات الرموز (سواء PDB أو MAP) بشكل اختياري. توفير ملفات الرموز ليس ضروريًا ولكنه يساعد في التفكيك في الملفات الثنائية المعقدة. تتطلب بعض الميزات مثل دعم الاستثناءات وتسطيح تدفق التحكم توفير ملف رموز.

2. إعادة كتابة الثنائيات

أداة إعادة كتابة الثنائيات تأخذ ملفًا ثنائيًا قابلاً للتنفيذ وتغيّر الكود أو البيانات بداخله لإنتاج ملف ثنائي ناتج مع تطبيق التغييرات.

2.1. تتبع العناوين النسبية

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

عندما يتم العثور على أي مرجع لعنوان نسبي (مثل تعليمة تحتوي على معاملات نسبية لـ rip أو أدلة بيانات PE)، يُضاف إلى قائمة تتبع ليتم تحديثه في نهاية إعادة الكتابة. يتم تتبع RVA حيث يقع المرجع (لمعرفة أين يتم تحديث المرجع) وكذلك RVA المُشار إليه (لمعرفة العنوان الذي سيتم تحديث المرجع به).

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

يجب تعديل جميع RVAs المتتبعة كلما تم إدراج أي بايتات أو إزالتها من الملف الثنائي. على سبيل المثال، هذا هو معالج إدراج البايتات:```cpp
void binwrite::binary_t::insert(const rva_t rva, const std::span data, const bool inclusive)
{
buffer_.insert_range(buffer_.begin() + rva.value(), data);

root@kitploit:~
update_rvas(rva, static_cast<rva_t::size_type>(data.size()), inclusive);  

}

root@kitploit:~
`update_rvas` هو المكان الذي يتم فيه تحديث كل RVA مُتتبَّع ليعكس التغيير الذي حدث في الملف الثنائي. فيما يلي مخطط لهذه العملية:

![](https://assets.kitploit.com/production/public/readmes/8091/12aaec3746c267c932ab3804b13df50c08a463c403417bf6ba4a52f39d12f48a.png)

الشكل 1. تتبّع العناوين النسبية (Relative address tracking).

البيانات المُدرَجة (بالأزرق) تُزيح البيانات الحالية (بالرمادي). يتم تحديث RVA المُشار إليه في التعليمات (بالبرتقالي) ليشير إلى نفس الموقع في الذاكرة، مع احتساب البيانات المُدرَجة (بالأزرق).

## 2.2. التفكيك (Disassembly)

تتم إضافة جميع نقاط الدخول المحتملة للكود (الصادرات، نقطة الدخول، عمليات إعادة التوطين التي تشير إلى قسم الكود، إلخ) إلى قائمة انتظار التفكيك. إذا كان ملف الرموز (symbol file) موجودًا، تتم أيضًا إضافة جميع الدوال الموصوفة بواسطة ملف الرموز إلى قائمة انتظار التفكيك. يتم التعامل مع كل إدخال في قائمة الانتظار ككتلة أساسية (basic block) مستقلة.

الكتلة الأساسية هي مجموعة من التعليمات بدون فروع؛ وهذا يعني أنها تُنهى عند تعليمات التحكم في تدفق التنفيذ (مثل jump، ret، int). لا تنتهي الكتل الأساسية عند استدعاءات الدوال (calls) لأنه من المتوقع أن تعود الدالة في معظم الحالات. بعض الدوال لا تعود (مثل `_CxxThrowException`) وستُشار إليها من الآن فصاعدًا باسم استدعاءات 'noreturn'.

عند معالجة كتلة أساسية من قائمة انتظار التفكيك، يتم تفكيك كل تعليمة بدءًا من الأعلى حتى يحدث أحد الأمور التالية:

- الوصول إلى كتلة أساسية أخرى تم تحليلها بالفعل، مما يؤدي إلى تداخل. انظر "تقسيم الكتل الأساسية".  
- العثور على تعليمة إنهاء (jump، return، int).  
- فشل تفكيك التعليمات.  
- العثور على حشوة كود (code padding).

فيما يلي مخطط لعملية التفكيك ودخول قائمة انتظار التفكيك (تم حذف فحص حشوة الكود في المخطط). تتكرر هذه العملية حتى تصبح قائمة انتظار التفكيك فارغة.

![](https://assets.kitploit.com/production/public/readmes/8091/7fe76b27e9f1f6ef620bc3aca182b85f9075c1dd31441e565099c05cfa396098.png)

الشكل 2. معالجة التفكيك.

### 2.2.1 تقسيم الكتل الأساسية

إذا تداخلت كتلتان أساسيتان، فيجب تقسيم إحداهما. وهذا يمنع كتلتين من وصف نفس التعليمات. على سبيل المثال:```asm  
wcslen proc  
    or      rax, 0FFFFFFFFFFFFFFFFh  
loc_140001078:  
    inc     rax  
    cmp     word ptr [rcx+rax*2], 0  
    jnz     short loc_140001078  
    retn  
wcslen endp  

هذا هو تنفيذ wcslen، الذي يحصل على طول سلسلة عريضة. عندما يتم تفكيك التعليمات الأولى 'or rax, FFFFFFFFFFFFFFFF' كبداية لكتلة أساسية، فإنها ستستمر في التفكيك حتى 'retn'.

تقفز التعليمات 'jnz loc_140001078' إلى الأعلى لتشكيل حلقة. ستتم إضافة هذا كمرجع، وسيتم إضافة هدف jnz إلى قائمة انتظار التفكيك بالإضافة إلى فرع التتابع (التعليمة التالية). تقفز التعليمات في منتصف الكتلة التي تم تحليلها بالفعل، لذا لا يمكنها ببساطة تشكيل كتلة جديدة والتفكيك مرة أخرى حتى 'retn' لأن ذلك سيمثل تكرارًا.

ستأخذ 'jnz' (القفزة الشرطية) أيضًا فرع التتابع لإنشاء كتلة أساسية جديدة أيضًا. الآن سيكون هناك 4 كتل أساسية تبدو كالتالي:

Block A:```asm
or rax, 0FFFFFFFFFFFFFFFFh
loc_140001078:
inc rax
cmp word ptr [rcx+rax*2], 0
jnz short loc_140001078

root@kitploit:~
الكتلة B:```asm  
loc_140001078:  
    inc     rax  
    cmp     word ptr [rcx+rax*2], 0  
    jnz     short loc_140001078  

الكتلة ج:```asm
retn

root@kitploit:~
الكتلة D:```asm  
    retn  

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

Block A:```asm
or rax, 0FFFFFFFFFFFFFFFFh

root@kitploit:~
الكتلة B:```asm  
    inc     rax  
    cmp     word ptr [rcx+rax*2], 0  
    jnz     short loc_140001078  

الكتلة C:```asm
retn

root@kitploit:~
### 2.2.2. تدفق التحكم غير المباشر

#### 2.2.2.1. جداول القفز

تُستخدم جداول القفز لتخزين عناوين معالجات جمل switch. بدلاً من وجود عدد كبير من عبارات if/قفزات شرطية لكل حالة داخل جملة switch، يتم الاحتفاظ بجدول يحتوي على عناوين معالجات الحالات. إليك مثال (ملف LLVM/CLANG الثنائي):```cpp  
std::int32_t sub_140004080(const std::int32_t a1)  
{  
  std::int32_t result;

  switch ( a1 )  
  {  
    case 0:  
      result = 9;  
      break;  
    case 1:  
      result = 4;  
      break;  
    case 2:  
      result = 3;  
      break;  
    case 3:  
      result = 1;  
      break;  
    default:  
      result = 0;  
      break;  
  }

  return result;  
}  

يتم تجميع عبارة switch هذه إلى كود التجميع التالي:```asm
; ecx = a1
cmp ecx, 3 ; check if above bounds, must be default case
ja short def_140004097 ; goto default if a1 above 3
mov ecx, ecx
mov eax, ecx
lea rcx, jpt_140004097
movsxd rax, ds:(jpt_140004097 - 140004100h)[rcx+rax*4] ; select correct index of jump table for a1
add rax, rcx
jmp rax ; goto index specified - the case handler

jpt_140004097:
dd offset loc_140004099 - 140004100h ; address of handler for first case
dd offset loc_1400040D5 - 140004100h ; address of handler for second case
dd offset loc_1400040BD - 140004100h ; address of handler for third case
dd offset loc_1400040C9 - 140004100h ; address of handler for fourth case

root@kitploit:~
تتم مقارنة قيمة 'a1' مع الحد الأقصى لقيم الحالات، وإذا كانت أعلى منه فسيتم الانتقال مباشرةً إلى المعالج الافتراضي. إذا كانت قيمة a1 ضمن نطاق الحالات، فسيتم الوصول إلى الإدخال المقابل لها في جدول القفز والقفز إلى عنوان المعالج المحسوب.

يتم تتبع إدخالات جدول القفز وكذلك المراجع إلى جدول القفز بحيث تظل سليمة.

##### 2.2.2.1.1. جداول القفز المحدودة

إذا كان جدول القفز لا يصف جميع نطاقات القيم التي يمكن أن تستخدمها عبارة التبديل، فسيستخدم جدولًا محددًا للتحقق من الحدود. والغرض من ذلك هو توجيه عبارة التبديل إلى العبارة الافتراضية إذا كانت القيمة خارج الحدود. لتحليل عدد عبارات الحالة، يتم فحص تعليمة المقارنة للعثور على عدد الإدخالات. على سبيل المثال، تُظهر التعليمة 'cmp ecx, 3' أن عدد الإدخالات في جدول القفز هو 3.

##### 2.2.2.1.2. جداول القفز غير المحدودة

إذا كان جدول القفز يغطي جميع القيم الممكنة التي يمكن أن تكون عليها عبارات الحالة (مثلًا من القيمة الدنيا UINT8 إلى القيمة القصوى UINT8 لنوع UINT8)، فسيستخدم جدولًا غير محدود بدون فحوصات للحدود. وذلك لأن المترجم يعلم أن جداول القفز تغطي جميع القيم الممكنة. لا توجد تعليمة مقارنة تشير إلى عدد إدخالات جدول القفز، لذلك يجب تخمين الإدخالات بالقوة الغاشمة. يتم فحص قاعدة الجدول بشكل تدريجي بحثًا عن RVAs صالحة في قسم الكود، ويتم تتبع كل إدخال صالح. هذا ليس آمنًا تمامًا لأنه قد يحلل بيانات/تعليمات أخرى كإدخالات لجدول القفز، ولهذا السبب يُستخدم فحص جداول القفز المحدودة حيثما أمكن.

##### 2.2.2.1.3. أنواع مختلفة من جداول القفز

يدعم مُعيد كتابة الثنائيات جداول القفز في الملفات الثنائية المبنية بواسطة MSVC (بما في ذلك الجداول متعددة المستويات)، وLLVM/CLANG، وGCC.

جداول القفز في MSVC لها شكلان: عادي ومتعدد المستويات. جداول القفز العادية في MSVC هي مصفوفة من RVAs. كل RVA يشير إلى معالج عبارة الحالة.

تُستخدم الجداول متعددة المستويات في MSVC لعبارات التبديل التي تحتوي على عدد كبير من عبارات الحالة التي تتشارك المعالجات. النسخة متعددة المستويات تحتوي على جدولين: الأول لمصفوفة عناوين RVA للمعالجات والثاني لمطابقة قيم الحالات مع الفهارس في الجدول الأول. يمنع هذا تكرار عناوين RVA في الجدول الأول، حيث أن كل قيمة حالة تحتاج فقط إلى وصف فهرس من بايت واحد بدلاً من RVA بحجم 4 بايت. فيما يلي مثال على جدول قفز متعدد المستويات:```asm  
lea     rdx, cs:140000000h  
movsxd  rax, edi ; load value  
movzx   eax, ds:(byte_1400023D8 - 140000000h)[rdx+rax] ; get handler index by value  
mov     ecx, ds:(jpt_14000209D - 140000000h)[rdx+rax*4] ; get RVA of handler by handler index  
add     rcx, rdx  
jmp     rcx

jpt_14000209D dd offset loc_14000209F - 140000000h  
dd offset loc_1400020AB - 140000000h  
dd offset loc_1400020B7 - 140000000h  
dd offset loc_1400020CF - 140000000h  
dd offset loc_1400020E7 - 140000000h  
dd offset loc_1400020F3 - 140000000h  
dd offset loc_1400020FF - 140000000h  
dd offset loc_14000210B - 140000000h  
dd offset loc_140002117 - 140000000h  
dd offset loc_140002123 - 140000000h  
dd offset loc_1400020C3 - 140000000h  
dd offset loc_14000213B - 140000000h  
dd offset loc_140002147 - 140000000h  
dd offset loc_140002153 - 140000000h  
dd offset loc_14000216B - 140000000h  
dd offset loc_140002177 - 140000000h  
dd offset loc_140002183 - 140000000h  
dd offset loc_14000219B - 140000000h  
dd offset loc_1400021A7 - 140000000h  
dd offset loc_1400021B3 - 140000000h  
dd offset loc_1400021BF - 140000000h  
dd offset loc_1400021CB - 140000000h  
dd offset loc_1400021D7 - 140000000h  
dd offset loc_1400021E3 - 140000000h  
dd offset loc_1400021EF - 140000000h  
dd offset loc_1400021FB - 140000000h  
dd offset loc_140002207 - 140000000h  
dd offset loc_140002213 - 140000000h  
dd offset loc_14000221F - 140000000h  
dd offset loc_14000222B - 140000000h  
dd offset loc_140002237 - 140000000h  
dd offset loc_140002243 - 140000000h  
dd offset loc_14000224F - 140000000h  
dd offset loc_14000225B - 140000000h  
dd offset loc_140002267 - 140000000h  
dd offset loc_140002273 - 140000000h  
dd offset loc_14000227F - 140000000h  
dd offset loc_14000228B - 140000000h  
dd offset loc_140002294 - 140000000h  
; ... more handler addresses

byte_1400023D8:  
db 0, 2Bh, 1, 2, 2Bh, 3, 2Bh, 4, 2Bh, 5, 6, 7, 8, 9, 0Ah, 0Bh, 0Ch, 0Dh  
db 2Bh, 0Eh, 0Fh, 10h, 2Bh, 11h, 12h, 13h, 14h, 15h, 2Bh, 16h, 2Bh, 17h  
db 18h, 19h, 1Ah, 1Bh, 1Ch, 2Bh, 1Dh, 1Eh, 1Fh, 20h, 21h, 22h, 2Bh, 23h  
db 24h, 25h, 26h, 2Bh, 2Bh, 2Bh, 2Bh, 2Bh, 2Bh, 2Bh, 2Bh, 2Bh, 2Bh, 2Bh  
; ... more indexes to handler table  

يستخدم LLVM جدولاً يحتوي على إزاحات نسبية إلى قاعدة الجدول. من خلال إضافة عنوان قاعدة الجدول إلى الإزاحة الموصوفة في المدخل، يمكن حساب عنوان المعالج.

جدول القفز الخاص بـ GCC هو مصفوفة من عمليات إعادة التوطين من نوع DIR64. تشير كل عملية إعادة توطين إلى عنوان تعليمة case. عمليات إعادة التوطين مُتتبَّعة بالفعل، لذا فإن مدخلات جدول القفز هذه مُصحَّحة بالفعل. في وقت التشغيل، ستتم إزاحة مدخلات إعادة التوطين هذه بمقدار العنوان الأساسي، بحيث يمكن إلغاء الإشارة إلى كل مدخل للحصول على عنوان تعليمة case.

2.2.2.2. الحالات الحدّية

هناك بعض الأشكال الأخرى من تدفق التحكم غير المباشر التي يجب دعمها. على سبيل المثال، في الملفات الثنائية لـ CLANG التي تستخدم استثناءات C++ من نوع FuncInfo3، يتم تحميل عنوان الاستمرار في السجل rax ثم يُعاد إلى المستدعي. سيقفز المستدعي إلى عنوان الاستمرار.```asm
lea rax, [rip+X]
retn

root@kitploit:~
حتى إذا كان العنوان الذي يشير إليه lea موجودًا في قسم كود، فليس هناك ضمان بأنه كود فعلًا. يمكن وضع جداول القفز والسلاسل النصية في أقسام كود الملف الثنائي لتحسين محلية التخزين المؤقت (cache locality). يجب اكتشاف مسارات الكود الصحيحة وفك تجميعها لكي نتمكن من تتبع مراجع RVA داخلها (وكذلك من أجل التمكن من تعتيمها)، لذا من الضروري أن يمكن تحديد هذه الحالات. هذه مراجع 'خطيرة'.

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

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

إذا تم توفير ملف رموز، فلن تتم إضافة أي رمز بيانات كمرجع خطير.

### 2.2.3. الدوال

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

لكل دالة، تُنفَّذ الخطوات التالية:

- الحصول على كتلة الدخول الأساسية للدالة (الكتلة الأساسية عند عنوان بداية الدالة).  
- تعيين كتلة الدخول هذه إلى الدالة.  
- العثور على جميع المخارج من الكتلة الأساسية (الفروع المتتابعة، الفروع المستهدفة).  
- لكل كتلة خروج أساسية، عيِّنها إلى الدالة إذا لم تكن كتلة دخول لدالة أخرى (RVA للكتلة الأساسية != RVA لأي دالة). تُكرَّر الخطوتان الأخيرتان لكل كتلة أساسية مكتشفة.  
- يتم تحليل أي جداول قفز في الكتل المكتشفة وتعيين كتلتها الأساسية المستهدفة إلى الدالة.

### 2.2.4. معالجة استدعاءات 'Noreturn'

الدوال من نوع Noreturn لا تعود. إذا حدث استدعاء noreturn، ستستمر الكتلة الأساسية في فك التجميع لأن الاستدعاءات لا تُنهي الكتل الأساسية. لا يُتوقع من الملف الثنائي أن ينفّذ ما بعد الاستدعاء، لذلك لم يُدرج المترجم كودًا مناسبًا يُنهي الكتلة الأساسية. من المرجح أن تلتقط فحوصات فك التجميع المذكورة أعلاه هذه الحالات وتُنهي الكتلة الأساسية.```asm  
sub_140006310 proc  
    sub     rsp, 38h  
    mov     rax, cs:__security_cookie  
    xor     rax, rsp  
    mov     [rsp+38h+var_8], rax  
    mov     [rsp+38h+pExceptionObject], 2Ah  
    lea     rdx, __TI1H  
    lea     rcx, [rsp+38h+pExceptionObject]  
    call    _CxxThrowException  
    db 0CCh  
sub_140006310 endp  
algn_14000633D:  
    align 20h  

على سبيل المثال، في استدعاء _CxxThrowException من نوع noreturn هذا، أدخل المترجم حشوًا على شكل تعليمات INT3، والتي سيتم التقاطها كحشو وكتعليمة إنهاء في الوقت نفسه. وهناك حالات أخرى مثل إدراج تعليمات UD2 بعد استدعاء noreturn ويتم التعامل معها أيضًا.

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

2.3. دعم الاستثناءات

2.3.1. دعم التراجع

تستخدم أداة التعتيم تخصيصات المكدس في مسارات التعتيم الخاصة بها. يتيح لها ذلك حفظ قيم السجلات التي تستخدمها (مثل push rax) واستعادتها بعد اكتمال مسار التعتيم حتى لا تتلف السجلات. مؤشر الإطار هو سجل يشير إلى موقع محدد على المكدس.

يجب توثيق جميع تخصيصات المكدس بواسطة رموز التراجع الخاصة بالدالة (في حال عدم وجود مؤشر إطار)، مما يسمح لمعالج استثناءات النظام بالتراجع عبر المكدس إلى عنوان العودة والبحث عن معالج استثناءات التطبيق. يجب أن تكون هذه التخصيصات في مقدمة الدالة (بداية الدالة) بسبب قيد على مدى بُعدها عن بداية الدالة. إذا تم إجراء تخصيصات المكدس خارج مقدمة الدالة، فلن تستطيع رموز التراجع وصفها ولن يتمكن النظام من التراجع، مما يكسر دعم الاستثناءات.

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

2.3.1.1 التنفيذ

سيدرج مُعيد الكتابة سجل مؤشر إطار في كل دالة وقت تشغيل لا تمتلك واحدًا بالفعل. يتيح ذلك للنظام التراجع عبر المكدس حتى بعد أن تقوم أداة التعتيم بتخصيصات للمكدس خارج مقدمة الدالة.

فيما يلي مثال على التغييرات التي تُجرى على مقدمة وخاتمة الدالة:

مقدمة الدالة الأصلية:```asm
DriverEntry proc
sub rsp, 38h

root@kitploit:~
مقدمة الدالة المعدلة:```asm  
DriverEntry proc  
    push    rbp  
    push    rbp  
    sub     rsp, 38h  
    lea     rbp, [rsp]  
    lea     rbp, [rsp]  

خاتمة الدالة الأصلية:```asm
add rsp, 38h
ren
DriverEntry endp

root@kitploit:~
خاتمة الدالة المعدلة:```asm  
    add     rsp, 38h  
    pop     rbp  
    pop     rbp  
    retn
DriverEntry endp  

الشكل 3. تخطيط المكدس قبل إدراج مؤشر الإطار.

يُستخدم السجل غير المتطاير rbp كمؤشر إطار بواسطة المُعيد الكاتب (rewriter). يجب الحفاظ على السجلات غير المتطايرة، لذلك تُدفع قيمة rbp في المقدمة (prologue) ويتم وصفها برموز فك الالتفاف (unwind codes) حتى يتمكن مُفكك الالتفاف في نظام التشغيل من استعادة القيمة الأصلية لـ rbp. يُدفع السجل rbp مرتين لإعادة محاذاة المكدس إلى 16 بايت، وتكون الدفعة الثانية لأغراض إعادة المحاذاة فقط.

بما أن قيمة rbp تُدفع مرتين في المقدمة، فيجب أيضًا إخراجها (pop) في نهاية الدالة لإعادة مؤشر المكدس إلى قيمته الأصلية. وذلك ليكون عنوان العودة عند [rsp] لتعليمة الإرجاع.

تُدرج رموز فك الالتفاف المناسبة لتعليمات الدفع هذه حتى يعرف نظام التشغيل أن هناك المزيد من تخصيصات المكدس في المقدمة (ليفك منها مؤشر الإطار). ولهذا توجد عمليتا إخراج (pop) في الخاتمة (epilogue).

للعثور على تلك الكتل الأساسية الخارجة/الخواتم (epilogues)، يتم البحث في جميع الكتل الأساسية داخل الدالة عن تعليمة 'ret' أو قفزة تخرج خارج الدالة الحالية. كما تُعد القفزات غير المباشرة (مثل jmp rcx) خروجًا خارج الدالة الحالية، باستثناء جداول القفز. ومع تجميع جميع الكتل الأساسية الخارجة معًا، يمكن إدراج تعليمتي الإخراج (pop) فيها لضمان إلغاء تأثيرات الدفع في المقدمة عند مغادرة الدالة.

تعليمة 'lea rbp, [rsp]' في نهاية المقدمة موجودة لإخبار نظام التشغيل بموقع ملموس للمكدس يمكنه فك الالتفاف منه بدلاً من فك الالتفاف من rsp. وتُدرج معلومات ورموز فك الالتفاف المناسبة لتعليمة ضبط مؤشر الإطار هذه.

الشكل 4. تخطيط مكدس خاطئ بعد إدراج مؤشر الإطار.

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

على سبيل المثال، ستحصل التعليمة 'mov rax, [rsp+0x90]' على إضافة 0x10 (أي 16 بالنظام العشري) إليها بحيث تظل تصل إلى نفس فتحة المكدس بعد تنفيذ عمليات الدفع. ستكون التعليمة المصححة: 'mov rax, [rsp+0xA0]'.

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

هناك حالة أخرى وهي معالجات الالتقاط (catch handlers) للاستثناءات، إذ تحصل في rdx على عنوان إطار المُنشئ (EstablisherFrame) (المساوي لقيمة سجل مؤشر الإطار لدينا وقت حدوث الاستثناء). وستصل معالجات الالتقاط إلى مكدس دالة الاستثناء من خلال rdx، وبالتالي يجب أيضًا تتبع rdx وتعديله.

يوجد أدناه مخطط تخطيط المكدس بعد تصحيحه باستخدام تعديلات مراجع المكدس.

الشكل 5. تخطيط المكدس بعد التصحيح عقب إدراج مؤشر الإطار.

يتطلب دعم الاستثناءات توفير ملف رموز (symbol file) لأداة التمويه (obfuscator) لأنه يتطلب أكبر قدر ممكن من المعلومات حول رموز الملف الثنائي.

2.3.2. تحليل معلومات الاستثناء

يقوم المُعيد الكاتب بتحليل معلومات فك الالتفاف للملف الثنائي للعثور على معلومات معالج الاستثناء. كما يتم تتبع أي عناوين RVA يتم العثور عليها. أنواع معلومات معالج الاستثناء المدعومة هي:

  • SEH/C_SCOPE_TABLE (استثناءات بأسلوب C).
  • استثناءات C++ من نوعي FuncInfo3 وFuncInfo4.

2.3.2.1. SEH/C_SCOPE_TABLE

تنسيق هذا النوع هو مصفوفة من إدخالات الجدول التالية:```cpp
struct c_scope_table_entry_t
{
std::uint32_t begin_rva; // where exception-throwing range begins
std::uint32_t end_rva; // where exception-throwing range ends
std::uint32_t handler_rva; // the handler type/rva (normally 1)
std::uint32_t target_rva; // the catch handler rva
};

struct c_scope_table_t
{
std::uint32_t entry_count;
c_scope_table_entry_t table[1];
};

root@kitploit:~
تصف عناوين RVA للبداية/النهاية نطاق الكود الذي يمكن أن يرمي استثناءً. أما عنوان RVA الهدف فيصف معالج الالتقاط الذي يعالج الاستثناء عند حدوثه.

#### 2.3.2.2. FuncInfo3 وFuncInfo4

تُستخدم لاستثناءات C++، والفرق الرئيسي بين FH3 وFH4 هو أن FH4 يستخدم تنسيقًا مضغوطًا لتوفير الذاكرة. وتشترك في الواصفات التالية:

- Unwind map - قائمة بكائنات C++ التي يجب إتلافها بالإضافة إلى إزاحة الكائن عن الإطار.  
- Try block map - قائمة بمعالجات الالتقاط والأنواع التي يمكن لكل منها التقاطها (مثل std::runtime_error).   
- IP2State map - تصف حالة الكائنات اعتمادًا على قيمة مؤشر التعليمات الحالي/الإزاحة داخل الدالة.

خصائص FH3:

- عنوان الاستمرار في خريطة كتلة المحاولة يُحفظ في الكود ويُرجعه معالج الالتقاط في السجل rax (مثل: lea rax, continuation_address).

خصائص FH4:

- يخزن معلومات الخريطة بتنسيق عدد صحيح مضغوط لتوفير المساحة.  
- تحتوي خريطة كتلة المحاولة على عنوان الاستمرار المشفّر في بنية معلومات FH4.

بالنسبة لاستثناءات C++، يُدعم فقط MSVC وCLANG/LLVM. ولا يُدعم GCC لاستثناءات C++ لأنه يستخدم تنسيقًا مختلفًا عن FH3/FH4.

### 2.3.3. تحليل RTTI وThrowInfo

معلومات النوع في وقت التشغيل (RTTI) ومعلومات الرمي (throw info) هي بنى تُستخدم لفحص أنواع C++ في وقت التشغيل، بما في ذلك لرمي الاستثناءات. تحتوي هذه البنى على العديد من عناوين RVA، وبالتالي يجب تتبعها لأغراض الاستقرار.

#### 2.3.3.1. RTTI

تحتوي خريطة 'كتلة المحاولة' في واصفات استثناءات C++ على معلومات نوع لمعرفة ما إذا كانت تلتقط النوع المُرمى. تُسمى هذه المعلومات RTTI، وتصف أيضًا أمورًا أخرى عن النوع، مثل:

- جداول الدوال الافتراضية.  
- اسم النوع.  
- الفئات الموروثة.

بالنسبة للفئات التي لا تحتوي على دوال افتراضية، كل ما يتم توليده هو واصف نوع:```cpp  
struct type_descriptor_t  
{  
	std::uint64_t vftable_address; // this is a DIR64 relocation  
	std::uint64_t unk;  
	char name[1];  
};  

يتم العثور على ذلك من خلال فحص أقسام البيانات للبحث عن إعادة التوطين DIR64 في حقل العضو 'vftable_address'، والذي يُفحص للتأكد من كونه جدول دوال افتراضية حقيقياً.

بالنسبة للفئات التي تحتوي على دوال افتراضية، يتم إنشاء محدد موقع الكائن الكامل وواصف التسلسل الهرمي للفئة. يحتوي واصف التسلسل الهرمي للفئة على مصفوفة من الفئات الأساسية.```cpp
struct complete_object_locator_t
{
std::uint32_t signature;
std::uint32_t offset;
std::uint32_t constructor_offset;
std::uint32_t type_rva;
std::uint32_t hierarchy_rva;
std::uint32_t self_rva;
};

struct hierarchy_descriptor_t
{
std::uint32_t signature;
std::uint32_t attributes;
std::uint32_t base_class_count;
std::uint32_t base_class_list_rva;
};

struct base_class_array_t
{
std::uint32_t class_rvas[1];
};

struct base_class_descriptor_t
{
std::uint32_t type_rva;
std::uint32_t element_count;
std::uint32_t member_displacement;
std::uint32_t unk;
std::uint32_t unk1;
std::uint32_t attributes;
std::uint32_t hierarchy_rva;
};

root@kitploit:~
للعثور على هذه، يتم فحص أقسام البيانات بحثًا عن عمليات إعادة التوطين `DIR64` التي تشير إلى محدد كائن مكتمل. تُجرى الفحوصات على هدف إعادة التوطين `DIR64` للتأكد من أنه يشير إلى محدد كائن مكتمل (على سبيل المثال، هل يشير `self_rva` إلى `RVA` لقاعدة الفئة، وهل تُحلَّل واصفات النوع وواصفات التسلسل الهرمي بشكل صحيح).

#### 2.3.3.2. ThrowInfo

يُستخدم ThrowInfo لوصف كيفية إتلاف كائن الاستثناء بمجرد معالجته، بالإضافة إلى النوع المُلقى. يتم وصف الفئات الموروثة من نوع الاستثناء المُلقى في مصفوفة الأنواع القابلة للالتقاط (تحتوي على مراجع RTTI) لضمان قدرة معالج الاستثناء على مطابقته مع عبارات الالتقاط.

يتم فحص ThrowInfo في أقسام البيانات عبر التحقق من محتويات مصفوفة الأنواع القابلة للالتقاط باستخدام معلومات RTTI المكتشفة مسبقًا. تتم إضافة جميع عناوين RVA للأنواع القابلة للالتقاط وThrowInfo إلى قائمة التتبع.```cpp  
struct throw_info_t  
{  
	std::uint32_t attributes;  
	std::uint32_t pmfn_unwind; // address of exception object destructor  
	std::uint32_t forward_compat;  
	std::uint32_t catchable_type_array;  
};

struct catchable_type_array_t  
{  
	std::uint32_t count;  
	std::uint32_t type_rvas[1];  
};

struct catchable_type_t  
{  
	std::uint32_t attributes;  
	std::uint32_t rva_type;  
	std::uint32_t mdisp;  
	std::uint32_t pdisp;  
	std::uint32_t vdisp;  
	std::uint32_t size_of_thrown_object;  
	std::uint32_t optional_copy_constructor_rva;  
};  

3. التعتيم

3.1. الآلة الافتراضية

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

يستخدم هذا التنفيذ نهجًا عامًا لتوليد معالجات الآلة الافتراضية بحيث يمكن تعتيم مجموعة واسعة من التعليمات دون الحاجة إلى ترميز معالجات ثابتة لكل تعليمة من تعليمات x86-64.



الشكل 6. بنية الآلة الافتراضية.

يتم استبدال تسلسل التعليمات الأصلي الذي تتم افتراضيته باستدعاء إلى الكتلة الافتتاحية للآلة الافتراضية.

عند الدخول إلى الآلة الافتراضية، يتم دفع جميع سجلات الأغراض العامة (باستثناء rsp) إلى المكدس. كما يتم دفع سجل rflags إلى المكدس. تُستخدم هذه الفتحات في المكدس كسجلات افتراضية، يتوافق كل منها مع سجله الأصلي (لذا يتم استخدام الفتحة الخاصة بـ rax بدلاً من rax). يتم ترتيب هذه السجلات الافتراضية في المكدس بشكل عشوائي، بحيث يتغير تخطيط السجلات في كل معالج من معالجات الآلة الافتراضية.

تعليمات الدفع التي تضع سجلات الأغراض العامة في تخطيط المكدس الافتراضي هي أيضًا عشوائية، ويمكن أن تكون 'sub rsp, 8; mov [rsp] reg' أو 'push reg'. يُفعل ذلك لجعل بناء التواقيع الخاصة بمدخل الآلة الافتراضية أكثر صعوبة.

السجلات المادية هي سجلات الأغراض العامة من بنية x86-64. الآن وبعد أن تم حفظ السجلات المادية في حالتها الافتراضية على المكدس، أصبحت حرة ليتم استخدامها والكتابة فوقها. تتتبع حالة الآلة الافتراضية قائمة بالسجلات المادية المتاحة حاليًا والتي يمكن استخدامها من قبل أجزاء الآلة الافتراضية. وبمجرد اكتمال الجزء، تُعاد تلك السجلات المادية إلى القائمة ليُعاد استخدامها.

الآن أصبح التطبيق داخل الآلة الافتراضية وحان الوقت لتسليم التنفيذ إلى المعالجات الخاصة بالتعليمات المستهدفة. التعليمات المستهدفة هي تعليمات x86-64 التي يتم تحويلها إلى هذه البنية الافتراضية.

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

التعتيم المطبق على قيم المعاملات هو كما يلي:

  • عملية xor برقم عشوائي 16-بت على قيمة المعامل.
  • عملية نفي بتكملة الواحد على قيمة المعامل.

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

المعاملات المخفية التي تتطلب سجلات محددة (مثل rsi و rdi من أجل 'rep movsb') يتم تحميلها في تلك السجلات المحددة بدلاً من سجل مادي عشوائي.

في كتلة معالج التعليمات، يتم إخراج المعاملات من المكدس وفك تعتيمها. يتم تنفيذ العملية العكسية للحصول على القيم الأصلية للمعاملات.

إذا كانت التعليمات الأصلية تقرأ من سجل rflags، فسيتم تحميل rflags من سياق المكدس قبل تنفيذ التعليمات.

إذا كانت التعليمات الأصلية تكتب إلى سجل rflags، فسيتم تحميل rflags من سياق المكدس قبل تنفيذ التعليمات. وبعد تنفيذ التعليمات الأصلية، تتم كتابة rflags المحدّثة مرة أخرى إلى سياق المكدس.

يضمن ذلك أن تكون لتعليمات الآلة الافتراضية نفس سلوك الأعلام تمامًا مثل التعليمات الأصلية.

داخل كتلة معالج التعليمات، يتم ترميز تعليمة x86-64 الأصلية لاستخدام معاملات مفكوكة التعتيم. وبمجرد تنفيذ التعليمات الأصلية، يتم تعتيم معاملات النتيجة ودفعها إلى المكدس.

إذا كان هذا هو آخر معالج للتعليمات، فستكون الكتلة الأساسية التالية هي كتلة خروج الآلة الافتراضية. إذا لم يكن كذلك، فستكون الكتلة التالية من المعالج التالي.

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

تتكرر هذه العملية حتى تعذر تحويل إحدى التعليمات في الكتلة الأساسية إلى صيغة افتراضية (مثل تعليمة تستخدم سجل rsp).

ثم يجب تفريغ سياق الآلة الافتراضية للانتقال مرة أخرى إلى الكود غير الافتراضي. يتم إخراج جميع السجلات الافتراضية من المكدس إلى سجلات الأغراض العامة المقابلة لها. كما يتم استعادة سجل rflags المعدّل بإخراجه من المكدس. الآن، تعود كتلة خروج الآلة الافتراضية إلى المُستدعي.

3.3.1 دعم فك المكدس

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

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

يتم تحميل مؤشر إطار في rbp لأن معالجات الآلة الافتراضية تستخدم تخصيصات مكدس خارج المقدمة (prologue). وهذا يعني أنه لا يُسمح لمعالجات الآلة الافتراضية باستخدام rbp كسجل مادي 'متاح'.

جميع السجلات المادية المستخدمة في الآلة الافتراضية يتم دفعها إلى سياق المكدس، لذلك يتم إدراج أكواد فك المكدس المقابلة لتلك الدفعات.

ثم يتم إدراج وظيفة التشغيل (runtime function) الخاصة بوظيفة الآلة الافتراضية في دليل الاستثناءات. أصبحت الآلة الافتراضية الآن قابلة لفك المكدس.

3.2. كتل المسندات المعتمة

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

المسندات المعتمة هي عبارات تُقيَّم إلى صواب أو خطأ فقط.

يتم تكرار الكتل الأساسية ولفها في جملة if ذات مسند معتم. ستكون إحدى الكتل ذات تدفق بيانات منحرف بحيث تكون مشابهة ولكنها غير صحيحة.```cpp
if (opaque_statement_always_true)
{
… original basic block
}
else
{
… incorrect but similar basic block
}

root@kitploit:~
لزيادة تضليل مهندس الهندسة العكسية، يتم جمع جميع معاملات التعليمات وجعلها عشوائية. سيتم إعادة تجميع كل تعليمة بمعاملات عشوائية من القائمة المجمّعة. يضمن هذا أن سلوك الكتلة المكررة يختلف عن سلوك الكتلة الأصلية.

كما يتم خلط مواقع الكتل عشوائيًا، بحيث لا يكشف موقعها الفعلي في الذاكرة عن أيٌّ منها هو الفرع الصحيح.

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

تعبير المسند المعتم نفسه مهم جدًا أيضًا، لأنه إذا كان من السهل تقييمه فلن يكون فعّالًا. لهذا السبب، تم اختيار [نظرية فيرما الأخيرة](https://en.wikipedia.org/wiki/Fermat's_Last_Theorem) كتعبير معتم. تنص نظرية فيرما الأخيرة على أنه «لا توجد ثلاثة أعداد صحيحة موجبة a وb وc تحقق المعادلة a^n + b^n = c^n لأي قيمة عددية صحيحة n أكبر من 2» (حيث تعني ^ الأس). لقد ثبت أن هذا التعبير دائمًا خاطئ في ظل هذه الشروط. يتم اختيار الأعداد الثلاثة a وb وc، ثم رفعها إلى قوة مختارة عشوائيًا من 3 إلى 7. يتم تنفيذ التعبير في كتلة أساسية منشأة حديثًا، ثم يتم اتخاذ الفرع الشرطي المؤدي إلى الكتلة الصحيحة.

إذا كانت المعلمات a وb وc وn معروفة، يمكن للمهاجم حل التعبير والعثور على الفرع الصحيح. لإعاقة ذلك، يتم اختيار المعلمات من قيم تُحدد في وقت التشغيل: مؤشر المكدس (rsp) ومؤشر التعليمات (حيث يعني ASLR أنه سيتم نقل عنوان rip في وقت التشغيل). على سبيل المثال:```cpp  
if ((rsp^n) + (rip^n) != (c^n))  
{  
     … incorrect but similar basic block  
}  
else  
{  
    … original basic block  
}  

3.3. تسطيح تدفق التحكم

تدفق التحكم هو مسارات التنفيذ التي يسلكها البرنامج (مثل 'عبارات if'). التغيير في تدفق التحكم يحدث عندما تقفز كتلة أساسية إلى أخرى. يمكن تجميع هذه التغييرات في تدفق التحكم معًا في كعب موزّع واحد ثم خلطها لجعل فهمها أكثر صعوبة. سيكون كعب الموزّع مسؤولًا عن تغيير تدفق التحكم إلى الكتلة الأساسية التالية بدلاً من أن يتم ذلك مباشرة.

الشكل 7. تسطيح تدفق التحكم.

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

يتم خلط التوزيع الفعلي للكتل الأساسية بحيث لا يعطي موقعها في الذاكرة أي تلميحات حول تدفق التحكم الأصلي.

3.4. الاستبدال الخطي

تأخذ هذه التقنية أي أرقام مشفّرة في تعليمة (إزاحات معاملات الذاكرة، المعاملات المباشرة) وتخفي قيمتها الحقيقية عن طريق حسابها وقت التشغيل.

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

في وقت التشغيل، يُطرح الرقم العشوائي من السجل، مما ينتج عنه الرقم الأصلي.

إذا كان R هو الرقم العشوائي و N هو الرقم الأصلي، فإن التعبير يكون فعليًا ((R+N) - R).

لإعادة ترميز معاملات الذاكرة، يتم إدراج السجل في معامل القاعدة. إذا كان هناك بالفعل معامل قاعدة، تُضاف قيمته فوقه.

بالنسبة لمعاملات الذاكرة في المكدس التي يتم استبدالها، هناك إزاحة تُضاف إلى القيمة لمراعاة إزاحة المكدس الناتجة عن عمليات الدفع. تُستخدم عمليات الدفع لحفظ سجل rflags بالإضافة إلى السجل غير المستخدم.

فيما يلي مثال على استبدال معامل الذاكرة في 'mov [rsp+24h], 0':```asm
push r11
pushfq ; save flags for now
mov r11, rsp
add r11, 1D4D9F71h
sub r11, 1D4D9F3Dh
popfq ; restore flags, the original instruction will now execute and populate flags with the correct values
mov dword ptr [r11], 0 ; original instruction substituted (r11 == rsp+24h)
pop r11

root@kitploit:~
هنا مثال آخر لاستبدال المعامل الفوري في 'add rbx, 8':```asm  
push    r10  
pushfq  
mov     r10, 0FFFFFFFFE3F78112h  
sub     r10, 0FFFFFFFFE3F7810Ah  
popfq  
add     rbx, r10  
pop     r10  

3.5. الحساب الثنائي المختلط (Mixed boolean arithmetic)

تأخذ هذه التقنية التعبيرات الحسابية العادية (مثل x+y) وتحوّلها إلى تعبيرات أكثر تعقيداً تُنتج النتيجة نفسها. وهي تفعل ذلك عن طريق استبدال التعبيرات بمطابقات خطية متساوية.

على سبيل المثال، (x+y) لها المطابقة المتساوية ((x & y) + (x | y)). عندما يعالج المُبهم (obfuscator) التعبير (x+y)، فإنه يستبدله بإحدى تلك المطابقات التي يصعب هندستها عكسياً.

توجد قائمة بالمطابقات لتعليمات الحساب التالية: 'add'، 'sub'، 'and'، 'or'، 'xor'. وهذا يعني أنه لكل واحدة من هذه التعليمات يتم اختيار بديل أكثر تعقيداً وعشوائياً ليحل محلها. اختيار المطابقات عشوائياً من قائمة يضمن أن يكون كل ناتج إبهام مختلفاً.

لجعل فك الإبهام أكثر صعوبة، يتم تطبيق هذا بشكل تكراري (recursively). وينتهي هذا الأمر بكل مطابقة مستبدلة يُعاد استبدالها عدة مرات، مما يضاعف التعقيد بشكل كبير. فيما يلي مثال على تحويل (x+y) بعد مرتين من تمرير هذه التقنية:

الشكل 8. الحساب الثنائي المختلط.

جانب يجب أخذه في الاعتبار هو نتيجة حساب الأعلام (flags) للتعليمات. التعليمات الأصلية كانت ستطبّق سلوك أعلام محدداً على سجل الأعلام، وهو لن يكون نفسه بسبب تقسيم التعبير إلى عمليات/تعليمات مختلفة. لحل هذا، يحاكي المُبهم سلوك الأعلام عن طريق إدراج كعب (stub) يحسب الأعلام الصحيحة ويطبّقها.

بالنسبة لـ 'and' و 'or' و 'xor'، فإن سلوك الأعلام هو نفسه سلوك تعليمة 'test'؛ بإدراج تعليمة test مع نفس المُعامَلات (operands)، سيتم محاكاة الأعلام بشكل صحيح لهذه التعليمات الثلاث المستبدَلة.

بالنسبة لـ 'sub'، فإن تعليمة 'cmp' لها نفس سلوك الأعلام، ويمكن إدراجها مع نفس المُعامَلات لحساب الأعلام.

بالنسبة لـ 'add'، يمكن حساب SF و ZF و PF بواسطة 'test'، لكن CF و AF و OF تحتاج إلى حساب يدوي. ثم يتم إدراج كعب يحسب يدوياً CF و AF و OF لتعليمة add.

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

  • تعليمة MBA هي آخر تعليمة تكتب الأعلام قبل تعليمة تقرأ الأعلام.
  • تعليمة MBA هي آخر تعليمة تكتب الأعلام في الكتلة الأساسية.

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

4. البناء (Building)

فيما يلي أوامر مثال لبناء المشروع باستخدام CMake. ابدأ تنفيذ هذه الأوامر من الدليل الجذري للمشروع.```
cmake -B build cmake --build build

root@kitploit:~
في أنظمة ويندوز مع تثبيت Visual Studio، يمكن تخطّي الأمر الأخير حيث يمكن بناء المشروع من خلال ملفات حلول Visual Studio المُولَّدة (.sln). ستكون ملفات حلول Visual Studio في مجلد 'build/'.

# 5. الاستخدام

تعتمد أداة الإبهام على نظام إعدادات قائم على سطر الأوامر. يمكن تكوين ما يلي عبر وسائط سطر الأوامر:

- أي مراحل إبهام سيتم استخدامها.  
- مسار ملف الإدخال الثنائي.  
- مسار ملف رموز الإدخال (اختياري).  
- مسار ملف الإخراج الثنائي (اختياري).

فيما يلي نظرة عامة على وسائط سطر الأوامر:

Usage: binprotect binary-path symbol-path [--out-binary-path VAR] [--control-flow-flattening VAR] [--virtual-machine VAR] [--opaque-predicates VAR] [--linear-substitution VAR] [--mixed-boolean-arithmetic VAR]

Positional arguments:  
  binary-path                                               file path of input binary [required]  
  symbol-path                                               file path of input binary's symbols [optional]

  --out, --out-path, --out-binary-path                      desired file path of output binary  
  --cff, --control-flow-flattening                          enable control flow flattening pass [default: 1]  
  --vm, --virtual-machine                                   enable virtual machine pass [default: 1]  
  --opa, --opaque, --opaque-predicate, --opaque-predicates  enable opaque predicate pass [default: 1]  
  --lin, --linear-substitution                              enable linear substitution pass [default: 1]  
  --mba, --mixed-boolean-arithmetic                         specify amount of mixed boolean arithmetic passes [default: 2]

# 6. الاختصارات

- bin2bin - ثنائي إلى ثنائي.
- RVA - عنوان نسبي.  
- MBA - حساب منطقي مختلط.  
- [SEH - معالجة الاستثناءات المنظمة](https://learn.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp).
- FH3 - FuncInfo3.
- FH4 - FuncInfo4.
- [MSVC - مايكروسوفت فيجوال سي بلس بلس](https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B).  
- [LLVM - آلة افتراضية منخفضة المستوى](https://en.wikipedia.org/wiki/LLVM).  
- [CLANG - الواجهة الأمامية للغة C/C++ الخاصة بـ LLVM](https://clang.llvm.org/).  
- [GCC - مجموعة مترجمات GNU](https://en.wikipedia.org/wiki/GNU_Compiler_Collection).
- SF - علم الإشارة.  
- ZF - علم الصفر.  
- PF - علم التكافؤ.  
- CF - علم الحمل.  
- AF - علم الحمل الإضافي.  
- OF - علم الفائض.

# 7. الشكر والتقدير

قدّم الأفراد التاليون نصائح لا تُقدَّر بثمن أثناء تطوير المشروع:

- Aita.  
- Papstuc.  
- Eriktion.  
- IDontCode.  
- Abdulla.  
- Brit.  
- Phage.
تنزيل الأداة