Narnia Writeup
- Intro
- BUT FIRST - SPOILER WARNING!
- Narnia0 –> Narnia1
- Narnia1 –> Narnia2
- Narnia2 –> Narnia3
- Narnia3 –> Narnia4
- Narnia4 –> Narnia5
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!
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:
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:
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
:
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.
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:
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!