Exploiting Protostar – Stack 0-3
In this article, we will be reverse engineering and exploiting simple C programs from Protostar VM by exploit-exercises.com. We will be mainly focusing at how and why of stack overflows.
Introduction:
Well, there are tons of tutorials out there on stack buffer overflow, but very few of them deduce the reasoning like "why only those number of bytes were used for overflowing the return address/some variable. "We will be mainly covering those questions and understanding how the C code gets placed in memory and how the pattern of placing your shellcode onto stack get changed when introducing different twists in code.
Downloads
VM used in this article can be downloaded from here.
Note
If you are new to exploit development, I will recommend to going through the comments in disassembled code sections too.
Stack 0
Following code is presented to us for solving stack0 and the objective of this level is to overwrite the modified variable not to overflow the EIP register.
#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' variablen");
} else {
printf("Try again?n");
}
}
Before diving straight into fuzzing the input parameters, let's deduce the offset of modified variable by simply looking into disassembled code of the main function.
By looking at the offsets of the variables placed onto the stack, we can see that modified variable is placed at $esp+x5c and our input is at $esp+0x1c. By subtracting 0x5c-0x1c we get 64 which is the exact offset, any bytes after that will start overwriting the modified variable. Let's confirm the above theory by executing some debugging commands in our terminal.
As can be seen, we placed a breakpoint at test eax, eax to check whether the location of variables deduced by analyzing the assembly code is correct or not. We further examine those offsets after passing our input to the program and concluded that modified variable will be overwritten at the 65th byte.
As can be seen, we further changed the modified variable's value by providing the 65th byte in our input as 1.
Stack 1
Till this point. We have built our basics on how the variables are placed on the stack and how we can directly calculate the offsets to variables by looking at the disassembled code. Let's use the knowledge gained from previous level here.
Following code is presented for stack 1, as can be seen we need to overwrite the modified variable value with specific value. Since we are using the microprocessors designed by intel we need to pass that value in little endian format.
#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 argumentn");
}
modified = 0;
strcpy(buffer, argv[1]);
if(modified == 0x61626364) {
printf("you have correctly got the variable to the right valuen");
} else {
printf("Try again, you got 0x%08xn", modified);
}
}
As can be seen, the disassembly of the main function is very similar to the previous level also the offsets remain same. Thus, we need to need to pass 0x61626364 after 64 bytes of our input to modify the modified variable value.
As can be seen, we were able to change the value of modified variable.
Stack 2
Following code is presented for stack 2, as can be seen, we need to overwrite the modified variable value with specific value, but in this case, the program takes input from environment variable named GREENIE instead of stdin/command line argument.
#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 variablen");
}
modified = 0;
strcpy(buffer, variable);
if(modified == 0x0d0a0d0a) {
printf("you have correctly modified the variablen");
} else {
printf("Try again, you got 0x%08xn", modified);
}
}
Now the question arises will the offset to the modified variable remains same. If yes, Why and how?
I have inserted some comments into the disassembly of the main function to get the clear picture of what's happening. As can be seen, getenv function gets the value of GREENIE env variable and store that in eax, why because in x86 arch function returns are stored in eax, the memory addr of eax is placed on the stack at $esp+0x4.
Further, the data in eax is copied onto the stack at $esp+0x18. Address after $esp+0x18 will hold our payload. Further, the same address i.e. $esp+0x18 is pushed on the top of the stack for strcpy function. After that value at $esp+0x58 is copied into eax and cmp instruction is used to check whether the value at $esp+0x58 is equal to 0x0d0a0d0a.
So, our input is at $esp+0x18 and we need to overwrite a variable which is at $esp+0x58. Subtracting these two values gets us (0x58-0x18) = 0x40 = 64 bytes. Hence any byte after 64 bytes will start overwriting modified variable.
Thus, following value of environment variable needs to be set to complete the objective of this challenge.
Stack 3
Following code is presented for stack 3, as can be seen, in this level we need to overwrite the function pointer fp with the address of win function to achieve our goal.
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win()
{
printf("code flow successfully changedn");
}
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%08xn", fp);
fp();
}
}
Upon looking at the disassembly of main function, we can see that our input is placed at $esp+0x1c and fp is at placed at $esp+0x5c therefore offset can be calculated as 0x5c – 0x1c = 0x40 = 64 bytes. So, anything after 64 bytes will start overwriting function pointer.
We can validate our deductions by passing the value of win function after 64 bytes. As can be seen, we were able to overwrite the value of function pointer with the address of win function.
We can also get an arbitrary code execution here by overwriting the esp+0x5c with the address of esp+0x1c. I will leave that an exercise for the diligent reader.
Conclusion
In this article, we have a look at how program variables are placed on the stack and how we can get the offset to overwrite any other part of the program by looking a disassembled code. In our next article, we will be looking at more advanced techniques of executing and debugging our shellcode and bypassing some memory protections such as Non-Executable Stack and ASLR as well.