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.5k2084 साल पहलेKitploit द्वारा समीक्षित

VMProtect Devirtualization

एक प्रायोगिक गतिशील दृष्टिकोण जो VMProtect 3.x द्वारा संरक्षित शुद्ध फ़ंक्शनों को डीवर्चुअलाइज़ करता है

 

 

  • TL;DR
  • परिचय
  • दृष्टिकोण
    • उदाहरण 1: एक सरल बिटवाइज़ ऑपरेशन संरक्षित
    • उदाहरण 2: एक MBA ऑपरेशन संरक्षित
    • उदाहरण 3: एक से अधिक बेसिक ब्लॉक
  • निष्कर्ष और सीमाएँ
  • संदर्भ

 

 

TL;DR

मैं VMProtect द्वारा संरक्षित शुद्ध फ़ंक्शनों को डीवर्चुअलाइज़ करने के एक गतिशील दृष्टिकोण के बारे में कुछ नोट्स साझा कर रहा हूँ। यह दृष्टिकोण बहुत अच्छे परिणाम दिखाता है यदि वर्चुअलाइज़्ड फ़ंक्शन में केवल एक बेसिक ब्लॉक हो (उसके आकार की परवाह किए बिना)। यह एक सामान्य परिदृश्य है जब बाइनरी अंकगणितीय संक्रियाओं को सुरक्षित करती हैं। हालाँकि, यह दृष्टिकोण थोड़ा अधिक प्रायोगिक है जब लक्ष्य फ़ंक्शन में एक से अधिक बेसिक ब्लॉक हों। फिर भी, हम उन नमूनों से बाइनरी कोड को डीवर्चुअलाइज़ और पुनर्निर्मित करने में सफल हुए जिनमें 2 बेसिक ब्लॉक हैं, जो बताता है कि छोटे फ़ंक्शनों को गतिशील रूप से पूरी तरह से डीवर्चुअलाइज़ करना संभव है।

परिचय

VMProtect एक सॉफ़्टवेयर सुरक्षा है जो कोड को गैर-मानक आर्किटेक्चर वाली वर्चुअल मशीन के माध्यम से चलाकर सुरक्षित करता है। यह सुरक्षा asm प्रेमियों के लिए एक बेहतरीन खेल का मैदान है [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) के बीच अंतर करने में सक्षम हैं, तो हम एक ट्रेस T' से मूल प्रोग्राम P के एक पथ का पुनर्निर्माण करने में सक्षम हैं। इस ऑपरेशन को वर्चुअलाइज्ड प्रोग्राम के सभी पथों को कवर करने के लिए दोहराकर, हम मूल प्रोग्राम 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` CPU रजिस्टर हैं। उदाहरण के लिए:```
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

हमारे पास एक मेमोरी रीड है जो 0x7ffda459d718 पते से 8 बाइट्स का स्थिरांक 0x227db4f8 लोड करता है। निर्देश 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` को बिटवाइज़ ऑपरेशन में अनुवादित कर दिया गया है। सौभाग्य से, हमने हाल ही में ट्राइटन प्रोजेक्ट में नई सुविधाएँ जारी की हैं जो एक [synthesizer](https://github.com/JonathanSalwan/Triton/issues/1074) और एक lifter to
[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`) के साथ देख सकते हैं। बीच के निर्देश केवल चिह्न से निपट रहे हैं। इस उदाहरण को सारांशित करने के लिए, हमने `attack_vmp.py` स्क्रिप्ट का उपयोग करके `secret` फ़ंक्शन को पूरी तरह से डीवर्चुअलाइज़ किया और फिर LLVM अनुकूलन का उपयोग करके MBA को पूरी तरह से तोड़ दिया।

## उदाहरण 3: एक से अधिक बुनियादी ब्लॉक

हमें बहुत अच्छे परिणाम मिले यदि `secret` फ़ंक्शन में इसके आकार की परवाह किए बिना केवल एक बुनियादी ब्लॉक होता है। तो इस बिंदु पर हम एक पथ को डीवर्चुअलाइज़ करने में सक्षम हैं। पूरे फ़ंक्शन व्यवहार का पुनर्निर्माण करने के लिए, हमें क्रमिक रूप से सुलभ पथों को डीवर्चुअलाइज़ करना होगा। ऐसा करने के लिए, हमें उपयोगकर्ता-निर्भर शाखाओं पर पथ कवरेज करना होगा। अंत में, हमें परिणामस्वरूप एक पथ वृक्ष मिलता है जो मूल फ़ंक्शन के विभिन्न पथों का प्रतिनिधित्व करता है। पथ वृक्ष दो अनुरेखों T1 और T2 से if-then-else निर्माण प्रस्तुत करके प्राप्त किया जाता है जिसमें समान उपसर्ग होता है और उसके बाद T1 में एक शर्त C और T2 में not(C) होता है। एक बार पथ वृक्ष बनने के बाद, हम 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` पर एक संभावित प्रतीकात्मक कूद (symbolic jump) मिल सकता है।
यह एक नया मॉडल (प्रतीकात्मक निष्पादन का उपयोग करके) भी प्रदान करती है जिसे दूसरा पथ लेना चाहिए। तो चलिए इस मॉडल का उपयोग करके दूसरा ट्रेस उत्पन्न करें (यदि आप मॉडल को देखें, तो यह हमारे स्रोत कोड के संबंध में सही है)।```
$ ./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:~
इस चरण में हमने दो ट्रेस को डीवर्चुअलाइज़ किया और उन्हें `if-then-else` अभिव्यक्तियों में विलय कर दिया। अभिव्यक्ति को LLVM-IR पर उठाने के बाद हमें केवल 480 LLVM निर्देशों वाला एक CFG मिलता है, जो वर्चुअल मशीन द्वारा निष्पादित हजारों निर्देशों की तुलना में पहले से ही एक अच्छी जीत है। लेकिन अगर हम 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:~
टूल डाउनलोड करें