Das Executable and Linking Format (kurz ELF) ist ein Dateiformat für ausführbare Dateien. Es gibt 3 verschiedene Arten von ELF-Dateien: * executable * relocatable * shared object == Aufbau == === ELF-Header === Der ELF-Header ist wie folgt aufgebaut. {| border="1" ! Name || Offset || Größe || Beschreibung |- | [[#e_ident | e_ident]] || 0x00 || 16 || Enthält Daten zur Identifikation |- | [[#e_type | e_type]] || 0x10 || 2 || Enthält den ELF-Typ |- | [[#e_machine | e_machine]] || 0x12 || 2 || Beschreibt die benötigte Architektur |- | [[#e_version | e_version]] || 0x14 || 4 || Version |- | [[#e_entry | e_entry]] || 0x18 || 4 || Entrypoint |- | [[#e_phoff | e_phoff]] || 0x1C || 4 || Das Offset des Programm-Headers |- | [[#e_shoff | e_shoff]] || 0x20 || 4 || Das Offset des Section-Headers |- | [[#e_flags | e_flags]] || 0x24 || 4 || Verschiedene Flags |- | [[#e_ehsize | e_ehsize]] || 0x28 || 2 || Größe des ELF-Headers |- | [[#e_phentsize | e_phentsize]] || 0x2A || 2 || Größe eines Eintrags im Programm-Header |- | [[#e_phnum | e_phnum]] || 0x2C || 2 || Anzahl an Einträgen im Programm-Header |- | [[#e_shentsize | e_shentsize]] || 0x2E || 2 || Größe eines Eintrags im Section-Header |- | [[#e_shnum | e_shnum]] || 0x30 || 2 || Anzahl an Einträgen im Section-Header |- | [[#e_shstrndx | e_shstrndex]] || 0x32 || 2 || Assoziiert einen Eintrag im Section-Header mit der String-Tabelle |} ==== e_ident ==== Das Feld e_ident ist 16 Byte groß und enthält Werte zur Identifikation der ELF-Datei. Dies sind die Indexes: {| border="1" ! Name || Wert || Beschreibung |- | [[#EI_MAG0 | EO_MAG0 bis EI_MAG3]] || 0 || Datei-Identifikation |- | [[#EI_MAG1 | EI_MAG0 bis EI_MAG3]] || 1 || Datei-Identifikation |- | [[#EI_MAG2 | EI_MAG0 bis EI_MAG3]] || 2 || Datei-Identifikation |- | [[#EI_MAG3 | EI_MAG0 bis EI_MAG3]] || 3 || Datei-Identifikation |- | [[#EI_CLASS | EI_CLASS]] || 4 || Datei-Klasse |- | [[#EI_DATA | EI_DATA]] || 5 || Prozessorspezifische Datenkodierung |- | [[#EI_VERSION | EI_VERSION]] || 6 || Datei-Version |- | [[#EI_PAD | EI_PAD]] || 7 || Letztes genutztes Byte in e_ident |- | EI_NIDENT || 16 || Anzahl an Bytes in e_ident |} ===== EI_MAG0 bis EI_MAG3 ===== Diese Bytes werden zur Datei-Identifikation benutzt. {| border="1" ! Name || Wert || Position |- | ELFMAG0 || 0x7f || e_ident[EI_MAG0] |- | ELFMAG1 || 'E' || e_ident[EI_MAG1] |- | ELFMAG2 || 'L' || e_ident[EI_MAG2] |- | ELFMAG3 || 'F' || e_ident[EI_MAG3] |} ===== EI_CLASS ===== Die Klasse gibt unter anderen an, wie viel Bits zur Adressierung verwendet werden. Also eine 32Bit-Maschine sollte überprüfen, ob der Code auch wirklich auf 32Bit-Maschinen geht. {| border="1" ! Name || Wert || Beschreibung |- | ELFCLASSNONE || 0 || Ungültige Klasse |- | ELFCLASS32 || 1 || 32bit-Objekte |- | ELFCLASS64 || 2 || 64bit-Objekte |} ===== EI_DATA ===== Das Byte e_ident[EI_DATA] gibt an, in welchen Format Daten gespeichert werden, Little-Endian oder Big-Endian {| border="1" ! Name || Wert || Beschreibung |- | ELFDATANONE || 0 || Ungültig |- | ELFDATA2LSB || 1 || Little-Endian |- | ELFDATA2MSB || 2 || Big-Endian |} ===== EI_VERSION ===== Hier sollte das gleiche wie in e_version drinstehen, also EV_CURRENT ===== EI_PAD ===== Alle Bytes in e_ident ab EI_PAD (inklusive) werden nicht benutzt und sollten Null gesetzt sein. ===== Architektur-Identifikation ===== Für einen 32bit Intel-Prozessor sollten folgende Felder folgende Werte haben: {| border="1" ! Feld || Wert |- | e_ident[EI_CLASS] || ELFCLASS32 |- | e_ident[EI_DATA] || ELFDATA2LSB |- | e_machine || EM_386 |} ==== e_type ==== Dieses Feld beschreibt, welches ELF-Format benutzt wird: {| border="1" ! Name || Wert || Beschreibung |- | ET_NONE || 0 || Kein Typ |- | ET_REL || 1 || Kein Typ |- | ET_EXEC || 2 || Kein Typ |- | ET_DYN || 3 || Kein Typ |- | ET_CORE || 4 || Kein Typ |- | ET_LOPROC || 0xff00 || Kein Typ |- | ET_HIPROC || 0x00ff || Kein Typ |} Für eine ausführbare ELF-Datei, müsste hier ET_EXEC stehen. ==== e_machine ==== Wie oben schonmal erwähnt, steht hier der Prozessor-Typ drin. {| border="1" ! Name || Wert || Beschreibung |- | EM_NONE || 0 || Kein Typ |- | EM_M32 || 1 || AT&T WE 32100 |- | EM_SPARC || 2 || SPARC |- | EM_386 || 3 || Intel 80386 |- | EM_68K || 4 || Motorola 68000 |- | EM_88K || 5 || Motorola 88000 |- | EM_860 || 7 || Intel 80860 |- | EM_MIPS || 8 || MIPS RS3000 |} ==== e_version ==== Beschreibt die Aktuelle ELF-Version {| border="1" ! Name || Wert || Beschreibung |- | EV_NONE || 0 || Ungültige Version |- | EV_CURRENT || 1 || Aktuelle Version |} ==== e_entry ==== Virtuelle Adresse des Entrypoints ==== e_phoff ==== Offset in der Datei, an dem die Programm-Header steht. Wenn die Datei keinen Programm-Header enthält, steht hier 0. ==== e_shoff ==== Offset in der Datei, an dem der Section-Header steht. Wenn die Datei keinen Section-Header enthält, steht hier 0. ==== e_flags ==== Dieses Feld enthält Prozessorspezifische Flags. ==== e_ehsize ==== Größe des ELF-Headers. ==== e_phentsize ==== Dieses Feld gibt an, wie groß ein Eintrag im Programm-Header ist. ==== e_phnum ==== Gibt an, wie viele Einträge im Programm-Header enthalten sind. Das Produkt von e_phentsize und e_phnum ergibt die insgesamte Größe des Programm-Headers. Wenn die Datei keinen Programm-Header hat, steht hier 0. ==== e_shentsize ==== Dieses Feld gibt an, wie groß ein Eintrag im Section-Header ist. ==== e_shnum ==== Gibt an, wie viele Einträge im Section-Header enthalten sind. Das Produkt von e_shentsize und e_shnum ergibt die insgesamte Größe des Section-Headers. Wenn die Datei keinen Section-Header hat, steht hier 0. ==== e_shstrndx ==== Gibt an, welcher Eintrag im Section-Header mit der String-Tabelle verknüpft wird. === Programm-Header === Der Programm-Header besteht aus e_phnum Einträgen, wobei jeder e_phentsize groß ist. Der Programm-Header ist also insgesamt e_phnum*e_phentsize Bytes groß. Im Programm-Header stehen z.B der Text- oder Daten-Teil eines Programms. Ein Eintrag im Programm-Header ist wie folgt aufgebaut: {| border="1" ! Name || Offset || Größe || Beschreibung |- | [[#p_type | p_type]] || 0x00 || 4 || Typ des Segments |- | [[#p_offset | p_offset]] || 0x04 || 4 || Dateioffset, an dem das Segment steht |- | [[#p_vaddr | p_vaddr]] || 0x08 || 4 || Virtuelle Adresse, an die das Segment kopiert werden soll |- | [[#p_paddr | p_paddr]] || 0x0C || 4 || Physikalische Adress (unrelevant) |- | [[#p_filesz | p_filesz]] || 0x10 || 4 || Größe des Segments in der Datei |- | [[#p_memsz | p_memsz]] || 0x14 || 4 || Größe des Segments, das es im Speicher haben soll |- | [[#p_flags | p_flags]] || 0x18 || 4 || Flags |- | [[#p_align | p_align]] || 0x1C || 4 || Alignment |} ==== p_type ==== Dieses Feld gibt den Typ des Segments an, für eine ausführbare ELF-Datei ist vor allem PT_LOAD relevant. Die folgenden Beschreibungen sind auf PT_LOAD bezogen (es kann sein, dass sie auch auf andere Typen zutreffen). {| border="1" ! Name || Wert || Beschreibung |- | PT_NULL || 0 || Ungültiges Segment |- | PT_LOAD || 1 || Ladbares Segment |- | PT_DYNAMIC || 2 || Dynamisches Segment |- | PT_INTERP || 3 || Position eines 0-Terminierten Strings, der den Interpreter angibt. |- | PT_NOTE || 4 || Universelles Segment |- | PT_SHLIB || 5 || Shared Lib |- | PT_PHDIR || 6 || Gibt Position und Größe des Programm-Headers an. |- | PT_LOPROC || 0x70000000 || Reserviert |- | PT_HIPROC || 0x7fffffff || Reserviert |} Außerdem habe ich noch diese (anscheinend unoffiziellen) Werte in Linux-Header-Dateien (http://www.cs.fsu.edu/~baker/devices/lxr/http/source/linux/include/linux/elf.h#L35) gefunden. {| border="1" ! Name || Wert |- | PT_TLS || 7 |- | PT_LOOS || 0x60000000 |- | PT_HIOS || 0x6fffffff |- | PT_GNU_EH_FRAME || 0x6474e550 |- | PT_GNU_STACK || PT_LOOS+0x474e551 |} ==== p_offset ==== Offset in der Datei, an dem das Segment beginnt. ==== p_vaddr ==== Virtuelle Adresse, an die das Segment kopiert werden soll. ==== p_paddr ==== Gibt die physikalische Zieladresse an, für Systeme, für welche dies relevant ist. ==== p_filesz ==== Gibt die Größe des Segments in der Datei wieder. Kann eventuell auch 0 sein, z.B für die BSS-Sektion ==== p_memsz ==== Gibt die Größe des Segments im RAM an. Kann 0 sein. ==== p_flags ==== Beschreibt Flags, z.B Zugriffsrechte. {| border="1" ! Name || Wert || Beschreibung |- | PF_X || 1 || Ausführbares Segment |- | PF_W || 2 || Beschreibbares Segment |- | PF_R || 4 || Lesbares Segment |} Damit kann man den Wert berechnen: flags = (read?PF_R:0)|(write?PF_W:0)|(exe?PF_X:0); ==== p_align ==== Alignment des Segmentes. Wenn 0 oder 1 angegeben wird, ist kein Alignment erforderlich. Ansonsten muss es eine 2er Potenz sein (2, 4, 8, 16, ...). == Überprüfen == Überprüfen kann man die ELF-Datei mit den folgenden Schritten: * MAGIC-String überprüfen * Architektur überpüfen * Klasse (32bit/64bit) überprüfen * Daten-Format (Little-/Big-Endian) überprüfen * Version überprüfen = Links = * [http://www.acm.uiuc.edu/sigops/rsrc/pfmt11.pdf ELF für x86] [[Category:Dateityp]]