I think it was the most difficult problem among the PWN problems I solved. I found the vulnerability really simple, but it wasn't easy for exploit. The vulnerability is fclose uaf and heap feng shui.

we can get heap leak and libc leak from this part.

Item *item_lookup(const char *key, size_t keylen) {
  for (Item *cur = top; cur != NULL; cur = cur->next) {
    if (memcmp(key, cur->key, keylen) == 0)
      return cur; /* Found item */
  }
  return NULL; /* Item not found */
}

...
...

main()
{
...
	case 2: { /* get */
        char *key;

        /* Find item by key */
        size_t key_len = readline("Key: ", &key);
        Item *item = item_lookup(key, key_len);

        /* Show value */
        if (item)
          printf("Item: %.02lf\n", item->value);
        else
          puts("Item not found");

        free(key);
        break;
      }
...
}

You can see that a key does not hold the original key length, but rather use length based on the length of the key you input, so that we can get leak it by 1 byte brute force. The chunk I chose is

------------------------------------
|		key value   |    main_arena    |
|   heap addr   |       ....       |
|      .....................       |
------------------------------------

so can get libc and heap leak at once.

we can use-after-free from here.

default: { /* exit */
        char ans;
        fclose(fp);

        if (!is_saved) {
          /* Ask when list is not saved */
          puts("The latest item list has not been saved yet.");
          puts("Would you like to discard the changes? [y/N]");
          scanf("%c%*c", &ans);
          if (ans != 'y' && ans != 'Y')
            break;
        }

        puts("Bye (^o^)ノシ");
        return 0;
      }

We can call fclose more than once.

fclose uses FILE*, so use malloc when open file and use free when close. It means double free bug or use-after-free occurs.

First, I want to use fclose → fprintf → fclose. It means tcache double free. When we call fprintf, tcache→key overwrite to other value.

However, I could not figure out how to allocate chunks of the same size as the free fp. In this case, even if I made double free, it would be impossible to alloc in the space, so I couldn't exploit.

getline function alloc chunks power of 2, actually likes malloc(0x78) -> realloc(0x78*2) -> ... . So then, fp’s chunk size is 0x1e0 but realloc just call 0x78 or 0x1f0 or above this.

So I thought about it a little bit, and I found out that the tcache of 0x1e0 can be filled because the difference between the chunk size 0x3d0 of realloc (0x78<<3) and the chunk size 0xf0 of realloc (0x78<1) is 0x3d0 - 0xf0 = 0x1e0.

Conclusionally, fill all 0x1e0 size of tcache and fclose(fp), fp's chunk would go into a small bin or unsorted bin, and since this location is a bin that can be allocated sufficiently by add function. I can eventually cover the fd with the desired value.

스크린샷 2022-03-23 오후 1.53.06.png

<0x1e0 Tcache is all covered.>