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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
VMProtect-devirtualization — اللعب مع حماية برنامج VMProtect. إزالة التعتيم التلقائي للدوال النقية باستخدام التنفيذ الرمزي و LLVM. | Kitploit
أدوات/GitHubGitHub/jonathansalwan/vmprotect-devirtualization
التحليل الثابتالتحليل الديناميكي (عزل)الهندسة العكسيةالاختبار العشوائيتحليل الملفات الثنائية
GitHubjonathansalwan/vmprotect-devirtualization

VMProtect-devirtualization

اللعب مع حماية برنامج VMProtect. إزالة التعتيم التلقائي للدوال النقية باستخدام التنفيذ الرمزي و LLVM.

عرض المستودع

الأكثر شعبية

عرض الكل →

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

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

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

عرض جميع الأدوات →
مشاركة
1.5k208منذ 4 سنواتتمت المراجعة من قبل Kitploit

VMProtect Devirtualization

نهج ديناميكي تجريبي لإلغاء الظاهرية عن الوظائف البحتة المحمية بـ VMProtect 3.x

 

 

  • الخلاصة
  • مقدمة
  • المنهج
    • مثال 1: عملية بت بسيطة محمية
    • مثال 2: عملية MBA محمية
    • مثال 3: أكثر من كتلة أساسية واحدة
  • الاستنتاج والقيود
  • المراجع

 

 

الخلاصة

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

مقدمة

VMProtect هي أداة حماية برمجيات تحمي الكود عن طريق تشغيله عبر آلة افتراضية ذات بنية غير قياسية. هذه الحماية هي ساحة لعب رائعة لعشاق لغة التجميع [0, 1, 2, 3, 4, 5, 6, 11]. أيضًا، هناك بالفعل العديد من الأدوات التي تهاجم هذه الحماية [7, 8, 9, 12, 13]. في عام 2016، نظرنا إلى حل الحماية Tigress وتمكنّا من هزيمة ظاهريته باستخدام التنفيذ الرمزي و LLVM. تم تقديم هذا النهج في DIMVA 2018 [10] وأردت اختباره على VMProtect. لاحظ أنه لا يوجد حل سحري يعمل على جميع الملفات الثنائية، فهناك دائمًا مقايضات حسب الهدف وأهدافك. يهدف هذا المساهمة المتواضعة إلى تقديم مثال لهجوم ديناميكي ضد الوظائف البحتة التي يتم ظاهريتها بواسطة VMProtect. الميزة الرئيسية للهجوم الديناميكي هي أنه يهزم بطبيعته بعض الحمايات الثابتة لـ VMProtect مثل الكود المعدل ذاتيًا، تشفير المفاتيح والمعاملات إلخ.

نعتبر الوظيفة البحتة وظيفة ذات عدد محدود من المسارات وليس لها آثار جانبية. يمكن أن يكون هناك عدة مدخلات ولكن مخرج واحد فقط. فيما يلي مثال لوظيفة بحتة:```cpp int secret(int x, int y) { int r = x ^ y; return r; }

root@kitploit:~
# النهج

نعتمد على الفكرة الأساسية القائلة إن أثرًا مبهمًا T' (من الكود المبهم P') يجمع بين
تعليمات أصلية من الكود الأصلي P (الأثر T المقابل لـ T' في الكود الأصلي) و
تعليمات الآلة الافتراضية VM بحيث يكون T' = T + VM(T). إذا تمكنا من التمييز بين
هاتين التتابعتين الفرعيتين من التعليمات T و VM(T)، فسنتمكن عندئذٍ من إعادة بناء مسار واحد من
البرنامج الأصلي P من أثر T'. وبتكرار هذه العملية لتغطية جميع مسارات البرنامج
الافتراضي، سنكون قادرين على إعادة بناء البرنامج الأصلي P. في مثالنا العملي، الكود
الأصلي له عدد محدود من المسارات القابلة للتنفيذ، وهو ما يحدث في العديد من الحالات التي تتضمن حماية
الملكية الفكرية. للقيام بذلك، نتبع الخطوات التالية:

1. تحديد الدالة الافتراضية ووسائطها
2. إنشاء أثر لـ VMProtect للهدف
3. إعادة تشغيل أثر VMP وبناء تعابير رمزية للحصول على العلاقة بين المدخلات والمخرجات
4. تطبيق تحسينات على التعبيرات الرمزية لتجنب قدر الإمكان تعليمات من VM
5. رفع تمثيلنا الرمزي إلى LLVM-IR لبناء نسخة جديدة غير محمية من الهدف

## مثال 1: عملية بت بسيطة

لنأخذ كمثال أول الدالة التالية: تأخذ مدخلين وتعيد `x ^ y` وهي محمية بواسطة VMProtect.```cpp
int secret(int x, int y) {
  VMProtectBegin("secret");
  int r = x ^ y;
  VMProtectEnd();
  return r;
}

نبدأ بتحديد أماكن استخدام الدوال لـ VMProtect وعدد الوسائط التي تمتلكها. بالنسبة للمثال الخاص بنا، قد يكون لدينا شيء مثل ما يلي:

بقراءة الكود فقط، نعلم أن الدالة تبدأ من العنوان 0x4011c0، ولها وسيطان 32-بت (edi و esi) وتعيد القيمة عند 0x4011ef. هذا كل ما نحتاجه من الهندسة العكسية. الأجزاء التالية ستكون تلقائية. الآن، علينا إنشاء تتبع تنفيذ لهذه الدالة الافتراضية. للقيام بذلك، نستخدم Pintool. يحتاج فقط إلى عنواني start و end (على سبيل المثال، 0x4011c0 و 0x4011ef) يمثلان نطاق التجهيز. لاحظ أن أي نوع من DBI أو المحاكي يمكنه القيام بهذه المهمة.``` $ ./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198848 -end 4198895 -- ./vmp_binaries/binaries/sample2.vmp.bin 1 2 &> ./vmp_traces/sample2.vmp.trace

root@kitploit:~
يمكنك رؤية النتيجة [هنا](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/vmp_traces/sample2.vmp.trace). يستخدم تنسيق التتبع ثلاثة أنواع من العمليات: `mr` و `r` و `i`.
`mr` هو وصول قراءة للذاكرة يتم بواسطة التعليمات `i`، و `r` هي سجلات وحدة المعالجة المركزية. على سبيل المثال:```
mr:0x7ffda459d718:8:0x227db4f8
r:0x40200a:0x0:0x7ffda459f571:0x2:0x40200a:0x0:0x0:0x7ffda459d688:0x0:0x0:0x7feee9b80ac0:0x7feee9b8000f:0xad1c3e:0x0:0x0:0x0
i:0x89173e:8:488BB42490000000

لدينا قراءة ذاكرة تقوم بتحميل ثابت 8 بايت 0x227db4f8 من العنوان 0x7ffda459d718. يتم تنفيذ التعليمات على العنوان 0x89173e ورمزها التشغيلي بطول 8 بايت هو 488BB42490000000 وهو mov rsi, qword ptr [rsp + 0x90]. حالة المسجلات قبل التنفيذ هي كالتالي:```python (1) RAX = 0x40200a (9) R8 = 0 (2) RBX = 0 (10) R9 = 0 (3) RCX = 0x7ffda459f571 (11) R10 = 0x7feee9b80ac0 (4) RDX = 0x2 (12) R11 = 0x7feee9b8000f (5) RDI = 0x40200a (13) R12 = 0xad1c3e (6) RSI = 0 (14) R13 = 0 (7) RBP = 0 (15) R14 = 0 (8) RSP = 0x7ffda459d688 (16) R15 = 0

root@kitploit:~
بمجرد إنشاء أثر VMP، نقوم بإعادة تشغيله باستخدام السكريبت [attack_vmp.py](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/attack_vmp.py). يستخدم هذا السكريبت [Triton](https://github.com/jonathansalwan/Triton) لبناء مسند المسار للأثر. لاحظ أن جميع التعبيرات التي تتضمن متغيرات رمزية (مدخلات الدالة) تبقى رمزية بينما يتم تجسيد جميع تعبيرات المدخلات غير المرتبطة. بعبارة أخرى، لا تحتوي تعبيراتنا الرمزية على أي عملية متعلقة بالآلة الافتراضية (الآلية نفسها لا تعتمد على المستخدم) ولكن فقط عمليات متعلقة بالبرنامج الأصلي.

على سبيل المثال، فيما يلي مثال على التجسيد. على اليسار لدينا شجرة AST تحتوي على تعبيرات فرعية لا تتضمن متغيرًا رمزيًا (`1 + 2` و `6 ^ 3`). لذا يتم تجسيد هذه الفروع واستبدالها بثوابت `3` و `5` مما يؤدي إلى شجرة AST على اليمين. **هذه هي الطريقة التي نلغي بها الافتراضية عن الكود.**

<p align="center">
  <img src="https://assets.kitploit.com/production/public/readmes/8096/857ed50cfe9cb2347f816ece1d8dc4c13174c971dcb65ebc5621be14269a5ca8.png">
</p>

**ملاحظة حول القطع العكسي على مستوى الصيغة**: كما هو شائع في التنفيذ الرمزي، يتم أولاً حساب التمثيل الرمزي بطريقة تقدمية على طول المسار، ثم تتم إزالة جميع العمليات المنطقية والتعريفات التي لا تؤثر على النتيجة النهائية ولا على المسار المتبع من التعبير الرمزي (قطع الصيغة، المعروف أيضًا بتقليم الصيغة). يتبين أن هذا يؤدي على الصيغة ما يعادل تحليل قطع عكسي للكود من مخرجات البرنامج. وبالتالي، عند عودة دالة `secret`، يكون لدينا تعبير للعلاقة بين المدخلات والمخرجات بدون تعليمات VMProtect.

يأخذ السكريبت `./attack_vmp.py` كمعاملات ملف الأثر وحجم المتغيرات الرمزية. تذكر، كانت `edi` و `esi`، لذا فإن طولهما 4 بايت. نتيجة السكريبت هي التالية:```
$ ./attack_vmp.py --trace1 ./vmp_traces/sample2.vmp.trace --symsize 4
[+] Replaying the VMP trace
[+] Symbolize inputs
[+] Instruction executed: 12462
[+] Emulation done
[+] Return value: 0x3
[+] Devirt expr: (bvor (bvnot (bvor (bvnot (bvnot x)) (bvnot y))) (bvnot (bvor (bvnot x) (bvnot (bvand (bvnot y) (bvnot y))))))
[+] Synth expr: (bvxor x y)

[+] LLVM IR ==============================

; ModuleID = 'tritonModule'
source_filename = "tritonModule"

define i32 @__triton(i32 %SymVar_0, i32 %SymVar_1) {
entry:
  %0 = xor i32 %SymVar_0, %SymVar_1
  ret i32 %0
}

[+] EOF LLVM IR ==============================

كما يمكننا أن نرى، فإن التعبير المفكوك افتراضياً الذي أعادته الدالة secret موجز جداً ولا يحتوي على تعليمات من الآلة الافتراضية.```smt (bvor (bvnot (bvor (bvnot (bvnot x)) (bvnot y) ) ) (bvnot (bvor (bvnot x) (bvnot (bvand (bvnot y) (bvnot y) ) ) ) ) )

root@kitploit:~
مع ذلك، لم نتمكن من استعادة التعبير الأصلي الذي كان عبارة عن عملية `XOR` بسيطة. يبدو أن `XOR` قد تُرجمت إلى عمليات بتية. لحسن الحظ، أصدرنا مؤخرًا ميزات جديدة في مشروع Triton وهي [أداة توليف](https://github.com/JonathanSalwan/Triton/issues/1074) ورافع إلى [LLVM-IR](https://github.com/JonathanSalwan/Triton/issues/1078). وبالتالي، يمكننا توليف التعبير الذي يعطينا التعبير `(bvxor x y)`. إنه فوز جيد ويمكننا الآن المضي قدمًا عن طريق رفع هذا التعبير إلى LLVM-IR ثم تجميع كود ثنائي جديد منزوع الظاهرية.


## مثال 2: عملية MBA محمية

حسنًا، لنلقِ نظرة على مثال آخر يحاول إخفاء عملية MBA. كود المصدر الأصلي هو التالي:```cpp
// This function is an MBA that computes: (x ^ 92) + y
// We will protect this MBA with VMProtect and see if we can recover "(x ^ 92) + y"
char secret(char x, char y) {
  VMProtectBegin("secret");
  int a = 229 * x + 247;
  int b = 237 * a + 214 + ((38 * a + 85) & 254);
  int c = (b + ((-(2 * b) + 255) & 254)) * 3 + 77;
  int d = ((86 * c + 36) & 70) * 75 + 231 * c + 118;
  int e = ((58 * d + 175) & 244) + 99 * d + 46;
  int f = (e & 148);
  int g = (f - (e & 255) + f) * 103 + 13;
  int r = (237 * (45 * g + (174 * g | 34) * 229 + 194 - 247) & 255) + y;
  VMProtectEnd();
  return r;
}

كما في المثال الأول، علينا تحديد أين تبدأ هذه الدالة وأين تنتهي وإنشاء تتبع VMP.``` $ ./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198857 -end 4199140 -- ./vmp_binaries/binaries/sample3.vmp.bin 1 2 &> ./vmp_traces/sample3.vmp.trace

root@kitploit:~
بمجرد إنشاء [VMP trace](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/vmp_traces/sample3.vmp.trace)، دعنا نشغّل النص البرمجي `./attack_vmp.py`.```
$ ./attack_vmp.py --trace1 ./vmp_traces/sample3.vmp.trace --symsize 1
[+] Replaying the VMP trace
[+] Symbolize inputs
[+] A potential symbolic jump found on CF flag: 0x821dac: popfq - Model: {0: x:32 = 0xa3, 1: y:32 = 0xff}
[+] A potential symbolic jump found on CF flag: 0x87f437: popfq - Model: {0: x:32 = 0xa3, 1: y:32 = 0xff}
[+] Instruction executed: 25085
[+] Emulation done
[+] Return value: 0x5f
[+] Devirt expr: In: (bvadd (bvadd (bvshl (bvadd (_ bv1 32) (bvnot (bvlshr (concat (_ bv0 8) (_ bv0 8) ((_ extract 15 8)  ...
[+] Synth expr: In: (bvadd (bvadd (bvshl (bvadd (_ bv1 32) (bvnot (bvlshr (concat (_ bv0 8) (_ bv0 8) ((_ extract 15 8)  ...

[+] LLVM IR ==============================

; ModuleID = 'tritonModule'
source_filename = "tritonModule"

define i32 @__triton(i8 %SymVar_0, i8 %SymVar_1) {
entry:
  %0 = xor i8 %SymVar_0, 92
  %1 = and i8 %SymVar_0, 0
  %2 = zext i8 %1 to i32
  %3 = or i32 0, %2
  %4 = shl i32 %3, 8
  %5 = zext i8 %0 to i32
  %6 = or i32 %4, %5
  %7 = and i8 %SymVar_1, 0
  %8 = zext i8 %7 to i32
  %9 = or i32 0, %8
  %10 = shl i32 %9, 8
  %11 = zext i8 %SymVar_1 to i32
  %12 = or i32 %10, %11
  %13 = zext i8 %7 to i32
  %14 = or i32 0, %13
  %15 = shl i32 %14, 8
  %16 = zext i8 %SymVar_1 to i32
  %17 = or i32 %15, %16
  %18 = lshr i32 %17, 7
  %19 = xor i32 %18, -1
  %20 = add i32 1, %19
  %21 = shl i32 %20, 8
  %22 = add i32 %21, %12
  %23 = add i32 %22, %6
  ret i32 %23
}

[+] EOF LLVM IR ==============================

النتيجة مثيرة للاهتمام لعدة أسباب. أولاً، تمكنا بنجاح من تجنب التعليمات الواردة من الآلة الافتراضية قدر الإمكان، حيث انتقلنا من تنفيذ 25085 تعليمة إلى 25 تعليمة LLVM. ومع ذلك، لم نتمكن من الحصول على نسخة مركبة جيدة من المخرجات (نعم، أعلم، نحن نتجاوز مجرد إلغاء الافتراضية). ميزة رفع تعبيراتنا الرمزية إلى LLVM-IR هي أننا يمكننا الاستفادة الكاملة من سلسلة التحسين الخاصة بـ LLVM. لنفعل ذلك:```llvm $ opt -S -O3 ./devirt/sample3.ll ; ModuleID = 'devirt/sample3.ll' source_filename = "tritonModule"

; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn define i32 @__triton(i8 %SymVar_0, i8 %SymVar_1) local_unnamed_addr #0 { entry: %0 = xor i8 %SymVar_0, 92 %1 = zext i8 %0 to i32 %2 = zext i8 %SymVar_1 to i32 %3 = shl nuw nsw i32 %2, 1 %4 = and i32 %3, 256 %5 = add nuw nsw i32 %1, %2 %6 = sub nsw i32 %5, %4 ret i32 %6 }

root@kitploit:~
باستخدام تحسينات LLVM، تمكنا من إزالة الضوضاء من المخرجات المفككة وبالتالي كسر الـMBA. نرى عملية `XOR` مع ثابت (`%0 = xor i8 %SymVar_0, 92`) و `+ y` (`%6 = add nsw i32 %5, %1`). التعليمات بينهما تتعلق فقط بالعلامة. لتلخيص هذا المثال، قمنا بفك التظليل الكامل لوظيفة `secret` باستخدام نص `attack_vmp.py` ثم كسرنا الـMBA بالكامل باستخدام تحسينات LLVM.

## مثال 3: أكثر من كتلة أساسية واحدة

لقد حصلنا على نتائج جيدة جدًا إذا كانت وظيفة `secret` تحتوي على كتلة أساسية واحدة فقط بغض النظر عن حجمها. لذا عند هذه النقطة، نحن قادرون على فك التظليل لمسار واحد. لإعادة بناء سلوك الوظيفة بالكامل، علينا فك التظليل للمسارات التي يمكن الوصول إليها تباعًا. للقيام بذلك، يجب إجراء تغطية مسار على الفروع المعتمدة على المستخدم. في النهاية، نحصل على شجرة مسار تمثل المسارات المختلفة للوظيفة الأصلية. يتم الحصول على شجرة المسار عن طريق إدخال بناء if-then-else من تتبعين T1 و T2 بنفس البادئة متبوعين بشرط C في T1 و not(C) في T2. بمجرد بناء شجرة المسار، يمكننا ترك LLVM لتوليد CFG.

<p align="center">
  <img src="https://assets.kitploit.com/production/public/readmes/8096/fc5d1bf9a1544b9b44ef164247f3fc38d04bf45d7c2d7a3c0b21ea3d6636cd39.png">
</p>

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

حسنًا، لننظر إلى العينة التالية:```cpp
int secret(int x, int y) {
  VMProtectBegin("secret");
  int r = 0;
  if (x + y == 1001)
    r = x + 1;
  else
    r = y - 1;
  VMProtectEnd();
  return r;
}

كما هو الحال مع الأمثلة الأولى، علينا إنشاء وتحليل التتبع.``` $./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198848 -end 4198928 -- ./vmp_binaries/binaries/sample5.vmp.bin 1 2 &> ./vmp_traces/sample5.vmp.trace.1

$ ./attack_vmp.py --trace1 ./vmp_traces/sample5.vmp.trace.1 --symsize 4 [+] Replaying the VMP trace [+] Symbolize inputs [+] A potential symbolic jump found of AF flag: 0x80d905: cmp r11b, dl - Model: {0: x:32 = 0x0, 1: y:32 = 0x3e9} [+] Instruction executed: 16164 [+] Emulation done [+] Return value: 0x4 [+] Devirt expr: (bvnot (bvadd (bvand (bvnot y) (bvnot y)) (_ bv1 32))) [+] Synth expr: (bvadd y (_ bv4294967295 32))

[+] LLVM IR ==============================

; ModuleID = 'tritonModule' source_filename = "tritonModule"

define i32 @__triton(i32 %SymVar_1) { entry: %0 = add i32 %SymVar_1, -1 ret i32 %0 }

[+] EOF LLVM IR ==============================

root@kitploit:~
يخبرنا السكربت أنه قد يكون هناك قفز رمزي محتمل موجود على العلم `AF` عند العنوان `0x80d905`. كما يوفر نموذجًا جديدًا (باستخدام التنفيذ الرمزي) والذي يجب أن يأخذ المسار الآخر. لذا دعنا ننشئ تتبعًا ثانيًا باستخدام هذا النموذج (إذا ألقيت نظرة على النموذج، فهو صحيح فيما يتعلق بكود المصدر الخاص بنا).```
$ ./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198848 -end 4198928 -- ./vmp_binaries/binaries/sample5.vmp.bin 0 1001 &> ./vmp_traces/sample5.vmp.trace.2

بمجرد إنشاء الأثر الثاني، يجب علينا توفير هذين الأثرين إلى السكربت attack_vmp.py حتى يتمكن من دمجها وإنشاء شجرة مسار. لدينا خيارات إضافية لتحديد موقع الشرط وعلى أي علم (علم AF في 0x80d905).``` $ ./attack_vmp.py --trace1 ./vmp_traces/sample5.vmp.trace.1 --symsize 4 --trace2 ././vmp_traces/sample5.vmp.trace.2 --vbraddr 0x80d905 --vbrflag af [+] Replaying the VMP trace [+] Symbolize inputs [+] A potential symbolic jump found of AF flag: 0x80d905: cmp r11b, dl - Model: {0: x:32 = 0x0, 1: y:32 = 0x3e9} [+] Instruction executed: 16164 [+] Emulation done [+] A second trace has been provided [+] Replaying the VMP trace [+] Symbolize inputs [+] Instruction executed: 15758 [+] Emulation done [+] Merging expressions from trace1 and trace2 [+] Return value: 0x3e9 [+] Devirt expr: In: (ite (= (ite (= (_ bv16 8) (bvand (_ bv16 8) (bvxor (bvsub (_ bv80 8) ((_ extract 7 0) (bvadd (bvlsh ... [+] Synth expr: In: (ite (= (ite (= (_ bv16 8) (bvand (_ bv16 8) (bvxor (bvsub (_ bv80 8) ((_ extract 7 0) (bvadd (bvlsh ...

[+] LLVM IR ==============================

; ModuleID = 'tritonModule' source_filename = "tritonModule"

define i32 @__triton(i32 %SymVar_0, i32 %SymVar_1) { entry: %0 = add i32 %SymVar_1, -1 %1 = add i32 %SymVar_0, 1 %2 = add i32 %SymVar_1, %SymVar_0 %3 = xor i32 %2, -1 %4 = xor i32 %2, -1 %5 = and i32 %4, %3 %6 = xor i32 %5, 1001 %7 = add i32 %5, 1001 %8 = xor i32 %5, 1001 %9 = xor i32 %8, %7 %10 = and i32 %9, %6 [... skip ...] %469 = add i64 %468, 140737488347280 %470 = trunc i64 %469 to i8 %471 = xor i8 80, %470 %472 = sub i8 80, %470 %473 = xor i8 %472, %471 %474 = and i8 16, %473 %475 = icmp eq i8 16, %474 %476 = select i1 %475, i1 true, i1 false %477 = icmp eq i1 %476, false %478 = select i1 %477, i32 %1, i32 %0 ret i32 %478 }

[+] EOF LLVM IR ==============================

root@kitploit:~
في هذه الخطوة، قمنا بإزالة الافتراضية (devirtualized) للتتبعين ودمجناهما في تعبيرات `if-then-else`.
بعد رفع التعبير إلى LLVM-IR، نحصل على CFG يحتوي على 480 تعليمة LLVM فقط، وهو
مكسب جيد مقارنة بآلاف التعليمات التي يتم تنفيذها بواسطة الآلة الافتراضية.
لكن يمكننا القيام بما هو أفضل إذا استخدمنا تحسينات LLVM:```llvm
$ opt -S -O3 ./devirt/sample5.ll
; ModuleID = './devirt/sample5.ll'
source_filename = "tritonModule"

; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn
define i32 @__triton(i32 %SymVar_0, i32 %SymVar_1) local_unnamed_addr #0 {
entry:
  %0 = add i32 %SymVar_0, 1
  %1 = add i32 %SymVar_1, -1
  %2 = add i32 %SymVar_1, %SymVar_0
  %.not = icmp eq i32 %2, 1001
  %3 = select i1 %.not, i32 %0, i32 %1
  ret i32 %3
}

attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone willreturn }

رائع، لقد استعدنا السلوك الأصلي للدالة secret!

الخاتمة والقيود

بينما أظهرت الطريقة نتائج جيدة جدًا للدوال التي تحتوي على مسار واحد، فإن القيد الرئيسي لهذه الطريقة هو أنها موجهة في الغالب نحو البرامج التي تحتوي على عدد صغير من المسارات بسبب الطريقة التي يقوم بها VMProtect بالقفزات الافتراضية. في حالة وجود عدد كبير جدًا من المسارات، قد تُفقد أجزاء من الشفرة الأصلية، مما يؤدي إلى استرداد غير كامل. لاحظ أننا نأخذ في الاعتبار المسارات القابلة للتنفيذ وليس المسارات النحوية في CFG. غالبًا ما تحتوي دوال التجزئة والتشفير الأخرى على عدد قليل جدًا من المسارات - مسار واحد فقط في حالة التنفيذات المقاومة لهجمات التوقيت.

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

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

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

إذا كنت ترغب في النظر بشكل أعمق، تحقق من هذه الموارد:

  • أداة Pintool لتوليد التتبع
  • نص برمجي لتحليل تتبع VMP
  • شفرة مصدر العينات
  • الملفات الثنائية الأصلية والمحمية
  • تتبعات VMP
  • النتائج المفككة

أخيرًا وليس آخرًا، شكر خاص لرفيقي @0vercl0k على المراجعة والتعديلات 🚀

المراجع```

[00] https://www.usenix.org/legacy/event/woot09/tech/full_papers/rolles.pdf [01] https://secret.club/2021/09/08/vmprotect-llvm-lifting-1.html [02] https://secret.club/2021/09/08/vmprotect-llvm-lifting-2.html [03] https://secret.club/2021/09/08/vmprotect-llvm-lifting-3.html [04] https://back.engineering/17/05/2021/ [05] https://back.engineering/21/06/2021/ [06] https://www.mitchellzakocs.com/blog/vmprotect3 [07] https://github.com/can1357/NoVmp [08] https://github.com/archercreat/vmpfix [09] https://github.com/void-stack/VMUnprotect [10] https://github.com/JonathanSalwan/Triton/blob/master/publications/DIMVA2018-slide-deobfuscation-salwan-bardin-potet.pdf [11] https://whereisr0da.github.io/blog/posts/2021-02-16-vmp-3/ [12] https://github.com/pgarba/UniTaint [13] https://github.com/mrexodia/VMProtectTest

root@kitploit:~
تنزيل الأداة