Pointer verfälschen



  • 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.



  • Er kennt alle Variablen und baut sich seinen Stackframe wie er ihn braucht - zur Compilezeit.

    Nein. Der Stackframe wird zur Laufzeit erzeugt. Ein zusätzlicher Grund für einen Frame- und einen Stackpointer.

    void foo() // Der Compiler weiß schon zur Compilezeit wieviel er allokieren möchte?
    {
        if( std::cin.get() == 'a' )
        {
            int b;
            std::cout << &b;
        }
    }
    

    Dynamische Allokation vom Stack ist schon länger möglich.

    Edit^x: Ich glaub ich habe dich missverstanden. 😕

    Caligulaminus schrieb:

    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.

    👍



  • Sone schrieb:

    Nein. Der Stackframe wird zur Laufzeit erzeugt.

    Wann denn sonst? Rabulist.
    Das Layout wird vom Compiler festgelegt und steht fix im Kompilat.

    Sone schrieb:

    void foo() // Der Compiler weiß schon zur Compilezeit wieviel er allokieren möchte?
    {
        if( std::cin.get() == 'a' )
        {
            int b;
            std::cout << &b;
        }
    }
    

    Ja.

    Sone schrieb:

    Dynamische Allokation vom Stack ist schon länger möglich.
    Edit^x: Ich glaub ich habe dich missverstanden. 😕

    Offensichtlich.



  • ...



  • Ok, da war wirklich ein Missverständnis.

    Caligulaminus schrieb:

    Sone schrieb:

    Nein. Der Stackframe wird zur Laufzeit erzeugt.

    Wann denn sonst? Rabulist.

    Lol.

    Das Layout wird vom Compiler festgelegt und steht fix im Kompilat.

    Ja! Aber nicht die Größe des Stacks! Das ist, worauf ich hinaus wollte. Die Größe des Stacks ist nicht schon vor Eintritt in bestimmte Statements überhaupt vorauszusagen.

    Sone schrieb:

    void foo() // Der Compiler weiß schon zur Compilezeit wieviel er allokieren möchte?
    {
        if( std::cin.get() == 'a' )
        {
            int b;
            std::cout << &b;
        }
    }
    

    Ja.

    Der Compiler weiß nicht, wie die Funktion ablaufen wird. Weil der User-Input nicht vorausgesagt werden kann. Er allokiert dynamisch den Speicher den er für die Objekte braucht.



  • ...



  • Swordfish schrieb:

    Entscheidet er sich für den Stack, wird der Stackframe dafür bei Funktionseintritt eingerichtet.

    Also beim Function-Prologue.
    Ich nehme alles zurück- ich hab mir mal den Assembler für folgendes Programm angeschaut:

    #include <iostream>
    
    void foo()
    {
        if( std::cin.get() == 'a' )
        {
            int arr[1000];
            std::cout << arr << " bis " << arr + sizeof arr / sizeof *arr << '\n';
        }
    }
    
    int main()
    {
        foo();
    }
    
    /* foo() */
    _Z3foov:
    .LFB1267:
    	pushq	%rbx	 #
    	.seh_pushreg	%rbx
    	movl	$4048, %eax	 #,
    	call	___chkstk_ms
    	subq	%rax, %rsp	 #,
    	.seh_stackalloc	4048
    	.seh_endprologue
    

    Anscheinend allokiert er tatsächlich die 4000 Byte für das Array. :schäm:
    sry.



  • Sone schrieb:

    Anscheinend allokiert er tatsächlich die 4000 Byte für das Array. :schäm:
    sry.

    Ja, natürlich tut er das. Wäre ja total ineffizient den Stackpointer wegen jedem int hin und herzuschieben.
    Wo er es nicht kann ist wenn man Dinge wie VLAs oder alloca verwendet.



  • hustbaer schrieb:

    Sone schrieb:

    Anscheinend allokiert er tatsächlich die 4000 Byte für das Array. :schäm:
    sry.

    Ja, natürlich tut er das. Wäre ja total ineffizient den Stackpointer wegen jedem int hin und herzuschieben.

    Ok, das habe ich wirklich nicht erwartet.

    Jetzt wird mir gerade etwas klar: Genau wegen so etwas bietet sich auch Heap-Allokation an, den diese kann natürlich völlig dynamisch erfolgen und belastet bei Funktionsaufrufen nicht immer den Stack 💡

    Wo er es nicht kann ist wenn man Dinge wie VLAs oder alloca verwendet.

    Das ist klar. 😃
    Das ist dann vollkommen dynamisch.



  • Wobei es möglich wäre dass Compiler ab einer gewissen Arraygrösse doch zu "on demand" übergehen.
    Unter Windows muss man z.B. Stackprobes machen ( call ___chkstk_ms ), um sicherzustellen dass die Stack-Pages in der richtigen Reihenfolge committed werden. Weil immer nur die erste noch nicht committete Stack-Page eine Guard-Page ist. Und das braucht natürlich Zeit.
    So lange das Stack-Frame aber kleiner als eine Page bleibt, sind keine Stack-Probes nötig.
    Ob Compiler das wirklich machen weiss ich aber nicht.


Anmelden zum Antworten