Intro

So, I’ve decided to try doing the Narnia wargame, and document my attempts. Let’s get started!

BUT FIRST - SPOILER WARNING!

Do not read this writeup if you plan to solve Narnia yourself!

Narnia0 –> Narnia1

First, we log in:

ssh -p 2226 narnia0@narnia.labs.overthewire.org

With the password narnia0.

We know that the data for the levels is in /narnia, so:

ls -la /narnia

Let’s read narnia0.c!

From a quick glimpse, we can deduce that the program reads 24 bytes from stdin into a 20 byte buffer. Then, it tests if val equals 0xdeadbeef. If the test passes, we get a shell with elevated privileges (only if the binary has the suid bit set!). If we lived in a perfect (but boring) world in which buffer overflows didn’t exist, the test will always fail because val is initialized as 0x41414141 at the beginning of main(). Let’s test our hypothesis!

As expected.

What we can do is try to buffer overflow. Give narnia0 20 random bytes, and then 0xdeadbeef to override var. Since we can’t just input these bytes (non-ASCII), we’ll write a quick Python script which will do this for us:

Damn

It’s alive! Kinda. It seems like the buffer overflow worked, but the order of the bytes is all messed up. This is because of little endian vs. big endian:

Let’s try turning the bytes order around:

It... worked?

The value is correct, we don’t get the failure string, why don’t we have an elevated shell?

After some tinkering, I’ve found out that if /bin/sh doesn’t have an stdin, it just terminates. Therefore, we need to find a way to give /bin/sh an stdin. We can do this by creating a command group which will run the exploit and then run cat without any parameters, so that it will wait for user input.

Boom!

Read the password file, and off we go to narnia1–>narnia2.

Narnia1 –> Narnia2

Let’s read narnia1.c:

Hey baby I hear the blues a-callin'

From a quick glimpse, it seems that narnia1 takes something from the EGG environment variable, and tries to execute it as a function.

We gave it computer gibberish, so of course it segfaulted.

Let’s read about getenv:

The getenv() function searches the environment list to find the environment variable name, and returns a pointer to the corresponding value string.

  • From the getenv(3) man page

In our case, getenv returns a pointer to EGG. If we put some malicious shellcode in EGG, we can execute whatever we want. From a quick Google search we can find plenty of shellcodes. We need to make sure that our shellcode matches our architecture, so let’s check what’s the bitness of narnia1:

I’ve found this x86 shellcode, let’s try it out.

Tossed salads and scrambled EGGs

Win.

Narnia2 –> Narnia3

No execution directives anywhere 😞

Seems like a basic echo ripoff; Get string argument and print it back to stdout. However, there’s a catch: It uses strcpy.

If the destination string of a strcpy() is not large enough, then anything might happen. Overflowing fixed-length string buffers is a favorite cracker technique for taking complete control of the machine. Any time a program reads or copies data into a buffer, the program first needs to check that there’s enough space. This may be unnecessary if you can show that overflow is impossible, but be careful: programs can get changed over time, in ways that may make the impossible possible.

  • From the strcpy(3) man page

We know that the input string gets copied to buf which is 128 bytes long. Since we control the input, that means that we can overflow the buffer and change allocated memory.

First of all, an overview of the stack:

If we overflow buf, we can override the return address to whatever we want - in this case, shellcode which runs /bin/sh.

First of all, let’s see where we need to put our return address. Let’s find out where the return address should be using this tool:

We can see that the program segfaulted at the address 0x41346541. Using the tool, we know that this is at the offset 132.

In order to successfully jump to our shellcode, we need to know where our input will be copied to.

Using GDB, we can break the program after the copy and examine the stack:

Finding the strcpy call and breaking after it

So we can see that our input gets copied to 0xffffd618.

Using this information, we can craft malicious input like so:

echo -ne "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x18\xd6\xff\xff" > exploit.txt

First, we have the shellcode. Then, we have bytes to trigger the buffer overflow. Then, at offset 132, we have the memory address to jump to in the stack.

Now, this is a bit of a guessing game, because of various changes in the stack due to environment variables. After a bit of fiddling, debugging, and examining the memory, I got it to work using the following input:

echo -ne "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xa8\xd5\xff\xff" > exploit.txt

However, there was a problem:

We’re still narnia2! That’s because GDB can’t run suid programs as actual suids unless its running as root. More here.

Well, let’s run it outside of GDB:

According to some very helpful StackOverflow posts, GDB sets some environment variables which invalidate our hardcoded address. Let’s try:

We can see that the address has changed to 0xffffd5c8. Quickly changing our malicious input and…

Hard one!

Narnia3 –> Narnia4

According to the usage (and to the code), this program copies from an input file given as a parameter to /dev/null.

There’s an strcpy call, so at first I thought “Easy, overflow the input to jump to the shellcode”. However, on closer inspection, there are no return directives in the program. So, what can we do?

We can use the strcpy call to override ofile to be whatever we want!

However, we can’t just copy /etc/narnia_pass/narnia3, because the filepath is shorter than 32 bytes, which is the length of ifile. However, we can create a symlink:

Let’s try our exploit! First, check if the symlink works:

Nope. It’s a permissions issue, the current directory is not world readable.

Now let’s try the overflow…

Off by one error 😫

After fixing the length, we can try writing to a real file. However, the buffer is only 16 bytes long, and our current directory is 19 bytes long. After finding a short enough available dir, let’s try again:

Why did the open check fail? Because of the open flag bits. The current open call only opens a file for reading and writing, and doesn’t create it if it doesn’t exist (for this the call needs O_CREAT).

Interesting!

The program managed to open our ofile, but failed to open the input. In fact, it’s not the input we expected at all! This happened because there’s no null terminator after the ending of our supposed ifile.

Well, let’s try to input a null terminator!

After some research, I found out that bash just ignores null bytes when given as input. I also tried to do this with os.execve, but execve also ignores null bytes.

After a good night’s sleep, it dawned on me:

The answer was in front of my eyes all along 😅 cat /tmp/daouj/o and off to the next level!

Narnia4 –> Narnia5

Still working on it 🤘

I’ll continue updating this page!