Archiv verlassen und diese Seite im Standarddesign anzeigen : PE Exe File Format
Delta 38
16.04.2011, 14:49
Hallo, ich hab Probleme mit dem PE Exe File Format.
Also soweit ich den Aufbau verstanden habe:
DOS Header
PE Header
Optional Header
Section Array
So ist der Aufbau einer PE Exe (?).
Die Adresse vom PE Header steht an der Stelle 0x3c in der Datei (in meinem Fall steht an der Stelle 0x3c : 1001 (Word), also fängt der PE-Header an der Fileadresse 0x0110 an (Little-Endian Format)
Die Adresse kommt auch hin, an der Stelle 0x0110 stehen die Magic Bytes PE (0x50, 0x40)
Der optionale Header (naja optional ist er ja nicht gerade) startet laut Dokumentation an der Adresse (PE_Adresse + 0x18) also in meinem Fall an der Adresse 0x128 ( 0x110 + 0x18)
kommt auch hin ( Magic Bytes 0x10b entsprechen einer PE32 Executable.
Soweit, so gut.
Weiter im Takt:
Laut Dokumentation befindet sich der Entry point des Programms an Adresse_optional_Header (hier 0x128) + 16 Bytes (also + 0x10)
nach meiner Rechnung ergibt die Adresse des Entry Points also:
0x138 DWORD (also 4 Bytes länge)
Die Werte die sich im Hex Editor ablesen lässt ist:
00 62 1F 00
___________________________
(Aber siehe Little-Endian, also:)
00 1F 62 00
So, bedeutet dass, das an der Adresse 0x1F6200 die erste Anweisung steht, die ausgeführt?
Schnell nachgeschaut-> nein (erste Anweisung bei mir
E8 5F 06 00
Diese Werte stehen allerdings an der Adresse
0x1F5600.
Jetzt meine Frage:
Wie komme ich auf die Adresse des Entry Points in dem File, also so dass ich auch die ersten Bytes des Programms verändern kann? (also nur als Beispiel die erste Anweisung in dem Program in C3 ändern, also ein return)
Ich hoffe ich habe mein Problem klar und deutlich geschildert und ihr könnt mit meinen Schritten etwas anfangen ;)
Gruß Delta
Wenn du die Spezifikation, nach der du vorgehst und die Exe, die du modifizieren willst, hier angibst, dann könnte man sich das mal anschauen. Sonst ist das ja nur ein ins Blaue Raten.
An der Stelle Optional_Header + 16 Bytes steht das Feld AddressOfEntryPoint, welches den virtuellen Einsprungspunkt angibt (relativ zu ImageBase). Das nächste Feld BaseOfCode gibt die virtuelle Adresse an, an die die .text-Section (welche den Code enthält) geladen wird. Wären AddressOfEntryPoint und BaseOfCode identisch, würde direkt zum Anfang der .text-Section gesprungen werden (bei einigen FASM-Beispielen ist das so, z.B. "Hello.exe").
Die Adresse der .text-Section findet du im Section-Header, in dem alle Sections beschrieben werden:
Offset
Größe
Feld
0
8
Name
8
4
VirtualSize
12
4
VirtualAddress
16
4
SizeOfRawData
20
4
PointerToRawData
Hier ist wohl das letzte Feld für dich interessant. Dieser Zeiger gibt den Anfang der Section an. Die .text-Section wird also zur Stelle BaseOfCode geladen und dann wird zur Stelle AddressOfEntryPoint gesprungen. Folglch müsstest du nur AddressOfEntryPoint - BaseOfCode rechnen um das Offset relativ zur .text-Section zu erhalten. Dieses dann zu PointerToRawData addieren und du dürftest, sofern ich mich nicht irre, die Adresse des ersten auszuführenden Befehls in der Datei haben.
freundliche Grüße, Rolus
Delta 38
18.04.2011, 16:43
Ok, erstmal danke für die Antworten.
Ich hab allerdings noch ein paar Verständnisfragen:
1.
welches den virtuellen Einsprungspunkt angibt (relativ zu ImageBase).
Also das die Angabe relativ ist, meint nur, dass der "echte" Einstiegspunkt in [Stelle im Arbeitspeicher] + VEP ( Virtueller Einstiegspunkt) ?
2. Nach dem VEP (DWORD) kommt die Adresse, an die später der Code geladen wird (auch DWORD), dh:
0 VEP = 7
1 BOC = 5
2 ptrToRawData = 6
3 ....
4 ....
5 .text_section_start
6 ....
7 ....
8 erster Befehl
BaseOfCode (BOC) enthält also eine virtuelle Adresse, die dem Anfang der .text Section enthält? Oder was ist mit
Das nächste Feld BaseOfCode gibt die virtuelle Adresse an, an die die .text-Section (welche den Code enthält) geladen wird. gemeint?
ptrToRawData enthält ... eine virtuelle Adresse, die den Anfang des wirklichen Codes beeinhaltet? Also z.B.
0 VEP = 7
1 BOC = 5
2 ptrToRawData = 8
3 ....
4 ....
5 .text_section_start
6 .test_section1_start
7 .test_section2_start
8 --- Ab hier stehen Befehle ----
9 noch ein Befehl
10 EP -> erster Befehl
Aber warum sagt man nicht einfach ptrToRawData of .text (Code Section) = EP? Das würde einem doch Arbeit ersparen.
3. Warum gibt es eigentlich Sektionen in der Exe? wäre es nicht ... einfacher, auch für den Loader, das C(opy)O(f)M(emory) zu benutzten?
(Aber war das nicht irgendwie auf 64KB begrenzt?)
Um diese Grenze zu umgehen wurde doch die Segmentierung eingeführt, kennst du/irgendjemand eine gute Seite, wo die Idee der Segmentierung umfangreich und verständlich erklärt wird?
Ansonsten danke für eure Hilfe, den Entry Point kann ich jetzt auslesen ;)
Gruß Delta
Also das die Angabe relativ ist, meint nur, dass der "echte" Einstiegspunkt in [Stelle im Arbeitspeicher] (CS?) + VEP ( Virtueller Einstiegspunkt) ?
Mit der echten, physischen Adresse im Arbeitsspeicher hast du gar nichts zu tun. Das Betriebssystem verwendet virtuelle Speicherverwaltung (http://de.wikipedia.org/wiki/Virtuelle_Speicherverwaltung). Die Adressen, mit denen deine Programme laufen sind reine Fantasieadressen. Und es sind häufig auch die gleichen virtuellen Adressen. Unter Windows wird eigentlich jede EXE an die virtuelle Adresse 0x00400000 (= 4MB = ImageBase) geladen. Dabei dürfte klar sein, dass an dieser Adresse nicht mehrere Programme gleichzeitig liegen können. Aber die Adresse ist eben virtuell und wird erst auf Hardware-Ebene von der Memory Management Unit (MMU) in eine physische Adresse umgewandelt. Dadurch hat quasi jeder Prozess einen ganzen Adressraum für sich.
BaseOfCode (BOC) enthält also eine virtuelle Adresse, die dem Anfang der .text Section enthält.
Richtig. Sehr häufig ist das 0x1000, natürlich zzgl. ImageBase. An der virtuellen Adresse 0x00401000 stehen also die Befehle. Der Einstiegspunkt (d.h. in C die Main-Funktion) kommt in der Regel erst später, da vorher z.B. eigene Funktionen definiert wurden oder Hilfsfunktionen der verwendeten Bibliothek stehen.
ptrToRawData enthält ... eine virtuelle Adresse, die den Anfang des wirklichen Codes beeinhaltet?
Nein, ptrToRawData enthält eine Adresse die sich direkt auf die Datei bezeiht. Die gibt nur an, wo die einzelnen Sektionen in der Datei zu finden sind. Diese Sektionen werden dann in den virtuellen Speicher kopiert. Der Loader kann die Sektionen im Speicher durchaus auch anders anordnen als es in der Datei der Fall war.
Warum gibt es eigentlich Sektionen in der Exe? wäre es nicht ... einfacher, auch für den Loader, das C(opy)O(f)M(emory) zu benutzten?
Unterschiedliche Sektionen werden für unterschiedliche Zwecke genutzt und können unterschiedliche Privilegien haben. Zum Beispiel kann es wünschenswert sein, dass die Code-Sektion nur lesbar ist. Oder dass eine Datensektion auch ausführbar ist ... Auch initialisierte und nicht-initialisierte Daten-Sektionen werden unterschieden.
(Aber war das nicht irgendwie auf 64KB begrenzt?)
Um diese Grenze zu umgehen wurde doch die Segmentierung eingeführt, kennst du/irgendjemand eine gute Seite, wo die Idee der Segmentierung umfangreich und verständlich erklärt wird?
Die alte Segmentierung auf 16-bit System ist auch überholt. Mittlerweile arbeiten die Betriebssysteme mit virtueller Speicherverwaltung (s.o.), d.h. Paging (http://de.wikipedia.org/wiki/Paging). Beim Paging kommt zwar auch noch eine Art der Segmentierung zur Anwendung, aber anders als bei 16-bit Systemen. Andere Quellen dazu kenne ich jetzt auch nicht, aber es schadet sicherlich nicht, sich für den Anfang die Artikel bei Wikipedia dazu mal durchzulesen.
PS: Dieser Artikel (http://www.lowlevel.eu/wiki/Paging) über Paging ist auch interessant. Aber auch deutlich technischer, da er sich an (Hobby-)OS-Entwickler richtet.
freundliche Grüße, Rolus
Delta 38
18.04.2011, 19:39
danke für die ausfürlichen Infos :)
Also legt Windows praktisch für jedes Programm einen eigenen virtuellen Speicher an?
(Hätte also jedes Programm 4GB virtuellen Adressraum zu Verfügung?)
Richtig. Sehr häufig ist das 0x1000, natürlich zzgl. ImageBase. An der virtuellen Adresse 0x00401000 stehen also die Befehle. Der Einstiegspunkt (d.h. in C die Main-Funktion) kommt in der Regel erst später, da vorher z.B. eigene Funktionen definiert wurden oder Hilfsfunktionen der verwendeten Bibliothek stehen.
Nein, ptrToRawData enthält eine Adresse die sich direkt auf die Datei bezeiht. Die gibt nur an, wo die einzelnen Sektionen in der Datei zu finden sind. Diese Sektionen werden dann in den virtuellen Speicher kopiert. Der Loader kann die Sektionen im Speicher durchaus auch anders anordnen als es in der Datei der Fall war.
Achso, BaseOfCode zeigt auf den Anfang der .text Sektion in dem virtuellen Adressraum (bzw. legt diese fest) und der Pointer zu den RawDatas ist nur für den WindowsLoader intern gedacht, damit er weiß, wo die Dateien in der Datei anfangen, damit er die dann anordnen kann.
EDIT:
Würde Adresse des EntryPoints + ImageBase den EntryPoint im geladenen Zustand anzeigen?
(bzw. ImageBase + BaseOfCode + ( EntryPoint - BaseOfCode ) )
Also, ich meine wenn die Exe gestartet wurde und im Speicher liegt.
Segmentierung:
Die Segmentierung ist, soweit ich das bis jetzt durchblicke, das Ergebnis eines Optimierungsversuches der Speicheradressierung. Da durch die 16Bit Register aber nur Adressen von 0 - 2^16 möglich waren ( und 64KB sind echt nicht viel, wenn man sich die heutigen Programme anschaut :D ) brauchte man eine neue Methode der Adressierung, denn mit der verdrahtung der 16Bit Register konnte man zwar 4GBRAM (Hardware) drin haben, aber genutzt werden können sowieso nur 64KB, da der Rest nicht adressierbar war.
Also entschloss man sich die Segmentregister einzuführen.
Die Adressierung erfolgte nun mittels Segmentregister:Offset.
Um zum Beispiel die Adresse des aktuellen Befehls zu adressieren ist die Angabe:
CS:IP möglich.
So... ich versuchs mal weiter.
Der Speicher wird in Segmente unterteilt, die wiederum in 65.536 Paragraphen á 16 Byte aufgeteilt werden.
16*65.536 = 1MByte ( = Länge eines Segmentes )
Die Segmentregister bekommen als Adresse immer den Anfang eines Segmentes und mittels der Offsetadresse kann dann in dem Segment gelesen/geschrieben/operiert werden.
In neueren Computer befinden sich oft 32Bit Register, das heißt, die Segmentregister entfallen, denn mit 32Bit lassen sich 2^32Bytes adressieren = 4GB.
Die Methode des Paging stellt eine Lösung zu dem Problem dar, dass so gut wie nie der virtuelle Speicher 1:1 auf den physikalischen Speicher abgebildet werden kann.
So, ich hoffe ihr verzeiht meine langen Ausführungen, aber wenn ich nochmal aufschreibe wird mir oft vieles klarer :D
(Außerdem könnt ihr mir jetzt sagen ob ich und vor allem was ich falsch gedacht habe )
Gruß Delta
Also legt Windows praktisch für jedes Programm einen eigenen virtuellen Speicher an?
(Hätte also jedes Programm 4GB virtuellen Adressraum zu Verfügung?)
Ja, theoretisch. Angefordert muss der Speicher trotzdem noch werden (z.B. mittels malloc() in C). Dem Betriebssystem steht es frei, Anfragen abzulehnen. Im Normalfall rückt Windows auch auf einem 4GB-System an einen Prozess nicht mehr als 2GB raus (kannst es ja mal mit einer malloc()-Schleife ausprobieren^^).
Achso, BaseOfCode zeigt auf den Anfang der .text Sektion in dem virtuellen Adressraum (bzw. legt diese fest) und der Pointer zu den RawDatas ist nur für den WindowsLoader intern gedacht, damit er weiß, wo die Dateien in der Datei anfangen, damit er die dann anordnen kann.
So ist es.
EDIT:
Würde Adresse des EntryPoints + ImageBase den EntryPoint im geladenen Zustand anzeigen?
(bzw. ImageBase + BaseOfCode + ( EntryPoint - BaseOfCode ) )
Also, ich meine wenn die Exe gestartet wurde und im Speicher liegt.
Ja, AddressOfEntryPoint + ImageBase ist der virtuelle Einsprungtpunkt.
In neueren Computer befinden sich oft 32Bit Register, das heißt, die Segmentregister entfallen, denn mit 32Bit lassen sich 2^32Bytes adressieren = 4GB.
Die Methode des Paging stellt eine Lösung zu dem Problem dar, dass so gut wie nie der virtuelle Speicher 1:1 auf den physikalischen Speicher abgebildet werden kann.
Genau, dadurch hat der Programmierer z.B. bei einem großen Array das Gefühl, dass es linear im Speicher liegt, obwohl es eventuell in mehreren Teilen im physischen Speicher liegt. Außerdem werden dadurch eventuelle Speicherauslagerungen vor dem Programmierer verborgen und Speicher-Zugriffs-Rechte können besser verwaltet werden. Jedes Programm direkt mittels Pointer im kompletten physischen Speicher operieren zu lassen, wäre keine gute Idee ...
freundliche Grüße, Rolus
Delta 38
20.04.2011, 12:47
Segmentierung:
Also wird die Segmentierung intern vom Prozessor benutzt?
Oder muss das Betriebssystem die Umsetztung von virtuell in physisch vornehmen?
Sektionen:
Das PE Format wird ja in Header und den Datensektionen aufgeteilt.
Also z.B. eine Sektion, in der nur der Code steht, in einer anderen die Variablen und in wieder einer anderen ASCII Zeichen, etc.
Diese Sektionen werden in dem Sektionsheader beschrieben:
Warum ist jetzt die Virtual Size kleiner als die RawDataSize ?
Theoretisch müsste die doch gleich sein, oder?
Gruß Delta
Also wird die Segmentierung intern vom Prozessor benutzt?
Oder muss das Betriebssystem die Umsetztung von virtuell in physisch vornehmen?
Das erledigt die Memory Management Unit (MMU).
Das PE Format wird ja in Header und den Datensektionen aufgeteilt.
Also z.B. eine Sektion, in der nur der Code steht, in einer anderen die Variablen und in wieder einer anderen ASCII Zeichen, etc.
Zumindest globale Variablen kommen in eine Datensektion. Lokale Variablen werden in der Regel auf dem Stack angelegt.
Warum ist jetzt die Virtual Size kleiner als die RawDataSize ?
Theoretisch müsste die doch gleich sein, oder?
Sektionen in der Datei müssen immer eine bestimmte Größe haben oder ein Vielfaches dieser Größe sein (siehe SectionAlignment im Optional Header). Der unnütze Rest wird dann mit 0en gefüllt. Damit diese 0en nicht mitkopiert werden müssen, gibt VirtualSize wohl nur die tatsächlich benutzte Größe der Sektion an. Der Unterschied sollte aber in der Regel nicht allzu groß sein.
freundliche Grüße, Rolus
Delta 38
25.04.2011, 14:44
Ok, danke Rolus :gratz
Also ist die virtuelle Speicheradressierung von der CPU gegeben, wenn die MMU die Übersetztung vornimmt?
Also z.b. wenn beim Start die BIOS Routinen einen 512Byte großen Sektor ( Bootsektor ) nach 0x7C0 lädt. Ist das nur eine virtuelle Adresse oder eine reale, physische Adresse?
Zumindest globale Variablen kommen in eine Datensektion. Lokale Variablen werden in der Regel auf dem Stack angelegt.
Hm, heißt das man könnte entsprechend der Größe des Datesegmentes bestimmen, oder schätzen, ob der Code Objektorientiert programmiert wurde? (Weil in der OOP globale Variablen ja vermieden werden sollen :D, folglich müsste in dem Datensegment weniger Daten stehen bzw. die VirtualSize ( also die Realgröße der Daten (?) ) relativ klein sei)
Sektionen in der Datei müssen immer eine bestimmte Größe haben oder ein Vielfaches dieser Größe sein (siehe SectionAlignment im Optional Header). Der unnütze Rest wird dann mit 0en gefüllt. Damit diese 0en nicht mitkopiert werden müssen, gibt VirtualSize wohl nur die tatsächlich benutzte Größe der Sektion an. Der Unterschied sollte aber in der Regel nicht allzu groß sein.
Kann es sein, dass die Größe 512 Bytes oder ein vielfaches davon beträgt, da die Festplatte ja in 512Byte Große Sektoren unterteilt wird, und immer ein Sektor (oder Sektor:Offset, aber nicht Sektorübergreifend) von der Festplatte gelesen werden kann?
Gruß Delta 38
Powered by vBulletin® Version 4.2.2 Copyright ©2025 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.