
Auf Russisch gibt es nicht genügend Informationen zum Arbeiten mit ELF-Dateien (ausführbares und verknüpfbares Format - das Hauptformat für ausführbare Dateien von Linux und vielen Unix-Systemen). Wir behaupten nicht, alle möglichen Szenarien der Arbeit mit Elfen vollständig abzudecken, hoffen jedoch, dass die Informationen in Form eines Nachschlagewerks und einer Sammlung von Rezepten für Programmierer und Reverse Engineers nützlich sein werden.
Es versteht sich, dass der Leser auf einer grundlegenden Ebene mit dem ELF-Format vertraut ist (andernfalls empfehlen wir die Artikelserie Executable and Linkable Format 101 ).
Unter dem Schnitt werden die Werkzeuge für die Arbeit aufgelistet, beschriebene Techniken zum Lesen von Metainformationen, Modifizieren, Verifizieren und Zucht Erstellen von Elfen sowie Links zu nützlichen Materialien.
„Ich bin auch ein Elf ... Blau in Rot ... Elfen sind sehr geduldig ... Blau in Rot ... Und wir sind Elfen! ... Blau in Rot ... Es gibt nur Probleme durch Magie ...
(c) Das kleine Königreich von Ben und Holly
Die Werkzeuge
In den meisten Fällen können die Beispiele sowohl unter Linux als auch unter Windows ausgeführt werden.
In Rezepten verwenden wir die folgenden Werkzeuge:
- Dienstprogramme aus dem Binutils-Set (objcopy, objdump, readelf, strip);
- radare2 Framework ;
- Hex-Editor mit Unterstützung für Dateivorlagen (Beispiele zeigen 010Editor , Sie können jedoch beispielsweise kostenlose Veles verwenden );
- Python und die LIEF- Bibliothek;
- andere Dienstprogramme (Links sind im Rezept enthalten).
Testelfen
Als "Experiment" werden wir die einfache ELF-Datei aus der PieIsMyFav -Aufgabe von nutcake auf crackmes.one verwenden, aber jeder Vertreter der "Elven" -Familie wird dies tun. Wenn die fertige Datei mit den erforderlichen Merkmalen nicht gemeinfrei gefunden wurde, wird eine Methode zum Erstellen eines solchen Elfen angegeben.
Freie Elfen finden Sie auch unter den folgenden Links:
Dateityp, Titel, Abschnitt
Abhängig von der Aufgabe kann Folgendes von Interesse sein:
- Dateityp (DYN - Bibliothek, EXEC - ausführbare Datei, RELOC - verknüpfbar);
- Zielarchitektur (E_MACHINE - x86_64, x86, ARM usw.);
- Anwendungseinstiegspunkt (Einstiegspunkt);
- Abschnittsinformationen.
010Editor
HEX Editor 010Editor bietet ein Vorlagensystem. Bei ELF-Dateien heißt die Vorlage seltsamerweise ELF.bt und befindet sich in der Kategorie Ausführbare Datei (Menü Vorlagen - Ausführbare Datei ).
Von Interesse kann beispielsweise der Einstiegspunkt in die ausführbare Datei (Einstiegspunkt) sein (im Dateikopf aufgezeichnet).

lesen Sie sich
Das Readelf-Dienstprogramm kann als De-facto-Standard zum Abrufen von Informationen zu einer ELF-Datei angesehen werden.
- Lesen Sie den Dateikopf:
$ readelf -h simple
Teamergebnis ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x1070 Start of program headers: 64 (bytes into file) Start of section headers: 14800 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 11 Size of section headers: 64 (bytes) Number of section headers: 30 Section header string table index: 29
- Lesen Sie Informationen zu Segmenten und Abschnitten:
$ readelf -l -W simple
TeamergebnisZur besseren Lesbarkeit werden Adressen in das 32-Bit-Format konvertiert:
Elf file type is DYN (Shared object file) Entry point 0x1070 There are 11 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000040 0x00000040 0x00000040 0x000268 0x000268 R 0x8 INTERP 0x0002a8 0x000002a8 0x000002a8 0x00001c 0x00001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x000000 0x00000000 0x00000000 0x0005f8 0x0005f8 R 0x1000 LOAD 0x001000 0x00001000 0x00001000 0x00026d 0x00026d RE 0x1000 LOAD 0x002000 0x00002000 0x00002000 0x0001b8 0x0001b8 R 0x1000 LOAD 0x002de8 0x00003de8 0x00003de8 0x000258 0x000260 RW 0x1000 DYNAMIC 0x002df8 0x00003df8 0x00003df8 0x0001e0 0x0001e0 RW 0x8 NOTE 0x0002c4 0x000002c4 0x000002c4 0x000044 0x000044 R 0x4 GNU_EH_FRAME 0x002070 0x00002070 0x00002070 0x00003c 0x00003c R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x000000 0x000000 RW 0x10 GNU_RELRO 0x002de8 0x00003de8 0x00003de8 0x000218 0x000218 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .plt.got .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .dynamic .got .got.plt .data .bss 06 .dynamic 07 .note.ABI-tag .note.gnu.build-id 08 .eh_frame_hdr 09 10 .init_array .fini_array .dynamic .got
- Informationen zum Abschnitt lesen:
$ readelf -S -W simple
TeamergebnisZur besseren Lesbarkeit werden Adressen in das 32-Bit-Format konvertiert:
There are 30 section headers, starting at offset 0x39d0: Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 000002a8 0002a8 00001c 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 000002c4 0002c4 000020 00 A 0 0 4 [ 3] .note.gnu.build-id NOTE 000002e4 0002e4 000024 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 00000308 000308 000024 00 A 5 0 8 [ 5] .dynsym DYNSYM 00000330 000330 0000d8 18 A 6 1 8 [ 6] .dynstr STRTAB 00000408 000408 0000a2 00 A 0 0 1 [ 7] .gnu.version VERSYM 000004aa 0004aa 000012 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 000004c0 0004c0 000030 00 A 6 1 8 [ 9] .rela.dyn RELA 000004f0 0004f0 0000c0 18 A 5 0 8 [10] .rela.plt RELA 000005b0 0005b0 000048 18 AI 5 23 8 [11] .init PROGBITS 00001000 001000 000017 00 AX 0 0 4 [12] .plt PROGBITS 00001020 001020 000040 10 AX 0 0 16 [13] .plt.got PROGBITS 00001060 001060 000008 08 AX 0 0 8 [14] .text PROGBITS 00001070 001070 0001f2 00 AX 0 0 16 [15] .fini PROGBITS 00001264 001264 000009 00 AX 0 0 4 [16] .rodata PROGBITS 00002000 002000 000070 00 A 0 0 8 [17] .eh_frame_hdr PROGBITS 00002070 002070 00003c 00 A 0 0 4 [18] .eh_frame PROGBITS 000020b0 0020b0 000108 00 A 0 0 8 [19] .init_array INIT_ARRAY 00003de8 002de8 000008 08 WA 0 0 8 [20] .fini_array FINI_ARRAY 00003df0 002df0 000008 08 WA 0 0 8 [21] .dynamic DYNAMIC 00003df8 002df8 0001e0 10 WA 6 0 8 [22] .got PROGBITS 00003fd8 002fd8 000028 08 WA 0 0 8 [23] .got.plt PROGBITS 00004000 003000 000030 08 WA 0 0 8 [24] .data PROGBITS 00004030 003030 000010 00 WA 0 0 8 [25] .bss NOBITS 00004040 003040 000008 00 WA 0 0 1 [26] .comment PROGBITS 00000000 003040 00001c 01 MS 0 0 1 [27] .symtab SYMTAB 00000000 003060 000630 18 28 44 8 [28] .strtab STRTAB 00000000 003690 000232 00 0 0 1 [29] .shstrtab STRTAB 00000000 0038c2 000107 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
- Symbolinformationen lesen:
$ readelf -s -W simple
TeamergebnisDie Ausgabe wird zur besseren Lesbarkeit gekürzt:
Symbol table '.dynsym' contains 9 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable 2: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2) 3: 00000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2) 4: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 5: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 6: 00000000 0 FUNC GLOBAL DEFAULT UND __isoc99_scanf@GLIBC_2.7 (3) 7: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 8: 00000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) Symbol table '.symtab' contains 66 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 000002a8 0 SECTION LOCAL DEFAULT 1 2: 000002c4 0 SECTION LOCAL DEFAULT 2 3: 000002e4 0 SECTION LOCAL DEFAULT 3 4: 00000308 0 SECTION LOCAL DEFAULT 4 5: 00000330 0 SECTION LOCAL DEFAULT 5 6: 00000408 0 SECTION LOCAL DEFAULT 6 7: 000004aa 0 SECTION LOCAL DEFAULT 7 .... 26: 00000000 0 SECTION LOCAL DEFAULT 26 27: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c 28: 000010a0 0 FUNC LOCAL DEFAULT 14 deregister_tm_clones 29: 000010d0 0 FUNC LOCAL DEFAULT 14 register_tm_clones 30: 00001110 0 FUNC LOCAL DEFAULT 14 __do_global_dtors_aux 31: 00004040 1 OBJECT LOCAL DEFAULT 25 completed.7389 ....
Die Option -W
wird benötigt, um die Breite der Konsolenausgabe zu erhöhen (Standard 80 Zeichen).
Lief
Sie können die Header- und Abschnittsinformationen mit Python-Code und der LIEF-Bibliothek lesen (bietet eine API nicht nur für Python):
import lief binary = lief.parse("simple.elf") header = binary.header print("Entry point: %08x" % header.entrypoint) print("Architecture: ", header.machine_type) for section in binary.sections: print("Section %s - size: %s bytes" % (section.name, section.size)
Informationen zum Compiler und Build finden Sie in den .note
.comment
und .note
.
objdump
$ objdump -s --section .comment simple
Teamergebnis simple: file format elf64-x86-64 Contents of section .comment: 0000 4743433a 20284465 6269616e 20382e32 GCC: (Debian 8.2 0010 2e302d39 2920382e 322e3000 .0-9) 8.2.0.
lesen Sie sich
$ readelf -p .comment simple
Teamergebnis String dump of section '.comment': [ 0] GCC: (Debian 8.2.0-9) 8.2.0
$ readelf -n simple
Teamergebnis Displaying notes found at file offset 0x000002c4 with length 0x00000020: Owner Data size Description GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag) OS: Linux, ABI: 3.2.0 Displaying notes found at file offset 0x000002e4 with length 0x00000024: Owner Data size Description GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) Build ID: dae0509e4edb79719a65af37962b74e4cf2a8c2e
Lief
import lief binary = lief.parse("simple") comment = binary.get_section(".comment") print("Comment: ", bytes(comment.content))
Ich werde dich berechnen mit ... RPATH
Elfen können Pfade speichern, um dynamisch verbundene Bibliotheken zu finden. Um die Systemvariable LD_LIBRARY_PATH
vor dem Starten der Anwendung nicht LD_LIBRARY_PATH
, können Sie diesen Pfad einfach in die ELF-Datei „einbetten“.
Verwenden Sie dazu den Eintrag im Abschnitt .dynamic
vom Typ DT_RPATH
oder DT_RUNPATH
(siehe Kapitel Verzeichnisse, die vom Runtime Linker DT_RUNPATH
in der Dokumentation).
Und seien Sie vorsichtig, junger Entwickler, schlafen Sie Ihr Projektverzeichnis nicht!
Wie erscheint RPATH?
Der Hauptgrund für das Erscheinen eines RPATH-Datensatzes in einem Elfen ist die Option linker -rpath
, um nach einer dynamischen Bibliothek zu suchen. Ungefähr so:
$ gcc -L./lib -Wall -Wl,-rpath=/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/ -o test_rpath.elf bubble_main.c -lbubble
Ein solcher Befehl erstellt einen RPATH-Datensatz im Abschnitt .dynamic
mit dem Wert /run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/
.
lesen Sie sich
Sie können Elemente aus dem Abschnitt .dynamic
(unter denen sich RPATH befindet) wie folgt anzeigen:
$ readelf -d test_rpath.elf
TeamergebnisZur Vereinfachung des Lesens wird das Ergebnis des Befehls gekürzt:
Dynamic section at offset 0x2dd8 contains 28 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libbubble.so] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000f (RPATH) Library rpath: [/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/] 0x000000000000000c (INIT) 0x1000 0x000000000000000d (FINI) 0x11c8 ....
Lief
Mit der LIEF-Bibliothek können Sie auch den RPATH-Datensatz im Elfen lesen:
import lief from lief.ELF import DYNAMIC_TAGS elf = lief.parse("test_rpath.elf") if elf.has(DYNAMIC_TAGS.RPATH): rpath = next(filter(lambda x: x.tag == DYNAMIC_TAGS.RPATH, elf.dynamic_entries)) for path in rpath.paths: print(path) else: print("No RPATH in ELF")
Lesen Sie mehr über den Abschnitt .dynamic
Elf auf Sicherheit prüfen
Das Sicherheitsüberprüfungsskript checksec.sh des Forschers Tobias Klein (Autor von A Bug Hunter's Diary ) wurde seit 2011 nicht aktualisiert. Dieses ELF-Dateiskript prüft die Verfügbarkeit der Optionen RelRO (schreibgeschützte Verschiebungen), NX (nicht ausführbarer Stapel), Stapelkanarien, PIE (positionunabhängige ausführbare Dateien) und verwendet das Dienstprogramm readelf für seine Arbeit.
Lief
Sie können Ihr eigenes Analogon erstellen Knie Python und LIEF (etwas kürzer als der Vorläufer und mit zusätzlicher Überprüfung der Option für separaten Code ):
import lief from lief.ELF import DYNAMIC_TAGS, SEGMENT_TYPES def filecheck(filename): binary = lief.parse(filename)
Radare2
Vielen Dank an dukebarman für die zusätzliche Verwendung von Radare2 zur Anzeige von Informationen ähnlich wie bei checksec :
> r2 -ci~pic,canary,nx,crypto,stripped,static,relocs test_stack_proteck
Rohcode von Elf (binär von ELF)
Es gibt Situationen, in denen „Elfenkleidung“ in Form einer ELF-Struktur nicht benötigt wird, sondern nur der „nackte“ ausführbare Anwendungscode.
Objekt
Die Verwendung von objcopy ist wahrscheinlich denjenigen bekannt, die Firmware schreiben:
$ objcopy -O binary -S -g simple.elf simple.bin
-S
- um Zeicheninformationen zu löschen;-g
- um Debugging-Informationen zu entfernen.
Lief
Keine Magie. Nehmen Sie einfach den Inhalt der geladenen Abschnitte und erstellen Sie daraus ein Binar:
import lief from lief.ELF import SECTION_FLAGS, SECTION_TYPES binary = lief.parse("test") end_addr = 0 data = [] for section in filter(lambda x: x.has(SECTION_FLAGS.ALLOC) and x.type != SECTION_TYPES.NOBITS, binary.sections): if 0 < end_addr < section.virtual_address: align_bytes = b'\x00' * (section.virtual_address - end_addr) data.append(align_bytes) data.append(bytes(section.content)) end_addr = section.virtual_address + section.size with open('test.lief.bin', 'wb') as f: for d_bytes in data: f.write(d_bytes)
Verstümmelte - entwirrte Funktionsnamen
In ELFs, die aus C ++ - Code erstellt wurden, werden Funktionsnamen dekoriert (entstellt), um die Suche nach der entsprechenden Klassenfunktion zu vereinfachen. Das Lesen solcher Namen in der Analyse ist jedoch nicht sehr bequem.
Testelfe
nm
Um Namen in lesbarer Form darzustellen, können Sie das Dienstprogramm nm aus dem binutils-Set verwenden:
Lief
Anzeigen von Symbolnamen in demangulierter Form mithilfe der LIEF-Bibliothek:
import lief binary = lief.parse("demangle-test-cpp") for symb in binary.symbols: print(symb.name, symb.demangled_name)
Montage, Aufnahme, Modifikation des Elfen
Nachdem die Anwendung debuggt und in der wilden Welt veröffentlicht wurde, ist es sinnvoll, die Metainformationen zu entfernen:
- Debug-Abschnitte - in den meisten Fällen nutzlos;
- Namen von Variablen und Funktionen - haben für den Endbenutzer keinerlei Auswirkungen (erschwert das Gegenteil geringfügig);
- Abschnittstabelle - wird zum Ausführen der Anwendung absolut nicht benötigt (das Fehlen erschwert das Gegenteil geringfügig).
Zeicheninformationen sind die Namen von Objekten und Funktionen. Ohne sie ist die Umkehrung der Anwendung etwas komplizierter.
Streifen
Im einfachsten Fall können Sie das Dienstprogramm strip aus dem Binutils-Set verwenden. Um alle Zeicheninformationen zu löschen, führen Sie einfach den folgenden Befehl aus:
sstrip
Um Zeicheninformationen (einschließlich unnötiger Null-Bytes am Ende der Datei) sorgfältig zu entfernen, können Sie das Dienstprogramm sstrip aus der ELFkickers-Suite verwenden . Um alle Zeicheninformationen zu löschen, führen Sie einfach den folgenden Befehl aus:
$ sstrip -z simple
Lief
Mit der LIEF-Bibliothek können Sie auch einen kurzen Streifen .symtab
(die Symboltabelle wird gelöscht - Abschnitt .symtab
):
import lief binary = lief.parse("simple") binary.strip() binary.write("simple.stripped")
Partitionstabelle löschen
Wie oben erwähnt, hat das Vorhandensein / Fehlen einer Abschnittstabelle keinen Einfluss auf den Betrieb der Anwendung. Gleichzeitig wird ohne eine Abschnittsübersicht die Umkehrung der Anwendung etwas komplizierter.
Wir werden die LIEF-Bibliothek unter Python und das Beispiel zum Löschen der Abschnittstabelle verwenden :
import lief binary = lief.parse("simple") binary.header.numberof_sections = 0 binary.header.section_header_offset = 0 binary.write("simple.modified")
RPATH ändern und löschen
chrpath, PatchELF
Um RPATH unter Linux zu ändern, können Sie die Dienstprogramme chrpath (auf den meisten Distributionen verfügbar) oder PatchELF verwenden .
RPATH ändern:
$ chrpath -r /opt/my-libs/lib:/foo/lib test_rpath.elf
oder
$ patchelf --set-rpath /opt/my-libs/lib:/foo/lib test_rpath.elf
RPATH entfernen:
$ chrpath -d test_rpath.elf
oder
$ patchelf --shrink-rpath test_rpath.elf
Lief
Mit der LIEF-Bibliothek können Sie auch einen RPATH-Datensatz ändern und löschen.
RPATH ändern:
import lief binary = lief.parse("test_rpath.elf") rpath = next(filter(lambda x: x.tag == lief.ELF.DYNAMIC_TAGS.RPATH, binary.dynamic_entries)) rpath.paths = ["/opt/my-lib/here"] binary.write("test_rpath.patched")
RPATH entfernen:
import lief binary = lief.parse("test_rpath.elf") binary.remove(lief.ELF.DYNAMIC_TAGS.RPATH) binary.write("test_rpath.patched")
Um die Umkehrung der Anwendung zu erschweren, können Sie Zeicheninformationen speichern, aber die Namen von Objekten verwechseln. Wir verwenden den Elfen crackme01_32bit von crackme01 von seveb als Testperson.
Eine vereinfachte Version eines Beispiels aus der LIEF-Bibliothek könnte folgendermaßen aussehen:
import lief binary = lief.parse("crackme01_32bit") for i, symb in enumerate(binary.static_symbols): symb.name = "zzz_%d" % i binary.write("crackme01_32bit.obfuscated")
Als Ergebnis erhalten wir:
$ readelf -s crackme01_32bit.obfuscated ... Symbol table '.symtab' contains 78 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND zzz_0 1: 08048154 0 SECTION LOCAL DEFAULT 1 zzz_1 2: 08048168 0 SECTION LOCAL DEFAULT 2 zzz_2 3: 08048188 0 SECTION LOCAL DEFAULT 3 zzz_3 4: 080481ac 0 SECTION LOCAL DEFAULT 4 zzz_4 5: 080481d0 0 SECTION LOCAL DEFAULT 5 zzz_5 6: 080482b0 0 SECTION LOCAL DEFAULT 6 zzz_6 7: 0804835a 0 SECTION LOCAL DEFAULT 7 zzz_7 8: 08048378 0 SECTION LOCAL DEFAULT 8 zzz_8 9: 080483b8 0 SECTION LOCAL DEFAULT 9 zzz_9 10: 080483c8 0 SECTION LOCAL DEFAULT 10 zzz_10 ...
Funktionsersetzung über PLT / GOT
Auch als ELF PLT INFECTION bekannt .
Um nicht zu kopieren und einzufügen, hinterlassen Sie einfach Links zum Thema:
Einstiegspunkt ändern
Dies kann nützlich sein, wenn Sie Patches erstellen, Hooks und andere dynamische Instrumente installieren oder versteckte Funktionen aufrufen. Als Experiment verwenden wir den Elfen crackme01_32bit von crackme01 von seveb
radare2
radare2 startet im Aufnahmemodus (Option -w
) - Änderungen werden an der Originaldatei vorgenommen:
$ ./crackme01_32bit Please enter the secret number: ^C $ r2 -w -nn crackme01_32bit [0x00000000]> .pf.elf_header.entry=0x0804860D [0x00000000]> q $ ./crackme01_32bit Nope.
Lief
import lief binary = lief.parse("crackme01_32bit") header = binary.header header.entrypoint = 0x0804860D binary.write("crackme01_32bit.patched")
Code-Patch
Nehmen Sie als einfachen Test den Crackmepal des Novn91 mit dem Crack . Beim Start ohne Parameter zeigt das Programm Folgendes an:
$ ./crackmeMario usage <password>
Beim Start mit einem beliebigen String-Parameter wird Folgendes angezeigt:
./crackmeMario qwerty try again pal.
Wir werden einen Patch erstellen, damit das Programm sofort beim Start die Meldung „Gute Arbeit! jetzt keygen mich! "
radare2
radare2 kann alle Formate patchen, die es selbst unterstützt. In diesem Fall können die Patches im Textformat beschrieben werden:
# Rapatch for https://crackmes.one/crackme/5ccecc7e33c5d4419da559b3 !echo Patching crackme 0x115D : jmp 0x1226
Sie können einen solchen Patch mit dem folgenden Befehl anwenden:
$ r2 -P patch.txt crackmeMario
Lesen Sie mehr über das Patchen von Code über radare2:
Lief
Mit LIEF können Sie den Elfen (Bytes überschreiben) an der angegebenen virtuellen Adresse patchen. Der Patch kann in Form eines Bytearrays oder als ganzzahliger Wert vorliegen:
import lief binary = lief.parse("crackmeMario") binary.patch_address(0x115D, bytearray(b"\xe9\xc4\x00\x00\x00")) binary.write("crackmeMario.patched")
Nach dem Anwenden des Patches gibt das Programm Folgendes aus:
$ ./crackmeMario.patched good job! now keygen me!
Abschnitt zu ELF hinzufügen
Objekt
Mit objcopy können Sie einen Abschnitt hinzufügen, dieser Abschnitt gehört jedoch keinem Segment an und wird beim Start der Anwendung nicht in den Arbeitsspeicher geladen:
$ objcopy --add-section .testme=data.zip \ --set-section-flags .testme=alloc,contents,load,readonly \ --change-section-address .testme=0x08777777 \ simple simple.patched.elf
Lief
Mit der LIEF-Bibliothek können Sie einem vorhandenen ELF einen neuen Abschnitt und das entsprechende Segment ( loaded=True
Flag) hinzufügen:
import lief binary = lief.parse("simple") data = bytearray(b"\xFF" * 16) section = lief.ELF.Section(".testme", lief.ELF.SECTION_TYPES.PROGBITS) section += lief.ELF.SECTION_FLAGS.EXECINSTR section += lief.ELF.SECTION_FLAGS.ALLOC section.content = data binary.add(section, loaded=True) binary.write("simple.testme.lief")
Abschnitt ändern
Objekt
Mit objcopy können Sie den Inhalt eines Abschnitts durch Daten aus einer Datei ersetzen sowie die virtuelle Adresse eines Abschnitts und der Flags ändern:
$ objcopy --update-section .testme=patch.bin \ --change-section-address .testme=0x08999999 simple simple.testme.elf
Lief
import lief binary = lief.parse("simple") data = bytearray(b"\xFF" * 17) section = binary.get_section(".text") section.content = data binary.write("simple.patched")
Abschnitt löschen
Objekt
Mit objcopy können Sie einen bestimmten Abschnitt nach Namen löschen:
$ objcopy --remove-section .testme simple.testme.elf simple.no_testme.elf
Lief
Das Löschen eines Abschnitts mithilfe der LIEF-Bibliothek sieht folgendermaßen aus:
import lief binary = lief.parse("simple.testme.elf") binary.remove_section(".testme") binary.write("simple.no_testme")
Elfenbehälter
Das Rezept ist von Gremlins und ELF-Magie inspiriert : Was ist, wenn die ELF-Datei ein Container ist? . Es gibt auch Informationen über das aus Solaris stammende Dienstprogramm elfwrap, mit dem Sie eine ELF-Datei aus beliebigen Daten erstellen können, und das ELF-Format wird einfach als Container verwendet.
Versuchen wir, dasselbe in Python und LIEF zu tun.
Leider kann die LIEF-Bibliothek derzeit keine Elf-Datei von Grund auf neu erstellen. Sie müssen also helfen - erstellen Sie eine leere ELF-Vorlage:
$ echo "" | gcc -m32 -fpic -o empty.o -c -xc - $ gcc -m32 -shared -o libempty.so empty.o
Jetzt können Sie diese Vorlage verwenden, um Daten zu füllen:
import lief binary = lief.parse("libempty.so") filename = "crackme.zip" data = open(filename, 'rb').read()
Elf "mit Anhänger"
Das ELF-Format legt keine Einschränkungen für die Daten in der Datei fest, gehört jedoch zu keinem Segment. Somit ist es möglich, eine ausführbare Datei zu erstellen, die nach der ELF-Struktur gespeichert wird. Dies wird zur Laufzeit nicht in den Arbeitsspeicher geladen, sondern auf die Festplatte geschrieben und kann jederzeit von der Festplatte gelesen werden.
- IDA Pro berücksichtigt diese Daten bei der Analyse nicht
Beispiel einer Dateistruktur mit einem Trailer

radare2
Das Vorhandensein eines „Trailers“ kann durch Vergleichen der tatsächlichen und berechneten Dateigröße festgestellt werden:
$ radare2 test.elf [0x00001040]> ?v $s 0x40c1 [0x00001040]> iZ 14699
lesen Sie sich
readelf zeigt keine Informationen über das Vorhandensein eines "Trailers" an, kann jedoch manuell berechnet werden:
$ ls -l test.elf
Lief
In der LIEF-Bibliothek können Sie sowohl das Vorhandensein eines „Trailers“ überprüfen als auch hinzufügen. Mit LIEF sieht alles ziemlich prägnant aus:
import lief binary = lief.parse("test")
Leerer Elf (ELF von Grund auf neu)
Im Internet finden Sie Projekte zum „manuellen“ Erstellen einer ELF-Datei - ohne Verwendung eines Compilers und Linkers unter dem allgemeinen Namen „ELF von Grund auf neu“:
Die Vertrautheit mit diesen Projekten wirkt sich positiv auf die Absorption des ELF-Formats aus.
Kleinster Elf
Interessante Experimente zur Minimierung der Größe des Elfen sind in den Artikeln beschrieben:
Kurz gesagt, der Elf Loader im Betriebssystem verwendet nicht alle Headerfelder und Segmenttabellen, und ein minimaler ausführbarer Code kann direkt in die ELF-Headerstruktur eingefügt werden (der Code stammt aus dem ersten Artikel):
; tiny.asm BITS 32 org 0x00010000 db 0x7F, "ELF" ; e_ident dd 1 ; p_type dd 0 ; p_offset dd $$ ; p_vaddr dw 2 ; e_type ; p_paddr dw 3 ; e_machine dd _start ; e_version ; p_filesz dd _start ; e_entry ; p_memsz dd 4 ; e_phoff ; p_flags _start: mov bl, 42 ; e_shoff ; p_align xor eax, eax inc eax ; e_flags int 0x80 db 0 dw 0x34 ; e_ehsize dw 0x20 ; e_phentsize db 1 ; e_phnum ; e_shentsize ; e_shnum ; e_shstrndx filesize equ $ - $$
Stellen Sie einen ELF mit einer Größe von 45 Bytes zusammen und erhalten Sie ihn :
$ nasm -f bin -o a.out tiny.asm $ chmod +x a.out $ ./a.out ; echo $? 42 $ wc -c a.out 45 a.out
Musterelfe
Um einen Elfen mit der LIEF-Bibliothek zu erstellen, können Sie die folgenden Schritte ausführen (siehe Rezept "Elfen-Container"):
- Nehmen Sie eine einfache ELF-Datei als Vorlage.
- Ersetzen Sie den Inhalt von Abschnitten, fügen Sie neue Abschnitte hinzu.
- Konfigurieren Sie die erforderlichen Parameter (Einstiegspunkt, Flags).
Anstelle einer Schlussfolgerung
Als wir den Artikel hinzufügten, stellten wir fest, dass es so etwas wie eine Ode an die LIEF-Bibliothek war. Dies war jedoch nicht geplant - ich wollte zeigen, wie mit ELF-Dateien mit verschiedenen Tools gearbeitet wird.
Sicher gibt es oder braucht Skripte, die hier nicht erwähnt wurden - schreiben Sie darüber in den Kommentaren.
Referenzen und Literatur