
En russe, il n'y a pas assez d'informations sur la façon de travailler avec les fichiers ELF (format exécutable et linkable - le format principal pour les fichiers exécutables de Linux et de nombreux systèmes Unix). Nous ne prétendons pas couvrir entièrement tous les scénarios possibles de travail avec les elfes, mais nous espérons que les informations seront utiles sous la forme d'un guide et d'une collection de recettes pour les programmeurs et les ingénieurs inversés.
Il est entendu que le lecteur au niveau de base est familier avec le format ELF (sinon, nous recommandons la série d'articles Executable et Linkable Format 101 ).
Sous la coupe seront listés les outils de travail, les techniques décrites pour la lecture des méta-informations, la modification, la vérification et élevage créer des elfes, ainsi que des liens vers des documents utiles.
"Je suis aussi un elfe ... Bleu en rouge ... Les elfes sont très patients ... Bleu en rouge ... Et nous sommes des elfes! .. Bleu en rouge ... Il n'y a que des problèmes de magie ...
(c) Le petit royaume de Ben et Holly
Les outils
Dans la plupart des cas, les exemples peuvent être exécutés sur Linux et Windows.
Dans les recettes, nous utiliserons les outils suivants:
- utilitaires de l'ensemble binutils (objcopy, objdump, readelf, strip);
- cadre radare2 ;
- éditeur hexadécimal avec prise en charge des modèles de fichiers (les exemples montrent 010Editor , mais vous pouvez utiliser, par exemple, Veles gratuit);
- Python et la bibliothèque LIEF ;
- d'autres utilitaires (les liens sont dans la recette).
Elfes de test
En tant que «expérimental», nous utiliserons le simple fichier ELF de la tâche PieIsMyFav de nutcake sur crackmes.one, mais tout représentant de la famille «elfique» fera l'affaire. Si le fichier fini avec les caractéristiques requises n'a pas été trouvé dans le domaine public, alors une méthode pour créer un tel elfe sera donnée.
Elfes gratuits peuvent également être trouvés sur les liens:
Type de fichier, titre, section
Selon la tâche, les éléments suivants peuvent être intéressants:
- type de fichier (DYN - bibliothèque, EXEC - exécutable, RELOC - pouvant être lié);
- architecture cible (E_MACHINE - x86_64, x86, ARM, etc.);
- point d'entrée d'application (Entry Point);
- informations sur la section.
010 Éditeur
HEX Editor 010Editor fournit un système de modèles. Pour les fichiers ELF, le modèle est appelé, curieusement, ELF.bt et se trouve dans la catégorie Exécutable (menu Modèles - Exécutable).
Par exemple, le point d'entrée vers le fichier exécutable (point d'entrée) (enregistré dans l'en-tête du fichier) peut être intéressant.

lire
L'utilitaire readelf peut être considéré comme la norme de facto pour obtenir des informations sur un fichier ELF.
- Lisez l'en-tête du fichier:
$ readelf -h simple
Résultat d'équipe 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
- Lisez les informations sur les segments et les sections:
$ readelf -l -W simple
Résultat d'équipePour plus de lisibilité, les adresses sont converties au format 32 bits:
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
- Lisez les informations de la section:
$ readelf -S -W simple
Résultat d'équipePour plus de lisibilité, les adresses sont converties au format 32 bits:
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)
- Lire les informations sur les symboles:
$ readelf -s -W simple
Résultat d'équipeLa sortie est raccourcie pour plus de lisibilité:
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 ....
L'option -W
est nécessaire pour augmenter la largeur de la sortie de la console (par défaut, 80 caractères).
Lief
Vous pouvez lire les informations d'en-tête et de section à l'aide du code Python et de la bibliothèque LIEF (fournit une API non seulement pour 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)
Pour plus d'informations sur le compilateur et la génération, consultez les .note
.comment
et .note
.
objdump
$ objdump -s --section .comment simple
Résultat d'équipe 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.
lire
$ readelf -p .comment simple
Résultat d'équipe String dump of section '.comment': [ 0] GCC: (Debian 8.2.0-9) 8.2.0
$ readelf -n simple
Résultat d'équipe 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))
Je vais te calculer par ... RPATH
Les elfes peuvent enregistrer des chemins pour trouver des bibliothèques connectées dynamiquement. Afin de ne pas définir la variable système LD_LIBRARY_PATH
avant de démarrer l'application, vous pouvez simplement «incorporer» ce chemin dans le fichier ELF.
Pour ce faire, utilisez l'entrée dans la section .dynamic
avec le type DT_RPATH
ou DT_RUNPATH
(voir le chapitre Répertoires recherchés par le Runtime Linker dans la documentation).
Et attention, jeune développeur, ne "dormez" pas votre répertoire de projet!
Comment RPATH apparaît-il?
La principale raison de l'apparition d'un enregistrement RPATH dans un elfe est l'option linker -rpath
pour rechercher une bibliothèque dynamique. Quelque chose comme ça:
$ gcc -L./lib -Wall -Wl,-rpath=/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/ -o test_rpath.elf bubble_main.c -lbubble
Une telle commande créera un enregistrement RPATH dans la section .dynamic
avec la valeur /run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/
.
lire
Vous pouvez afficher les éléments de la section .dynamic
(parmi lesquels il y a RPATH) comme suit:
$ readelf -d test_rpath.elf
Résultat d'équipePour faciliter la lecture, le résultat de la commande est réduit:
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
En utilisant la bibliothèque LIEF, vous pouvez également lire l'enregistrement RPATH dans l'elfe:
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")
Lisez à propos de la section .dynamic
Vérification de la sécurité d'Elf
Le script de contrôle de sécurité checksec.sh du chercheur Tobias Klein (auteur de A Bug Hunter's Diary ) n'a pas été mis à jour depuis 2011. Ce script pour les fichiers ELF vérifie la disponibilité des options RelRO (Read Only Relocations), NX (Non-Executable Stack), Stack Canaries, PIE (Position Independent Executables) et utilise l'utilitaire readelf pour son travail.
Lief
Vous pouvez créer votre propre analogique sur genou Python et LIEF (légèrement plus court que le progéniteur et avec vérification supplémentaire de l'option de code séparé ):
import lief from lief.ELF import DYNAMIC_TAGS, SEGMENT_TYPES def filecheck(filename): binary = lief.parse(filename)
Radare2
Merci à dukebarman pour l'ajout de l'utilisation de Radare2 pour afficher des informations similaires à checksec :
> r2 -ci~pic,canary,nx,crypto,stripped,static,relocs test_stack_proteck
Code brut d'Elf (binaire d'ELF)
Il y a des situations où des «vêtements elfes» sous la forme d'une structure ELF ne sont pas nécessaires, mais seul le code d'application exécutable «nu» est nécessaire.
objcopy
L'utilisation d' objcopy est probablement familière à ceux qui écrivent le firmware:
$ objcopy -O binary -S -g simple.elf simple.bin
-S
- pour supprimer les informations sur les caractères;-g
- pour supprimer les informations de débogage.
Lief
Pas de magie. Prenez simplement le contenu des sections chargées et créez-en un binaire:
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)
Mangled - noms de fonction démêlés
Dans les ELF créés à partir du code C ++, les noms des fonctions sont décorés (modifiés) pour simplifier la recherche de la fonction de classe correspondante. Cependant, la lecture de ces noms dans l'analyse n'est pas très pratique.
Elfe de test
nm
Pour représenter des noms sous une forme lisible par l'homme, vous pouvez utiliser l'utilitaire nm de l'ensemble binutils:
Lief
Affichage des noms de symboles sous forme démangulée à l'aide de la bibliothèque LIEF:
import lief binary = lief.parse("demangle-test-cpp") for symb in binary.symbols: print(symb.name, symb.demangled_name)
Assemblage, enregistrement, modification de l'elfe
Une fois l'application déboguée et publiée dans le monde sauvage, il est logique de supprimer les méta-informations:
- sections de débogage - inutiles dans la plupart des cas;
- noms des variables et des fonctions - n'affectent absolument rien pour l'utilisateur final (complique légèrement l'inverse);
- table de section - absolument pas nécessaire pour exécuter l'application (son manque compliquera légèrement l'inverse).
Les informations sur les caractères sont les noms des objets et des fonctions. Sans cela, l'inverse de l'application est un peu plus compliqué.
bande
Dans le cas le plus simple, vous pouvez utiliser l'utilitaire strip de l'ensemble binutils. Pour supprimer toutes les informations sur les caractères, exécutez simplement la commande:
sstrip
Pour supprimer soigneusement les informations sur les caractères (y compris les octets zéro inutiles à la fin du fichier), vous pouvez utiliser l'utilitaire sstrip de la suite ELFkickers . Pour supprimer toutes les informations sur les caractères, exécutez simplement la commande:
$ sstrip -z simple
Lief
En utilisant la bibliothèque LIEF, vous pouvez également faire une bande rapide (la table des symboles est supprimée - section .symtab
):
import lief binary = lief.parse("simple") binary.strip() binary.write("simple.stripped")
Suppression d'une table de partition
Comme mentionné ci-dessus, la présence / absence d'une table de section n'affecte pas le fonctionnement de l'application. Mais en même temps, sans table de section, l'inverse de l'application devient un peu plus compliqué.
Nous utiliserons la bibliothèque LIEF sous Python et l' exemple de suppression de la table de section :
import lief binary = lief.parse("simple") binary.header.numberof_sections = 0 binary.header.section_header_offset = 0 binary.write("simple.modified")
Modifier et supprimer RPATH
chrpath, PatchELF
Pour changer RPATH sous Linux, vous pouvez utiliser les utilitaires chrpath (disponibles sur la plupart des distributions) ou PatchELF .
Changer RPATH:
$ chrpath -r /opt/my-libs/lib:/foo/lib test_rpath.elf
ou
$ patchelf --set-rpath /opt/my-libs/lib:/foo/lib test_rpath.elf
Supprimer RPATH:
$ chrpath -d test_rpath.elf
ou
$ patchelf --shrink-rpath test_rpath.elf
Lief
La bibliothèque LIEF vous permet également de modifier et de supprimer un enregistrement RPATH.
Changer RPATH:
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")
Supprimer RPATH:
import lief binary = lief.parse("test_rpath.elf") binary.remove(lief.ELF.DYNAMIC_TAGS.RPATH) binary.write("test_rpath.patched")
Pour compliquer l'inverse de l'application, vous pouvez enregistrer des informations symboliques, mais confondre les noms des objets. Nous utilisons l'elfe crackme01_32bit de crackme01 par seveb comme sujet de test.
Une version simplifiée d'un exemple de la bibliothèque LIEF pourrait ressembler à ceci:
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")
En conséquence, nous obtenons:
$ 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 ...
Substitution de fonctions via PLT / GOT
Aussi connu comme ELF PLT INFECTION .
Afin de ne pas copier-coller, il suffit de laisser des liens sur le sujet:
Changer le point d'entrée
Il peut être utile lors de la création de correctifs, de l'installation de crochets et d'autres instruments dynamiques ou pour invoquer des fonctions cachées. À titre expérimental, nous utilisons l'elfe crackme01_32bit de crackme01 par seveb
radare2
radare2 démarre en mode enregistrement (option -w
) - des modifications seront apportées au fichier d'origine:
$ ./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")
Patch de code
Comme test simple, prenez le crackmepal du novn91 avec la fissure . Lorsqu'il est lancé sans paramètres, le programme affiche:
$ ./crackmeMario usage <password>
Lorsqu'il est lancé avec un paramètre de chaîne arbitraire, il affiche:
./crackmeMario qwerty try again pal.
Nous allons faire un patch pour que le programme affiche immédiatement au démarrage le message «bon travail! maintenant keygen moi! "
radare2
radare2 peut patcher tous les formats qu'il prend en charge lui-même. Dans ce cas, il est possible de décrire les patchs au format texte:
# Rapatch for https://crackmes.one/crackme/5ccecc7e33c5d4419da559b3 !echo Patching crackme 0x115D : jmp 0x1226
Vous pouvez appliquer un tel patch avec la commande:
$ r2 -P patch.txt crackmeMario
Lisez à propos de la correction du code via radare2:
Lief
LIEF vous permet de patcher l'elfe (écraser les octets) à l'adresse virtuelle spécifiée. Le patch peut être sous la forme d'un tableau d'octets ou sous forme de valeur entière:
import lief binary = lief.parse("crackmeMario") binary.patch_address(0x115D, bytearray(b"\xe9\xc4\x00\x00\x00")) binary.write("crackmeMario.patched")
Après avoir appliqué le patch, le programme affichera:
$ ./crackmeMario.patched good job! now keygen me!
Ajouter une section à ELF
objcopy
objcopy vous permet d'ajouter une section, mais cette section n'appartiendra à aucun segment et ne sera pas chargée dans la RAM au démarrage de l'application:
$ objcopy --add-section .testme=data.zip \ --set-section-flags .testme=alloc,contents,load,readonly \ --change-section-address .testme=0x08777777 \ simple simple.patched.elf
Lief
La bibliothèque LIEF vous permet d'ajouter une nouvelle section et son segment correspondant ( loaded=True
flag) à un ELF existant:
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")
Changer de section
objcopy
objcopy vous permet de remplacer le contenu d'une section par des données d'un fichier, ainsi que de changer l'adresse virtuelle d'une section et des indicateurs:
$ 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")
Supprimer la section
objcopy
objcopy vous permet de supprimer une section spécifique par son nom:
$ objcopy --remove-section .testme simple.testme.elf simple.no_testme.elf
Lief
La suppression d'une section à l'aide de la bibliothèque LIEF ressemble à ceci:
import lief binary = lief.parse("simple.testme.elf") binary.remove_section(".testme") binary.write("simple.no_testme")
Conteneur d'elfe
La recette est inspirée de Gremlins et de la magie ELF: que faire si le fichier ELF est un conteneur? . Il existe également des informations sur l'utilitaire elfwrap originaire de Solaris, qui vous permet de créer un fichier ELF à partir de données arbitraires, et le format ELF est utilisé simplement comme conteneur.
Essayons de faire de même en Python et LIEF.
Malheureusement, pour le moment, la bibliothèque LIEF n'est pas en mesure de créer un fichier elf à partir de zéro, vous devez donc l'aider - créez un modèle ELF vide:
$ echo "" | gcc -m32 -fpic -o empty.o -c -xc - $ gcc -m32 -shared -o libempty.so empty.o
Vous pouvez maintenant utiliser ce modèle pour remplir les données:
import lief binary = lief.parse("libempty.so") filename = "crackme.zip" data = open(filename, 'rb').read()
Elfe "avec une remorque"
Le format ELF n'impose pas de restrictions sur les données contenues dans le fichier, mais n'appartient à aucun segment. Ainsi, il est possible de créer un fichier exécutable, qui sera stocké après la structure ELF. C'est quelque chose qui ne sera pas chargé dans la RAM au moment de l'exécution, mais il sera écrit sur le disque et à tout moment, il pourra être lu à partir du disque.
- IDA Pro ne tiendra pas compte de ces données lors de l'analyse
Exemple de structure de fichier avec une bande-annonce

radare2
La présence d'une «bande-annonce» peut être établie en comparant les tailles de fichier réelles et calculées:
$ radare2 test.elf [0x00001040]> ?v $s 0x40c1 [0x00001040]> iZ 14699
lire
readelf n'affiche pas d'informations sur la présence d'une "bande-annonce", mais peut être calculé manuellement:
$ ls -l test.elf
Lief
La bibliothèque LIEF vous permet à la fois de vérifier la présence d'une «bande-annonce» et de l'ajouter. En utilisant LIEF, tout semble assez succinct:
import lief binary = lief.parse("test")
Elfe vide (ELF à partir de zéro)
Sur Internet, vous pouvez trouver des projets pour créer un fichier ELF «manuellement» - sans utiliser un compilateur et un éditeur de liens sous le nom général «ELF from scratch»:
La familiarité avec ces projets a un effet bénéfique sur l'absorption du format ELF.
Le plus petit elfe
Des expériences intéressantes pour minimiser la taille de l'elfe sont décrites dans les articles:
En bref, le chargeur elf dans le système d'exploitation n'utilise pas tous les champs d'en-tête et les tables de segments, et un code exécutable minimal peut être placé directement dans la structure d'en-tête ELF (le code est tiré du premier article):
; 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 $ - $$
Assemblez et obtenez un ELF de taille ... 45 octets :
$ nasm -f bin -o a.out tiny.asm $ chmod +x a.out $ ./a.out ; echo $? 42 $ wc -c a.out 45 a.out
Motif elfe
Pour créer un elfe à l'aide de la bibliothèque LIEF, vous pouvez suivre les étapes suivantes (voir la recette "Elf-container"):
- prendre un simple fichier ELF comme modèle;
- remplacer le contenu des sections, ajouter de nouvelles sections;
- configurer les paramètres nécessaires (point d'entrée, drapeaux).
Au lieu d'une conclusion
Ajoutant à l'article, nous avons constaté qu'il s'est avéré être quelque chose comme une ode à la bibliothèque LIEF. Mais ce n'était pas prévu - je voulais montrer comment travailler avec des fichiers ELF en utilisant différents outils.
Il y a sûrement ou besoin de scripts qui n'ont pas été mentionnés ici - écrivez à ce sujet dans les commentaires.
Références et littérature