LEVEL 20
[level20] passcode : we are just regular guys
(NO DRAG, NO PASSCODE)
hint 의 코드를 분석해보자.
우선 메모리 구조를 살펴보자.
main 함수 부분의 symbol 이 날라가서 gdb로 분석을 할 수 없다.
코드 마지막 줄에서 printf(bleh) 라는 형식으로 출력을 하게 하는데, printf 함수는 원래 printf("%d", a) 처럼 %d 라는 서식 문자를 사용하여야 한다. 여기서 발생하는 취약점이 바로 포맷 스트링 버그(FSB) 이다.
printf(bleh) -> bleh에서 bleh라는 배열에 일반 문자열이 있다면 아무 문제 없이 출력 되지만, 배열 안에 서식문자(%x, %c, %s 등)가 있다면 buf 배열의 시작 주소 다음 4바이트 위치를 참조하여 그 서식문자의 기능대로 출력한다. %x는 16진수로, %d는 정수로 출력한다는 뜻이다.
우선 포맷 스트링을 이용하여 메모리 구조를 파악해보자.
- 예상되는 메모리 구조 → printf(bleh) 출력 버퍼(?) + dummy(12) + bleh(80) + dummy(?) + SFP(4) + RET(4)
여기서 우리는 입력해준 AAAA 라는 문자열이 4번째 영역에 존재한다는 것을 알 수 있다. 따라서 저 부분이 bleh 문자열의 시작 주소 부분이라는 것을 알 수 있다. 이로써 ret 주소에 쉘코드의 주소를 덮는다면 쉘을 딸 수 있을 것 같다.
gdb를 통하여 ret 주소를 구하려고 했으나 symbol이 사라져 있다. 따라서 bleh 와 sfp 사이의 dummy 값을 구할 방법이 없기 때문에 ret 주소를 알 수가 없다. 따라서 우리는 ret 주소에 어떠한 값을 덮을 수 없다.
이를 대체하기 위하여 우리는 c언어의 구조에 대한 정보가 필요하다. main 함수는 실행되기 직전에 .ctors 속성 함수를 실행하고, main 함수가 종료된 직후에는 .dtors 속성의 함수가 실행된다.
따라서 .dtors 의 주소값을 구하여 그 주소값에 +4를 해준다. +4를 해주는 이유는 다수의 .dtors 영역에서 실행이 되게 하기 위해서는 +4를 해줘야 한다. (정확한 이유는 추후에 추가)
따라서 바이너리 파일들의 정보를 보여주는 objdump 명령어를 통하여 .dtors의 주소를 찾아내자.
.dtors 의 주소가 0x08049594 이므로 우리가 사용할 주소는 여기에 +4를 해준 값인 0x08049598 이다.
그리고 attackme의 소스코드에는 shell을 실행시키는 함수가 없으므로 shellcode를 export 하여 이 주소를 방금 구한 0x08049598 에 덮어버리자.
우선 shellcode 를 export 해주고,
shellcode의 주소를 출력해줄 프로그램을 만들고 실행하자.
shellcode 의 주소도 구했다. → 0xbffffc15
이제 우리는 소멸자(.dtors) 주소 대신 shellcode의 주소를 넣어주어야 한다. 그러나 fgets 함수에서 입력받을 바이트수를 79바이트로 정의했기 때문에 일반적인 버퍼 오버플로우로는 문제를 해결할 수 없다. 따라서 아까 살펴보았던 FSB로 해결해야 한다. 이제 문제를 해결해보자.
- 입력받은 문자열이 아까 살펴보았던 4번째 영역부터 저장되기 때문에 .dtors 영역의 주소를 우선 입력해준다.
- 그 뒤에 %n을 입력하면 bleh의 시작 주소에 들어있던 값(위에서는 AAAA, 0x41414141)을 주소로 인식하여 그전에 입력받은 문자들의 바이트수를 모두 합하여 그 주소에 삽입한다.
- 여기서 우리의 목적은 .dtors 의 주소에 shellcode의 주소를 집어넣는 것이다.
- 따라서 우리는 1번에서 입력해준 .dtors 주소 다음에 shellcode 의 주소값을 10진수로 변환하여 %n 으로 전달해야 한다.
- payload = “.dtors 주소” + “%shellcode 주소 십진수 변환 값d” + “%4$n”
우리는 .dtors의 주소 값은 구해놓았으니 shellcode 주소를 10진수로 변환한 값을 얻어보자.
0xbffffc15 -> 3221224469
하지만 우리가 하나의 주소값에 넣을 수 있는 값은 4바이트로 한정되어 있기 때문에 10자리인 수를 4자리 4자리로 나누어서 계산해야한다. (bfff, fc15)
따라서 우리는 .dtors의 주소도 두개로 나누어서 하위주소(0x08049598)에는 shellcode의 하위주소를, 상위주소(0x0804959a)에는 shellcode의 상위주소를 넣어주어야 한다.
bfff -> 49151
fc15 -> 64533
또한 우리는 %n에 shellcode의 하위주소값만큼의 바이트를 넘겨주어야 하기 때문에 앞에 사용한
8바이트(“0x08049598”+“0x0804959a”)
를 빼서 넣어주어야 한다. 따라서 64525바이트를 넘겨주면 된다.
그리고 그 다음 %n에는 shellcode의 상위주소값 바이트, 즉 49151을 넘겨주기 전에 앞에 사용한 64525바이트를 빼주어야 한다. 그러나 주소값은 양수이기 때문에 0xbfff을 0x1bfff로 바꾸어 사용한다.
따라서
114687(0x1bfff) - 64533 = 50154
바이트를 넘겨주면 된다.
%n은 단지 값을 넣어주는 것이기 때문에 아무런 값이 출력되지 않으므로 바이트에 포함되지 않는다.
이제 payload를 짜보자.
<payload.
“0x08049598” + “0x0804959a” + “%64525d” + “%4$n” + “%50154d” + “%5$n”
payload를 바탕으로 이제 쉘을 얻어보자.
FTZ CLEAR
[clear] : i will come in a minute
'Pwnable > FTZ' 카테고리의 다른 글
FTZ - level19 [Buffer Overflow 𝜇] (0) | 2020.08.28 |
---|---|
FTZ - level18 [Buffer Overflow 𝜽] (0) | 2020.08.28 |
FTZ - level17 [Buffer Overflow 𝜼] (0) | 2020.08.28 |
FTZ - level16 [Buffer Overflow 𝜻] (0) | 2020.08.28 |
FTZ - level15 [Buffer Overflow 𝜺] (0) | 2020.08.28 |