[FTZ]level20
드디어
드디어어어
level20
성인이 되기 일보직전입니다.
시작해 볼까요?
자세히 보면
fgets함수에서는
문제가 없어보이쥬?
그르나
printf함수를 사용할 때
변수를 바로 찍어버리네요.
결국
Format String Bug를 이용해서
문제를 해결하라는 건가요?
Format String Bug란?
음.. "포맷스트링을 사용하지 않고 출력하는 함수에게 하는 공격?" 정도가 되겠네요.
보통 입력에 "%x, %x, %x"와 같은 것을 넣고,
매칭되는 변수가 없을 경우
스택을 4바이트씩 높여가며 해당 주소의 값을 출력해주는
버그가 발생하게 됩니다.
+
%n을 통해서 메모리 변조도 가능하지요.
어떻게 가능하냐면?
printf("abcd%10d%n");
을 하게되면
%n앞에서 사용한 문자열의 바이트 수를 세서
%n이 가리키는 주소에 저장합니다.
위의 경우는 14가 %n이 가리키는 곳에
저장이 되겠지요?
정말로 주소가 잘 출력이 되네요.
%x를 4번 적었는데
4번째 %x에 41414141이
출력되었네요?
그 말은
printf함수와 bleh배열 사이에
0x4f와
0x4212ecc0과
0x4207a750
총 3개의 dummy가 존재한다는 말이겠지유?
위 내용을 바탕으로 그림을 그려볼까요?
위와 같이 구성되어 있겠네요.
허.. symbol이 없네요??
누군가 지웠네요!!
파일의 symbol정보를 확인하기위해
nm을 사용해보겠습니다.
예상은 했지만 symbol정보가
다 지워져 있네요..
이 프로그램은 어차피 ret를 덮어쓸 수도 없기 때문에
소멸자를 덮어씌우는 방식으로 하는 것이 좋을 듯 싶습니다.
사실 Format String Bug는
주로 소멸자가 가리키는 주소를 덮어씌우는 방식을 사용합니다.
프로그램이 시작되면
1) Constructor에 적힌 주소에서
생성자 함수를 실행합니다.
2) Main함수를 실행합니다.
3) Destructor에 적힌 주소에서
소멸자 함수를 실행합니다.
4) 프로그램을 종료합니다.
그래서 공격 시나리오를 생각해보면
main함수는 끝나고 소멸자함수를 호출할 것입니다.
이 소멸자 주소는 .dtor 섹션+4바이트에 위치하고 있습니다.
여기를
환경변수에 적어놓은
SHELLCODE주소로 변조하려고 합니다.
변조는 위에서 말한 것과 같이
%n이 가리키는 주소에 문자열 길이만큼 적는다고 했습니다.
.dtor Section에 있는
__DTOR_END__(소멸자)를
먼저 찾아봅시다.
readelf가 objdump보다
상세한 정보를 얻을 수 있으므로
readelf를 이용해보도록 하겠습니다.
헤더에 대한 옵션을 적어보면
-h = ELF 파일 헤더
-l = 프로그램 헤더
-S = 섹션 헤더
-e = 위 3가지 헤더
우리는 .dtor의 섹션헤더의 정보가 필요하므로
.dtors 영역의 주소를 찾았네요.
.dtors = 0x08049594
그러나 __DTOR_END__(소멸자)의 주소는
+4byte를 더한
0x08049598이 됩니다.
다음은 소멸자주소에
덮어 씌울 SHELLCODE를
환경변수에 등록해 줍니다.
export SHELLCODE=$(python -c "print '\x90'*100 + '\x31\xc0\x50\x68\x2f\x2f
\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xd2\x52\x53\x89\xe1\xb0\x0b\xcd\x80'")
환경변수에 SHELLCODE를 삽입하였습니다.
#은 16진수일 때 0x를 붙여서 표기해줍니다.
%p는 빈공간에도 0을 넣어서 출력해주고
%x는 16진수 값만 출력해 줍니다.
편하신 것을 사용해도 됩니다.
SHELLCODE의 주소는 0xbffffbca네요.
위 그림과 같아지도록
이제 payload를 작성해 보도록 하겠습니다.
__DTOR_END__의 하위주소(4) + __DTOR_END__의 상위주소(4) + SHELLCODE의 하위주소(4) + SHELLCODE의 상위주소(4) +%4$n + SHELLCODE의 상위주소(4) + %5$n
payload가 이해가 안가는 분만 보세요!!
예시를 들어볼께요.
빨간 박스를 보면
4바이트 더미가 3개 12바이트인 것을 알 수 있죠?
이제 4번째 %p가 bleh를 가리키는 것은 알고 계실겁니다.
노란 박스를 보면
%4$p이라고 적었고
이 말은 4번째에 있는 주소의 값을 출력해주게 됩니다.
그래서 0x41414141이 나왔습니다.
만약 %4$n이라면 4번째에 있는 주소에
문자열 길이만큼 덮어쓰겠죠?
초록 박스를 보면
%100p가 있는데 이것은
100바이트만큼 공간을 확보하고
첫 번째에 있는 주소의 값을 찍지요.
%n은 가리키고 있는 주소에 앞 문자의 길이만큼 찍는다고 했죠?
1번째는 0x4f
2번째는 0x4212ecc0
3번째는 0x4207a750
까지는 주소에 이미 있는 값이 출력되지만
4번째는 AAAA를 넣으면 0x41414141이 나오는 것을 보셨죠?
즉, 4번째가 배열의 시작주소을 가리키고 있으며
그 곳은 fgets로 입력을 받고 있는 시작주소입니다.
여기에 소멸자의 주소(0x08049598)를 적고 %4$n을 한다면
문자열의 길이만큼 소멸자의 주소를 덮어쓰겠네요?
여기서 이해가 안가신다면 %n을 다시 확인해보면
좋을 듯 싶습니다.
이어서
스택영역은 최상단에 위치하기 때문에 주소가 큽니다..
그래서 2바이트씩 끊어서 덮어쓰기를 하려고 합니다.
소멸자의 주소 = 0x08049598
SHELLCODE의 주소 = 0xbffffbca
payload를 다시보면
__DTOR_END__의 하위주소(4) + __DTOR_END__의 상위주소(4) + SHELLCODE의 하위주소(4) + SHELLCODE의 상위주소(4) +%4$n + SHELLCODE의 상위주소(4) + %5$n
에서
__DTOR_END__(소멸자 함수의 주소를 담고 있음)
[0x08049598, 0x08049599 / 0x0804959a, 0x0804959b ]
= 0x 00 00 00 00
하위 2바이트는 빨간색이 되겠죠?
0x08049598에 SHELLCODE의 하위 2바이트 주소로 변경하고
상위 2바이트는 파란색이 되겠죠?
0x0804959a에 SHELLCODE의 상위 2바이트 주소로 변경합니다.
그러면 소멸자를 호출할 때 SHELLCODE가 호출이 될 것으로 예상이 됩니다.
SHELLCODE의 하위 2바이트 주소는?
0xfbca, 10진수로 64458입니다.
그러나 8바이트를 주소로 작성했기 때문에
빼줍니다.
즉, %64450x를 적어주면
기존 8바이트가 있으니
문자열 길이가 64458이 되고
이 값이 0x08049598에 들어가게 됩니다.
SHELLCODE의 상위 2바이트 주소는?
0xbfff입니다.
상위바이트 수도 덮기 위해
기존 바이트를 빼면?
0xbfff - 0xfbca
음수가나오네요?
그래서 상위주소를 1 증가시켜 빼기를 합니다.
0x1bfff - 0xfbca
= 0xc435, 10진수로 50229입니다.
이제 대망의 공격코드를 작성하는 일만 남았습니다.
(python -c 'print ("\x98\x95\x04\x08\x9a\x95\x04\x08"+"%64450c%4$n%50229c%5$n")'; cat) | ./attackme
는 일만 남았습니다.
화면이 사라져서
사용했던 공격코드를 다시 적었습니다.
기왕이면 스크립트로 만들어볼까요?
(python /tmp/20_exploit.py; cat) | ./attackme
이렇게 실행하시면 됩니다.
'Wargame > FTZ' 카테고리의 다른 글
[FTZ]level19 (0) | 2019.09.08 |
---|---|
[FTZ]level18 (0) | 2019.09.08 |
[FTZ]level17 (0) | 2019.09.08 |
[FTZ]level16 (0) | 2019.09.08 |
[FTZ]level15 (0) | 2019.09.08 |
댓글