Recettes pour ELF

image


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:



Lire, s'informer


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.


image


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'équipe

Pour 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'équipe

Pour 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'équipe

La 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) 

Informations sur le compilateur


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'équipe

Pour 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) # check RELRO if binary.has(SEGMENT_TYPES.GNU_RELRO): print("+ Full RELRO") if binary.has(DYNAMIC_TAGS.BIND_NOW) else print("~ Partial RELRO") else: print("- No RELRO") # check for stack canary support print("+ Canary found") if binary.has_symbol("__stack_chk_fail") else print("- No canary found") # check for NX support (check X-flag for GNU_STACK-segment) print("+ NX enabled") if binary.has_nx else print("- NX disabled") # check for PIE support print("+ PIE enabled") if binary.is_pie else print("- No PIE") # check for rpath / run path print("+ RPATH") if binary.has(DYNAMIC_TAGS.RPATH) else print("- No RPATH") print("+ RUNPATH")if binary.has(DYNAMIC_TAGS.RUNPATH) else print("- No RUNPATH") # check separate-code option if set(binary.get_section('.text').segments) == set(binary.get_section('.rodata').segments): print("- Not Separated Code Sections") else: print("+ Separated Code Sections") filecheck('test_rpath.elf') 

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:


 #        $ nm -D demangle-test-cpp ... U _Unwind_Resume U _ZdlPv U _Znwm U _ZSt17__throw_bad_allocv U _ZSt20__throw_length_errorPKc #        $ nm -D --demangle demangle-test-cpp ... U _Unwind_Resume U operator delete(void*) U operator new(unsigned long) U std::__throw_bad_alloc() U std::__throw_length_error(char const*) 

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


Elfe sans méta-information


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).

Supprimer les informations sur les personnages


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:


  • pour le fichier exécutable:
     $ strip -s simple 
  • pour la bibliothèque dynamique:
     $ strip --strip-unneeded libsimple.so 

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") 


Obfuscation des informations sur les personnages


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() # Add section with zip-archive as content section = lief.ELF.Section() section.content = data section.name = ".%s"%filename binary.add(section, loaded=True) # Add symbol as a reference to zip-archive symb = lief.ELF.Symbol() symb.type = lief.ELF.SYMBOL_TYPES.OBJECT symb.binding = lief.ELF.SYMBOL_BINDINGS.GLOBAL symb.size = len(data) symb.name = filename symb.value = section.virtual_address binary.add_static_symbol(symb) binary.write("libdata.crackme.container") 

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
image


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 #   16577  $ readelf -h test.elf Start of section headers e_shoff 14704 Size of section headers e_shentsize 64 Number of section headers e_shnum 29 #  ELF-: e_shoff + ( e_shentsize * e_shnum ) = 16560 

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") # check if overlay exists print('ELF has overlay data') if binary.has_overlay else print("No overlay data") # add overlay data to ELF data = bytearray(b'\xFF'*17) binary.overlay = data binary.write('test.overlay') 

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


Source: https://habr.com/ru/post/fr460247/


All Articles