Ausführen von Maschinencode im Speicher

Lesezeit: 12 Minuten

Ich versuche herauszufinden, wie man im Speicher gespeicherten Maschinencode ausführt.

Ich habe folgenden Code:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    FILE* f = fopen(argv[1], "rb");

    fseek(f, 0, SEEK_END);
    unsigned int len = ftell(f);
    fseek(f, 0, SEEK_SET);

    char* bin = (char*)malloc(len);
    fread(bin, 1, len, f);

    fclose(f);

    return ((int (*)(int, char *)) bin)(argc-1, argv[1]);
}

Der obige Code lässt sich in GCC gut kompilieren, aber wenn ich versuche, das Programm über die Befehlszeile wie folgt auszuführen:

./my_prog /bin/echo hello

Das Programm segfaults. Ich habe herausgefunden, dass das Problem in der letzten Zeile liegt, da das Auskommentieren den Segfault stoppt.

Ich glaube nicht, dass ich es ganz richtig mache, da ich mich immer noch mit Funktionszeigern auseinandersetze.

Liegt das Problem an einer fehlerhaften Besetzung oder an etwas anderem?

  • Charlie: Wenn Sie all diese Antworten jemals verstehen, anstatt einen gecasteten Zeiger auf eine Funktion zu verwenden, wie Sie sie haben, sind Sie vielleicht besser geeignet, einen einfachen Thunk zu schreiben, der die Stack-Argumente dynamisch verwaltet. Bei Verwendung von gcc eine wie “function()” deklarierte Funktion Attribut ((nackt));” und siehe gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html für weitere Beispiele. Auf diese Weise rufen Sie dieselbe Funktion auf, die entscheidet, ob der dynamisch geladene Code mit einer Anzahl von N Argumenten/Aufrufkonventionen usw. versorgt werden muss … In jedem Fall sollten Sie wahrscheinlich nach FFI und so suchen.

    – RandomNickName42

    7. Januar 2010 um 13:26 Uhr

  • Ich bin mir ziemlich sicher, dass das OP nur die Grundlagen der Funktionsweise ausführbarer Dateien missversteht. Verwenden Sie eine dynamische Linkbibliothek zum Ausführen Ihres eigenen dynamischen Codes und exec zum Ausführen anderer Apps.

    – Jimbo

    7. Januar 2010 um 16:10 Uhr

  • @Jimbo – Du hast vollkommen Recht. Ich wollte sehen, ob ich das tun könnte, also dachte ich: “Wo finde ich Maschinencode?”, Und beschloss, einfach eine ausführbare Datei zu holen, ohne weiter darüber nachzudenken:

    Benutzer47322

    8. Januar 2010 um 3:14 Uhr

  • Möglicherweise haben Sie etwas Glück beim Kompilieren in die Webassembly.

    – Mike

    13. November 2018 um 8:49 Uhr

Sie benötigen eine Seite mit Schreib- und Ausführungsberechtigungen. Siehe mmap(2) und mprotect(2), wenn Sie unter Unix arbeiten. Sie sollten dies nicht mit malloc tun.

Lesen Sie auch, was die anderen gesagt haben, Sie können mit Ihrem Lader nur rohen Maschinencode ausführen. Wenn Sie versuchen, einen ELF-Header auszuführen, wird er wahrscheinlich trotzdem segfault.

In Bezug auf den Inhalt von Antworten und Downmods:

1- OP sagte, er versuche, Maschinencode auszuführen, also antwortete ich darauf, anstatt eine ausführbare Datei auszuführen.

2- Sehen Sie, warum Sie malloc- und mman-Funktionen nicht mischen:

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

int main()
{
    char *a=malloc(10);
    char *b=malloc(10);
    char *c=malloc(10);
    memset (a,'a',4095);
    memset (b,'b',4095);
    memset (c,'c',4095);
    puts (a);
    memset (c,0xc3,10); /* return */

    /* c is not alligned to page boundary so this is NOOP.
     Many implementations include a header to malloc'ed data so it's always NOOP. */
    mprotect(c,10,PROT_READ|PROT_EXEC);
    b[0]='H'; /* oops it is still writeable. If you provided an alligned
    address it would segfault */
    char *d=mmap(0,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);
    memset (d,0xc3,4096);
    ((void(*)(void))d)();
    ((void(*)(void))c)(); /* oops it isn't executable */
    return 0;
}

Es zeigt genau dieses Verhalten unter Linux x86_64, andere hässliche Verhaltensweisen, die sicherlich bei anderen Implementierungen auftreten werden.

  • Ich werde dem nachgehen. Ich hatte das Gefühl, es könnte etwas damit zu tun haben.

    Benutzer47322

    7. Januar 2010 um 11:38 Uhr

  • Das ist eigentlich nicht korrekt, Sie können es mit malloc tun, Sie müssen nur mprotect verwenden.

    – RandomNickName42

    7. Januar 2010 um 12:15 Uhr

  • OK, wenn Sie seinen CODE LESEN, sehen Sie, wie er eine DATEI LADET, um AUSZUFÜHREN. Die Tatsache, dass es sich um eine KOMPILIERTE BINARY-Datei handelt, bedeutet, dass der Textbereich BEREITS auf die SEITENGRÖSSE AUSGERICHTET ist. Wenn er den HEAP mprotect ist, dann ist das EINZIG MÖGLICHE PROBLEM, dass die Datei, die er in EXECUTE GELADEN hat, einige der .data möglicherweise MARKED EXEC enthält, wenn er das nicht selbst angepasst hat. Aber es ist KEIN PROBLEM, HEAP +x, JAVA und MONO dazu zu bringen, dies die ganze Zeit zu tun.

    – RandomNickName42

    7. Januar 2010 um 13:12 Uhr

  • Seien Sie nicht zu aufgeregt, mmap, mprotect usw. schützen/entschützen nur in Seiten, nicht in Bytes. Malloc-Implementierungen platzieren mallocierte Daten in vorab zugewiesenen Chunks. Wenn Sie also den Schutz in Ihrem Chunk ändern, wird dieser wahrscheinlich an andere mallocierte Daten angehängt oder ihnen vorangestellt, die dieselbe Seite bzw. dieselben Seiten teilen. Wenn Sie mprotect verwenden, werden die Schutzmaßnahmen entweder (r|)w|x oder r|x sein, auf jeden Fall werden Ihre r|w-Daten auf der/den Seite(n) es nicht mögen, dh. Segmentfehler oder Sie lassen diese Daten verfügbar, um ausführbaren Code einzuführen.

    Benutzer14554

    7. Januar 2010 um 13:21 Uhr


  • Ja, keine Sorge, ich habe mich beruhigt und sogar entschieden, dass Ihr Beitrag nach Ihrem Codebeispiel hilfreich ist. Wie Sie jedoch in meinem Code sehen, funktioniert malloc einwandfrei + rwx, selbst wenn Sie allen 3 dem Heap zugeordneten Speicher, den das von mir gezeigte Beispiel nennt, freie Speicher hinzufügen, ist dies kein Problem oder Stabilitätsproblem. Die einzige Sache ist, dass Sie unbeabsichtigt etwas Speicher auf dem Heap als +x zulassen können, aber das ist wirklich keine große Sache.

    – RandomNickName42

    7. Januar 2010 um 16:22 Uhr

Benutzer-Avatar
Zufälliger Spitzname42

Die Verwendung von malloc funktioniert einwandfrei.

OK, das ist meine endgültige Antwort. Bitte beachten Sie, dass ich den Code des ursprünglichen Posters verwendet habe. Ich lade die kompilierte Version dieses Codes von der Festplatte in einen Heap-zugewiesenen Bereich “bin”, genau wie der ursprüngliche Code (der Name ist festgelegt, ohne argv zu verwenden, und der Wert 0x674 stammt von;

objdump -F -D foo|grep -i hoho
08048674 <hohoho> (File Offset: 0x674):

Dies kann zur Laufzeit mit der BFD (Binary File Descriptor Library) oder etwas anderem nachgeschlagen werden, Sie können andere Binärdateien (nicht nur sich selbst) aufrufen, solange sie statisch mit demselben Satz von Bibliotheken verknüpft sind.

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

unsigned char *charp;
unsigned char *bin;

void hohoho()
{
   printf("merry mas\n");
   fflush(stdout);
}

int main(int argc, char **argv)
{
   int what;

   charp = malloc(10101);
   memset(charp, 0xc3, 10101);
   mprotect(charp, 10101, PROT_EXEC | PROT_READ | PROT_WRITE);

   __asm__("leal charp, %eax");
   __asm__("call (%eax)" );

   printf("am I alive?\n");

   char *more = strdup("more heap operations");
   printf("%s\n", more);

   FILE* f = fopen("foo", "rb");

   fseek(f, 0, SEEK_END);
   unsigned int len = ftell(f);
   fseek(f, 0, SEEK_SET);

   bin = (char*)malloc(len);
   printf("read in %d\n", fread(bin, 1, len, f));
   printf("%p\n", bin);

   fclose(f);
   mprotect(&bin, 10101, PROT_EXEC | PROT_READ | PROT_WRITE);

   asm volatile ("movl %0, %%eax"::"g"(bin));
   __asm__("addl $0x674, %eax");
   __asm__("call %eax" );
   fflush(stdout);

   return 0;
}

laufend…

co tmp # ./foo
am I alive?
more heap operations
read in 30180
0x804d910
merry mas

Sie können verwenden UPX um das Laden/Ändern/Ausführen einer Datei zu verwalten.

PS Entschuldigung für den vorherigen defekten Link 😐

  • Beachten Sie dies IST plattformübergreifend und völlig abstrahiert von den Details der Dateiformatspezifikationen oder jeder Art von Anforderung, mit Seitenschutz und dergleichen herumzuspielen.

    – RandomNickName42

    7. Januar 2010 um 11:54 Uhr

  • Pffft, ich liebe es, ohne Begründung runtergewählt zu werden, werde echt. UPX ist DER Weg, dies zu tun, alles andere ist naiv. Sie können es einfach verwenden, um Exe’s für Sie zu laden, oder es sind APIs auf niedrigerer Ebene, die dynamische Assembly-Stubs ausgeben, die beliebige Speicherblöcke komprimiert oder auf andere Weise laden/ausführen können.

    – RandomNickName42

    7. Januar 2010 um 12:02 Uhr

  • Nun, wir wissen nicht, wie er den Maschinencode in den Speicher bekommt. Was ist, wenn er einen Bytecode-Interpreter schreibt und der Code im Speicher generiert wird? Das Laden von “Echo” (so falsch der Code auch war) hätte ein Proof-of-Concept sein können, dass Code im laufenden Betrieb generiert und ausgeführt werden konnte.

    – ta.speot.is

    7. Januar 2010 um 13:48 Uhr

  • malloc die Seitenausrichtung nicht sicherstellt, funktioniert Ihr Code möglicherweise oder auch nicht. Sie könnten eine seitenausgerichtete Teilmenge des mallocd-Blocks verwenden, was sicher wäre, oder möglicherweise verwenden posix_memalign Wenn du es hast

    – Hasturkun

    7. Januar 2010 um 14:43 Uhr

  • Ich hoffe, Sie haben nichts gegen meine Bearbeitung, Ihr UPX-Link hat auf einen schmutzigen Ort verwiesen

    – Hasturkun

    7. Januar 2010 um 14:48 Uhr

Es scheint mir, dass Sie ein ELF-Bild laden und dann versuchen, direkt in den ELF-Header zu springen? http://en.wikipedia.org/wiki/Executable_and_Linkable_Format

Wenn Sie versuchen, eine andere Binärdatei auszuführen, warum verwenden Sie dann nicht die Prozesserstellungsfunktionen für die von Ihnen verwendete Plattform?

  • Ich denke, das liegt daran, dass er versucht, eine ihm zugewiesene App im Arbeitsspeicher auszuführen. Ich glaube nicht, dass eine Prozesserstellungsfunktion so funktioniert. Thread-Erstellungsfunktionen können, aber er lädt eine Festplattendatei in den Speicher und versucht dann, diesen Speicher auszuführen.

    – RandomNickName42

    7. Januar 2010 um 12:14 Uhr

  • Wenn der Speicher nicht als ausführen gekennzeichnet ist, kann er ihn nicht ausführen, aber er lädt auch eine ELF-Datei in den Speicher und versucht dann, den ELF-Header aufzurufen, dessen erste vier Bytes 0x7f ‘E’ ‘L’ sind. ‘F’

    – ta.speot.is

    7. Januar 2010 um 12:42 Uhr

  • Fun Fact: 0x7F ist der primäre Opcode für JNLE. Vielleicht versucht der Code also als Erstes, zu einer Mülladresse zu springen? So oder so: Das Ausführen eines ELF-Headers wird nicht funktionieren.

    – ta.speot.is

    7. Januar 2010 um 12:46 Uhr


Eine typische ausführbare Datei hat:

  • eine Überschrift
  • Zugangscode, der vorher aufgerufen wird main(int, char **)

Das erste bedeutet, dass Sie im Allgemeinen nicht erwarten können, dass Byte 0 der Datei ausführbar ist; Stattdessen beschreiben die Informationen im Header, wie der Rest der Datei in den Speicher geladen und wo mit der Ausführung begonnen wird.

Das zweite bedeutet, dass Sie, wenn Sie den Einstiegspunkt gefunden haben, nicht erwarten können, ihn wie eine C-Funktion zu behandeln, die Argumente entgegennimmt (int, char **). Es kann vielleicht als Funktion verwendet werden, die keine Parameter akzeptiert (und daher vor dem Aufrufen nichts gepusht werden muss). Sie müssen jedoch die Umgebung füllen, die wiederum vom Eingabecode verwendet wird, um die an main übergebenen Befehlszeilenzeichenfolgen zu erstellen.

Dies von Hand unter einem bestimmten Betriebssystem zu tun, würde in eine Tiefe gehen, die mir ein Rätsel ist. aber ich bin sicher, es gibt einen viel schöneren Weg, das zu tun, was Sie zu tun versuchen. Versuchen Sie, eine externe Datei als Ein-Aus-Vorgang auszuführen oder eine externe Binärdatei zu laden und ihre Funktionen als Teil Ihres Programms zu behandeln? Für beides sorgen die C-Bibliotheken in Unix.

Benutzer-Avatar
Clifford

Es ist wahrscheinlicher, dass es der Code ist, zu dem der Aufruf durch den Funktionszeiger gesprungen ist, der den Segfault verursacht, und nicht der Aufruf selbst. Anhand des von Ihnen geposteten Codes kann nicht festgestellt werden, ob dieser in den Papierkorb geladene Code gültig ist. Am besten verwenden Sie einen Debugger, wechseln zur Assembler-Ansicht, unterbrechen die return-Anweisung und hineinsteigen den Funktionsaufruf, um festzustellen, ob der Code, den Sie ausführen möchten, tatsächlich ausgeführt wird und gültig ist.

Beachten Sie auch, dass der Code ausgeführt werden muss, um überhaupt ausgeführt zu werden stellungsunabhängig und vollständig gelöst.

Wenn Ihr Prozessor/Betriebssystem die Datenausführungsverhinderung aktiviert, ist der Versuch außerdem wahrscheinlich zum Scheitern verurteilt. Es ist in jedem Fall bestenfalls nicht ratsam, das Laden von Code ist das, wofür das Betriebssystem da ist.

  • Ya, gut auf der Position unabhängig, Charlie kann -fPIC verwenden, wenn er gcc verwendet, aber leider ist es unter Windows kein einfacher Weg, kompilierte PIC C-Anwendungen zu erhalten.

    – RandomNickName42

    7. Januar 2010 um 12:18 Uhr

Was Sie zu tun versuchen, ähnelt dem, was Dolmetscher tun. Abgesehen davon, dass ein Interpreter ein Programm liest, das in einer interpretierten Sprache wie Python geschrieben ist, diesen Code spontan kompiliert, ausführbaren Code in den Speicher legt und ihn dann ausführt.

Vielleicht möchten Sie auch mehr über die Just-in-Time-Kompilierung lesen:

Just-in-Time-Zusammenstellung
Java HotSpot JIT-Laufzeit

Für die Generierung von JIT-Code sind Bibliotheken verfügbar, z GNU-Blitz und libJIT, wenn Sie interessiert sind. Sie müssten jedoch viel mehr tun, als nur aus der Datei zu lesen und zu versuchen, Code auszuführen. Ein beispielhaftes Nutzungsszenario ist:

  1. Lesen Sie ein Programm, das in einer Skriptsprache (vielleicht Ihrer eigenen) geschrieben ist.
  2. Analysieren und kompilieren Sie die Quelle in eine Zwischensprache, die von der JIT-Bibliothek verstanden wird.
  3. Verwenden Sie die JIT-Bibliothek, um Code für diese Zwischendarstellung für die CPU Ihrer Zielplattform zu generieren.
  4. Führen Sie den JIT-generierten Code aus.

Und um den Code auszuführen, müssten Sie Techniken wie die Verwendung von mmap() verwenden, um den ausführbaren Code in den Adressraum des Prozesses abzubilden, diese Seite als ausführbar zu markieren und zu diesem Teil des Speichers zu springen. Es ist komplizierter als das, aber es ist ein guter Anfang, um zu verstehen, was unter all diesen Interpretern von Skriptsprachen wie Python, Ruby usw. vor sich geht.

Das online Version des Buches “Linker und Lader” gibt Ihnen weitere Informationen über Objektdateiformate, was hinter den Kulissen passiert, wenn Sie ein Programm ausführen, die Rolle der Linker und Lader und so weiter. Es ist eine sehr gute Lektüre.

  • Ya, gut auf der Position unabhängig, Charlie kann -fPIC verwenden, wenn er gcc verwendet, aber leider ist es unter Windows kein einfacher Weg, kompilierte PIC C-Anwendungen zu erhalten.

    – RandomNickName42

    7. Januar 2010 um 12:18 Uhr

Benutzer-Avatar
Emvee

Sie können eine Datei mit dlopen() öffnen, das Symbol “main” nachschlagen und es mit 0, 1, 2 oder 3 Argumenten (alle vom Typ char*) über eine Umwandlung in Zeiger-auf-Funktion-Rückgabe-Int-Take aufrufen. 0,1,2 oder 3 Zeichen*

  • Wenn Sie eine Methode wie diese verwenden, möchten Sie wahrscheinlich __libc_start_main nachschlagen

    – RandomNickName42

    7. Januar 2010 um 11:55 Uhr

1377600cookie-checkAusführen von Maschinencode im Speicher

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy