I had some extra free time this month with the Lab closing twice due to the
snow. I used one of these days to modernize my blog and website and the other,
the subject of this post, I spent working through the Narnia wargame. For
those of you unfamiliar, OverTheWire hosts a number of “wargames” - series of
exploitation challenges that vary in difficulty from “never touched a command
line in my life” to “write an exploit for a modern version of gzip”. Narnia
is one of the simpler binary exploitation series with only a few levels so I
thought I’d try and tackle it on my day off.
Warning: this blog post contains solutions to the Narnia wargame. If you’re
interested in solving it yourself DO NOT READ THIS. Passwords have been
redacted with XXXXXXXXXX.
SSH to narnia0@narnia.labs.overthewire.org with password narnia0 - easy enough.
Level0 → Level1
This is as simple as buffer overflows get - buf is 20 bytes long but the
scanf reads in 24 bytes, allowing you to overwrite the next value on the
stack - in this case val. If val is 0xdeadbeef we get a shell. I didn’t
bother writing a script for this one and used python to construct an input
string (making sure to reverse the order of bytes for 0xdeadbeef since this
is a little endian system).
Well, it looks like we’re passing the value check - no “WAY OFF!!!!” message
but we still don’t seem to have narnia1 privileges… This is because the
bash | replaces stdin for ./narnia0 with a pipe from the stdout of my
python command. This causes /bin/sh to get an EOF after the python command
completes, closing the process before I can interact with it. We should be able
to pass it the command we want to execute after our python code via the same
pipe.
Success, on to the next one.
Level1 → Level2
Looks like this one is walking us through putting shellcode in an environment
variable - it checks to see if EGG is set and, if so, jumps to its address
and executes its content. I generated a /bin/sh shellcode with pwntools
shellcraft module on my system.
We can then put this shellcode in a simple script that prints it to stdout,
save it to a tmp directory, and run narnia1, passing it our shellcode as an
environment variable.
Another one down.
Level2 → Level3
This one looks a lot more like a traditional buffer overflow. We’ve got a 128
byte buffer buf and we strcpyargv[1] directly into it with no bounds
checking. First, let’s find how big our input buffer needs to be to give us
control of eip.
Great, looks like we can set eip after 140 bytes. Now we need somewhere to
store our shellcode. We might as well just use the same egg as the last level
and store it in an environment variable but we’ll need to find it on the stack.
To give us some wiggle room, let’s also add a sizable nop sled to the
beginning of our shellcode. My egg.py now looks like this:
We can find our shellcode using gdb by reading memory on the stack as strings
(I used a command like x/500s $sp) and scrolling until we start to see
environment variables and a ton of nops.
Looks like 0xffffd516 should be right in the middle of our nop sled. So
combining everything we’ve got so far…
On to level4.
Level3 → Level4
There are a lot of potentially exploitable bugs here due to unbounded reads and
writes. However, since we’re only looking for the password to narnia4,
there’s no need to pop a shell - this code will copy a file for us if we can
give it the right input. Since the strcpy is unbounded, we can overflow the
ifile buffer and write into the ofile buffer - its just a matter of
crafting a string that gives us what we want. I started by moving into a tmp
directory and the following input worked for me.
First, we can create a file of the same name that we control (narnia4) in the
current directory. The file we want to read is in /etc/narnia_pass/narnia4 so
we just need to pad this input with useless path junk so that it’s just long
enough to overflow only the string narnia4 into ofile - referring to the
narnia4 file in the current directory. Since read/write don’t adjust
permissions, we still have access to narnia4 after it’s written.
Level4 → Level5
It looks like this program was designed to defeat our environment variable
shellcode - before the strcpy, it nulls all environment variables. Instead,
we’ll have to store our shellcode in the buffer itself. First, let’s get
control of eip.
Great, looks like 272 bytes will do the trick. Now, we need the address of of
argv[1] so we can jump there after filling it with shellcode.
Looks like argv[1] starts at 0xffffd79a - we should have what we need to
write an exploit string. I wrote a script for this one because the command
line input was getting a bit long.
There we go.
Level5 → Level6
This seems to be a beginner formatstring exploit - the snprintf isn’t
vulnerable to overflow here since sizeof is evaluated at compile time so it
can’t be manipulated like strlen. Luckily, this program provides us with some
helpful output if we fail to get a shell. Let’s start by feeding it some %xs.
Great, so we know the snprintf is vulnerable since we’re leaking memory
values, we know we get back to the beginning of our buffer 0x41414141 after 5
%xs, and we know the address of i that we want to write to. If we insert
the address of i at the beginning and replace the last %x with a %n, we
should be able to write to i.
Awesome, so now it’s just a matter of reading the right number of characters
before %n to set the value of i to 500. Adjusting the last %x, we can set
i and get the password.
Level6 → Level7
It looks like we’ve got a writable function pointer fp via overflowing one of
the two buffers in a strcpy. However, in addition to zeroing environment
variables, this challenge tries to detect if fp points to an address on the
stack and quits if it does - which means we can’t just jump to our shellcode in
argv like we did last time.
Fortunately, we have control of a function pointer that gets called and one
argument passed to it - we could call system("/bin/sh") if we can find the
address of system, classic return to libc. We can get it by opening narnia6
in a debugger.
Great, so we need to overwrite fp with 0xf7e62e70 and set b1 to
"/bin/sh". We’ll use two overflows, so that we can get the null byte at the
end of "/bin/sh" in the right place.
Level7 → Level8
This looks like another formatstring exploit - the snprintf in vuln uses
raw user input for a format. This time, we want to write ptrf to point to
hackedfunction instead of goodfunction before it is called. Luckily, the
program gives us all the addresses we need if we just run it.
So we want to change the value of ptrf at 0xffffd61c from 0x80486e0 to
0x8048706, in other words, we want to write 0x8706 to the halfword
0xffffd61c using the format string vulnerability.
Let’s start by using ltrace to find the address of our format string.
Judging by the snprintf call it looks like we need 6 %xs before we reach
our string. Now we can prepend the address we want to write 0xffffd61c and
replace the last %x with a %hn. We use %hn this time because we want to
write the lower half of ptrf.
With a bit of trial and error, I came up with the following explot string
script.
On to the final challenge.
Level8 → Level9
I’ll admit, this one had me stumped for quite a while. At first glance this
looks like a straightforward buffer overflow, I should be able to store some
shellcode in an environment variable, overflow the return address of func,
and jump to it. However, because blah is a local variable of func, it
resides on the stack which means that if we overflow bok, we’ll start writing
into the address stored in blah before we can overwrite the return address.
Unfortunately, also because blah is a local variable, its position on the
stack isn’t fixed and will depend on the environment variables, and string we
pass to the binary. Let’s start by just naively overflowing bok.
The program didn’t segfault, but we did get some interesting output - the
contents of the bok buffer with the address blah appended to the end. This
address acts as a sort of canary - we need to write the correct value here or
the loop will stop and we won’t be able to overwrite eip. We can see here
that we’ve overwritten the least significant byte of the address of blah.
After some trial and error, I was able to generate a segfault at an address I
control. Using the same environment variable shellcode as the previous
challenge, I can pass its address in place of BBBB.
Success, and that’s the end of the Narnia wargame.