2012-05-04 6 views
7

Ich habe an einem (C++) Projekt gearbeitet, das erfordert vollständig dynamisch zugewiesene Funktionen, die malloc/new und mprotect bedeutet und dann den Puffer manuell ändern Assemblercode. Aus diesem Grund habe ich mich genau gefragt, was in diesem "Puffer" von mir erforderlich ist, denn es ist ein Replikat irgendeiner anderen _cdecl-Funktion. Zum Beispiel:Verwendung von C++ mit Assembly zum Zuweisen und Erstellen neuer Funktionen zur Laufzeit

int ImAcDeclFunc(int a, int b) 
{ 
    return a + b; 
} 

Wenn ich möchte buchstäblich ein Duplikat dieser Funktion erstellen, aber völlig dynamisch, was würde erforderlich machen (und denken Sie daran, es ist C++ mit Inline Montag)? Für den Anfang, ich denke, ich würde so etwas zu tun habe (oder eine ähnliche Lösung):

// My main.... 
byte * ImAcDeclFunc = new byte[memory]; 
mprotect(Align(ImAcDeclFunc), pageSize, PROT_EXEC | PROT_READ | PROT_WRITE); 

Danach würde ich den Assembler-Code für die ImAcDeclFunc(int a, int b); herauszufinden muß. Jetzt bin ich immer noch mies bei der Montage, also wie wäre diese Funktion in AT & T Syntax? Hier ist mein kühner Versuch:

push %ebp 
movl %%ebp, %%esp 
movl 8(%ebp), %%eax 
movl 12(%ebp), %%edx 
addl edx, eax 
pop ebp 
ret 

Nun, wenn dieser Code korrekt ist (was ich sehr Zweifel, bitte korrigieren Sie mich) würde ich muss nur diesen Code Wert in Hex finden (zum Beispiel ‚JMP‘ ist 0xE9 und ' inc 'ist 0xFE), und verwenden Sie diese Werte direkt in C++? Wenn ich meinen früheren C++ Code weiter:

*ImAcDeclFunc = 'hex value for push'; // This is 'push' from the first line 
*(uint)(ImAcDeclFunc + 1) = 'address to push'; // This is %ebp from the first line 
*(ImAcDeclFunc + 5) = 'hex value for movl' // This is movl from the second line 
// and so on... 

Nachdem ich dies für den gesamten Code/Puffer getan haben, wäre das für eine völlig dynamische _cdecl Funktion genug sein (dh konnte ich warf es einfach auf einen Funktionszeiger und tun int result = ((int (*)(int, int))ImAcDeclFunc)(firstArg, secondArg)?). Und ich bin nicht daran interessiert, mit boost :: function oder etwas ähnlich, ich die Funktion muß vollständig dynamisch, daher mein Interesse sein :)

HINWEIS: Diese Frage ist eine Fortsetzung auf meinem previous one, aber mit viel mehr Details.

+0

Warum sollten Sie eine Funktion kopieren? Das Original ist genauso gut. Möchten Sie aus einer übergeordneten Darstellung eine völlig neue Funktion generieren? –

+0

@ n.m. Ja, das war alles nur ein Beispiel für mich, um alles zu verstehen und zu präsentieren. Ich werde ungefähr zwanzig davon brauchen. Wenn Sie meinen Link lesen (zu meiner anderen Frage), würden Sie genau verstehen warum :) –

+0

Ich habe versucht, diese Frage beim ersten Mal zu verstehen, ohne Erfolg. –

Antwort

5

Wenn Sie nehmen diese lala.c:

int ImAcDeclFunc(int a, int b) 
{ 
    return a + b; 
} 

int main(void) 
{ 
    return 0; 
} 

Sie können es mit gcc -Wall lala.c -o lala kompilieren. Sie können dann die ausführbare Datei mit objdump -Dslx lala >> lala.txt zerlegen. Sie werden feststellen, ImAcDeclFunc zu zusammengesetzt ist:

00000000004004c4 <ImAcDeclFunc>: 
ImAcDeclFunc(): 
    4004c4: 55      push %rbp 
    4004c5: 48 89 e5    mov %rsp,%rbp 
    4004c8: 89 7d fc    mov %edi,-0x4(%rbp) 
    4004cb: 89 75 f8    mov %esi,-0x8(%rbp) 
    4004ce: 8b 45 f8    mov -0x8(%rbp),%eax 
    4004d1: 8b 55 fc    mov -0x4(%rbp),%edx 
    4004d4: 8d 04 02    lea (%rdx,%rax,1),%eax 
    4004d7: c9      leaveq 
    4004d8: c3      retq 

Eigentlich ist diese Funktion relativ einfach ist, an anderer Stelle zu kopieren. In diesem Fall haben Sie vollkommen Recht damit zu sagen, dass Sie die Bytes kopieren können und es würde einfach funktionieren.

Probleme treten auf, wenn Sie beginnen, Anweisungen zu verwenden, die relative Offsets als Teil des Opcodes verwenden. Zum Beispiel ein relativer Sprung oder ein relativer Aufruf. In diesen Fällen müssen Sie die Anweisung richtig verschieben, es sei denn, Sie können es an die Adresse kopieren, an der es ursprünglich war.

Kurz gesagt, müssen Sie, um neu zu lokalisieren, wo sie ursprünglich basierte, den Unterschied berechnen, wo Sie sie basieren, und jede relative Anweisung in Bezug auf diesen Offset verschieben. Dies ist machbar.Ihre eigentliche Schwierigkeit besteht in der Bearbeitung von Aufrufen anderer Funktionen, insbesondere von Funktionsaufrufen an Bibliotheken. In diesem Fall müssen Sie sicherstellen, dass die Bibliothek verknüpft ist, und sie dann auf die Weise aufrufen, die durch das ausführbare Format definiert ist, auf das Sie ausgerichtet sind. Dies ist sehr nicht trivial. Wenn Sie immer noch interessiert sind, kann ich Sie in die Richtung zeigen, wo Sie für das lesen sollten.


In Ihrem einfachen Fall oben, können Sie dies tun:

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

int main(void) 
{ 
    char func[] = {0x55, 0x48, 0x89, 0xe5, 0x89, 0x7d, 0xfc, 
    0x89, 0x75, 0xf8, 0x8b, 0x45, 0xf8, 
    0x8b, 0x55, 0xfc, 0x8d, 0x04, 0x02, 
    0xc9, 0xc3}; 

    int (* func_copy)(int,int) = mmap(NULL, sizeof(func), 
     PROT_WRITE | PROT_READ | PROT_EXEC, 
     MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 

    memcpy(func_copy, func, sizeof(func)); 
    printf("1 + 2 = %d\n", func_copy(1,2)); 

    munmap(func_copy, sizeof(func)); 
    return EXIT_SUCCESS; 
} 

Dies funktioniert auf x86-64 gut. Er druckt:

1 + 2 = 3 
+1

Ich würde es schätzen _so viel_, wenn Sie mich mit einem Arbeitsbeispiel versorgen könnten. Das wäre solides Gold für mich! Über die relativen Anrufe, von denen ich weiß, dass es genau so ist; 'targetAddress - currentAddress -/+ beliebige Offsets'? Über die 'Behandlung von Bibliotheksaufrufen', wäre das ein Problem, wenn ich nur Mitgliedsfunktionen aufrufen würde? Da ich GCC verwende, ist es _exactly_ wie ein Cdecl-Aufruf, aber mit einem zusätzlichen Zeiger (der 'dieser' Zeiger). Oder würde es Probleme verursachen, wenn ich dann Bibliotheksfunktionen mit vielleicht _stdcall von der Mitgliedsfunktion aufrufen würde; d. h. dynamic_func-> member_func-> library_func? –

+0

Oh, übrigens, schlägt mprotect nicht fehl, weil Sie den Speicher nicht ausrichten? Ich werde es selbst versuchen :) –

+0

@ElliottDarfink: Ja, ich habe gerade die Ausrichtung auch bemerkt. Es segmentiert immer noch nach dem Ändern, also muss etwas mehr gespielt werden. Ja, relative Offsets funktionieren hauptsächlich mit dem Delta von targetAddress und currentAddress. –

1

Sie möchten vielleicht GNU Blitz: http://www.gnu.org/software/lightning/. Es könnte dir bei dem, was du versuchst, helfen.

+1

Ja, ich habe darüber gelesen, aber ich habe nicht ganz verstanden, wie es funktioniert. Ganz zu schweigen davon, wie dünn die Dokumentation ist. Sie wissen nicht, ob es Dokumentationsressourcen gibt, die Ihnen helfen könnten? Es scheint zu sein, was ich will, ich weiß es einfach nicht. –

1

Ich denke, dass es bessere Idee, werde einige Skriptsprache in Ihr Projekt einbinden, anstatt sich selbst modifizierende Programm zu schreiben. Es dauert weniger Zeit und Sie werden flexibler.

Wenn ich buchstäblich ein Duplikat dieser Funktion erstellen möchte, aber vollständig dynamisch, was würde das erfordern (und daran erinnern, es ist C++ mit Inline-Assembly)?

Es würde Mensch mit Disassembler erfordern. Technisch gesehen sollte die Funktion bei einer Adresse beginnen und bei der Rückkehranweisung enden. Es ist jedoch unbekannt, was genau der Compiler während der Optimierungsphase mit der Funktion gemacht hat. Ich wäre nicht überrascht, wenn sich der Funktionseintrittspunkt an einer Art seltsamer Stelle befindet (wie am Ende der Funktion, nach der return-Anweisung) oder wenn die Funktion in mehrere Teile aufgeteilt wurde, die mit anderen Funktionen geteilt wurden.

+0

"Es würde Mensch mit Disassembler erfordern" - Das ist falsch. Es gibt automatisierte Tools, die eine statische Analyse durchführen, die dem widerspricht (wie Dyninst). –

+0

@MikeKwan: ​​Es gibt keinen Widerspruch, und ich bin richtig. Obwohl es automatisierte Tools gibt, sind sie nicht 100% zuverlässig, benötigen möglicherweise menschliche Unterstützung und ziehen häufig Hilfsdaten aus den Debug-Informationen. SO wie IDA Pro dauert Minuten, um Dateien in Routinen aufzuteilen, und kann trotzdem einige von ihnen vermissen. Es wird noch lustiger, wenn Sie versuchen, Software zu analysieren, die verschleiert wurde, um Disassembler zu verwirren. – SigTerm

+0

Und Sie glauben, dass ein Mensch mit einem Disassembler in solchen Fällen besser machen kann? Meist fällt statische Analyse mit indirekter Verzweigung aus. In diesen Fällen ist die menschliche Analyse nicht viel besser. Es gibt auch mehr Ungenauigkeiten in Ihrer Antwort.Die Größe einer Funktion kann (zumindest bei ELF) mit Hilfe von Symbolinformationen ermittelt werden. –