Pointer verfälschen



  • Meine Frage war ernst gemeint!! Ich kriege es leider nicht hin, einfach mal "pointer" mit der Adresse der anderen Funktion durch einen Bufferoverflow zu überschreiben.



  • freakC++ schrieb:

    Meine Frage war ernst gemeint!! Ich kriege es leider nicht hin, einfach mal "pointer" mit der Adresse der anderen Funktion durch einen Bufferoverflow zu überschreiben.

    Mein Code läuft bei mir und erzeugt die Ausgabe 2.
    Allerdings nur im Debug-Modus.

    Im Release-Modus klappt er nicht. Vielleicht ist die Funktion da inline-expandiert.



  • mmhh...vielleicht liegt da auch der Fehler bei mir. Ich stell mal alle Optimierungen aus.



  • welchen Compiler verwendest Du und wie rufst Du ihn auf?



  • freakC++ schrieb:

    welchen Compiler verwendest Du und wie rufst Du ihn auf?

    unter windows den gcc, der bei mingw dabei war.
    -g -Wall -std=c++11 -fexceptions



  • schad...bei mir wird auch mit gcc sets 1 ausgeben. HAt jemand ne Idee?



  • freakC++ schrieb:

    schad...bei mir wird auch mit gcc sets 1 ausgeben. HAt jemand ne Idee?

    Schau mal den assemblercode an.
    (Mit -save-temps compilieren, dann taucht die Datei auf.)
    Lass die beiden Funktionen sonderliche Zahlen returnen wie 4711 und 0815 (/*haha, einer meiner Lieblingswitze*/), damit man die magic numbers besser im Assembler-Code findet.



  • Der Compiler von ideone(C++11) legt mir den Funktionszeiger immer 'unten' auf den Stack, unabhängig von der Reihenfolge der Deklarationen (erst buf[] oder erst pointer ist egal), so daß ein Überlauf von buf ihn nicht erreichen kann. (Sicherheitsfeature?)
    Wenn man buf[-4] nimmt funktioniert es nur, wenn man den manipulierten pointer vor Benutzung einmal ausliest (bspw. cout << (void*) pointer; ). Der liegt da wohl schon in einem Register. Ich hatte für die Tests übrigens aus buf[12] ein buf[16] gemacht. Keine Ahnung, ob das nötig war. EDIT: da es sich wohl um einen 32-Bit-Compiler handelt, dürfte das hier keine Bedeutung haben.



  • aufklär0r schrieb:

    http://de.wikipedia.org/wiki/Nonsens

    http://de.wiktionary.org/wiki/Kommutativität* (nur für skalare Typen natürlich.)

    Im Release-Modus klappt er nicht. Vielleicht ist die Funktion da inline-expandiert.

    Edit³: Versteh jetzt erst den Trick 😃





  • Ich habs: Der Stack wächst nach unten.

    Das liegt wohl an der GCC-ABI.

    volkard schrieb:

    freakC++ schrieb:

    schad...bei mir wird auch mit gcc sets 1 ausgeben. HAt jemand ne Idee?

    Schau mal den assemblercode an.
    (Mit -save-temps compilieren, dann taucht die Datei auf.)
    Lass die beiden Funktionen sonderliche Zahlen returnen wie 4711 und 0815 (/*haha, einer meiner Lieblingswitze*/), damit man die magic numbers besser im Assembler-Code findet.

    Ich hab den Assembler-Code nicht angeschaut, aber wahrscheinlich wolltest du genau darauf hinaus, oder?



  • Und die Lösung lautet:

    int main()
    {
        char buf[12];
        int (*pointer)();
    
        pointer = function_one;
    
        std::cout << &buf << &pointer << '\n';
    
        *((int(**)())&buf[-sizeof(pointer)]) = function_two;
    
        std::cout << pointer();
    }
    

    Edit²: Wtf? Es klappt nur, wenn man die Adressen nimmt und ausgibt. Auch volatile gilt nicht (as-if)!



  • Sone schrieb:

    Wtf?

    Wir bewegen uns außerhalb des Standards.
    Siehe "nasal demons".
    Da muss man halt fummeln uns ausprobieren. Und die nächste Compilerversion macht es ganz anders.

    edit: Bitte die demnächst vollständige main.cpp posten, damit man leichter damit experimentieren kann.



  • Caligulaminus schrieb:

    Der Compiler von ideone(C++11) legt mir den Funktionszeiger immer 'unten' auf den Stack, unabhängig von der Reihenfolge der Deklarationen (erst buf[] oder erst pointer ist egal), so daß ein Überlauf von buf ihn nicht erreichen kann. (Sicherheitsfeature?)

    Kann ich mir nicht vorstellen, da der Compiler ja nicht in die Zukunft schauen kann. Was macht er denn, wenn noch n bissl Code dazwischen steht? Oder ist das libsafe?

    Sone schrieb:

    Und die Lösung lautet:

    Cool danke! Klappt bei mir ohne die Ausgabe der Adressen. Ich schau mir aber morgen auch erst noch einmal den Assemblercode an 🙂

    Viele Grüße
    freakC++



  • freakC++ schrieb:

    Was macht er denn, wenn noch n bissl Code dazwischen steht?

    Das hat damit nichts zu tun. Es geht um die lokalen Variablen auf dem Stack.



  • Also bei mir geht

    int main(void) { 
    
        char buf[12]; 
        int (*pointer)(void); 
    
        pointer = function_one; 
        sprintf(buf,"1234567890ab\x10\x10\x40\x00");
        std::cout << pointer(); 
    
        return 0; 
    }
    

    Bei Visual Studio und

    - Puffersicherheitsüberprüfung deaktivert
    - Funktionslevellinking deaktiviert
    - Optimierungen deaktiviert
    - Zufällige Basisadresse (ASLR) deaktiviert

    Der Compiler von ideone(C++11) legt mir den Funktionszeiger immer 'unten' auf den Stack, unabhängig von der Reihenfolge der Deklarationen (erst buf[] oder erst pointer ist egal), so daß ein Überlauf von buf ihn nicht erreichen kann. (Sicherheitsfeature?)

    Ja das ist ein Sicherheitsfeature dass manche Compiler Arrays immer so angelegt werden dass keine Pointer oder andere lokale Variablen überschrieben werden können.



  • Caligulaminus schrieb:

    Das hat damit nichts zu tun. Es geht um die lokalen Variablen auf dem Stack.

    Vielleicht vertehe ich dich falsch, aber stell dir vor, dass ganz oben auf dem Stack ein Pointer liegt. Jetzt im Nachhinein will ich irgendeinen Buffer drauflegen. Die Reihenfolge kann nur geändert werden, wenn bereits zur Compilierzeit bekannt ist, welche Pointer und überlauffähigen Konstrukte es geben wird. Sonst müsste der Compiler zur Laufzeit den Pointer erst wieder vom Stack runternehmen, dann den Buffer drauflegen und erneut den Zeiger pushen. Das bezweifel ich.

    Sone schrieb:

    Ich habs: Der Stack wächst nach unten.

    Wie rum man den Stack betrachtet, ist eigentlich egal. In meinem Kopf wächst er immer nach oben. Hauptsache deine Vorstellung ist konsistent. Wenn Du vom Stack also subtrahierst, wächst er noch weiter nach oben bzw. bei dir nach unten.

    Sone schrieb:

    Und die Lösung lautet:

    //...
    
        *((int(**)())&buf[-sizeof(pointer)]) = function_two;
    //...
    

    Ehrlich gesagt, habe ich noch ein bisschen Schwierigkeiten mit diesem Ausdruck. Warum brauce ich den Dopelzeiger?

    Vielen Dank für eure Hilfe 🙂
    LG, freakC++



  • Einen Doppelzeiger brauchst du, weil du in &buf[..] einen Zeiger auf einen Funktionszeiger speichern möchtest.
    Die erste Indirektion ist einfach der Funktionszeiger an sich - der Wert, den wir speichern wollen, ist diese Adresse. Die Zweite Indirektion bezieht sich auf den Zeiger, mit dem wir die gewünschte Byte-Sequenz überschreiben möchten ( &buf[-8] o.ä. ).

    Wenn du diesen dann Dereferenzierst, speicherst du den eigentlichen Zeiger in einem "Arrayelement"*.

    *Natürlich kein Element des Arrays, aber naja



  • Sone schrieb:

    Ich habs: Der Stack wächst nach unten.

    Das liegt wohl an der GCC-ABI.

    Breaking News. Das wird doch heutzutage auf praktisch allen Plattformen gängigerweise so gemacht. Auf vielen CPUs lässt sich auch ein aufsteigender Stack recht bequem realisieren, praktisch schreiben aber viele Calling-Conventions/ABIs etwas anderes vor.

    freakC++ schrieb:

    Wie rum man den Stack betrachtet, ist eigentlich egal. In meinem Kopf wächst er immer nach oben. Hauptsache deine Vorstellung ist konsistent.

    Schon richtig, vorstellen kannst du dir das, wie auch immer du möchtest oder wie du die Vorgänge am besten nachvollziehen kannst. Dennoch ist es nicht gänzlich egal. Der Unterschied liegt eben darin, ob später auf den Stack geschobene Elemente eine höhere oder eine niedrigere Adresse haben als die vorigen. Aber auch das reicht noch nicht aus. Eigentlich müsstest du auch noch wissen, ob du bei einer Push-Operation den Stackpointer zuerst inkrementierst/dekrementierst und dann schreibst oder zuerst schreibst und dann den Stackzeiger manipulierst. Man spricht dabei von einem "vollen" oder "leerem" Stack.



  • freakC++ schrieb:

    Die Reihenfolge kann nur geändert werden, wenn bereits zur Compilierzeit bekannt ist, welche Pointer und überlauffähigen Konstrukte es geben wird.

    Was soll da unbekannt sein? Du hast den Quelltext geschrieben, der Compiler hat ihn gelesen. Er kennt alle Variablen und baut sich seinen Stackframe wie er ihn braucht - zur Compilezeit.

    freakC++ schrieb:

    Wie rum man den Stack betrachtet, ist eigentlich egal.

    Nicht, wenn Du solche Spielchen mit Überlauf zur Manipulation machen willst. Da mußt Du schon genau wissen, was passiert.


Anmelden zum Antworten