Narnia a series of reverse engineering puzzles developed by OverTheWire and is an introduction to reverse engineering. Below is the methodology used to solve these puzzles.
Login and Execution
In the previous level, Narnia 0, we covered the difference between Source Code and Executable Code. All Narnia binaries and source files are located in
/narnia/. Using the password we obtained in Narnia 0, we can now login as user
narnia1 and change to the narnia directory. Then reviewing the source code and the binary, we can determine how to exploit this particular level. Let’s get started.
When listing the directory we can see all levels of Narnia. The concern of this walkthrough is
narnia1. Viewing this directory listing we can see that
narnia1 when executed runs as the user narnia2 and is executable by the user narnia1. Running
./narnia1we see the response “Give me something to execute at the env-variable EGG”. With this we can gleam some information from it’s output. Env-Variable is expecting the environment variable
EGG to contain data. Let’s break down the source code.
Source code and breakdown
cat is an essential tool for any Linux distribution and can assist users in reading files. This in turn allows us to read the
narnia1.c source file. Running the command
cat narnia1.c from the
/narnia/ directory we get the following source code.
If you’ve never seen the C programming language before it might be a little foreign to you. Let’s break it down line by line and hopefully get an idea of what’s happening in our binary. I have copied the code to VSCode for ease of commenting.
#include <stdio.h> is used to import all standard input and output functions defined in the stdio header file, such as
Every C program has a
main() function and is the entry point of the binary once the source code has been compiled. Within
main is where we get to the real meat of the program.
main() we see the above code which declares the
ret()function as a prototype to use further down in the code. Because C programming is procedural, defining it first will save us from an error upon compilation. Now let’s visit the
The above 4 lines of code are a conditional statement and will execute a specific set of instructions in the code. This case it is pulling in the environment variable “EGG”, if it is empty, or rather
NULL in this case, it will print “Give me something to execute at the env-variable EGG” and
exit(1) will then exit the program.
If the environment variable “EGG” has data, the conditional statement covered in lines 6-9 will be skipped and will then print “Trying to execute EGG!”. The program will assign the data in the environment variable “EGG” into
ret, then execute the data, and then exit.
Hopefully with all that explained above we can start diving into exploiting the
narnia1 binary using what we’ve gathered from the code breakdown.
Debugging and Analysis
Leaving the “EGG” environment variable empty we receive the expected output based on the code review. But what happens when we give it something to execute. To set an environment variable in Linux we can run the simple command
export EGG=cd. To view the env variable we just set, run
With data now set on variable “EGG”, we can now run
./narnia1 and see what happens.
Look at that, we have a “Segmentation fault”. This means we have an abnormal condition that caused the program to exit. Lucky for us, GDB is on the narnia host, loading the binary into gdb using
gdb narnia1 we can start looking at what the binary is doing. Once gdb has loaded, we can run the command
disassemble main which is going to print the memory addresses and assembly code for everything happening in the
main() function we saw above.
Your memory addresses will differ so refer to the image above for the rest of the article. From memory address
0x0804848f this is the conditional statement discussed previously that prints a msg to the screen and exits the program if “EGG” has no data. Since we have data in the “EGG” variable it will be skipped and move to the
0x0804849b memory location where it will print “Trying to execute EGG!” to the console. Moving down to memory address
0x080484b3, this is where the program calls getenv(“EGG”) in the program, makes space on the stack, and assigns the data in “EGG” to the EAX register. Looking at the
0x080484b6 memory address we see the program calling
*%eax which is the ret() function being executed. Our first stop to see whats happening is to set a break point on that call and see what is on the stack.
Using the memory address of eax, we set a breakpoint. This will allow us to stop program execution as the environment variable “EGG” is put on the stack. Set your breakpoint on the (gdb) prompt using
break *0x080484b6. Then proceed to run the command by simply typing
runat the (gdb) prompt. The program should stop at the set breakpoint.
This allows some analysis of the stack. While we are stopped on the
0x080484b6memory address, let’s view what’s on the stack at that address. Using the
x/25x $eax command we can see (in hex) the next 25 values that are set for the eax register.
Looking at the first address on the left (
0xffffdea7), we see the first value
0x5f006463. If we convert this from hex to ascii, we can see the last two bytes
63 convert to
c respectively. If you recall from earlier, we made our “EGGS” variable equal to
cd, with memory being in little-endian format this matches our environment variable data. Knowing that
./narnia1 is executable by user narnia1 and runs as user narnia2, let’s use this ability to put data on the stack to get a shell as user narnia2.
We know that shellcode is what we are after since we want to spawn a shell as user narnia2. The site shell-storm.org has a plethora of different OS shellcodes available. Running
uname -a we can see the host is an x86_64 bit based OS so any x86 or x86_64 shells are possibly usable. After a few frustrating shellcode tests and failures later, I found shellcode-607 worked wonderfully. Within this page is the hex code we are after. Copying the shellcode value and removing any quotes in the value, we end up with something like
\xeb\x11\x5e\x31\...\x8a\xe2\xce\x81. Using this information we can now prepare our exploit. To place our shellcode into the “EGG” variable we use python to get the hexcode in an acceptable format.
With our environment variable set, we can now execute the
./narnia1 binary and get the password for user narnia2.