Reputation: 63
I am trying to follow along with a tutorial about buffer overflow (Buffer Overflow Primer by Vivek Ramachandran). I am literally following his code, which works for him in the demo, and which has worked for me until this point.
The goal of the C program below is to assign shellcode for the exit system call to a variable, and then to replace the default return address for the main function, which points to __lib_start_main, with the memory address of the shellcode variable, such that the program executes the shellcode upon completing the main function, and then exits the program gracefully, with a value of 20 (as in execiting "exit(20)"). Unfortunately, the program ends with a segmentation fault instead. I am running this on 32-bit Linux Mint. I'm using gcc to compile the code, and have compiled it with the --ggdb and -mpreferred-stack-boundary=2 options, and I've tried both with and without the -fno-stack-protector option.
Here is the code:
#include<stdio.h>
char shellcode[] = "\xbb\x16\x00\x00\x00"
"\xb8\x01\x00\x00\x00"
"\xcd\x80";
int main(){
int *ret;
ret = (int *)&ret +2;
(*ret) = (int)shellcode;
}
I have run this through gdb, and everything seems to check out: The memory location of the shellcode variable is 0x804a01c
Thanks in advance!
Upvotes: 2
Views: 2038
Reputation: 1
Your SHellcode Contain Null Bytes, Try Using Smallest Register and use xor when do you need to zero out a register, The Null Byte Problem is when C See This null byte, it stop reading after this null byte '\x00', Causing problem with execution like segmentation Fault.
Upvotes: 0
Reputation: 140886
Traditional buffer overflow exploits did involve executing code on the stack, but your program doesn't do that. Your shellcode
array is not on the stack, and the construct you used to clobber main
's return address to point to the shellcode
array does not involve executing code on the stack. When I run your program on my Linux box (also running on an x86 CPU), compiled with gcc -O0 -m32
, it does wind up setting the EIP register to point to the machine code in shellcode
. But then, as it does for you, it crashes with a segmentation fault.
The reason it crashes is because shellcode
is loaded into a region of memory that is marked as not executable. (The name of this memory region is "the data segment".) The processor refuses to execute machine instructions from that area, instead generating an "exception" (this is a hardware concept and not the same as a C++ exception) that the kernel translates to a SIGSEGV signal.
Old tutorials on writing shellcode and buffer overflow exploits don't warn you about this possibility, because older generations of the x86 architecture could not mark memory as not executable on a per-page basis. In the "flat" segment-register configuration used by most 32-bit x86-based operating systems, any page that was readable was also executable. However, the last several generations of the architecture have been able to mark individual pages as not executable, and you have to work around this. (If I remember correctly, per-page executability was added to the x86 architecture circa 2003, at the same time as 64-bit mode, but it took quite a bit longer for operating system support to become universal.)
On my Linux box, as above, this modified version of your program successfully transfers control to and executes the machine code in shellcode
. It uses the mprotect
system call to make the memory region containing shellcode
executable.
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
const char shellcode[] =
"\xbb\x16\x00\x00\x00"
"\xb8\x01\x00\x00\x00"
"\xcd\x80";
int main(void)
{
uintptr_t pagesize = sysconf(_SC_PAGESIZE);
if (mprotect((void *)(((uintptr_t)shellcode) & ~(pagesize - 1)),
pagesize, PROT_READ|PROT_EXEC)) {
perror("mprotect");
return 1;
}
void **ret;
ret = (void **) &ret;
ret[9] = (void *)shellcode;
return 0;
}
As well as the mprotect
operation itself, note how adding that chunk of code changed the stack layout and put the return address in a different place. If I compile with optimization on, the stack layout changes again and the return address isn't overwritten. Also note how I made shellcode
be const char
. If I hadn't done that, I would have needed to use PROT_READ|PROT_WRITE|PROT_EXEC
in the mprotect
call to avoid crashing too early because some random global variable suddenly wasn't writable when the C library expected it to be, and the kernel might then have failed the mprotect
call because of the "W^X" security policy.
Depending on the age of your kernel and C library, making shellcode
be const char
might have been enough by itself, but with kernel 4.19 and glibc 2.28, which is what I have, read-only data isn't executable either.
Upvotes: 5
Reputation: 63
Adding the following option when compiling resolved the issue:
-z execstack
Upvotes: 1