Wie erstelle ich eine ausführbare ELF-Datei in Linux mit einem Hex-Editor?

7

Nur neugierig. Dies ist offensichtlich keine sehr gute Lösung für die eigentliche Programmierung, aber ich wollte eine ausführbare Datei in Bless (einem Hex-Editor) machen.

Meine Architektur ist x86. Was ist ein sehr einfaches Programm, das ich machen kann? Eine Hallo Welt? Eine Endlosschleife? Ähnlich wie diese Frage, aber unter Linux.

    
Jamie 10.10.2014, 07:25
quelle

2 Antworten

8

Wie in meinem Kommentar erwähnt, schreiben Sie im Grunde Ihren eigenen Elf-Header für die ausführbare Datei und eliminieren die nicht benötigten Abschnitte. Es gibt noch einige erforderliche Abschnitte. Die Dokumentation in Muppetlabs-TinyPrograms macht einen guten Job, um diesen Prozess zu erklären. Zum Spaß, hier sind ein paar Beispiele:

Das Gegenstück zu / bin / true (45 Byte):

%Vor%

Dein Klassiker "Hallo Welt!" (160 Bytes):

%Vor%

Vergessen Sie nicht, sie ausführbar zu machen ...

    
David C. Rankin 10.10.2014, 09:55
quelle
31

Dekompiliere eine NASM Hallo Welt und verstehe jedes Byte darin

Version dieser Antwort mit einem netten Inhaltsverzeichnis und mehr Inhalt: Ссылка (hier wird das 30k Char Limit erreicht)

Standards

ELF wird vom LSB angegeben:

Das LSB verlinkt im Wesentlichen auf andere Standards mit kleineren Erweiterungen, insbesondere:

  • generisch (beide von SCO):

    • System V ABI 4.1 (1997) Ссылка , keine 64 Bit, obwohl eine magische Nummer reserviert ist dafür. Gleiches gilt für Core-Dateien.
    • System V ABI Update ENTWURF 17 (2003) Ссылка , fügt 64 hinzu Bit. Aktualisiert nur die Kapitel 4 und 5 des vorherigen Dokuments: die anderen bleiben gültig und werden weiterhin referenziert.
  • architekturspezifisch:

Eine praktische Zusammenfassung finden Sie unter:

%Vor%

Seine Struktur kann durch Programme wie readelf und objdump lesbar untersucht werden.

Generieren Sie das Beispiel

Lassen Sie uns ein minimal ausführbares x86-64-Beispiel für Linux ausführen:

%Vor%

Kompiliert mit:

%Vor%

Versionen:

  • NASM 2.10.09
  • Binutils Version 2.24 (enthält ld )
  • Ubuntu 14.04

Wir benutzen kein C-Programm, da dies die Analyse verkomplizieren würde, nämlich Level 2: -)

Hexdumps

%Vor%

Ausgabe unter: Ссылка

Globale Dateistruktur

Eine ELF-Datei enthält die folgenden Teile:

  • ELF-Header. Zeigt auf die Position der Abschnitts-Header-Tabelle und der Programm-Header-Tabelle.

  • Abschnitt Header-Tabelle (optional für ausführbare Datei). Jeder hat e_shnum -Abschnitt Header, jeder auf die Position eines Abschnitts zeigen.

  • N Abschnitte mit N <= e_shnum (optional für ausführbare Dateien)

  • Programm-Header-Tabelle (nur für ausführbare Dateien). Jeder hat e_phnum -Programmköpfe, von denen jeder auf die Position eines Segments zeigt.

  • N Segmente, mit N <= e_phnum (optional für ausführbare Dateien)

Die Reihenfolge dieser Teile ist nicht behoben: die einzige feste Sache ist der ELF-Header, der das erste Ding in der Datei sein muss: Generische Dokumente sagen:

ELF-Header

Der einfachste Weg, den Header zu beobachten, ist:

%Vor%

Ausgabe unter: Ссылка

Bytes in der Objektdatei:

%Vor%

Ausführbare Datei:

%Vor%

Struktur repräsentiert:

%Vor%

Manuelle Aufteilung:

  • 0 0: EI_MAG = 7f 45 4c 46 = 0x7f 'E', 'L', 'F' : ELF magische Zahl

  • 0 4: EI_CLASS = 02 = ELFCLASS64 : 64-Bit-Elf

  • 0 5: EI_DATA = 01 = ELFDATA2LSB : große Endian-Daten

  • 0 6: EI_VERSION = 01 : Formatversion

  • 0 7: EI_OSABI (nur in 2003 Update) = 00 = ELFOSABI_NONE : keine Erweiterungen.

  • 0 8: EI_PAD = 8x 00 : reservierte Bytes. Muss auf 0 gesetzt sein.

  • 1 0: e_type = 01 00 = 1 (großes Endian) = ET_REl : verschiebbares Format

    Auf der ausführbaren Datei ist 02 00 für ET_EXEC .

  • 1 2: e_machine = 3e 00 = 62 = EM_X86_64 : AMD64-Architektur

  • 1 4: e_version = 01 00 00 00 : muss 1 sein

  • 1 8: e_entry = 8x 00 : Ausführungsadresse Einsprungpunkt, oder 0, falls nicht anwendbar wie für die Objektdatei, da es keinen Einsprungpunkt gibt.

    In der ausführbaren Datei ist b0 00 40 00 00 00 00 00 . TODO: Woran können wir das noch richten? Der Kernel scheint die IP direkt auf diesen Wert zu setzen, er ist nicht fest codiert.

  • 2 0: e_phoff = 8x 00 : Offset der Kopfzeile der Tabelle, 0 falls nicht vorhanden.

    40 00 00 00 auf der ausführbaren Datei, d. h. es beginnt unmittelbar nach dem ELF-Header.

  • 2 8: e_shoff = 40 7x 00 = 0x40 : Abschnitt Header-Tabellen-Datei Offset, 0 falls nicht vorhanden.

  • 3 0: e_flags = 00 00 00 00 TODO. Arch-spezifisch.

  • 3 4: e_ehsize = 40 00 : Größe dieses Elf-Headers. TODO warum dieses Feld? Wie kann es variieren?

  • 3 6: e_phentsize = 00 00 : Größe jedes Programmheaders, 0 falls nicht vorhanden.

    38 00 auf ausführbare Datei: es ist 56 Bytes lang

  • 3 8: e_phnum = 00 00 : Anzahl der Programmkopfeinträge, 0 falls nicht vorhanden.

    02 00 auf ausführbare Datei: Es gibt 2 Einträge.

  • 3 A: e_shentsize und e_shnum = 40 00 07 00 : Headergröße und Anzahl der Einträge

  • 3 E: e_shstrndx ( Section Header STRing iNDeX ) = 03 00 : Index des Abschnitts .shstrtab .

Abschnittskopftabelle

Array von Elf64_Shdr Strukturen.

Jeder Eintrag enthält Metadaten zu einem bestimmten Abschnitt.

e_shoff des ELF-Headers gibt die Startposition, hier 0x40.

e_shentsize und e_shnum aus dem ELF-Header sagen, dass wir 7 Einträge haben, jeder 0x40 Bytes lang.

Also nimmt die Tabelle Bytes von 0x40 bis 0x40 + 7 + 0x40 - 1 = 0x1FF.

Einige Abschnittsnamen sind bestimmten Abschnittstypen vorbehalten: Ссылка z .text erfordert einen SHT_PROGBITS type und SHF_ALLOC + SHF_EXECINSTR

readelf -S hello_world.o :

%Vor%

struct , dargestellt durch jeden Eintrag:

%Vor%

Abschnitte

Index 0 Abschnitt

Enthalten in den Bytes 0x40 bis 0x7F.

Der erste Abschnitt ist immer magisch: Ссылка sagt:

  

Wenn die Anzahl der Abschnitte größer oder gleich SHN_LORESERVE (0xff00) ist, hat e_shnum den Wert SHN_UNDEF (0) und die tatsächliche Anzahl der Abschnitts-Header-Tabelleneinträge ist im Feld sh_size des Abschnitts-Headers mit dem Index 0 enthalten ( Andernfalls enthält das Element sh_size des ursprünglichen Eintrags 0).

Es gibt auch andere magische Abschnitte in Figure 4-7: Special Section Indexes .

SHT_NULL

In Index 0 ist SHT_NULL obligatorisch. Gibt es andere Verwendungen dafür? What is die Verwendung des Abschnitts SHT_NULL in ELF? ?

.data-Abschnitt

.data ist Abschnitt 1:

%Vor%
  • 80 0: sh_name = 01 00 00 00 : Index 1 in der .shstrtab Zeichenkettentabelle

    Hier bedeutet 1 , dass der Name dieses Abschnitts mit dem ersten Zeichen dieses Abschnitts beginnt und mit dem ersten NUL-Zeichen endet, das die Zeichenfolge .data bildet.

    .data ist einer der Abschnittsnamen, der eine vordefinierte Bedeutung Ссылка hat

      

    Diese Abschnitte enthalten initialisierte Daten, die zum Speicherabbild des Programms beitragen.

  • 80 4: sh_type = 01 00 00 00 : SHT_PROGBITS : Der Inhalt der Sektion wird nicht von ELF spezifiziert, sondern nur wie das Programm sie interpretiert. Normal seit einem .data -Abschnitt.

  • 80 8: sh_flags = 03 7x 00 : SHF_ALLOC und SHF_EXECINSTR : Ссылка , wie von einer .data Sektion

  • benötigt
  • 90 0: sh_addr = 8x 00 : In welcher virtuellen Adresse wird der Abschnitt während der Ausführung platziert, 0 , falls nicht platziert

  • 90 8: sh_offset = 00 02 00 00 00 00 00 00 = 0x200 : Anzahl der Bytes vom Start des Programms bis zum ersten Byte in diesem Abschnitt

  • a0 0: sh_size = 0d 00 00 00 00 00 00 00

    Wenn wir 0xD Bytes beginnend mit sh_offset 200 nehmen, sehen wir:

    %Vor%

    AHA! Also ist unsere "Hello world!" -Zeichenkette in der Datensektion, wie wir ihr auf der NASM gesagt haben.

    Sobald wir hd abgeschlossen haben, sehen wir uns das an:

    %Vor%

    welche Ausgaben:

    %Vor%

    NASM legt für diesen Abschnitt vernünftige Eigenschaften fest, weil%% mag% en_de behandelt wird: Ссылка

    Beachten Sie auch, dass dies eine schlechte Abschnittswahl war: Ein guter C-Compiler würde stattdessen die Zeichenfolge in .data setzen, da sie schreibgeschützt ist und weitere Betriebssystemoptimierungen ermöglichen würde.

  • a0 8: .rodata und sh_link = 8x 0: gilt nicht für diesen Abschnittstyp. Ссылка

  • b0 0: sh_info = sh_addralign = TODO: Warum ist diese Ausrichtung notwendig? Ist es nur für 04 oder auch für Symbole in sh_addr ?

  • b0 8: sh_addr = sh_entsize = Der Abschnitt enthält keine Tabelle. Wenn! = 0, bedeutet dies, dass der Abschnitt eine Tabelle mit festen Größeneinträgen enthält. In dieser Datei sehen wir von der Ausgabe 00 , dass dies für die Abschnitte readelf und .symtab der Fall ist.

.text-Abschnitt

Nachdem wir nun einen Abschnitt manuell erstellt haben, lassen Sie uns graduieren und verwenden Sie .rela.text der anderen Abschnitte.

%Vor%

readelf -S ist ausführbar, aber nicht beschreibbar: wenn wir versuchen, Linux-Defaults darauf zu schreiben. Mal sehen, ob wir wirklich etwas Code haben:

%Vor%

gibt:

%Vor%

Wenn wir .text auf b8 01 00 00 grepsen, sehen wir, dass dies nur bei hd auftritt, was der Abschnitt sagt. Und die Größe ist 27, was auch passt. Also müssen wir über den richtigen Abschnitt sprechen.

Das sieht nach dem richtigen Code aus: a 00000210 gefolgt von write .

Der interessanteste Teil ist die Zeile exit , die folgendes bewirkt:

%Vor%

, um die Adresse der Zeichenfolge an den Systemaufruf zu übergeben. Momentan ist a nur ein Platzhalter. Nachdem die Verknüpfung erfolgt ist, wird sie wie folgt geändert:

%Vor%

Diese Änderung ist aufgrund der Daten des Abschnitts 0x0 möglich.

SHT_STRTAB

Abschnitte mit .rela.text heißen Zeichenkettentabellen .

Sie enthalten ein null-getrenntes Array von Strings.

Solche Abschnitte werden von anderen Abschnitten verwendet, wenn String-Namen verwendet werden sollen. Der Verwendungsabschnitt sagt:

  • welche String-Tabelle sie verwenden
  • Was ist der Index für die Ziel-String-Tabelle, wo der String beginnt

Zum Beispiel könnten wir eine String-Tabelle haben, die folgendes enthält: TODO: Muss es mit sh_type == SHT_STRTAB beginnen?

%Vor%

Und wenn ein anderer Abschnitt den String d e f verwenden möchte, muss er auf den Index 5 dieses Abschnitts verweisen (Buchstabe d ).

Bemerkenswerte String-Tabellenabschnitte:

  • .shstrtab
  • .strtab

.shstrtab

Abschnittstyp: sh_type == SHT_STRTAB .

Allgemeiner Name: Abschnittsheader-Zeichenfolgentabelle .

Der Abschnittsname .shstrtab ist reserviert. Der Standard sagt:

  

Dieser Abschnitt enthält Bereichsnamen.

Auf diesen Abschnitt zeigt das Feld e_shstrnd des ELF-Headers selbst.

String-Indizes dieses Abschnitts werden durch das sh_name -Feld der Abschnittsheader angegeben, die Zeichenfolgen bezeichnen.

In diesem Abschnitt ist SHF_ALLOC nicht markiert, daher wird es im ausführenden Programm nicht angezeigt.

%Vor%

Gibt:

%Vor%

Die Daten in diesem Abschnitt haben ein festes Format: Ссылка

Wenn wir uns die Namen anderer Abschnitte ansehen, sehen wir, dass sie alle Nummern enthalten, z. Der Abschnitt .text ist die Nummer 7 .

Dann endet jede Kette, wenn das erste NUL-Zeichen gefunden wird, z. Zeichen 12 ist .textsh_type == SHT_SYMTAB direkt nach sh_link .

.symtab

Abschnittstyp: 5 .

Allgemeiner Name: Symboltabelle .

Zuerst notieren wir:

  • sh_info = 6
  • SHT_SYMTAB = .strtab

Für .rela.text Abschnitte bedeuten diese Zahlen:

  • Zeichenfolgen, die Symbolnamen enthalten, befinden sich in Abschnitt 5, ELF64_R_TYPE == STT_FILE
  • die Umzugsdaten sind in Abschnitt 6, ELF64_R_TYPE

Ein gutes High-Level-Werkzeug zum Zerlegen dieses Abschnitts ist:

%Vor%

was ergibt:

%Vor%

Dies ist jedoch eine Ansicht auf hoher Ebene, die einige Arten von Symbolen auslässt und in denen die Symboltypen enthalten sind. Eine detailliertere Demontage kann erhalten werden mit:

%Vor%

was ergibt:

%Vor%

Das Binärformat der Tabelle ist in Ссылка

Die Daten sind:

%Vor%

Was gibt:

%Vor%

Die Einträge sind vom Typ:

%Vor%

Wie in der Abschnittstabelle ist der erste Eintrag magisch und auf feste Werte ohne Bedeutung gesetzt.

STT_FILE

Eintrag 1 hat st_info . st_name wird innerhalb von 01000000 fortgesetzt.

Byte-Analyse:

  • 10 8: .strtab = hello_world.asm = Zeichen 1 in st_info , was bis zum nächsten 04 ELF64_R_TYPE

    macht

    Dieser Teil der Informationsdatei kann vom Linker verwendet werden, um zu entscheiden, welche Segmentabschnitte gehen.

  • 10 12: 4 = STT_FILE

    Bits 0-3 = st_name = Typ = ELF64_ST_BIND = 0 : Hauptzweck dieses Eintrags ist es, mit STB_LOCAL den Namen der Datei anzugeben, die diese Objektdatei erzeugt hat.

    Bits 4-7 = STT_FILE = Bindung = st_shndx = f1ff . Erforderlicher Wert für SHN_ABS .

  • 10 13: STT_FILE = Kopfzeile der Symboltabelle Abschnitt Index = st_value = 00 . Erforderlich für STT_FILE .

  • 20 0: st_size = 8x 00 : erforderlich für den Wert für readelf

  • 20 8: .data = 8x .text : keine zugewiesene Größe

Jetzt von der 1 interpretieren wir die anderen schnell.

STT_SECTION

Es gibt zwei solche Einträge, von denen einer auf 2 und der andere auf hello_world zeigt (Abschnittsindizes .data und _start ).

%Vor%

TODO Was ist ihr Zweck?

STT_NOTYPE

Dann kommen die wichtigsten Symbole:

%Vor%

GLOBAL string befindet sich im Abschnitt hello_world_len (Index 1). Sein Wert ist 0: Er zeigt auf das erste Byte dieses Abschnitts.

st_shndx == SHN_ABS == 0xF1FF ist mit 0xF1FF visibility markiert, seit wir geschrieben haben:

%Vor%

in NASM. Dies ist notwendig, da es als Einstiegspunkt angesehen werden muss. Im Gegensatz zu C sind NASM-Labels standardmäßig lokal.

SHN_ABS

st_value == 0xD == 13 zeigt auf das spezielle Hello World! .

hello_world_len wurde gewählt, um nicht mit anderen Abschnitten in Konflikt zu geraten.

SHN_ABS Dies ist der Wert, den wir dort in der Assembly gespeichert haben: die Länge der Zeichenkette .symtab .

Dies bedeutet, dass die Verlagerung diesen Wert nicht beeinflusst: Es ist eine Konstante.

Dies ist eine kleine Optimierung, die unser Assembler für uns erledigt und die ELF-Unterstützung bietet.

Wenn wir die Adresse von objcopy irgendwo benutzt hätten, wäre der Assembler nicht in der Lage gewesen, sie als sh_type == SHT_STRTAB zu markieren, und der Linker hätte später zusätzliche Verschiebungsarbeiten daran.

SHT_SYMTAB auf der ausführbaren Datei

Standardmäßig legt NASM auch eine sh_link == 5 auf die ausführbare Datei.

Dies wird nur zum Debuggen verwendet. Ohne die Symbole sind wir völlig blind und müssen alles zurückentwickeln.

Sie können es mit .symtab entfernen, und die ausführbare Datei wird weiterhin ausgeführt. Solche ausführbaren Dateien heißen abgestreifte ausführbare Dateien .

.strtab

Enthält Strings für die Symboltabelle.

Dieser Abschnitt enthält sh_type == SHT_RELA .

Darauf wird von .rela.text des Abschnitts sh_info verwiesen.

%Vor%

Gibt:

%Vor%

Dies bedeutet, dass es eine Beschränkung der ELF-Ebene ist, dass globale Variablen keine NUL-Zeichen enthalten dürfen.

.rela.text

Abschnittstyp: 6 .

Allgemeiner Name: Umzugsabschnitt .

.symtab enthält Verschiebungsdaten, die angeben, wie die Adresse geändert werden soll, wenn die endgültige ausführbare Datei verknüpft wird. Dies zeigt auf Bytes des Textbereichs, der geändert werden muss, wenn die Verknüpfung auf die richtigen Speicherorte verweist.

Im Grunde übersetzt es den Objekttext, der die Platzhalteradresse 0x0 enthält:

%Vor%

zum eigentlichen ausführbaren Code, der die letzte 0x6000d8 enthält:

%Vor%

Darauf wurde von readelf -r hello_world.o = struct des Abschnitts r_offset hingewiesen.

.text gibt:

%Vor%

Der Abschnitt existiert nicht in der ausführbaren Datei.

Die tatsächlichen Bytes sind:

%Vor%

Das dargestellte r_info ist:

%Vor%

Also:

  • 370 0: ELF64_R_TYPE = 0xC: Adresse in die ELF64_R_SYM , deren Adresse bei dieser Verschiebung geändert wird

  • 370 8: .data = 0x200000001. Enthält 2 Felder:

    • 1 = 0x1: Bedeutung hängt von der genauen Architektur ab.
    • R_X86_64_64 = 0x2: Index des Abschnitts, auf den die Adresse zeigt, also S + A , das auf Index 2 steht.

    Der AMD64 ABI sagt, dass der Typ S 0 heißt und dass er die Operation 00 00 00 00 00 00 00 00 wo:

    darstellt
    • movabs Ax0,%rsi : der Wert des Symbols in der Objektdatei, hier r_added , da wir auf%%%%%%% verweisen%
    • r_addend : Der Summand, vorhanden im Feld S + A

    Diese Adresse wird zu dem Bereich hinzugefügt, in dem der Umzug stattfindet.

    Diese Umlagerungsoperation wirkt auf insgesamt 8 Bytes.

  • 380 0: .data + 0 = 0

Also in unserem Beispiel schließen wir, dass die neue Adresse sein wird: ld --verbose = ld -T , und damit die erste Sache in der Datenabteilung.

Programm-Header-Tabelle

Erscheint nur in der ausführbaren Datei.

Enthält Informationen darüber, wie die ausführbare Datei in den virtuellen Prozessspeicher eingefügt werden sollte.

Die ausführbare Datei wird aus Objektdateien vom Linker generiert. Die Hauptaufgaben, die der Linker ausführt, sind:

  • Bestimmen Sie, welche Abschnitte der Objektdateien in welche Segmente der ausführbaren Datei gelangen.

    In Binutils kommt es darauf an, ein Linkerscript zu analysieren und eine Reihe von Standardeinstellungen zu behandeln.

    Sie können das Linkerskript mit readelf -l hello_world.out verwenden und ein benutzerdefiniertes mit e_phoff festlegen.

  • Verschieben Sie Textabschnitte. Dies hängt davon ab, wie die einzelnen Abschnitte gespeichert werden.

e_phnum gibt:

%Vor%

Auf dem ELF-Header sagten uns e_phentsize , 0x40 und 0x38 , dass es 2 Programmheader gibt, die bei p_type beginnen und jeweils 01 00 00 00 bytes lang sind, also:

%Vor%

und:

%Vor%

Struktur repräsentiert Ссылка :

%Vor%

Aufschlüsselung des ersten:

  • 40 0: PT_LOAD = p_flags = 05 00 00 00 : TODO. Ich denke, es bedeutet, dass es tatsächlich in den Speicher geladen wird. Andere Typen müssen nicht unbedingt sein.
  • 40 4: p_offset = 00 = Ausführen und Lesen von Berechtigungen, keine Schreib-TODO
  • 40 8: gcc -Wl,-Ttext-segment=0x400030 hello_world.c = 8x p_vaddr TODO: Was ist das? Sieht wie Offsets vom Anfang der Segmente aus aus. Aber das würde bedeuten, dass einige Segmente miteinander verflochten sind? Es ist möglich, ein bisschen damit zu spielen: 00 00 40 00 00 00 00 00
  • 50 0: p_paddr = 00 00 40 00 00 00 00 00 : Anfangsadresse des virtuellen Speichers, um dieses Segment nach
  • zu laden
  • 50 8: p_vaddrr = p_filesz : erste physische Adresse, die in den Speicher geladen werden soll. Nur für Systeme, in denen das Programm seine physikalische Adresse festlegen kann. Ansonsten, wie in System V wie Systeme, kann alles sein. NASM scheint nur d7 00 00 00 00 00 00 00 zu kopieren
  • 60 0: p_memsz = p_memsz : TODO vs d7 00 00 00 00 00 00 00
  • 60 8: p_align = 00 00 20 00 00 00 00 00 : TODO
  • 70 0: readelf = .text : 0 oder 1 bedeuten keine Ausrichtung erforderlich TODO was bedeutet das? sonst redundant mit anderen Feldern

Die zweite ist analog.

Dann das:

%Vor% Der

-Abschnitt von .data sagt uns Folgendes:

  • 0 ist das %code% -Segment. Aha, deshalb ist es ausführbar und nicht beschreibbar
  • 1 ist das %code% -Segment.
quelle

Tags und Links