2016-05-09 28 views
1

Gibt es eine Möglichkeit Prozessorbefehl in einem Array zu setzen, sein Speichersegment ausführbar zu machen und sie als eine einfache Funktion auszuführen:C: x86-Befehle in einem Array gelegt und führen sie

int main() 
{ 
    char myarr[13] = {0x90, 0xc3}; 
    (void (*)()) myfunc = (void (*)()) myarr; 
    myfunc(); 
    return 0; 
} 
+3

Dies sieht aus wie ein [XY Problem] (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Sagen Sie uns, welches Problem Sie lösen möchten, bevor Sie um Hilfe bitten, um es auf eine bestimmte Weise zu lösen. –

+0

Ich möchte x86-Code in meinem Programm generieren, dann führen Sie es aus. –

+0

Meinst du etwas wie [VirtualProtect] (https://msdn.microsoft.com/en-us/library/windows/desktop/aa366898 (v = V.85) .aspx) in Windows? – Tomer

Antwort

7

Auf Unix (heutzutage bedeutet das "alles außer Windows und einige eingebettete und Mainframe-Sachen, von denen Sie wahrscheinlich noch nie gehört haben") Sie dies tun, indem Sie eine ganze Anzahl von Seiten mit mmap zuweisen, den Code in sie schreiben, und dann machen sie ausführbar mit mprotect.

void execute_generated_machine_code(const uint8_t *code, size_t codelen) 
{ 
    // in order to manipulate memory protection, we must work with 
    // whole pages allocated directly from the operating system. 
    static size_t pagesize; 
    if (!pagesize) { 
     pagesize = sysconf(_SC_PAGESIZE); 
     if (pagesize == (size_t)-1) fatal_perror("getpagesize"); 
    } 

    // allocate at least enough space for the code + 1 byte 
    // (so that there will be at least one INT3 - see below), 
    // rounded up to a multiple of the system page size. 
    size_t rounded_codesize = ((codelen + 1 + pagesize - 1) 
          /pagesize) * pagesize; 

    void *executable_area = mmap(0, rounded_codesize, 
           PROT_READ|PROT_WRITE, 
           MAP_PRIVATE|MAP_ANONYMOUS, 
           -1, 0); 
    if (!executable_area) fatal_perror("mmap"); 

    // at this point, executable_area points to memory that is writable but 
    // *not* executable. load the code into it. 
    memcpy(executable_area, code, codelen); 

    // fill the space at the end with INT3 instructions, to guarantee 
    // a prompt crash if the generated code runs off the end. 
    // must change this if generating code for non-x86. 
    memset(executable_area + codelen, 0xCC, rounded_codesize - codelen); 

    // make executable_area actually executable (and unwritable) 
    if (mprotect(executable_area, rounded_codesize, PROT_READ|PROT_EXEC)) 
     fatal_perror("mprotect"); 

    // now we can call it. passing arguments/receiving return values 
    // is left as an exercise (consult libffi source code for clues). 
    ((void (*)(void)) executable_area)(); 

    munmap(executable_area, rounded_codesize); 
} 

Sie können sich wahrscheinlich sehen, dass dieser Code fast das gleiche ist wie der Windows-Code in cherrydt's answer gezeigt. Nur die Namen und Argumente der Systemaufrufe sind unterschiedlich.

Wenn mit Code wie dieser arbeitet, ist es wichtig, dass viele modernen Betriebssysteme zu wissen, dass Sie nicht zulassen, dass eine Seite des RAM haben, die gleichzeitig beschreibbar und ausführbar ist. Wenn ich PROT_READ|PROT_WRITE|PROT_EXEC in den Anruf zu mmap oder mprotect geschrieben hätte, würde es fehlschlagen. Dies wird W^X policy genannt; Das Akronym steht für Write XOR eXecute. Es originates with OpenBSD, und die Idee ist es für einen Buffer-Overflow-Exploit zu machen, um Code in RAM schreiben und dann ausführen. (Es ist immer noch möglich , die Exploit nur find a way to make an appropriate call to mprotect first hat.)

5

auf der Plattform abhängig.

Für Windows können Sie diesen Code verwenden:

// Allocate some memory as readable+writable 
// TODO: Check return value for error 
LPVOID memPtr = VirtualAlloc(NULL, sizeof(myarr), MEM_COMMIT, PAGE_READWRITE); 

// Copy data 
memcpy(memPtr, myarr, sizeof(myarr); 

// Change memory protection to readable+executable 
// Again, TODO: Error checking 
DWORD oldProtection; // Not used but required for the function 
VirtualProtect(memPtr, sizeof(myarr), PAGE_EXECUTE_READ, &oldProtection);  

// Assign and call the function 
(void (*)()) myfunc = (void (*)()) memPtr; 
myfunc(); 

// Free the memory 
VirtualFree(memPtr, 0, MEM_RELEASE); 

Diese Codes nimmt eine myarr Array als in Code Ihrer Frage ist, und es wird davon ausgegangen, dass sizeof funktioniert auf es also eine direkt definierte Größe hat und nicht nur ein Zeiger von anderswo. Wenn das der Fall ist, müssten Sie die Größe auf andere Weise angeben.

Hinweis hier, dass es zwei „Vereinfachungen“ möglich, falls Sie sich fragen, aber ich würde davon abraten sie:

1) Sie VirtualAlloc mit PAGE_EXECUTE_READWRITE nennen könnte, aber dies ist im Allgemeinen schlechte Praxis, weil es würde Öffnen Sie einen Angriffsvektor für die Ausführung von unerwünschtem Code.

2) Sie VirtualProtect auf &myarr direkt anrufen können, aber das wäre nur eine zufällige Seite in Ihrem Gedächtnis machen, die Ihr Array ausführbare enthalten passiert, die dann 1 # noch schlimmer ist, weil es in dieser Seite andere Daten sein könnte als Nun, das ist jetzt auch plötzlich ausführbar.

Für Linux habe ich this auf Google gefunden, aber ich weiß nicht viel darüber.

+0

Für Linux: http://web.archive.org/web/20090203055327/http://people.redhat.com/drepper/selinux-mem.html – ninjalj

+0

@ninjalj Ich hoffe, Sie erkennen, dass es eine Seite ist, wie man * angreift * SELinux. Wir brauchen keine Leute, die an SELinux als Hindernis denken. Für die richtige Vorgehensweise siehe [zwols Antwort] (http://stackoverflow.com/a/37122499/6292850). –

+0

@ user6292850: Inkorrekt, das ist eine Seite vom vorherigen glibc Maintainer, Ulrich Drepper, wie man dynamisch generierten Code ausführt. Es kann nicht als Angriff verwendet werden, entweder Sie können bereits beliebigen Code ausführen und müssen dies nicht tun, oder Sie tun es nicht, und Sie können das nicht ausführen. – ninjalj

1

Sehr Betriebssystem-abhängig: nicht alle Betriebssysteme werden absichtlich (lesen: ohne einen Fehler) können Sie Code im Datensegment ausführen. DOS wird, weil es im Real Mode läuft, auch Linux mit den entsprechenden Privilegien haben. Ich weiß nichts über Windows.

Casting ist oft undefiniert und hat seine eigenen Vorbehalte, so einige Ausführungen zu diesem Thema hier. Von C11 Standardentwurf N1570, §J.5.7/1:

Ein Zeiger zu einem Objekt oder zu void kann auf eine Funktion, in einen Zeiger umgewandelt werden, so dass Daten als Funktion (6.5 aufgerufen werden. 4).

(Formatierung hinzugefügt.)

So ist es völlig in Ordnung ist und sollte wie erwartet funktionieren. Natürlich müssten Sie sich an die Call-Konvention des ABI halten.

+0

@Downvoter Mind zu erklären? – Downvoter