Wir drängen Parameter in einen sicheren Code in unsichere Operationen

    Hallo allerseits Dieses Mal lachen wir weiterhin über den normalen Methodenaufruf. Ich schlage vor, sich mit dem Methodenaufruf mit Parametern vertraut zu machen, ohne Parameter zu übergeben. Wir werden auch versuchen, den Referenztyp in eine Nummer zu konvertieren - seine Adresse, ohne Zeiger und unsicheren Code zu verwenden.

    Haftungsausschluss


    Bevor Sie mit der Geschichte fortfahren , sollten Sie sich unbedingt mit dem vorherigen Beitrag über StructLayout vertraut machen Dinge, die dort angegeben sind, werden hier nicht wiederholt.

    Ich möchte auch darauf hinweisen, dass dieser Artikel kein Material enthält, das in echten Projekten verwendet werden sollte.

    Einige Hintergrundinformationen.


    Bevor wir mit dem Üben beginnen, erinnern wir uns, wie C # -Code konvertiert wird.
    Schauen wir uns ein einfaches Beispiel an. Ich möchte Sie daran erinnern, dass ich, um Spaß an StructLayout zu haben, nur virtuelle Methoden verwende.

    publicclassHelper 
    {
            publicvirtualvoidFoo(int param)
            {
            }
    }
    publicclassProgram 
    {
        publicvoidMain() 
        {
            Helper helper = new Helper();
            var param = 5;
            helper.Foo(param);
        }
    }
    

    Dieser Code enthält nichts Kompliziertes, aber die von JiT generierten Anweisungen enthalten mehrere wichtige Punkte. Ich schlage vor, nur einen kleinen Teil des generierten Codes zu analysieren.

        1: movdword[ebp-0x8], 0x5
        2: movecx, [ebp-0xc]
        3: movedx, [ebp-0x8]
        4: moveax, [ecx]
        5: moveax, [eax+0x28]
        6: calldword[eax+0x10]

    In diesem kleinen Beispiel können Sie fastcall beobachten - eine Vereinbarung über die Übertragung von Parametern durch Register (die ersten beiden Parameter von links nach rechts in den Registern ecx und edx), und die übrigen Parameter werden von rechts nach links im Stapel übergeben. Der erste (implizite) Parameter ist die Adresse der Instanz der Klasse, für die die Methode aufgerufen wird. Es wird als erster impliziter Parameter für jede Instanzmethode übergeben. Der zweite Parameter ist eine lokale Variable vom Typ int (in unserem Fall).

    In der ersten Zeile sehen wir also die lokale Variable 5, hier gibt es nichts Interessantes.
    In der zweiten Zeile kopieren wir die Adresse der Helper-Instanz in das ecx-Register. Dies ist die Adresse der Methodentabelle selbst.
    Die dritte Zeile enthält eine Kopie der lokalen Variablen 5 zum Register edx
    FourthDer String kopiert die Adresse der Methodentabelle in das eax-Register.
    Die fünfte Zeile enthält die Verschiebung des eax-Registers um 40 Byte, wobei der Wert aus dem Speicher an einer Adresse geladen wird, die 40 Byte größer ist als die Adresse der Methodentabelle: zur Startadresse der Methoden in der Methodentabelle. (Die Methodentabelle enthält verschiedene Informationen, die zuvor gespeichert wurden. Zu diesen Informationen gehören beispielsweise die Adresse der Methodentabelle der Basisklasse, die EEClass-Adresse, verschiedene Flags, einschließlich des Garbage Collector usw.). Dementsprechend wird nun die Adresse der ersten Methode aus der Methodentabelle im eax-Register gespeichert.
    Im sechstenDie Zeile ruft die Methode mit dem Offset 16 vom Anfang an auf, dh der fünfte in der Methodentabelle. Warum steht unsere einzige Methode an fünfter Stelle? Ich erinnere Sie daran, dass das Objekt 4 virtuelle Methoden (ToString, Equals, GetHashCode und Finalize) hat, die dementsprechend in allen Klassen vorhanden sind.

    Gehe zu üben


    Es ist Zeit für eine kleine Demonstration. Ich biete gerade so einen Rohling an (sehr ähnlich dem Rohling aus dem vorigen Artikel).

    [StructLayout(LayoutKind.Explicit)]
        publicclassCustomStructWithLayout
        {
            [FieldOffset(0)]
            public Test1 Test1;
            [FieldOffset(0)]
            public Test2 Test2;
        }
        publicclassTest1
        {
            publicvirtualintUseless(int param)
            {
                Console.WriteLine(param);
                return param;
            }
        }
        publicclassTest2
        {
            publicvirtualintUseless()
            {
                return888;
            }
        }
        publicclassStub
        {
            publicvoidFoo(int stub) { }
        }
    

    Und die folgende Füllung der Main-Methode:

    classProgram
        {
            staticvoidMain(string[] args)
            {
                Test2 fake = new CustomStructWithLayout
                {
                    Test2 = new Test2(),
                    Test1 = new Test1()
                }.Test2;
                Stub bar = new Stub();
                int param = 55555;
                bar.Foo(param);
                fake.Useless();
                Console.Read();
            }
        }
    

    Wie Sie sich vorstellen können, wird die Useless (int j) -Methode des Typs Test1 aus den Erfahrungen des vorherigen Artikels aufgerufen.

    Aber was wird angezeigt? Ich glaube, der aufmerksame Leser hat diese Frage bereits beantwortet. "55555" wird auf der Konsole angezeigt.

    Aber lassen Sie uns noch die erzeugten Codefragmente betrachten.

    movecx, [ebp-0x20]movedx, [ebp-0x10]cmp[ecx], ecxcallStub.Foo(Int32)
         nopmovecx, [ebp-0x1c]moveax, [ecx]moveax, [eax+0x28]calldword[eax+0x10]

    Ich glaube, Sie kennen das virtuelle Methodenaufrufmuster. Es beginnt nach L00cc: nop. Wie wir sehen, wird die Adresse der Instanz, in der die Methode aufgerufen wird, in ecx geschrieben. Aber seit wir nennen angeblich eine Methode vom Typ Test2, die keine Parameter hat, dann wird nichts in edx geschrieben. Zuvor wurde jedoch die Methode aufgerufen, an die der Parameter nur durch das edx-Register übergeben wurde, bzw. der Wert darin blieb. und wir können es im Ausgabefenster sehen.

    Es gibt noch eine weitere interessante Nuance. Ich habe speziell den sinnvollen Typ verwendet. Ich empfehle, den Parametertyp der Foo-Methode des Stub-Typs durch einen beliebigen Referenztyp zu ersetzen, beispielsweise eine Zeichenfolge. Der Parametertyp der Methode Useless ändert sich jedoch nicht. Unten sehen Sie das Ergebnis auf meiner Maschine mit einigen klärenden Elementen: WinDBG und Rechner :) Das Bild ist anklickbar




    Das Ausgabefenster zeigt die Adresse des Referenztyps in Dezimalschreibweise an.

    Das Ergebnis


    Sie haben das Wissen über Aufrufmethoden mit der Fastcall-Konvention aktualisiert und sofort das wunderbare edx-Register verwendet, um jeweils eine Methode von Parameter 2 zu übergeben. Sie spuckten auch alle Typen aus und erinnerten sich daran, dass alles nur Bytes ist, die leicht die Adresse des Objekts erhalten, ohne Zeiger und unsicheren Code zu verwenden. Ich habe vor, die erhaltene Adresse für noch mehr unpassende Zwecke zu verwenden!

    Vielen Dank für Ihre Aufmerksamkeit!

    Den PS C # -Code finden Sie unter dem Link

    Jetzt auch beliebt: