Huhh which column is the PID? Let's include the header in the output by
changing our pattern to: must contain either “PID” or “elk”. Since “or” in
regular expressions is the same as “pipe” in shell, we'll wrap our pattern
in double quotes:

Helpfully, these three areas are annotated with “heap” - and that makes sense! We
did heap-allocate the memory for the data field of ProgramHeader. Our code
is stored contiguously into a Vec<u8>, since we used to_vec on a &[u8].

There's something funny about these areas, too - only a5000..a6000 is
executable, whereas a2000..a5000 and a6000..c3000 are just read+write.

This seems too close to be a coincidence. To find out what's going on, let's
also pause before we make code executable:

AhAH! So there was one unified heap, with read+write protection, and when we made
code executable, it split into three regions - two read+write, as before, and a
4KiB one, in the middle, that's now read+write+execute.

This all makes sense.

But we're still no closer to finding out why it doesn't print “hi there”.

At this point, since the program expects input, we need to press “Esc”,
“Right arrow”, and “Enter” to switch to the program output pane, then press
“Enter” twice. Then, “Esc”, “Left arrow”, and “Enter” again to switch back to
the GDB command prompt.

We are now in the jmp function. We can inspect addr:

(gdb) print addr
$1 = (*mut u8) 0x5555555e4000 "\277\001\000"

Very good. Let's stepi our way forward.

We're about to move 0x1 into the edi register - that's good. Note that in
our original assembly, we had mov rdi, 1. But rdi and edi are names for
the same register, only as a 64-bit register or a 32-bit one.

I'm guessing nasm picked edi because our constant did fit in a 32-bit
integer and it was more compact.

Cool bear's hot tip

That is correct.

mov rdi, 1 assembles to 48 c7 c7 01 00 00 00

mov edi, 1 assembles to bf 01 00 00 00

Onward!

So, edi should now be set to 1, which we can check with info registers edi,
or info reg edi for short:

(gdb) info reg edi
edi 0x1 1

And it is! And we're just about to, uh, movabs something to rsi, and the thing
we're moving is a constant equal to… 0x402000.

There's our perp! I mean, our address! It's supposed to be mapped from the ELF file
into memory! And the file range is 2000..2009, which is a suspiciously small range,
I wonder what could be hiding in th…

It's pretty clear what's going on now. Our ELF file, samples/hello, contains not
only x86 instructions (at 1000..1025), but also some data (at 2000..2009), and
when we run it via elk, the data is definitely not at the address it expects.

When malloc is not enough

Good job everyone, I can't believe this series was so short, but we did it,
we wrote a program that packs.. well, that executes, any ELF progr… well,
ELF programs that do not have any data and uhh… yeah okay we're not quite
done.

I do think we should take some time to celebrate our achievement properly,
though. Working our way through the innards of executable files is no small
feat, and we're doing great so far. I think we should reward ourselves with
a cookie, maybe a warm drink.

Ahh. Success.

Okay, back to work. We've executed samples/nodata without exec, sure. But
can we execute samples/hello?

The main problem is that the code in hello assumes there's going to be some
data at 0x402000. I don't suppose there's a way to… allocate memory at a
given address, right?

So, as it turns out, mmap is crazy powerful. It's mostly brought up when
we want to map part of a file into memory (which, as we've seen before, is what
the operating system definitely does when running an executable). But it can
also create “anonymous” mappings, that aren't backed by any particular file.

It also usually picks its own address (so, 0x0 is passed to addr, and then
whatever it returns is what we get), but it can also create “fixed” mappings,
at a precise address - and that sounds like exactly what we want right now.

“But Amos” - I can already hear you interject - “where do these come from? Is
there a reference online with all these segment types?” And the answer is no.
I've had to go hunt for the last three hours all over the internet for you.
This is your reference.

ErrUnknown isn't exactly the most expressive error but hey, no abstraction
is perfect. It's true that we're trying to map a memory area whose size isn't
a multiple of 4KiB, but the first parameter to MemoryMap::new is min_len, so
I'm assuming it can round up as needed.

(For brevity, I've also commented out the part that disassembles the code -
turns out, the code section is pretty long in entry_point. But that's to be
expected - C is not very low-level, so it generates a lot of assembly.)

We'll just make the segment slightly bigger than requested (ie. we'll make it start
earlier, on the left-adjacent 4KiB boundary), and if we're careful to copy the segment
data in the right place, everything should work out.

I don't see any movabs, which is great. In fact, most memory operations
seem to be computed: [rip+0x236], [rip+0x1bf], etc.

GDB helpfully annotates those, and we can see that 0x4012c0, 0x401250
etc. are all in mapped memory regions. That seems good!

And here we are. Baby's first call.

Is the annotation accurate? Let's see what's in the rip register:

(gdb) info reg rip
rip 0x401098 0x401098

Very well! If we add 0x2f42 to it, we get…

(gdb) print 0x401098+0x2f42
$1 = 4210650

…a decimal value, apparently. Luckily, we can get GDB to print hexadecimal
values by using formatting directives, like so: print/:format. In our case,
we want hexadecimal, the directive is just x:

(gdb) print/x 0x401098+0x2f42
$2 = 0x403fda

That's uhhh better, but still not what GDB is showing us.

What is rip anyway?

The instruction pointer is called ip in 16-bit mode, eip in 32-bit mode,
and rip in 64-bit mode. The instruction pointer register points to the memory
address which the processor will next attempt to execute.

10.6 Examining Memory
You can use the command x (for “examine”) to examine memory in any of several formats, independently of your program’s data types.
x/nfu addr
x addr
x
Use the x command to examine memory.
n, f, and u are all optional parameters that specify how much memory to display and how to format it; addr is an expression giving the address where you want to start displaying memory. If you use defaults for nfu, you need not type the slash ‘/’. Several commands set convenient defaults for addr.
n, the repeat count
The repeat count is a decimal integer; the default is 1. It specifies how much memory (counting by units u) to display. If a negative number is specified, memory is examined backward from addr.
f, the display format
The display format is one of the formats used by print (‘x’, ‘d’, ‘u’, ‘o’, ‘t’, ‘a’, ‘c’, ‘f’, ‘s’), and in addition ‘i’ (for machine instructions). The default is ‘x’ (hexadecimal) initially. The default changes each time you use either x or print.
u, the unit size
The unit size is any of
b
Bytes.
h
Halfwords (two bytes).
w
Words (four bytes). This is the initial default.
g
Giant words (eight bytes).

ahAH! So we want to display one (n=1) hexadecimal (f=x) giant word (u=g).

Let's go:

(gdb) x/1xg 0x555555557fe0
0x555555557fe0: 0x00007ffff7df2060

That's better! First off, that's not 0x0, so the program is not about to
segfault. Well, that is, if it's mapped to anything.

It does! (Without the part after the @). And you can see how the target of
our jump and the address of __libc_start_main align:

0x00007ffff7df2060
0x0000000000027060
^^^

In other words, they're equal modulo 0x1000.

I know, I know, that's a lot to take in. What I'm gathering from this is that:

entry_point's entry point points to _start

_start is defined in the code section of entry_point

after some moving around, it jumps to the address stored
in another segment

that address is 0x0 when we run it through elk

but it's not when run normally. when run normally, it's
a valid memory area that's mapped to some .so file somewhere.

The valley of the shadow of ELF

Well, I take it back.

This series is never going to end.

In 2060, when I'm 70, and everybody will have switched to using Fuschia on
the desktop, my friends will still poke fun at me: “Hey amos, remember your
ELF series? When's it gonna end?", and I'll feign a smile, but inside I will
be acutely, painfully aware that I have angered the binary gods and that I
should have left well enough alone.

Serves me right.

But hey, at least ./samples/hello executes fine under elk right? That's
a win! Let's run it again just to get our spirits up again:

And samples/hello has a movabs. And the data it expects to be at
0x402000 is now actually at 0x802000. That won't work.

It's kind of annoying when executables do that. When their code depends
on the position in memory at which they're mapped.

It doesn't even really make sense - to me. An executable is composed of many
object files, right? If you have a foo.c source file and a bar.c source file,
they get compiled to separate foo.o and bar.o files, right?

And these are kinda cobbled together by the linker, right? That's what a linker
does. And then you get a single executable.

Now if the code that's in .o files is position-dependent… how does the
compiler know which address to pick so that multiple .o files don't clash?

And if it doesn't know, how does the linker do to arrange .o files so that they
movabs from the right address? Does it look for movabs instructions and
modify them?

At this point in the article, nothing really makes sense anymore.
Machine code magically turns from 0x0 into non-0x0. Symbols
are undefined, then defined again. Files are mapped left and right.
In the distance, sirens.

It's a wonderful day for PIE

So we better search for the title of the article on DuckDuckGo or something.

In computing, position-independent code (PIC) or position-independent
executable (PIE) is a body of machine code that, being placed somewhere in
the primary memory, executes properly regardless of its absolute address. PIC
is commonly used for shared libraries, so that the same library code can be
loaded in a location in each program address space where it will not overlap
any other uses of memory (for example, other shared libraries). PIC was also
used on older computer systems lacking an MMU, so that the operating system
could keep applications away from each other even within the single address
space of an MMU-less system.

That's it. I give up. I quit. Thanks for the support everyone, but I can't take
this anymore. Nothing makes sense, up is down, files exist and are executables
but they won't execute, fuck all this, I'm out.

Cool bear's hot tip

Hey what's that “interpreter” part of file's output?

I don't know cool bear. I don't know what interpreter is because I am tired,
and I'm starting to get a headache, and computers were almost certainly a mistake,
and, and, and I guess it's a library or something, because it ends in .so.1,
and if we check it we can see that:

In 64-bit mode, NASM will by default generate absolute addresses. The REL
keyword makes it produce RIP–relative addresses. Since this is frequently the
normally desired behaviour, see the DEFAULT directive (section 6.2). The
keyword ABS overrides REL.

When we loaded our program, it was put in memory somewhere other than
what the program headers said it should be. And that didn't turn out so well.

Sure, the machine code we'd seen at first seemed like it would work. But then
we tried to use some data, and that didn't work. So we stopped using data,
and that worked.

But a program that uses data still does work, when we don't try to run it via
elk (our command-line tool). In fact, when we debug it, the disassembly
shown by GDB does not match the disassembly shown by ndisasm and
objdump.

If we change our assembly a little though, we can get it to work. Because
we're accessing data by addressing memory relative to where the code is.
And they are in the right place, relative to each other.

But, if we're being honest, there are still a lot of questions left
unanswered.