shellphish / how2heap

A repository for learning various heap exploitation techniques.
MIT License
7.2k stars 1.14k forks source link

added House of Tangerine #174

Closed gfelber closed 6 months ago

gfelber commented 7 months ago

House of Tangerine is a modernized version of the House of Orange technique that worked for glibc < 2.26. House of Tangerine targets tcache instead of unsorted-bin to achieve arbitrary reads and writes. It also includes an easy way to leak heap and libc ASLR offsets.

https://github.com/gfelber/House_of_Tangerine

Kyle-Kyle commented 6 months ago

So, the high-level idea of how the technique works is (more like how the PoC works):

  1. corrupt the top chunk size using a OOB primitive
  2. force creating a freed chunk by mallocing a chunk with a size that the corrupted top chunk cannot provide (this is like what the original house of orange did)
  3. do step 1-2 3 times to create an unsorted bin and two tcache chunks.
  4. do an allocation in the unsorted bin (in fact, large bin) to leak heap info and libc info (notice that this step uses another use-of-uninitialized-value primitive)
  5. perform tcache-poisoning using the OOB primitive
Kyle-Kyle commented 6 months ago

The fantastic thing about this technique is that it requires no free at all (just like the original house-of-orange technique). And I really like the idea of combining tcache-poisoning and sysmalloc free chunk creation idea.

Kyle-Kyle commented 6 months ago

I'd love to merge the technique. But before I merge it, can you please make the following changes?

  1. what are the checks/constraints do libc have that this technique needs to bypass? If my memory serves me correctly, one of the constraints if that the end of the freed chunk needs to be page-aligned, right? What are the other constraints? Can you please add them to the PoC so when people are trying to use the technique and encounters crashes, they know how to fix it?
  2. For the arbitrary allocation to work, the first largebin (unsortedbin) is not needed. Can you remove that part so this PoC only demonstrates how to do arbitrary allocation with only malloc and OOB (so use-of-uninitialized-value is no longer a dependency?)
Kyle-Kyle commented 6 months ago

And all the users will appreciate it if you could explain a bit more about why this technique works (either in the comment or in prints) :D

gfelber commented 6 months ago
  1. what are the checks/constraints, ...

Understandable i will try to make the main concept behind this technique easier to understand (and link/explain relevant checks in glibc).

  1. For the arbitrary allocation to work, the first largebin (unsortedbin) is not needed. ....

So the largebin (unsortedbin) actually serves three purposes:

  1. leak primitive in case of use-of-uninitialized-value
  2. allowing us to create an allocation on a lower address (i.e. the current PoC also works without editing previous chunks, only the current allocated chunk is in scope), this allows using an OOB but also a BOF vulnerability to be abused in this context. This among other things also allows some fun heap feng shui shenanigans
  3. making the size of the top chunk (wilderness) more predictable, otherwise allocated chunks (e.g. from stream buffering) can be ignored in a newly grown heap page (not directly related to largebin, but generally true for the first corruption of the top chunk)

If the largebin (unsortedbin) is removed a negative and positive OOB (or an unsafe edit) would be required to recreate this technique.

If this is desired i would like to know your opinion what Vulnerability the PoC should focus on:

  1. (current without leaks) only allow modification of newly initialized chunk, with positive OOB (also include BOF) vulnerability, requires 2x Tcaches & 1x alternative (e.g. LargeBin but a differently sized tcache also works)
  2. (minimalist) negative and positive OOB (or unsafe edit), requires 2x Tcaches
  3. others? (please specify)
Kyle-Kyle commented 6 months ago

So the largebin (unsortedbin) actually serves three purposes

Yeah, I understand that the first unsortedbin allocation is needed and also good to have in many cases (e.g. heap fengshui, info leak). But in my personal opinion, it is not the main point of the technique but an add-on. Depending on one more primitive for an add-on does not quite follow how2heap's philosophy of teaching one technique at a time. So yeah, I still prefer the minimalist approach if you are also OK with it.

If the largebin (unsortedbin) is removed a negative and positive OOB (or an unsafe edit) would be required to recreate this technique.

May I ask why? I might be missing something, but I think it is still possible if you OOB from the chunk right above the second created tcachebin, right? (Assuming you know the heap address)

So, if it can be achieved with only overflow (positive OOB), I prefer the minimalist approach. If it cannot, please let me know, we can work on merging the current one.

gfelber commented 6 months ago

May I ask why?

One interesting aspect about the current PoC is that it only requires a positive OOB on the newest initialized chunk. Generally speaking there is no (simple) way to allocate a chunk on a lower address than the newest initialized chunk (without using free).

This is how the technique would look like on a newly grown heap map:

  1. create initial malloc on newly grown heap map
    -------------------------
    |          NEW          |   <- initial malloc
    |                       |
    -------------------------
    |          TOP          |   <- top chunk (wilderness)
    |       SIZE (0x2150)   | 
    |          ...          | 
    -------------------------   <- end of current heap page
  2. corrupt the size of top chunk to be less, but still page aligned
    -------------------------
    |          NEW          |  
    | AAAAAAAAAAAAAAAAAAAAA |   <- positive OOB (i.e. BOF)
    -------------------------
    |          TOP          |   <- corrupt size of top chunk (wilderness)
    |       SIZE (0x150)    | 
    -------------------------   <- still page aligned
    |          ...          | 
    -------------------------   <- end of current heap page
  3. create an allocation larger than the remaining top chunk ( minus fencepost, explained it new PoC ), to trigger heap growth. The now corrupt top_chunk triggers sysmalloc to call _init_free on it
    -------------------------
    |          OLD          |   
    | AAAAAAAAAAAAAAAAAAAAA | 
    -------------------------
    |        TCACHE         |   <- old top got freed because it couldn't be merged
    |                       |
    -------------------------
    |       FENCEPOST       |   <- just some architecture depending padding
    -------------------------   <- still page aligned
    |          ...          | 
    -------------------------   <- end of previous heap page
    |          NEW          |   <- new malloc
    -------------------------
    |          TOP          |   <- top chunk (wilderness)
    |          ...          | 
    -------------------------   <- end of current heap page

There are two ways to trigger a UAF in the freed chunk we either need to be allowed to edit an old chunk (and also have an positive OOB in the edit function) or have a negative OOB in the new chunk.

I believe just being allowed to postive OOB in the newest chunk would be preferable (and more common), but I'm fine with both approaches.

Here is an overview table to compare the complexity between the current and minimal approach:

current minimal
mallocs 7 6* (5)
unqiue sizes 3 3

*to make the PoC more reliable we need to malloc and probe the current top chunk size, this should be predictable in an actual exploit and therefore, can be removed to get 5 mallocs instead

WIP versions of both: current.c minimal.c

I hope this helps making a decision which option is preferred

gfelber commented 6 months ago

It might also be sensible to add a PoC that simply demonstrates the sysmalloc _int_free trick, because it has potential to be combined with other techniques. If this is desirable i don't mind including an additional short PoC.

Kyle-Kyle commented 6 months ago

Thank you for crafting two versions of the technique. I think the minimal poc looks great and would love to merge it.

It might also be sensible to add a PoC that simply demonstrates the sysmalloc _int_free trick, because it has potential to be combined with other techniques. If this is desirable i don't mind including an additional short PoC.

I totally agree with this and would love to see such a PoC.

gfelber commented 6 months ago

Exchanged house_of_tangerine.c with the minimal version. Also added a nice showcase of the sysmalloc _int_free() trick in sysmalloc_int_free.c.

gfelber commented 6 months ago

Made a mistake with the fencepost location, it's acutally after the freed top_chunk (not before, as seen in the diagram before, fixed it now)

Kyle-Kyle commented 6 months ago

It seems you have been updating it constantly. Please let me know when you think it is ready to merge :D

gfelber commented 6 months ago

Yeah should be ready to merge, the last changes were some minor stuff. If this version is good with you it's good with me

Kyle-Kyle commented 6 months ago

The current version looks good to me! Thank you for the effort!

gfelber commented 6 months ago

sry to bump this put i saw you modified sysmalloc_int_free.c https://github.com/shellphish/how2heap/commit/ae4dbf558203d72296e443e326d885b0f7994e63 https://github.com/shellphish/how2heap/commit/4ed6f1954565ed12e23aa84931ed9c36d00a3d8b

<< #define MALLOC_ALIGN 0x10L
>> #define MALLOC_ALIGN (SIZE_SZ*2)

i believe this happened because you merged the 2.23 example with the others, but this breaks the showcase for x86 (32 bit), so maybe revert theses changes if possible? in some earlier heap version glibc changed their heap alignment on x86 (32bit) to always be 0x10 (and not SIZE_SZ*2) https://elixir.bootlin.com/glibc/glibc-2.39/source/sysdeps/i386/malloc-alignment.h#L22 this wasn't true for 2.23, that's why this version was slightly different https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L353