2010-04-12 11 views
6

में ढेर के साथ खिलवाड़ मैं निम्नलिखित करना चाहते हैं:विधानसभा और C++

मुझे लगता है कि मेरा नहीं है एक समारोह है (यह वास्तव में बात यहाँ नहीं है लेकिन सिर्फ इतना कहना है कि मैं नियंत्रण अपने हाथ में नहीं है यह) और मैं पैच करना चाहता हूं ताकि यह मेरा कार्य कह सके, तर्क सूची को संरक्षित करना (कूदना एक विकल्प नहीं है)।

जो मैं करने की कोशिश कर रहा हूं वह है कि स्टैक पॉइंटर डालने के लिए, जैसा कि उस समारोह से पहले था और फिर मेरा कॉल करें (जैसे वापस जाकर और फिर एक ही काम के साथ)। यह सीधे काम नहीं करता है क्योंकि ढेर गड़बड़ हो जाता है। मेरा मानना ​​है कि जब मैं कॉल करता हूं तो यह वापसी पते को बदल देता है। इसलिए, मैंने वैश्विक स्तर पर इसे सहेजने वाले रिटर्न पते को संरक्षित करने के लिए एक कदम उठाया और यह काम करता है लेकिन यह ठीक नहीं है क्योंकि मैं इसे पुनर्संरचना का विरोध करना चाहता हूं और आपको पता है कि मेरा क्या मतलब है। वैसे भी, मैं असेंबली में नौसिखिया हूं इसलिए मैं यहां हूं।

कृपया मुझे ऐसा करने के लिए पहले से ही बनाए गए सॉफ़्टवेयर के बारे में न बताएं क्योंकि मैं चीजों को अपना रास्ता बनाना चाहता हूं।

बेशक, यह कोड संकलक और अनुकूलन स्वतंत्र होना चाहिए।

मेरे कोड (यदि यह क्या स्वीकार्य है से बड़ा है तो कृपया मुझे यह कैसे पोस्ट करने के लिए बता):

// A function that is not mine but to which I have access and want to patch so that it calls a function of mine with its original arguments 
void real(int a,int b,int c,int d) 
{ 

} 

// A function that I want to be called, receiving the original arguments 
void receiver(int a,int b,int c,int d) 
{ 
printf("Arguments %d %d %d %d\n",a,b,c,d); 
} 

long helper; 

// A patch to apply in the "real" function and on which I will call "receiver" with the same arguments that "real" received. 
__declspec(naked) void patch() 
{ 
_asm 
{ 
    // This first two instructions save the return address in a global variable 
    // If I don't save and restore, the program won't work correctly. 
    // I want to do this without having to use a global variable 
    mov eax, [ebp+4] 
    mov helper,eax 

    push ebp 
    mov ebp, esp 

    // Make that the stack becomes as it were before the real function was called 
    add esp, 8 

    // Calls our receiver 
    call receiver 

    mov esp, ebp 
    pop ebp 

    // Restores the return address previously saved 
    mov eax, helper 
    mov [ebp+4],eax 

    ret 
} 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
FlushInstructionCache(GetCurrentProcess(),&real,5); 

DWORD oldProtection; 
VirtualProtect(&real,5,PAGE_EXECUTE_READWRITE,&oldProtection); 

// Patching the real function to go to my patch 
((unsigned char*)real)[0] = 0xE9; 
*((long*)((long)(real) + sizeof(unsigned char))) = (char*)patch - (char*)real - 5; 

// calling real function (I'm just calling it with inline assembly because otherwise it seems to works as if it were un patched 
// that is strange but irrelevant for this 
_asm 
{ 
    push 666 
    push 1337 
    push 69 
    push 100 
    call real 
    add esp, 16 
} 

return 0; 
} 

प्रिंटों (और है के लिए):

तर्क 100 69 1337 666

संपादित करें:

कोड मैं Vlad सुझाव निम्न परीक्षण कर रहा हूँ (एसटीआई ll काम नहीं कर रहा)

// A patch to apply in the real function and on which I will call receiver with the same arguments that "real" received. 
__declspec(naked) void patch() 
{ 
    _asm 
    { 
     jmp start 

     mem: 

     nop 
     nop 
     nop 
     nop 

     start : 

     // This first two instructions save the return address in a global variable 
     // If I don't save and restore the program won't work correctly. 
     // I want to do this without having to use a global variable 
     mov eax, [ebp+4] 
     mov mem, eax 

     push ebp 
     mov ebp, esp 

     // Make that the stack becomes as it were before the real function was called 
     add esp, 8 

     // Calls our receiver 
     call receiver 

     mov esp, ebp 
     pop ebp 

     // Restores the return address previously saved 
     mov eax, mem 
     mov [ebp+4],eax 

     ret 
    } 
} 
+5

एएसएम में स्पष्ट कॉल के बिना, आपका फ़ंक्शन शायद कंपाइलर द्वारा रेखांकित किया गया है। यही कारण है कि इसे बुलाया नहीं गया था। – Vlad

+1

मुझे यह सोचने के लिए बुरा महसूस करने के लिए धन्यवाद: डी। मजाक कर रहा हूं। धन्यवाद, समझ में आता है। – user246100

+0

आपका फ़ंक्शन 'असली' वास्तव में खाली है? यदि ऐसा है, तो यह 5 बाइट से छोटा हो सकता है, इसलिए आपका पैच अगले फ़ंक्शन को ओवरराइट कर सकता है। – Vlad

उत्तर

0

आप एक बार add esp, 8 और एक बार add esp, 16 है। उनमें से एक गलत होना चाहिए।

संपादित करें:
ओह, मैं देख रहा हूँ, add esp, 8 के बाद आप ढेर ebp से हटा दिया गया होगा धक्का दे दिया 2 निर्देश से पहले, और वापसी पता।

[ebp + 4] पर _tmain पर कॉल का वापसी पता होना चाहिए।

EDIT2:
आप ऐसा ही कुछ द्वारा एक "आंतरिक" चर आवंटित कर सकते हैं:

call next 
    dd 0 
next: 
    pop eax 
    mov [eax], yourinfo 

लेकिन फिर भी स्पष्ट नहीं क्यों हम सब पर है कि मूल्य को बचाने की जरूरत है।

Edit3: (हटा दिया, गलत था)

Edit4:
एक और विचार:

__declspec(naked) void patch() 
{ 
_asm 
{ 
    call next 
    // here we temporarily save the arguments 
    dd 0 
    dd 0 
    dd 0 
    dd 0 
next: 
    pop eax 
    // eax points to the first dd 

    // now store the args 
    pop edx 
    mov [eax], edx 
    pop edx 
    mov [eax+4], edx 
    pop edx 
    mov [eax+8], edx 
    pop edx 
    mov [eax+12], edx 

    // now we can push the value 
    mov edx, [ebp+4] 
    push edx 

    // now, push the args again 
    mov edx, [eax+12] 
    push edx 
    mov edx, [eax+8] 
    push edx 
    mov edx, [eax+4] 
    push edx 
    mov edx, [eax] 
    push edx 

    // now continue with the old code 
    // -------------------------------- 
    // restore the arguments  
    push ebp 
    mov ebp, esp 

    // Make that the stack becomes as it were before the real function was called 
    add esp, 8 

    // Calls our receiver 
    call receiver 

    mov esp, ebp 
    pop ebp 

    // ---------------------------- 
    pop edx 
    mov [ebp+4], edx 

    ret 
} 
} 

यह समाधान प्रत्यावर्तन बचता है, लेकिन 2 अलग धागे से नहीं एक साथ निष्पादन।

+0

नहीं। विस्थापन में से एक धक्का के बाद है, अन्य बाद में है एक कॉल। जैसा कि मैंने एक टिप्पणी में कहा था: कोड काम करता है। मैं सिर्फ वैश्विक चर से छुटकारा पाना चाहता हूं। – user246100

+0

@ user246100 अच्छी तरह से डीबग बिल्ड में स्टैक-फिक्सिंग कोड शामिल है ... – Joshua

+0

मैं टी डीबग में अनुमान लगाया गया है, ऑप्टिमाइज़ेशन ओ 2 के साथ रिलीज करें और इसके बिना और यह हर तरह से काम करता है। वास्तव में, यह अनुकूलन में काम करने के लिए दर्द था लेकिन इसे स्वयं आज़माएं। – user246100

0

मैंने कभी निम्न स्तर की सामग्री के लिए सी ++ का उपयोग नहीं किया है, इसलिए मैं आपके उदाहरण के विनिर्देशों में नहीं जाऊंगा, लेकिन आम तौर पर यदि आप कॉल को रोकना चाहते हैं और अपना तर्क समर्थन रिकर्सन चाहते हैं, तो आपके पास दो विकल्प हैं : या तो पूरे स्टैक फ्रेम (पैरामीटर) की प्रतिलिपि बनाएँ और नई प्रतिलिपि के साथ 'हुक' मूल को कॉल करें, या यदि यह संभव नहीं है, तो मूल टीएलएस में मूल वापसी मूल्य (जैसे एक लिंक की गई सूची के रूप में) को पकड़ने के लिए अपना खुद का छोटा ढेर बनाए रखें आधारित डेटा संरचना।

0

सामान्य निष्पादन के दौरान, फ़ंक्शन के संचालन को रिवर्स ऑर्डर में स्टैक पर धकेल दिया जाता है। call ऑपोड को निष्पादित करते समय, प्रोसेसर पहले ईआईपी (या सीएस/आईपी) रजिस्टर को स्टैक पर धक्का देता है। यह वापसी का पता है।

Return address 1 
Operand 1 
Operand 2 
Operand 2 

इस बिंदु आप अपने खुद के समारोह कॉल करने के लिए जा रहे हैं जो इस तरह एक ढेर होगा पर:

Return address 2 
Return address 1 
Operand 1 
Operand 2 
Operand 3 
जब निष्पादन समारोह आप बदलना चाहते तक पहुँच जाता है, यह कैसे शेयर दिखता है

आपके फ़ंक्शन को यह जानने की आवश्यकता होगी कि स्टैक पर एक अतिरिक्त DWORD है क्योंकि यह वही कर रहा है जो आप चाहते हैं। यदि आपने अपना प्रतिस्थापन फ़ंक्शन असेंबली भी लिखी है, तो इसे संभालना आसान है, जब भी आपका संदर्भ ईएसपी 4 जोड़ें। जब आप अपने फ़ंक्शन में आरईटी कहते हैं, तो पहला रिटर्न पता पॉप किया जाएगा और निष्पादन आपके द्वारा प्रतिस्थापित किए जा रहे फ़ंक्शन पर वापस आ जाएगा। ढेर एक बार फिर से हो जाएगा:

Return address 1 
Operand 1 
Operand 2 
Operand 3 

इस समारोह में कॉलिंग आरईटी एक बार फिर से ढेर से वापसी पता पॉप और बुला कार्य करने के लिए नियंत्रण वापस आ जाएगी। यह आपके संचालन को अभी भी ढेर पर छोड़ देता है जिसके परिणामस्वरूप भ्रष्टाचार होता है। मैं इस तरह समारोह ऑपरेंड की संख्या के साथ आरईटी बुला सलाह देते हैं:

RET 3 

यह 3 (मेरे उदाहरण में), या फिर भी कई ऑपरेंड पॉप जाएगा वहाँ ढेर से दूर रहे थे,। यहां पर कुछ संदर्भ आप उपयोगी लग सकते हैं:

http://pdos.csail.mit.edu/6.828/2009/readings/i386/CALL.htm http://pdos.csail.mit.edu/6.828/2009/readings/i386/RET.htm

2

निम्नलिखित कोड अंश के साथ की जाँच की है MinGW-जी ++, लेकिन मामूली संशोधनों के साथ कुलपति में ++ काम करना चाहिए। पूर्ण स्रोत लॉन्चपैड से उपलब्ध हैं: 1

कॉल-विशिष्ट डेटा को सुरक्षित रूप से सहेजने का एकमात्र तरीका यह है कि इसे स्टैक पर स्टोर करना है। एक तरीका स्टैक के हिस्से को घुमाने के लिए है।

patch.s अंश (patchfun-rollstack):

sub esp, 4   # allocate scratch space 

mov eax, DWORD PTR [esp+4] # first we move down 
mov DWORD PTR [esp], eax # our return pointer 

mov eax, DWORD PTR [esp+8] # then our parameters 
mov DWORD PTR [esp+4], eax 
mov eax, DWORD PTR [esp+12] 
mov DWORD PTR [esp+8], eax 
mov eax, DWORD PTR [esp+16] 
mov DWORD PTR [esp+12], eax 
mov eax, DWORD PTR [esp+20] 
mov DWORD PTR [esp+16], eax 

mov eax, DWORD PTR [esp] # save return pointer 
mov DWORD PTR [esp+20], eax # behind arguments 

add esp, 4   # free scratch space 
call __Z8receiveriiii 

mov eax, DWORD PTR [esp+16] # restore return pointer 
mov DWORD PTR [esp], eax 

ret 

हम ebp यहाँ छोड़े गए। यदि हम इसे जोड़ते हैं, तो हमें 8 बाइट्स स्क्रैच स्पेस का उपयोग करने और ebp को सहेजने और पुनर्स्थापित करने के साथ-साथ eip को पुनर्स्थापित करने की आवश्यकता होगी। ध्यान दें कि जब हम रिटर्न पॉइंटर को पुनर्स्थापित करते हैं, तो हम a पैरामीटर को ओवरराइट करते हैं। इससे बचने के लिए हमें फिर से स्टैक को घुमाने की आवश्यकता होगी।


अन्य तरीके से कॉल प्राप्त करने वाला ढेर पर अतिरिक्त डेटा के बारे में जानते हैं और उस पर ध्यान नहीं है।

patch.s (patchfun-ignorepointers):

push ebp 
mov ebp, esp 
call receiver 
leave 
ret 

रिसीवर।सीसी:

void receiver(const void *epb, const void *eip, int a,int b,int c,int d) 
{ 
printf("Arguments %d %d %d %d\n",a,b,c,d); 
} 

यहाँ मैं ईपीबी, अगर आप सब कि रहता है call और ret है एएसएम से हटाने, और रिसीवर ही स्वीकार करते हैं और eip की अनदेखी करने की आवश्यकता होगी शामिल थे।


बेशक, यह सब मजेदार और जिज्ञासा के लिए है। वहाँ वास्तव में सरल समाधान पर कोई प्रमुख लाभ है:

void patch(int a,int b,int c,int d) 
{ 
receiver(a,b,c,d); 
} 

उत्पन्न विधानसभा हमारे ढेर रोल की तुलना में कम होगा, लेकिन क्योंकि मूल्यों नीचे एक ताजा क्षेत्र को कॉपी कर रहे हैं, ढेर के 16 बाइट्स अधिक की आवश्यकता होगी patch() ढेर फ्रेम।

(वास्तव में जीसीसी-उत्पन्न एएसएम ढेर पर 28 बाइट्स आवंटित करता है, हालांकि यह का उपयोग करता है केवल 16. मुझे यकीन है कि क्यों। हो सकता है कि अतिरिक्त 12 बाइट्स कुछ ढेर-स्मैश सुरक्षा योजना का हिस्सा हैं नहीं कर रहा हूँ।)