2011-01-27 11 views
43

में स्वयं-संशोधित कोड कैसे लिखें, मैं हाल ही में एक शौक वर्चुअल मशीन के लिए एक जेआईटी कंपाइलर लिख रहा हूं। मुझे कुछ असेंबली पता है, (मैं मुख्य रूप से एक सी प्रोग्रामर हूं। मैं उन ओपोड्स के संदर्भ में अधिकांश असेंबली पढ़ सकता हूं जिन्हें मैं समझ नहीं पा रहा हूं, और कुछ सरल प्रोग्राम लिखते हैं।) लेकिन मुझे कुछ उदाहरणों को समझने में कठिनाई हो रही है स्व-संशोधित कोड का मुझे ऑनलाइन मिला है। http://asm.sourceforge.net/articles/smc.htmlx86 असेंबली

उदाहरण प्रदान कार्यक्रम, जब चलाने के बारे में चार अलग-अलग संशोधन करता है जिनमें से कोई भी स्पष्ट रूप से व्याख्या कर रहे हैं:

यह ऐसे ही एक उदाहरण है। लिनक्स कर्नेल इंटरप्ट्स कई बार उपयोग किया जाता है, और समझाया या विस्तृत नहीं किया गया है। (लेखक ने इंटरप्ट्स को कॉल करने से पहले कई रजिस्टरों में डेटा ले जाया। मुझे लगता है कि वह तर्क पारित कर रहा था, लेकिन पाठकों को अनुमान लगाने के लिए इन तर्कों को बिल्कुल समझाया नहीं गया है।)

जो मैं ढूंढ रहा हूं वह सबसे आसान है , स्वयं-संशोधित कार्यक्रम के कोड में सबसे सरल उदाहरण। कुछ ऐसा जो मैं देख सकता हूं, और यह समझने के लिए उपयोग करता हूं कि x86 असेंबली में स्वयं-संशोधित कोड कैसे लिखा जाना चाहिए, और यह कैसे काम करता है। क्या कोई संसाधन है जो आप मुझे इंगित कर सकते हैं, या कोई भी उदाहरण जो आप दे सकते हैं जो पर्याप्त रूप से इसका प्रदर्शन करेगा?

मैं अपने असेंबलर के रूप में NASM का उपयोग कर रहा हूं।

संपादित करें: मैं भी इस कोड को लिनक्स पर चला रहा हूं।

+1

http://linux.die.net/man/2/mprotect को यह समझाना चाहिए कि mprotect के लिए तर्क क्या हैं। कॉल करने के लिए फ़ंक्शन आईडी ईएक्स में पारित की जाती है और अगले तर्क ईबीएक्स ईसीएक्स और ईडीएक्स में पास किए जाते हैं। – KitsuneYMG

उत्तर

44

वाह, यह मेरी अपेक्षा से बहुत अधिक दर्दनाक साबित हुआ। दर्द का 100% लिनक्स प्रोग्राम को अधिलेखित और/या डेटा निष्पादित करने से बचा रहा था।

नीचे दिखाए गए दो समाधान। और बहुत सारे गुगलिंग शामिल थे इसलिए कुछ सरल बाइट्स को कुछ सरल बाइट डालें और उन्हें निष्पादित किया गया था, पेज आकार पर mprotect और संरेखित Google खोजों से खींचा गया था, सामान मुझे इस उदाहरण के लिए सीखना था।

स्वयं संशोधित कोड सीधे आगे है, यदि आप प्रोग्राम लेते हैं या कम से कम केवल दो सरल कार्यों को संकलित करते हैं, संकलित करते हैं और फिर उन्हें अलग करते हैं तो आपको उन निर्देशों के लिए ऑपकोड मिलेंगे। या असेंबलर के ब्लॉक संकलित करने के लिए नासम का उपयोग करें, आदि। इससे मैंने ओपेक को तुरंत ईएक्स में लोड करने के लिए निर्धारित किया है।

आदर्श रूप से आप बस कुछ बाम में उन बाइट डालते हैं और उस राम को निष्पादित करते हैं। ऐसा करने के लिए लिनक्स प्राप्त करने के लिए आपको सुरक्षा को बदलना होगा, जिसका अर्थ है कि आपको इसे एक पॉइंटर भेजना होगा जो एमएमएपी पेज पर गठबंधन है। तो आपको अपनी आवश्यकता से अधिक आवंटित करें, पृष्ठ आवंटन पर उस आवंटन के भीतर गठबंधन पता ढूंढें और उस पते से mprotect करें और उस मेमोरी को अपने opcodes डालने के लिए उपयोग करें और फिर निष्पादित करें।

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

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <sys/mman.h> 

unsigned char *testfun; 

unsigned int fun (unsigned int a) 
{ 
    return(a+13); 
} 

unsigned int fun2 (void) 
{ 
    return(13); 
} 

int main (void) 
{ 
    unsigned int ra; 
    unsigned int pagesize; 
    unsigned char *ptr; 
    unsigned int offset; 

    pagesize=getpagesize(); 
    testfun=malloc(1023+pagesize+1); 
    if(testfun==NULL) return(1); 
    //need to align the address on a page boundary 
    printf("%p\n",testfun); 
    testfun = (unsigned char *)(((long)testfun + pagesize-1) & ~(pagesize-1)); 
    printf("%p\n",testfun); 

    if(mprotect(testfun, 1024, PROT_READ|PROT_EXEC|PROT_WRITE)) 
    { 
     printf("mprotect failed\n"); 
     return(1); 
    } 

    //400687: b8 0d 00 00 00   mov $0xd,%eax 
    //40068d: c3      retq 

    testfun[ 0]=0xb8; 
    testfun[ 1]=0x0d; 
    testfun[ 2]=0x00; 
    testfun[ 3]=0x00; 
    testfun[ 4]=0x00; 
    testfun[ 5]=0xc3; 

    ra=((unsigned int (*)())testfun)(); 
    printf("0x%02X\n",ra); 


    testfun[ 0]=0xb8; 
    testfun[ 1]=0x20; 
    testfun[ 2]=0x00; 
    testfun[ 3]=0x00; 
    testfun[ 4]=0x00; 
    testfun[ 5]=0xc3; 

    ra=((unsigned int (*)())testfun)(); 
    printf("0x%02X\n",ra); 


    printf("%p\n",fun); 
    offset=(unsigned int)(((long)fun)&(pagesize-1)); 
    ptr=(unsigned char *)((long)fun&(~(pagesize-1))); 


    printf("%p 0x%X\n",ptr,offset); 

    if(mprotect(ptr, pagesize, PROT_READ|PROT_EXEC|PROT_WRITE)) 
    { 
     printf("mprotect failed\n"); 
     return(1); 
    } 

    //for(ra=0;ra&lt;20;ra++) printf("0x%02X,",ptr[offset+ra]); printf("\n"); 

    ra=4; 
    ra=fun(ra); 
    printf("0x%02X\n",ra); 

    ptr[offset+0]=0xb8; 
    ptr[offset+1]=0x22; 
    ptr[offset+2]=0x00; 
    ptr[offset+3]=0x00; 
    ptr[offset+4]=0x00; 
    ptr[offset+5]=0xc3; 

    ra=4; 
    ra=fun(ra); 
    printf("0x%02X\n",ra); 

    return(0); 
} 
+1

न केवल लिनक्स बल्कि अधिकांश आधुनिक ओएस भी –

+0

निष्पादित करने से लिखने योग्य स्मृति की रक्षा करते हैं। क्या यह विंडोज़ में किया जा सकता है, यानी राम के एक पृष्ठ को असुरक्षित करना, या क्या हम मौत की नीली स्क्रीन से फंस जाएंगे? मैं एक स्व-संशोधित एन्क्रिप्शन सिस्टम बनाने के लिए इस विधि का उपयोग करना चाहता हूं। – tentimes

+0

कोड 32-बिट आर्क लिनक्स पर ठीक काम करता था, लेकिन 64-बिट आरएचईएल (64-बिट ईएलएफ दोनों में, लेकिन 32-बिट ईएलएफ का उपयोग करते समय भी) में असफल रहा। पता नहीं है कि इसे आरएचईएल या कुछ और पर अतिरिक्त मेमोरी सुरक्षा के साथ क्या करना है।उत्पादन किया गया था: '' ' 0x9a00008 0x9a01000 mprotect में विफल रहा है ' '' – Alexander

3

आप GNU lightning जैसी परियोजनाओं को भी देख सकते हैं। आप इसे सरलीकृत आरआईएससी-प्रकार मशीन के लिए कोड देते हैं, और यह गतिशील रूप से सही मशीन उत्पन्न करता है।

विदेशी पुस्तकालयों के साथ इंटरफेसिंग करने के बारे में आपको एक बहुत ही वास्तविक समस्या के बारे में सोचना चाहिए। आपको अपने वीएम के लिए कम से कम कुछ सिस्टम-स्तरीय कॉल/ऑपरेशंस का समर्थन करने की आवश्यकता होगी। किट्स्यून की सलाह आपको सिस्टम-स्तरीय कॉल के बारे में सोचने के लिए एक अच्छी शुरुआत है। आप शायद यह सुनिश्चित करने के लिए mprotect का उपयोग करेंगे कि आपके द्वारा संशोधित स्मृति कानूनी रूप से निष्पादन योग्य हो जाती है। (@KitsuneYMG)

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

0

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

8

आप एक JIT कम्पाइलर लिख रहे हैं के बाद से, आप शायद नहीं स्वयं संशोधित कोड चाहते हैं, आप रनटाइम पर निष्पादन योग्य कोड जनरेट करना चाहते हैं। ये दो अलग-अलग चीजें हैं। स्व-संशोधित कोड कोड है जो संशोधित किया गया है, इसके बाद यह चलाना शुरू हो चुका है। स्व-संशोधित कोड में आधुनिक प्रोसेसर पर एक बड़ा प्रदर्शन जुर्माना है, और इसलिए एक जेआईटी कंपाइलर के लिए अवांछित होगा।

रनटाइम पर निष्पादन योग्य कोड उत्पन्न करना PROT_EXEC और PROT_WRITE अनुमतियों के साथ कुछ स्मृति में mmap() का एक साधारण मामला होना चाहिए। आप कुछ स्मृति पर mprotect() को भी कॉल कर सकते हैं जिसे आपने स्वयं आवंटित किया था, जैसा कि ऊपर किया गया था।

+0

स्व संशोधित कोड हमेशा आधुनिक प्रोसेसर पर प्रदर्शन दंड था नहीं करता है। आपको जो बदलना है उसके बारे में सावधान रहना होगा, और सुनिश्चित करें कि सीपीयू कैश सिंक हो रहा है और शाखा सुरक्षा में बदलाव नहीं आया है। उनको बदलना आपके प्रदर्शन को टैंक करेगा। – Beachhouse

+0

यदि स्व-संशोधन अपेक्षाकृत बार-बार होता है और/या कोड के उन हिस्सों पर होता है जो _currently_ निष्पादित नहीं होते हैं, तो क्या अस्थायी प्रदर्शन नगण्य है? –

3

ऊपर दिए गए उदाहरण के आधार पर थोड़ा सा सरल उदाहरण। Dwelch के लिए धन्यवाद बहुत मदद की।

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 
#include <sys/mman.h> 

char buffer [0x2000]; 
void* bufferp; 

char* hola_mundo = "Hola mundo!"; 
void (*_printf)(const char*,...); 

void hola() 
{ 
    _printf(hola_mundo); 
} 

int main (void) 
{ 
    //Compute the start of the page 
    bufferp = (void*)(((unsigned long)buffer+0x1000) & 0xfffff000); 
    if(mprotect(bufferp, 1024, PROT_READ|PROT_EXEC|PROT_WRITE)) 
    { 
     printf("mprotect failed\n"); 
     return(1); 
    } 
    //The printf function has to be called by an exact address 
    _printf = printf; 

    //Copy the function hola into buffer 
    memcpy(bufferp,(void*)hola,60 //Arbitrary size); 


    ((void (*)())bufferp)(); 

    return(0); 
} 
+0

यदि आप 'hola()' के लिए स्वतंत्र कोड उत्पन्न नहीं करते हैं, तो यह काफी असफल हो सकता है। – CoffeeandCode

+0

यह काम नहीं कर रहा है !! सीजी गलती! – ANTHONY

0

यह AT & टी असेंबली में लिखा गया है। जैसा कि आप प्रोग्राम के निष्पादन से देख सकते हैं, स्व-संशोधित कोड के कारण आउटपुट बदल गया है।

संकलन: जीसीसी -m32 modify.s modify.c

-m32 विकल्प प्रयोग किया जाता है क्योंकि उदाहरण के 32 बिट मशीनों

Aessembly पर काम करता है:

.globl f4 
.data  

f4: 
    pushl %ebp  #standard function start 
    movl %esp,%ebp 

f: 
    movl $1,%eax # moving one to %eax 
    movl $0,f+1 # overwriting operand in mov instuction over 
       # the new immediate value is now 0. f+1 is the place 
       # in the program for the first operand. 

    popl %ebp # standard end 
    ret 

सी परीक्षण कार्यक्रम :

#include <stdio.h> 

// assembly function f4 
extern int f4(); 
int main(void) { 
int i; 
for(i=0;i<6;++i) { 
printf("%d\n",f4()); 
} 
return 0; 
} 

आउटपुट:

1 
0 
0 
0 
0 
0 
संबंधित मुद्दे