Everything about linux
Welcome back to the eighteenth and final part of "Making our own executable packer".
In the last article, we had
a lot of fun. We already had a "packer" executable,
minipak, which joined
stage1 (a launcher), and a compressed version of whichever executable
we wanted to pack.
In the last article, we
did foundational work on
minipak, our ELF packer.
It is now able to receive command-line arguments, environment variables, and
auxiliary vectors. It can parse those command-line arguments into a set of
options. It can make an ELF file smaller using the LZ4 compression
algorithm, and pack
it together with
stage1, our launcher.
And we're back!
In the last article, we thanked our old code and bade it adieu, for it did not spark joy. And then we made a new, solid foundation, on which we planned to actually make an executable packer.
You're still here! Fantastic.
I have good news, and bad news. The good news is, we're actually going to make an executable packer now!
Good morning, and welcome back to "how many executables can we run with our custom dynamic loader before things get really out of control".
I write a ton of articles about rust. And in those articles, the main focus is about writing Rust code that compiles. Once it compiles, well, we're basically in the clear! Especially if it compiles to a single executable, that's made up entirely of Rust code.
Welcome back and thanks for joining us for the reads notes... the thirteenth installment of our series on ELF files, what they are, what they can do, what does the dynamic linker do to them, and how can we do it ourselves.
In our last installment of
"Making our own executable packer", we did some code cleanups. We got rid of
a bunch of
unsafe code, and found a way to represent memory-mapped data
In the last article, we managed to load a program (
hello-dl) that uses a single
dynamic library (
libmsg.so) containing a single exported symbol,
Let's pick up where we left off: we had just taught
elk to load
not only an executable, but also its dependencies, and then their
dependencies as well.
Up until now, we've been loading a single ELF file, and there wasn't much
structure to how we did it: everyhing just kinda happened in
main, in no
In our last article, we managed to load and execute a PIE (position-independent executable) compiled from the following code:
; in `samples/hello-pie.asm` global _start section .text _start: mov rdi, 1 ; stdout fd lea rsi, [rel msg] mov rdx, 9 ; 8 chars + newline mov rax, 1 ; write syscall syscall xor rdi, rdi ; return code 0 mov rax, 60 ; exit syscall syscall section .data msg: db "hi there", 10
The last article, Position-independent code, was a mess. But who could blame us? We looked at the world, and found it to be a chaotic and seemingly nonsensical place. So, in order to blend in, we had to let go of a little bit of sanity.
In the last article, we found where code was hiding in our
executable, by disassembling the whole file and then looking for syscalls.
In part 1, we've looked at three executables:
sample, an assembly program that prints "hi there" using the
entry_point, a C program that prints the address of
/bin/trueexecutable, probably also a C program (because it's part of GNU coreutils), and which just exits with code 0.
Executables have been fascinating to me ever since I discovered, as a kid,
that they were just files. If you renamed a
.exe to something else, you
could open it in notepad! And if you renamed something else to a
you'd get a neat error dialog.
Everybody knows how to use files. You just open up File Explorer, the Finder, or a File Manager, and bam - it's chock-full of files. There's folders and files as far as the eye can see. It's a genuine filapalooza. I have never once heard someone complain there were not enough files on their computer.
I'll keep updating this article as I go, just to put stuff in all the same place.