ELF (Executable and Linkable Format) files are the backbone of executables and shared libraries on Linux systems. While typically generated by compilers and linkers, there are times when you might need to delve into their structure and make direct modifications. In this repo, we'll explore a hands-on approach to editing an ELF shared library without relying on traditional tools.
Not there yet.
$ strace ./hello_lib.so
execve("./hello_lib.so", ["./hello_lib.so"], 0x7ffc6fea7010 /* 58 vars */) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x7fe328522000} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)
[manas@arch elf-play (master)]$ strace ./hello
hello.c hello_c hello_lib.so hello_lib_so.aout hello_lib_so.aout.hex hello_lib_so.aout.txt
$ strace ./hello_lib_so.aout
execve("./hello_lib_so.aout", ["./hello_lib_so.aout"], 0x7fff0bbda0f0 /* 58 vars */) = 0
brk(NULL) = 0x56456675f000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffe37251d00) = -1 EINVAL (Invalid argument)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x2a8} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)
I ran ./dev.sh
. It contains the steps.
Following sections try to explain the idea.
- File Structure: ELF files are organized into headers, sections, and segments.
- Program Headers: These describe crucial segments like code, data, and dynamic linking information.
- Interp Segment: It specifies the program interpreter, essential for shared libraries.
The goal is to manually add an interp segment to a shared library that initially lacked one. Here's a breakdown of the steps involved:
-
Inspecting Headers:
- Use
readelf --headers
to examine the original library's header information. - Note the offset of the interp segment in the program headers.
- Use
-
Extracting Interp Data:
- Use the
dd
command to extract the interp segment bytes from an executable containing it. - Convert the raw bytes to human-readable hexadecimal format using
xxd
.
- Use the
-
Preparing the Library for Editing:
- Convert the library to a text representation using
xxd -i
. - This facilitates manual hex editing.
- Convert the library to a text representation using
-
Inserting the Interp Segment:
- Locate the correct offset within the text representation.
- Paste the interp segment bytes from step 2.
-
Updating Program Headers:
- Modify the number of program headers to reflect the added segment.
-
Setting the Entry Point:
- Use
readelf --symbols
to find the address of themain
function. - Edit the library's entry point to point to this address.
- Use
-
Converting Back to Binary:
- Use
xxd -r -p
to transform the edited text file back into a binary. - Inspect the modified library with
readelf
to ensure integrity.
- Use
- Byte Ordering: Pay close attention to little-endian byte ordering when editing values directly.
- Tools for Handling ELF Files: While manual editing is possible, consider using libraries like
libelf
for more complex tasks.
This tweet
Today I learnt, while examining the ELF headers of an OCaml program (and likely, for any other language using gcc) we have been building shared libraries :D Not executables!
— prometheansacrifice (@ManasJayanth) January 27, 2024
readelf displays Type as DYN pic.twitter.com/WJSoC73AtT
This post demonstrates the intricacies of working with ELF files at a low level. While manual editing can be valuable for specific purposes, it's crucial to have a solid understanding of ELF structures and tools to ensure file integrity.