This blog post is heavily inspired by the series making-our-own-executable-packer written by FasterThanLi.me, his twitter and his mastodon.
Introduction
In this post I’m not going to simply copy-and-past his code, I want to deeply understand what’s going on therefore I’m going to add support for the AARCH64. I’m going to emphasize on the part I didn’t know, or had problems with. All the assembly will be different from the original post, because it’s going to be arm64 assembly and not x86_64.
PS: I’m currently writing before I finished the project therefore I might fail during the process.
What’s the background ?
I won’t be as detailed as the original post, therefore i recommand you to read his series first, because it is good. If you want to have a thorough reading that’s where you’ll find it. If you don’t, you’ll just read me struggling to understand what’s going on. 😅 I’ll let myself use external tools and I assume knowledge of the memory (stack, heap, the registers and that stuff is mapped to page with some permissions when you allocate memory). That’s all, i’ll explain the rest on the way.
The code structure
Just like Amos, I’m using rust and nom to parse the ELF file (it’s heavily inspired I warned you).
I’m using the same code structure as Amos aka a lib parsing the ELF and a binary running it therefore we need to understand the structure of an ELF and then know what to do with each section.
Here is the structure of the code.
|
|
Let’s create the structure project.
|
|
We can now add the dependencies to the Cargo.toml file for the elk binary let’s see how it looks like. cat elk/Cargo.toml
|
|
and the dependencies for the delf library. cat delf/Cargo.toml
|
|
OK, we are good to go !
Before we start
Because GCC or other high-end compiler are pulling a lot of tricks to make our life easier, we need to create the simplest possible binary, and then we will try to parse it and run it (hopefully).
|
|
If you never have written assembly, here is a quick explanation of what’s going on:
.textdefines a section of code.globl _startdefines the entry point of the program as being the label_start, it’s the first line called when the program is executed_startdefines a label where we can goto (it’s like a function name) where we can jump. This label is used as the entry point of the program.
In the arm doc Procedure call standard, it is said that arguments are passed (up to 8 of them !) in the register x0… x7. You can read more about the calling convention there. The assembly code is already commented.
Now we can compile the assembly and run it using the following commands:
|
|
We see the “hello world” output, it should be ok, but let’s see the exit code to see if everything really went well
|
|
Let’s try to parse it now !
Parsing an ELF file
Before starting we need to know what’s in an ELF file. Basically an ELF file is composed of a magic number which is [0x7f, 0x45, 0x4c, 0x46] a header describing sections of the file and the sections themselves. The header also contains some info on the elf such as which target it should run on. For example here is the list of target that rustc (rust compiler) supports: rustc target list.
Let’s create a type of input for the parser.
|
|
The Result type is really horrible, but at leat we don’t write it every time! let’s create a file structure to hold our elf data.
|
|
Let’s go parsing now !
|
|
OK, we know the magic number, but not much more to be honest. But we will improvise on the way.
tag is a nom parser that checks if the input starts with the given tag.
We can see from the doc that it returns a function, we call the returned function with the input to get the remaining bytes after the tag, or an error (propagated using the ? operator).
We can the header part that doesn’t change using the following code:
|
|