Frage zu den mit einem C++-Compiler erstellten EXE-Dateien

  • C++/CLI

Es gibt 8 Antworten in diesem Thema. Der letzte Beitrag () ist von nafets.

    Frage zu den mit einem C++-Compiler erstellten EXE-Dateien

    Guten Morgen zusammen,

    ich wollte mal wieder in die Programmierung einsteigen und würde gerne ganz vorne anfangen. Mich beschäftigt seit einigen Tagen der Maschinencode und wie dieser vom Prozessor verarbeitet wird.
    Man mag das für Grundwissen halten, aber ich dachte bis dato immer, dass EXE-Dateien völlig verschlüsselt wären und man damit nichts mehr machen kann, aber ich wurde des besseren belehrt. Die Zeichen, die man da immer sieht, wenn man so eine Datei in den Editor zieht und sich halt als ASCII anzeigen lässt, sind ja, soweit ich das richtig verstanden habe, Hexadezimalzahlen. Ich habe es also mal ausprobiert, in Visual Studio schnell einen kleinen Code getippt und eine char-Variable mit "bla" gefüttert. Dann die kompilierte EXE in Notepad++ gezogen, nach "bla" gesucht, das bla einfach in "cla" geändert und geschaut was passiert. Tatsächlich zeigte mir die Konsole dann cla. Ich hatte vorher nicht gedacht, dass das geht. So viel dazu, kleine Anekdote zum Anfang ;)
    Wenn ich nun eine EXE erstelle, die zuvor in Cpp mit dem nackten

    C-Quellcode

    1. int main()
    2. {
    3. return 0;
    4. }


    geschrieben wurde, verwundert mich aber, dass die Output-EXE trotzdem einige KB hat. Woher kommen die? Also was packt der Compiler zusätzlich außer meinem nackten Code in die EXE rein? Ist das da alles zwingend erforderlich, um dem Prozessor zu sagen, dass er eine Konsole öffnen soll?

    Ich danke schon mal im Voraus für sämtliche Antworten ;)

    LG

    rwCapt schrieb:

    Hexadezimalzahlen
    sind eine String-Repräsentation von Bytes.
    CLI ist trotzdem .NET, also eine NET-Assembly.
    Native C/C++-Programme sehen noch anders aus.
    Die Frage ist dann immer, was für eine Laufzeit-Umgebung diese Dateien brauchen, um laufen zu können.
    Verschlüsselt ist da nix, es sei denn, es wird tatsächlich verschlüsselt, da braucht es zum Entschlüsseln z.B. einen Dongle.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich habe die Vermutung, dass ich ein natives Cpp-Programm habe. Ich habe in Visual Studio einfach eine Konsolen-App erstellt. Da müsste ich mit .NET erstmal nichts zutun haben oder gibt es in den Projekteinstellungen Punkte, die das trotzdem mit einbeziehen?
    Welche Laufzeitumgebung braucht denn so eine normale Konsole? Ich vermute, dass eine Konsole an sich erst mal aufgerufen werden muss, das könnte noch zusätzlich in der EXE-Datei stehen. Also dass sie dieses Konsolen-Programm an sich erst mal laden muss, um erst dann halt programmspezifisch Dinge da drauf darstellen zu können.
    @rwCapt Sieh Dir mal die Includes an und die Verweise. da kannst Du nativ und .NET gut unterscheiden.
    Natives C++ braucht eine Laufzeit-Umgebung oder auch nicht, je nach den Projektsettings.
    CLI braucht ein Framework.
    Wenn Du CLI mit nativem C++ mischst (feine Sache) weiß ich jetzt nicht, ob neben dem Framework noch was gebraucht wird.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    So ist es. Unter .rdata kann man die VirtualAddress ermitteln, unter der die Strings abgespeichert werden. Das konnte ich letzten Endes mit PE Explorer ermitteln. Visual Studio hat mir immer schön die (wahrscheinlich) absolute Adresse angezeigt, während im Hexa-Code nur mit den relativen Adressen (RVAs) gearbeitet wird. Kein Wunder, dass ich ewig gesucht habe. Diese Art der Verlinkung macht aber scheinbar nur der Compiler und Linker, den Visual Studio verwendet. Benutze ich den GNU GCC Compiler, wie man ihn in Dev C++ vorfindet, scheint die Adresszuweisung wieder eine andere zu sein. Da komme ich gerade nicht weiter. Ich versuche mal, das zu erläutern.

    Da der PE Explorer leider nicht mit den EXE-Dateien des GCC Compilers klar kommt, habe ich mal den IDA (interactive disassembler) bemüht. Im Folgenden sieht man die Festlegung der Strings:


    Die Adresse lautet 0x488000, unter .rdata wird die relative Adresse mit 0x088000 angegeben, die ImageBase ist somit, wie anscheinend bei allen EXEs, bei 0x400000.


    aHalloWelt wird wohl ein JumpPoint sein, was lea in meinen Augen nochmal bestätigt. Unter 0x488000 wird folgendes angezeigt: ("JumpPoint" in der Hinsicht, dass er nach 488000 springt, um sich den String zu holen)


    Im Assembler-Code, den der IDA hoffentlich einigermaßen richtig darstellt (woran ich teilweise zweifle), ist die Verlinkung nach 488000 offensichtlich, im Hexadezimal-Code allerdings überhaupt nicht. Kann einer aus dem folgenden Anhang hier:


    erkennen, wie die Verlinkung zustande kommt? Das grün markierte im letzten Bild zeigt anscheinend die lea-Verlinkung mit "aHalloWelt". Wofür soll eigentlich das a hier stehen? Das wird wohl nicht das IDA-Programm erfunden haben, sondern kommt vermutlich aus dem Compiler. Vielleicht soll es array heißen, nichts anderes ist "Hallo Welt" ja.

    Was mir aufgefallen ist: Im letzten Bild ist beim grün markierten Teil einmal ein "BC". Ändere ich dies zu "CA", scheint meine Ausgabevariable plötzlich doch den String "Test" zu nehmen. BC muss also irgendwas mit der Adressierung auf "Hallo Welt" und CA auf "Test" zutun haben.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „rwCapt“ ()

    rwCapt schrieb:

    aHalloWelt wird wohl ein JumpPoint sein, was lea in meinen Augen nochmal bestätigt.


    Da komm ich nicht mit. LEA lädt doch nur die Speicher-Adresse und packt sie hier ins RAX, geht doch in in nächsten Zeile weiter, sehe ich nicht als Sprungpunkt.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Takafusa“ ()

    Da habe ich etwas verwechselt. LEA macht natürlich genau das, was du beschrieben hast, lädt aber doch auch dann die Speicheradresse von "Hallo Welt".

    Ich habe noch etwas nachgeforscht und heraus gefunden, dass es einige Unterschiede in der Hex-Architektur zwischen 32-bit und 64-bit gibt. Ich habe den GCC Compiler eine 64-bit Datei ausgeben lassen, während ich im Visual Studio Win32 ausgewählt habe. Wähle ich auch beim GCC 32-bit, dann ist der Aufruf wieder "sichtbar":

    In Zeile 910 ist einmal ein 0B 80 48 sichtbar, was exakt der Verweis auf "Hi" ist. Ich habe direkt in der Zeile da drunter den cmp Befehl gefunden, der dann prüft, ob "Hi", das ja in eine Variable geschrieben wurde, gleich zu setzen ist mit "Hallo Welt" (siehe in Zeile 920 "00 80 48", was der direkte Verweis auf "Hallo Welt" ist).

    Das scheint bei 32-bit so einfach zu sein, bei der 64-bit Ausgabe aber um so schwieriger, den Adressaufruf da raus zu filtern.

    Ich fasse noch einmal zusammen: Was in 64-bit "48 8D 05 BC 6A 08 00" ist, scheint in 32-bit das zu sein "C7 45 F4 00 80 48 00". Wie kommt man von dem einen auf das andere? Beides sind die Verweise auf 0x488000.
    Das sind unterschiedliche Befehle - in der x86-64-Version hast du lea und in x86 mov. Der Maschinencode muss bei den beiden Architekturen nicht exakt aus den gleichen Befehlen bestehen - es gibt (fast) immer verschiedene Möglichkeiten, wie man eine Aufgabe ausdrücken kann.

    Nachtrag: aHalloWelt ist nur ne Hilfestellung von IDA - IDA hat da gesehen, dass die Adresse auf den String Hallo Welt zeigt und stellt das dann für bessere Übersichtlichkeit so dar. Das machen auch andere Disassembler und co. wie Ghidra genauso. Wenn du die Korrespondenz zwischen Assembler und C++-Code besser verstehen willst, kann ich übrigens Ghidra empfehlen - das hat nen Decompiler, der dir neben dem Assembler eine Rekonstruktion vom C++-Code anzeigt und hat auch nen synchronisierten Cursor, man kann also sehen, wo genau man jeweils ist. Cutter kann das über das Ghidra-Plugin auch - da ist dann die Benutzeroberfläche etwas einfacher für den Einstieg.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „nafets“ ()