Do you speak ELF?

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

A hexdump of the first bytes of an ELF binary, with the magic, class, machine type and entry point each boxed in a different colour

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!

Jackie Chan holding his head in disbelief, captioned "MIND = BLOWN"

Agenda :

  1. ELF Architecture
  2. Processing ELF binaries

a. Preparation phase. b. Stack Population phase.

  1. Start? What’s next
  2. Summary

ELF Architecture

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?

Ange Albertini's ELF101 poster, dissecting simple64.elf into its ELF header, program header table, sections and section header table

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.

  1. It’s like the file’s signature, proudly waving the binary metadata flag with details about the architecture, version, entry point, and more.
  2. Holds the binary metadata, like the architecture, the version, entry point, different offsets, and more …
  3. Think of it as the ID card for the binary but with a lot more secrets tucked inside.
readelf -h your_binary

. The Sections:

  1. These contain the deets necessary for linking the target object file to make that sweet, sweet executable.
  2. Remember, these are the heroes of the link time, but they slink into the shadows at runtime.
  3. And don’t forget the Section Header Table, a sort of directory for all the important stuff.
readelf -s your_binary

. Segments :

  1. These are the ones you need for the runtime show, not so much for the linking party.
  2. They hold all the crucial binary intel, sliced and diced into chunks, like the .text that’s got all that juicy binary code.
  3. Think of them as the stagehands making sure everything runs smoothly behind the scenes.
readelf -S your_binary

Processing ELF binaries

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.

Preparation phase:

Note: Just to be clear, thro these explanation I am assuming we have a statically compiled binary.

  1. Processing Program Header Entries:

Focuses on three critical aspects:

2. Flushing the Old kernel State:

3. Updating the new State:

4. Memory Setup :

5\. Installing the binary credentials:

Stack Populating

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 :

A stack dump at process start: environment strings, arguments, the ELF auxiliary vector, then environ, argv and argc

Start? What’s next

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 .

Summary

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 ❤️

Reference


Originally published on Medium.