📜 ⬆️ ⬇️

Recipes for ELFs

image


There is quite a bit of information in Russian about how to work with ELF files (Executable and Linkable Format is the main format of Linux executable files and many Unix systems). We do not claim full coverage of all possible scenarios of working with elves, but we hope that the information will be useful in the form of a reference book and a collection of recipes for programmers and reverse engineers.


It is understood that the reader is familiar with the ELF format at the basic level (otherwise we recommend the cycle of articles Executable and Linkable Format 101 ).


Under the cat will be listed the tools for the work, described techniques for reading meta-information, modifications, checks and breeding creating elves, as well as links to useful materials.


- I am also an elf ... Blue to red ... Elves are very patient ... Blue to red ... And we are elves! .. Blue to red ... Magic is the only trouble ...
(c) The small kingdom of Ben and Holly

Instruments


In most cases, examples can be run on both Linux and Windows.


In the recipes we will use the following tools:



Test Elves


We will use the simple ELF file from nutcake 's PieIsMyFav on crackmes.one as the “test subject,” but any member of the “elven” family will do. If the finished file with the required characteristics was not found in the public domain, then a way to create such an elf will be given.


"Free" elves can also be found on the links:



Reading, receiving information


File type, title, sections


Depending on the task of interest may be:



010Editor


HEX-editor 010Editor provides a template system. For ELF files, the template is called, oddly enough, ELF.bt and is in the Executable category (menu Templates - Executable).
Of interest may be, for example, the entry point to the executable file (entry point) (recorded in the file header).


image


readelf


The readelf utility can be considered a de facto standard for obtaining information about the ELF file.



Team Result
 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 


Team Result

For readability, the addresses are given in 32-bit format:


 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 


Team Result

For readability, the addresses are given in 32-bit format:


 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) 


Team Result

The output is shortened for readability:


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

The -W option is needed to increase the width of console output (by default, 80 characters).


LIEF


You can read the header and section information using the Python code and the LIEF library (it provides APIs not only for 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) 

Compiler info


For information on the compiler and build, see the .comment and .note .


objdump


 $ objdump -s --section .comment simple 

Team Result
 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 

Team Result
 String dump of section '.comment': [ 0] GCC: (Debian 8.2.0-9) 8.2.0 

 $ readelf -n simple 

Team Result
 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)) 

I will calculate you by ... RPATH


Elves can save paths to search for dynamically linked libraries. In order not to set the LD_LIBRARY_PATH system variable before starting the application, you can simply “sew” this path into the ELF file.


To do this, use the entry in the .dynamic section with the DT_RPATH or DT_RUNPATH type (see the Directories section .dynamic Searched by the Runtime Linker in the documentation).


And be careful, young developer, do not “sleep” your project directory!


How does RPATH appear?


The main reason for the appearance of RPATH records in an elf is the -rpath linker option for searching for a dynamic library. Like that:


 $ gcc -L./lib -Wall -Wl,-rpath=/run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/ -o test_rpath.elf bubble_main.c -lbubble 

Such a command will create in the .dynamic section a .dynamic record with the value /run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/ .


readelf


View elements from the .dynamic section ( .dynamic is one of them) like this:


 $ readelf -d test_rpath.elf 

Team Result

For readability, the result of the command is abbreviated:


 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


With the help of the LIEF library, you can also read the RPATH entry in the elf:


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

Read about the .dynamic section


Check Elf for security


The security check script checksec.sh from researcher Tobias Klein (author of A Bug Hunter's Diary ) has not been updated since 2011. This script for ELF files checks for the presence of the options RelRO (Read Only Relocations), NX (Non-Executable Stack), Stack Canaries, PIE (Position Independent Re- ferent Executables) and uses the readelf utility for its work.


You can make your analog on the Python and LIEF knee (slightly shorter than the progenitor and with the additional check of the separate-code option):


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

"Crude code" from elf (binary from ELF)


There are situations when “elf clothes” in the form of an ELF structure are not needed, and only the “bare” executable code of the application is needed.


objcopy


Using objcopy is probably familiar to those who write firmware:


 $ objcopy -O binary -S -g simple.elf simple.bin 


LIEF


No magic Just take the contents of the loaded sections and blind them binary:


 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 - demangled function names


In ELFs created from C ++ code, the function names are decorated (mangled) to simplify the search for the corresponding class function. However, reading such names in the analysis is not very convenient.


Test elf


nm


You can use the nm utility from the binutils suite to present names in a readable form:


 #        $ 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


The output of the names of characters in demanganized form using the library LIEF:


 import lief binary = lief.parse("demangle-test-cpp") for symb in binary.symbols: print(symb.name, symb.demangled_name) 

Assembling, recording, modifying an elf


Elf without metainformation


After the application is debugged and released into the wild world, it makes sense to remove the meta-information:



Removing character information


Symbolic information is the names of objects and functions. Without it, the reverse of the application is a bit more complicated.


strip


In the simplest case, you can use the strip utility from the binutils suite. To delete all symbolic information, just run the command:



sstrip


To carefully remove character information (including unnecessary zero bytes at the end of the file), you can use the sstrip utility from the ELFkickers suite . To delete all symbolic information, just run the command:


 $ sstrip -z simple 

LIEF


Using the LIEF library you can also make a quick strip (the symbol table is deleted - the .symtab section):


 import lief binary = lief.parse("simple") binary.strip() binary.write("simple.stripped") 

Deleting a section table


As mentioned above, the presence / absence of a partition table does not affect the operation of the application. But at the same time, without the section table, the reverse of the application becomes a bit more complicated.
Let's use the LIEF library under Python and an example of deleting a partition table :


 import lief binary = lief.parse("simple") binary.header.numberof_sections = 0 binary.header.section_header_offset = 0 binary.write("simple.modified") 

Modifying and deleting RPATH


chrpath, PatchELF


To change RPATH under Linux you can use the chrpath utilities (available on most distributions) or PatchELF .



LIEF


The LIEF library also allows you to both change and delete the RPATH record.



Obfuscation of character information


To complicate the reverse of the application, you can save symbolic information, but confuse the names of the objects. We use the elf crackme01_32bit from crackme01 by seveb as the test subject .


A simplified version of an example from the LIEF library might look like this:


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

As a result, we get:


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

Function substitution via PLT / GOT


Also known as ELF PLT INFECTION .


In order not to copy-paste, just leave links on the topic:



Change entry point


It can be useful when creating patches, installing hooks and other dynamic instrumentation, or for invoking hidden functions. We use crackme01_32bit from crackme01 by seveb as a test subject .


radare2


radare2 starts in recording mode ( -w option) - the changes will be made to the original file:


 $ ./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 patching


Let's take novn91's crackmepal as a simple test subject . When run without parameters, the program displays:


 $ ./crackmeMario usage <password> 

When starting with the parameter-arbitrary string, it is issued:


 ./crackmeMario qwerty try again pal. 

Let's make a patch so that the program displays the message “good job! now keygen me! ”


radare2


radare2 can patch any formats it supports. At the same time, it is possible to describe patches in text format:


 # Rapatch for https://crackmes.one/crackme/5ccecc7e33c5d4419da559b3 !echo Patching crackme 0x115D : jmp 0x1226 

Apply this patch can command:


 $ r2 -P patch.txt crackmeMario 

Read about patching code via radare2:



LIEF


LIEF allows you to patch an elf (rewrite bytes) to the specified virtual address. A patch can be in the form of an array of bytes or in the form of an integer value:


 import lief binary = lief.parse("crackmeMario") binary.patch_address(0x115D, bytearray(b"\xe9\xc4\x00\x00\x00")) binary.write("crackmeMario.patched") 

After applying the patch, the program will display:


 $ ./crackmeMario.patched good job! now keygen me! 

Add section to ELF


objcopy


objcopy allows you to add a section, but this section will not belong to any segment and will not load into RAM when the application starts:


 $ objcopy --add-section .testme=data.zip \ --set-section-flags .testme=alloc,contents,load,readonly \ --change-section-address .testme=0x08777777 \ simple simple.patched.elf 

LIEF


The LIEF library allows you to add a new section and its corresponding segment ( loaded=True flag) to the existing ELF:


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

Edit section


objcopy


objcopy allows you to replace the contents of the section with data from a file, as well as change the virtual address of the section and flags:


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

Delete section


objcopy


objcopy allows you to delete a specific section by name:


 $ objcopy --remove-section .testme simple.testme.elf simple.no_testme.elf 

LIEF


Deleting a partition using the LIEF library looks like this:


 import lief binary = lief.parse("simple.testme.elf") binary.remove_section(".testme") binary.write("simple.no_testme") 

Elf container


The recipe is inspired by the Gremlins article and ELF magic: what if the ELF file is a container? . There are also man's about the elfwrap utility from Solaris, which allows you to create an ELF file from arbitrary data, and the ELF format is used simply as a container.


Let's try to do the same in Python and LIEF.
Unfortunately, at the moment the LIEF library does not know how to create an elf file from scratch, so you need to help her — create an empty ELF template:


 $ echo "" | gcc -m32 -fpic -o empty.o -c -xc - $ gcc -m32 -shared -o libempty.so empty.o 

Now you can use this template for filling data:


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

Elf "with a trailer"


ELF-format does not impose restrictions on data that is in the file, but does not belong to any segment. Thus, you can create an executable file that will have something stored after the ELF structure. This something will not be loaded into RAM during execution, but it will be written to disk, and at any moment it can be read from the disk.



An example of the structure of the file "with a trailer"
image


radare2


The presence of a "trailer" can be established by comparing the actual and calculated file size:


 $ radare2 test.elf [0x00001040]> ?v $s 0x40c1 [0x00001040]> iZ 14699 

readelf


readelf does not show information about the presence of a "trailer", but can be calculated manually:


 $ 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


The LIEF library allows you to both check for the presence of a “trailer” and add it. With LIEF, everything looks quite concise:


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

ELF from scratch


On the Internet, you can find projects to create an ELF file "manually" - without using a compiler and linker under the general title "ELF from scratch":



Familiarity with these projects has a beneficial effect on the absorption of the ELF format.


Smallest elf


Interesting experiments with minimizing the size of an elf are described in the articles:



In short, the OS loader elf uses far from all the header fields and tables of segments, with some minimal executable code can be placed directly into the ELF header structure (the code is taken from the first 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 $ - $$ 

Assemble and get ELF size ... 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 

Elf by pattern


To create an elf using the LIEF library, you can take the following steps (see the recipe "Elf container"):



Instead of conclusion


While writing the article, we found out that something like Ode to the LIEF library turned out. But it was not so planned - I wanted to show ways to work with ELF files using different tools.


Surely there are or need scripts that were not mentioned here - write about it in the comments.


References and literature



')

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


All Articles