Cyberthreatforce CTF의 Bof_1 문제다. 문제 자체가 어렵기 보다는 혼자 어렵게(쉐도우 복싱 미쳤다..ㅋㅋ) 생각해서 풀이 시간이 오래 걸렸다. 공격 시나리오만 제대로 안다면 10분도 안 걸리고 풀 수 있는 문제다.
문제 파일을 실행시킨다면 위와 같이 동작한다. "hello who are you?"라는 문구가 출력되고 입력을 받는데, 입력한 값이 그 다음 줄에 "Hello "와 함께 출력된다.
checksec으로 보호기법을 확인해보면 Canary와 NX가 걸려 있는 것을 알 수 있다. 이 때까지만 해도 canary를 우회하는 상상을 했다. canary는 그냥 장식이었다..
ida로 메인 코드를 확인해보면 bof에 취약한 포맷 스트링 %s와 scanf 함수로 입력을 받는 것을 알 수 있다. nx도 걸려 있고, libc도 주어져 있지 않기 때문에 쉘을 얻게해주거나 flag를 출력해주는 함수가 있는지 살펴봤다.
magie라는 함수가 주어져 있다. flag를 읽거나 쉘을 주는 함수는 딱히 보이지 않지만 if 조건에 맞으면 CYBERTF{______}를 출력해주는 것을 봐서, 서버에 접속했을 때는 진짜 flag를 출력해주지 않을까 생각이 들었다.
그렇다면 1차적으로 magie 함수로 ret를 변조한 다음 저 if문의 조건이 참이 되도록 해야한다.(사실 printf_plt로 ret를 변조하고 flag의 주소를 인자로 넘겨주면 된다...)
gdb로 main의 함수 프롤로그와 scanf 호출 지점에 bp를 걸고 실행하여 ret와 입력 값이 저장되는 스택 주소를 구했다.
ret : 0x7ffffffffdb78
buf : 0x7ffffffffdb40
따라서 56byte의 더미 값을 입력으로 준다면 ret에 접근할 수 있다.
ida 상에서는 버그인지 magie if문에서 사용되는 sub_4010A8에 대한 sysbol 정보가 없었기 때문에, 이를 확인하고자 set으로 ret를 조작하고 magie 함수로 진입했다.
인자의 값이 -1인지 체크하는 부분을 우회해 sub_4010A8 주소를 호출하는 부분에 와, 진입한다면 strcmp 함수인 것을 알 수 있다.
즉 그렇다면 magie는 인자의 값이 -1이 아니면서 v2의 값이 "CYBERTF{YZY}"가 아니라면 flag를 출력해주는 것이다.
하지만 문제는 if문 전에 v2와 v3에 "CYBERTF{YZY}"를 넣어주는 부분이 있다. 어쩌피 v2와 v3에 나눠서 넣는거라 상관 없지 않느냐라고 생각할 수도 있지만, 문제는 이 두 변수가 메모리 상으로 딱 붙어 있기 때문에 strcmp의 인자로 v2의 주소를 주는 순간 v2, v3의 값이 합쳐진 값인 "CYBERTF{YZY}"가 "CYBERTF{YZY}"와 비교를 해 리턴 값이 0이되는 것이다.
따라서 if문은 직접 패치하지 않는 이상 false가 돼 flag를 얻을 수 없다.
이때 내가 생각했던 공격 시나리오는 다음과 같다.
ROPgadget를 통해 gadget을 찾아보면 syscall gadget이 유독 많이 있는 것을 볼 수 있는데, 이 syscall을 이용해 0번 read로 strcmp 함수로 v2와 비교하는 문자열의 주소를 입력 값으로 덮어쓰는 방법이다.
하지만.. 문자열의 주소는 rodata section이었다. 이 section은 ro, 말 그대로 read only이기 때문에 값을 덮어 쓸 수 없다.
또 생각했던 것은 srop나 sysrop를 통해 쉘을 얻는 것인데, 이미 같은 팀으로 출전했던 분이 시도하셨고 쉘까지 따냈다.
하지만 이 방법도 문제는 flag가 binary 내부에 존재해 쉘을 얻어도 flag를 읽을 수 없다.
그래서 strings 명령어로 문자열만 추출해 flag를 얻는게 어떠냐고 제안했지만, 쉘을 얻었을 때 부여된 계정의 권한으로는 strings 명령을 사용할 수 없었다.
생각해보니 flag는 binary 내부에 하드코딩 돼 있다. if문 조건을 맞춰줄 필요없이, main 함수에 존재하는 printf의 plt 주소를 이용해 flag 값의 주소를 인자로 flag 값을 출력해주면 되는 것이다!!
from pwn import *
context.log_level = 'debug'
p = remote('144.217.73.235', 24498)
#p = process('./service')
printf_plt = 0x0000000000409e50
payload = b'A'*56 + p64(pop_rdi) + p64(0x49f021) + p64(printf_plt)
p.sendlineafter('hello who are you?', payload)
p.interactive()
flag의 주소는 원래 0x49f020이지만 0x20은 아스키 상에서 공백 문자로 처리되기 때문에, 이 뒤에 입력 값들이 제대로 입력되지 않을 수도 있다. 따라서 어쩌피 flag 포맷은 알고 있으므로 +1한 0x49f021 주소의 값을 출력하도록 했다.
결과 값으로 YBERTF{B@sic_Buff3r_Ov3rflow}가 출력됐는데, 해당 CTF의 flag 포맷이 CYBERTF{_____________________}라는 사실을 이미 알고 있으므로 bof_1의 flag 값은 CYBERTF{B@sic_Buff3r_Ov3rflow}다.
'CTF' 카테고리의 다른 글
[redpwnCTF 2021] pwn - beginner-generic-pwn-number-0 (0) | 2021.07.13 |
---|---|
[redpwnCTF 2021] misc problems (0) | 2021.07.12 |
[HackASet 2021] iq (0) | 2021.06.27 |
[HSCTF 8] pwn - stonks (0) | 2021.06.20 |
[HSCTF 8] misc - seeded-randomizer (0) | 2021.06.20 |