
En ruso, no hay suficiente información sobre cómo trabajar con archivos ELF (Formato ejecutable y enlazable, el formato principal para archivos ejecutables de Linux y muchos sistemas Unix). No pretendemos cubrir completamente todos los escenarios posibles de trabajar con elfos, pero esperamos que la información sea útil en forma de un libro de referencia y una colección de recetas para programadores e ingenieros inversos.
Se entiende que el lector en un nivel básico está familiarizado con el formato ELF (de lo contrario, recomendamos la serie de artículos Executable and Linkable Format 101 ).
Bajo el corte se enumerarán las herramientas para el trabajo, las técnicas descritas para leer metainformación, modificación, verificación y cria creando elfos, así como enlaces a materiales útiles.
"También soy un elfo ... Azul en rojo ... Los elfos son muy pacientes ... Azul en rojo ... ¡Y nosotros somos elfos! ... Azul en rojo ... Solo hay problemas de magia ...
(c) El pequeño reino de Ben y Holly
Las herramientas
En la mayoría de los casos, los ejemplos se pueden ejecutar tanto en Linux como en Windows.
En las recetas usaremos las siguientes herramientas:
- utilidades del conjunto binutils (objcopy, objdump, readelf, strip);
- marco radare2 ;
- editor hexadecimal con soporte para plantillas de archivos (los ejemplos muestran 010Editor , pero puede usar, por ejemplo, Veles gratis);
- Python y la biblioteca LIEF ;
- otras utilidades (los enlaces están en la receta).
Elfos de prueba
Como "experimental" usaremos el archivo ELF simple de la tarea PieIsMyFav de nutcake en crackmes.one, pero cualquier representante de la familia "elfo" lo hará. Si el archivo terminado con las características requeridas no se encontró en el dominio público, se proporcionará un método para crear dicho duende.
Los elfos libres también se pueden encontrar en los enlaces:
Tipo de archivo, título, sección
Dependiendo de la tarea, lo siguiente puede ser de interés:
- tipo de archivo (DYN - biblioteca, EXEC - ejecutable, RELOC - enlazable);
- arquitectura de destino (E_MACHINE - x86_64, x86, ARM, etc.);
- punto de entrada de la aplicación (punto de entrada);
- sección de información.
010Editor
HEX Editor 010Editor proporciona un sistema de plantillas. Para archivos ELF, la plantilla se llama, curiosamente, ELF.bt y se encuentra en la categoría Ejecutable (Plantillas de menú - Ejecutable).
Puede ser de interés, por ejemplo, el punto de entrada al archivo ejecutable (punto de entrada) (registrado en el encabezado del archivo).

readelf
La utilidad readelf puede considerarse el estándar de facto para obtener información sobre un archivo ELF.
- Lea el encabezado del archivo:
$ readelf -h simple
Resultado del equipo 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
- Leer información sobre segmentos y secciones:
$ readelf -l -W simple
Resultado del equipoPara facilitar la lectura, las direcciones se convierten al formato de 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
- Lea la información de la sección:
$ readelf -S -W simple
Resultado del equipoPara facilitar la lectura, las direcciones se convierten al formato de 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)
- Leer la información del símbolo:
$ readelf -s -W simple
Resultado del equipoLa salida se acorta para facilitar la lectura:
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 ....
La opción -W
es necesaria para aumentar el ancho de salida de la consola (predeterminado, 80 caracteres).
Lief
Puede leer la información del encabezado y la sección utilizando el código Python y la biblioteca LIEF (proporciona una API no solo para 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)
Para obtener información sobre el compilador y la compilación, consulte las .note
.comment
y .note
.
objdump
$ objdump -s --section .comment simple
Resultado del equipo 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.
readelf
$ readelf -p .comment simple
Resultado del equipo String dump of section '.comment': [ 0] GCC: (Debian 8.2.0-9) 8.2.0
$ readelf -n simple
Resultado del equipo 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))
Te calcularé por ... RPATH
Los elfos pueden guardar rutas para encontrar bibliotecas conectadas dinámicamente. Para no establecer la variable de sistema LD_LIBRARY_PATH
antes de iniciar la aplicación, simplemente puede "incrustar" esta ruta en el archivo ELF.
Para hacer esto, use la entrada en la sección .dynamic
con el tipo DT_RPATH
o DT_RUNPATH
(consulte el capítulo Directorios buscados por el Runtime Linker en la documentación).
Y tenga cuidado, joven desarrollador, ¡no "duerma" el directorio de su proyecto!
¿Cómo aparece RPATH?
La razón principal para la aparición de un registro RPATH en un duende es la opción de enlace -rpath
para buscar una biblioteca dinámica. Algo como esto:
$ gcc -L./lib -Wall -Wl,-rpath=/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/ -o test_rpath.elf bubble_main.c -lbubble
Tal comando creará un registro RPATH en la sección .dynamic
con el valor /run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/
.
readelf
Puede ver elementos de la sección .dynamic
(entre los cuales hay RPATH) de la siguiente manera:
$ readelf -d test_rpath.elf
Resultado del equipoPara facilitar la lectura, el resultado del comando se acorta:
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
Usando la biblioteca LIEF, también puede leer el registro RPATH en el duende:
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")
Lea sobre la sección .dynamic
Comprobando la seguridad de Elf
El script de verificación de seguridad checksec.sh del investigador Tobias Klein (autor de A Bug Hunter's Diary ) no se ha actualizado desde 2011. Este script de archivo ELF verifica la disponibilidad de las opciones RelRO (reubicaciones de solo lectura), NX (pila no ejecutable), canarias de pila, PIE (ejecutables independientes de posición) y utiliza la utilidad readelf para su trabajo.
Lief
Puedes hacer tu propio análogo en rodilla Python y LIEF (ligeramente más corto que el progenitor y con verificación adicional de la opción de código separado ):
import lief from lief.ELF import DYNAMIC_TAGS, SEGMENT_TYPES def filecheck(filename): binary = lief.parse(filename)
Radare2
Gracias a dukebarman por agregar Radare2 para mostrar información similar a checksec :
> r2 -ci~pic,canary,nx,crypto,stripped,static,relocs test_stack_proteck
Código sin formato de Elf (binario de ELF)
Hay situaciones en las que no se necesita "ropa élfica" en forma de estructura ELF, pero solo se necesita el código de aplicación ejecutable "desnudo".
objcopy
El uso de objcopy probablemente sea familiar para quienes escriben firmware:
$ objcopy -O binary -S -g simple.elf simple.bin
-S
- para borrar la información del personaje;-g
: para eliminar la información de depuración.
Lief
Sin magia Simplemente tome el contenido de las secciones cargadas y cree un binario a partir de ellas:
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 - nombres de funciones demandadas
En los archivos ELF creados a partir del código C ++, los nombres de las funciones se decoran (alteran) para simplificar la búsqueda de la función de clase correspondiente. Sin embargo, leer tales nombres en el análisis no es muy conveniente.
Prueba duende
nm
Para representar nombres en forma legible para humanos, puede usar la utilidad nm del conjunto binutils:
Lief
Mostrar nombres de símbolos en forma reglamentaria utilizando la biblioteca LIEF:
import lief binary = lief.parse("demangle-test-cpp") for symb in binary.symbols: print(symb.name, symb.demangled_name)
Montaje, grabación, modificación del duende.
Después de que la aplicación se depure y se lance al mundo salvaje, tiene sentido eliminar la metainformación:
- secciones de depuración - inútiles en la mayoría de los casos;
- nombres de variables y funciones: absolutamente no afectan nada para el usuario final (complica un poco lo contrario);
- tabla de sección: no es absolutamente necesario para ejecutar la aplicación (su falta complicará ligeramente lo contrario).
La información del personaje son los nombres de objetos y funciones. Sin ella, el reverso de la aplicación es un poco más complicado.
tira
En el caso más simple, puede usar la utilidad strip del conjunto binutils. Para eliminar toda la información de los personajes, simplemente ejecute el comando:
sstrip
Para eliminar cuidadosamente la información de los caracteres (incluidos los bytes cero innecesarios al final del archivo), puede usar la utilidad sstrip del paquete ELFkickers . Para eliminar toda la información de los personajes, simplemente ejecute el comando:
$ sstrip -z simple
Lief
Usando la biblioteca LIEF, también puede hacer una tira rápida (se elimina la tabla de símbolos - sección .symtab
):
import lief binary = lief.parse("simple") binary.strip() binary.write("simple.stripped")
Eliminar una tabla de particiones
Como se mencionó anteriormente, la presencia / ausencia de una tabla de sección no afecta el funcionamiento de la aplicación. Pero al mismo tiempo, sin una tabla de secciones, el reverso de la aplicación se vuelve un poco más complicado.
Utilizaremos la biblioteca LIEF en Python y el ejemplo de eliminación de la tabla de secciones :
import lief binary = lief.parse("simple") binary.header.numberof_sections = 0 binary.header.section_header_offset = 0 binary.write("simple.modified")
Modificar y eliminar RPATH
chrpath, PatchELF
Para cambiar RPATH en Linux, puede usar las utilidades chrpath (disponibles en la mayoría de las distribuciones) o PatchELF .
Cambiar RPATH:
$ chrpath -r /opt/my-libs/lib:/foo/lib test_rpath.elf
o
$ patchelf --set-rpath /opt/my-libs/lib:/foo/lib test_rpath.elf
Eliminar RPATH:
$ chrpath -d test_rpath.elf
o
$ patchelf --shrink-rpath test_rpath.elf
Lief
La biblioteca LIEF también le permite modificar y eliminar un registro RPATH.
Cambiar 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")
Eliminar RPATH:
import lief binary = lief.parse("test_rpath.elf") binary.remove(lief.ELF.DYNAMIC_TAGS.RPATH) binary.write("test_rpath.patched")
Para complicar el reverso de la aplicación, puede guardar información simbólica, pero confundir los nombres de los objetos. Utilizamos el elfo crackme01_32bit de crackme01 por seveb como sujeto de prueba.
Una versión simplificada de un ejemplo de la biblioteca LIEF podría verse así:
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")
Como resultado, obtenemos:
$ 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 ...
Sustitución de funciones a través de PLT / GOT
También conocido como ELF PLT INFECTION .
Para no copiar y pegar, simplemente deje enlaces sobre el tema:
Cambiar punto de entrada
Puede ser útil al crear parches, instalar ganchos y otra instrumentación dinámica, o para invocar funciones ocultas. Como experimental, usamos el elfo crackme01_32bit de crackme01 por seveb
radare2
radare2 comienza en modo de grabación (opción -w
): se realizarán cambios en el archivo original:
$ ./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")
Parche de código
Como prueba simple, tome el crackmepal del novn91 con el crack . Cuando se inicia sin parámetros, el programa muestra:
$ ./crackmeMario usage <password>
Cuando se inicia con un parámetro de cadena arbitraria, muestra:
./crackmeMario qwerty try again pal.
Haremos un parche para que el programa inmediatamente al inicio muestre el mensaje "¡buen trabajo! ahora keygen me!
radare2
radare2 puede parchear cualquier formato que sea compatible. En este caso, es posible describir los parches en formato de texto:
# Rapatch for https://crackmes.one/crackme/5ccecc7e33c5d4419da559b3 !echo Patching crackme 0x115D : jmp 0x1226
Puede aplicar dicho parche con el comando:
$ r2 -P patch.txt crackmeMario
Lea sobre el parcheo de código a través de radare2:
Lief
LIEF le permite parchear el duende (sobrescribir bytes) en la dirección virtual especificada. El parche puede tener la forma de una matriz de bytes o como un valor entero:
import lief binary = lief.parse("crackmeMario") binary.patch_address(0x115D, bytearray(b"\xe9\xc4\x00\x00\x00")) binary.write("crackmeMario.patched")
Después de aplicar el parche, el programa generará:
$ ./crackmeMario.patched good job! now keygen me!
Agregar sección a ELF
objcopy
objcopy le permite agregar una sección, pero esta sección no pertenecerá a ningún segmento y no se cargará en la RAM cuando se inicie la aplicación:
$ 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 biblioteca LIEF le permite agregar una nueva sección y su segmento correspondiente ( loaded=True
Indicador loaded=True
) a un ELF existente:
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")
Cambiar sección
objcopy
objcopy le permite reemplazar el contenido de una sección con datos de un archivo, así como cambiar la dirección virtual de una sección y marcas:
$ 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")
Eliminar sección
objcopy
objcopy le permite eliminar una sección específica por nombre:
$ objcopy --remove-section .testme simple.testme.elf simple.no_testme.elf
Lief
Eliminar una sección usando la biblioteca LIEF se ve así:
import lief binary = lief.parse("simple.testme.elf") binary.remove_section(".testme") binary.write("simple.no_testme")
Contenedor elfo
La receta está inspirada en los Gremlins y la magia ELF: ¿y si el archivo ELF es un contenedor? . También hay información sobre la utilidad elfwrap originalmente de Solaris, que le permite crear un archivo ELF a partir de datos arbitrarios, y el formato ELF se usa simplemente como un contenedor.
Tratemos de hacer lo mismo en Python y LIEF.
Desafortunadamente, en este momento, la biblioteca LIEF no puede crear un archivo elf desde cero, por lo que debe ayudarlo: cree una plantilla ELF vacía:
$ echo "" | gcc -m32 -fpic -o empty.o -c -xc - $ gcc -m32 -shared -o libempty.so empty.o
Ahora puede usar esta plantilla para completar datos:
import lief binary = lief.parse("libempty.so") filename = "crackme.zip" data = open(filename, 'rb').read()
Elf "con un trailer"
El formato ELF no impone restricciones en los datos que están en el archivo, pero no pertenece a ningún segmento. Por lo tanto, es posible crear un archivo ejecutable, que se almacenará después de la estructura ELF. Esto es algo que no se cargará en la RAM en tiempo de ejecución, pero se escribirá en el disco y en cualquier momento se puede leer desde el disco.
- IDA Pro no considerará estos datos al analizar
Ejemplo de estructura de archivo con un trailer

radare2
La presencia de un "avance" se puede establecer comparando los tamaños de archivo real y calculado:
$ radare2 test.elf [0x00001040]> ?v $s 0x40c1 [0x00001040]> iZ 14699
readelf
readelf no muestra información sobre la presencia de un "trailer", pero puede calcularse manualmente:
$ ls -l test.elf
Lief
La biblioteca LIEF le permite verificar la presencia de un "trailer" y agregarlo. Usando LIEF, todo parece bastante sucinto:
import lief binary = lief.parse("test")
Elfo vacío (ELF desde cero)
En Internet puede encontrar proyectos para crear un archivo ELF "manualmente", sin utilizar un compilador y un vinculador con el nombre general "ELF desde cero":
La familiaridad con estos proyectos tiene un efecto beneficioso en la absorción del formato ELF.
El elfo más pequeño
Experimentos interesantes para minimizar el tamaño del elfo se describen en los artículos:
En resumen, el cargador elf en el sistema operativo no usa todos los campos de encabezado y tablas de segmentos, y se puede colocar un código ejecutable mínimo directamente en la estructura de encabezado ELF (el código se toma del primer artículo):
; 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 $ - $$
Ensamble y obtenga un ELF de tamaño ... 45 bytes :
$ nasm -f bin -o a.out tiny.asm $ chmod +x a.out $ ./a.out ; echo $? 42 $ wc -c a.out 45 a.out
Patrón elfo
Para crear un elfo usando la biblioteca LIEF, puedes seguir los siguientes pasos (ver la receta "Elf-container"):
- tome un archivo ELF simple como plantilla;
- reemplazar el contenido de las secciones, agregar nuevas secciones;
- configurar los parámetros necesarios (punto de entrada, banderas).
En lugar de una conclusión
Añadiendo al artículo, encontramos que resultó ser algo así como una oda a la biblioteca LIEF. Pero esto no fue planeado: quería mostrar cómo trabajar con archivos ELF usando diferentes herramientas.
Seguramente hay o necesita guiones que no se mencionaron aquí; escriba sobre esto en los comentarios.
Referencias y literatura