Source: https://moabid.me/blog/do-you-speak-elf/ Published: 2023-10-25 Domains: Systems Programming, Offensive Security Topics: Binary Formats, Reverse Engineering Languages: C

Ahoy, folks! Welcome to the mystical land of Linux Kernel, where the quest for the holy grail, also known as the ‘one piece’ 🤠, is reserved for only the bravest out there, yee-haw! But hold your horses, ’cause in this wild universe, we’re diving deep into the realm of ELF. Nope, not the magical creatures 🧝, but the ELF file format, the fancy schmancy standard for running things in the Linux world. So saddle up, ’cause today, we’re playing Kernel, trying to manually make this ELF file go vroom-vroom!

a. Preparation phase. b. Stack Population phase.
Let’s get the technical specs straight. The ELF format is like that one friend who’s super chill, not tied down to any specific group or clique — doesn’t care if you’re ARM, x86_64, 32-bit, or 64-bit. It’s a format with no biases. Once it’s compiled, it’s all about that CPU architecture life — no time for mingling with other architectures, very loyal indeed. Now, the real star of the show is its anatomy. Let’s dive into that banner once more, shall we?

Take this little 64-bit binary on an x86_64 architecture as an example. When we crack it open, we find it’s made up of three crucial components, the ELF Header, the Sections, and the Segments. Each of these has its own gig in the grand linking/loading show of the ELF lifecycle.
readelf -h your_binary
. The Sections:
readelf -s your_binary
. Segments :
readelf -S your_binary
All the processing of ELF binaries is done by the Kernel, and thro this section I will try to explain and provide links to the source code for reference. If you are reading this, I assume you are already familiar with the kernel source code, if not this is your chance, get yourself comfortable reading the Kernel source code (Every explained later can be allocated starting from fs/binfmt_elf.c in the Linux source code). The starting point is exactly load_elf_binary().
The Kernel is responsible for all the intricacies involved in handling ELF binaries. In this section, I aim to elucidate this process and offer references to the source code. If you’re perusing this, I assume you possess some familiarity with the kernel source code. If not, consider this an opportunity to delve into the rich world of the Kernel source code. You can begin exploring from fs/binfmt_elf.c in the Linux source code, and in today’s journey we commence precisely from the function load_elf_binary().
Again diving into the kernel source code should become a habit, especially if you are keen on unraveling the inner workings of the system.
Note: Just to be clear, thro these explanation I am assuming we have a statically compiled binary.
Focuses on three critical aspects:
2. Flushing the Old kernel State:
3. Updating the new State:
4. Memory Setup :
5\. Installing the binary credentials:
a. Inserting the argc first. b. Inserting the argv. c. Inserting the environ (environment variable). d. Inserting the auxiliary vector just after.
For further clarity, check the picture below :

When dealing with a statically linked ELF binary that does not involve any dynamic loading, the ELF interpreter isn’t required during the execution process. Instead, the operating system’s loader directly starts the execution of the program which eventually means passing control to the entry point specified by the AT_ENTRY .
Every program that runs on a Linux system passes through the portal of execve(); as such it’s a key piece of kernel functionality that’s worth understanding in detail. ELF is a complicated format, but fortunately, the kernel can ignore most of that complexity — it only needs to understand just enough ELF to load segments into memory, and to invoke a user space run-time linker program to finish the job of assembling a complete running program. Okay Folks! Last but not least, I just wanna thank you for reading and sticking around til the end ❤️
Originally published on Medium.