드디어 마지막 문제 Level20입니다.

우선 힌트를 확인하도록 하겠습니다.


 

… gets에서 79글자만 입력 받아오네요. BOF로는 어려울듯 합니다.

하지만 그래도 아직 취약한 코드는 남아 있습니다.

바로 printf(bleh);입니다.

이 코드는 Format String Bug를 갖고 있습니다.

 

우선 간단하게 Format String Bug에 대해서 설명 드리겠습니다.

일반적으로 printf는 다음과 같이 사용합니다.

Char buf[] = “Hello World”;

Printf(“%s\n”, buf);

매우 일반적인 사용법이죠.

그런데 printf(“%s\n”, buf); 대신에 printf(buf); 이렇게 사용해도 똑같이 문자열이 출력 됩니다.

그래서 편의성을 위해서 printf(buf);와 같은 코드를 사용하는 사람들도 상당히 많죠.

근데 여기서 생각해 보아야 할 것이 있습니다.

만약, 버퍼에 일반적인 문자열이 아니고 형식지정자를 넣으면 어떻게 될까요?

그럼 printf(“%s %d”); 이런 코드가 되지 않을까요?

한번 확인해 보도록 하겠습니다.

 

일반적으로 buf에 문자열을 입력한 경우입니다.

정상적으로 문자열이 출력 되었네요.

 

그럼 이번엔 형식 지정자를 입력해 보도록 하겠습니다.

뜬금 없이 4f가 나왔네요. 4f79이므로 fgets에서 사용하기위해 스택에 push한 값이 남아 있는게 아닌가 싶습니다.

형식 지정자를 여러게 넣어보도록 하겠습니다.

.. 여러 값들이 나오네요..

아직은 잘 모르겠습니다. 이번엔 일반 문자 뒤에 형식 지정자를 이어서 넣어보도록 하겠습니다.

이번에도 여러 값들이 나왔는데 익숙한 41414141이 보입니다. 이는 우리가 입력해준 AAAA의 아스키 코드 값이죠.

이를 통해서 형식 지정자를 입력해주면 stack의 값들을 확인할 수 있음을 알 수 있습니다.

 

여기서 잠시 printf를 호출할때 스택의 상황을 한번 생각해보도록 하겠습니다.

Printf(“%s %d”, buf, x);의 경우 스택은 다음과 같이 될것입니다.

Buf : Hello World

X : 40

………

&x

&buf

……….

어셈블리어 언어로 생각하면 x의 주소를 push하고 buf의 주소를 push 하고 printf함수를 호출하겠죠.

그렇게 되면 %s 형식 지시자는 &buf의 내용을 해석해서 값을 읽어오고, %d&x의 내용을 해석해서 값을 읽어 옵니다.

 

AAAA %x %x %x %x라고 값을 입력하면 스택 구조는 다음과 같이 되겠죠.

%x

%x

%x

%x

Buf : AAAA

0x4207a750

0x4212ecc0

0x4f

&buf

 

Printf(buf)했으니 바로 buf의 내용을 가져오게 됩니다.

내용은 AAAA %x %x %x %x이구요. 따라서 AAAA를 출력한 뒤 %x를 만나 그 다음 스택의 정보들을 출력하게 되는겁니다. %x4개니 전체 출력 결과는 AAAA 4f 4212ecc0 4207a750 41414141이 되는거구요. 만약 %x4개 입력 입력하면 그 위 “ %x “에 해당하는 아스키 값까지 출력하게 될것입니다. 그 값은 20782520이구요.

이것이 바로 Format String Bug입니다.

 

그런데 형식 지정자로 값을 읽어 오는것은 좋은데, 값을 입력하지 못하면 메모리 변조를 할수가 없으니 이대로는 무용지물입니다.

하지만 형식지정자 중에 유일하게 값을 입력하는 형식지정자가 있습니다. 바로 %n입니다. %n은 바로 이전에 출력한 값의 크기를 해당 변수에 저장하는 기능을 합니다.

코드에서 보면 알수 있다시피 1234를 출력하였을 경우에 %n을 통해 n에 출력 크기 4byte가 입력 되었음을 알 수 있습니다.

 

이를 이용해 원하는 위치에 원하는 값을 입력 할 수 있겠네요.

 

그럼 한번 원하는 주소에 %n을 이용해 값을 입력할 수 있도록 스택을 짜보겠습니다.

%n

%c

%n

%c

0xbffff2bc

AAAA

0xbfff2ba

AAAA

 

이러한 스택을 생각해 보겠습니다.

AAAA\xba\xf2\xff\xbfAAAA\xbffff2bc%c%n%c%n이라고 입력하면 이렇게 스택이 구성됩니다.

그럼 우선 AAAA\xba\xf2\xff\xbfAAAA\xbffff2bc가 출력이 되곘죠.

그리고 %c를 만나서 AAAA가 들어있는 스택에 대하여 진행하고, 그리고 %n을 만나게 됩니다. AAAA위에 있는 스택은 0xbffff2ba이죠. 따라서 메모리 0xbffff2ba번지에 %n을 통해 현재까지 출력한 크기를 입력하게 됩니다.

AAAA\xba\xf2\xff\xbfAAAA\xbffff2bc이므로 16byte0xbffff2ba에 입력되게 됩니다. 그다음에 만나는 값은 %c이므로 AAAA를 건너뛰고 다시 %n을 만나 그다음 스택인 0xbffff2bc주소에 16을 입력하게 됩니다.

 

그러나 우리가 입력해야 할 주소값은 굉장히 큰 숫자입니다. 그 많은 문자를 직접 입력하기에는 메모리 용량도 그렇고, 힘도 들기 때문에 형식 지정자의 편리함을 이용하면 됩니다.

만약 형식지정자를 %100d라고 입력하면 공백이 100개 입력이 됩니다. 즉 이를 이용하면 쉽게 원하는 만큼의 입력이 가능합니다.

 

이러한 방식으로 원하는 주소에 원하는 값을 입력해주면 됩니다.

 

그럼 우선 쉘코드를 등록하고, 주소를 얻어오도록 하겠습니다.

쉘코드를 등록하였고 주소는 0xbffffc6e입니다.

 

쉘코드를 얻어왔으니 이제 ret의 주소값을 얻어올 차례입니다. 

그러나 gdb로 확인했더니 main symbol을 찾을 수 없다고 나옵니다.

 

이때 사용할 수 있는 것이 .dtors영역이라는 것입니다.

간단하게 설명하면 gcc는 컴파일 할때 .ctors.dtors 두 세그먼트를 생성합니다.

 

두 영역의 특징은

.ctros 속성의 함수는 main()전에 실행되고

.dtros 속성의 함수는 main()종료 후에 실행 된다는 것입니다.

그러므로 .dtros 세그먼트에 쉘코드의 주소를 넣어주면 main()이 종료된 뒤에 실행이 될것입니다.

 

그럼 .dtors의 주소를 알아내도록 하겠습니다.

.dtors 세그먼트의 주소는 0x08049594이네요. 이 주소에 4byte를 더한 0x08049598에 쉘코드의 주소를 등록해 주면 됩니다.

 

그러면 필요한 정보는 다 구해졌습니다.

쉘코드의 주소는 0xbffffc6e이고 저장이 되는 대상 메모리의 주소는 0x08049598입니다.

 

AAAA\x98\x95\x04\x08%3221224558c%n이렇게 입력해 주면 될 것 같습니다.

하지만 몇가지 더 보완을 해주어야 합니다.

그림과 같이 AAAA가 들어있는 메모리에 접근하기 까지 형식지정자를 4개를 사용했습니다. 따라서 AAAA를 받아오는 형식지정자 %c앞에 다른 형식지정자를 3개 추가해 주어야 합니다.

 

추가된 입력은 다음과 같습니다.

AAAA\x98\x95\x04\x08%x%x%x%3221224558c%n

그러나 여기서 문제가 하나 더 발생합니다. X86 pc 에서는 3221224558과 같은 큰 수를 입력 할 수 없습니다. 따라서 0xBFFFFC6E를 반씩 나눠서 입력해야 합니다.

0x08049598에는 0xFC6E2바이트 증가한 0x0804959A에는 0xBFFF을 입력해 주면 됩니다.

 

이를 반영한 입력은 다음과 같습니다.

AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%x%x%x%64622c%n%49151c%n

그러나 아직 문제가 하나더 남았습니다. %n%n이전까지의 입력 용량을 저장한다고 했습니다.

AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%x%x%x%64622c%n에서 %n까지의 입력 용량은 4+4+4+4+?+?+?+64622입니다.

우선 %x는 메모리에 어떤 값이 있느냐에 따라서 출력 결과가 다르기 때문에 8byte로 통일해주어야 합니다. %x대신에 %8x를 쓰면 균일하게 8byte가 출력됩니다.

그럼 입력되는 값이 4+4+4+4+8+8+8+64622이므로 64622에 맞추기 위해서는 64622에서 40만큼 빼주어야 합니다.

 

따라서 입력은 다음과 같이 변경 됩니다.

AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64582c%n%49151c%n

마찬가지로 뒤에 49151도 변경해 주어야 합니다. 그 이전까지 출력된 양이 64622이므로 49151에서 64622를 빼어 주어야 하는데 음수가 발생합니다. 따라서 0x1BFFF에서 0xFC6E를 뺀 값인 50065를 넣어주면 됩니다.

 

이렇게 최종적으로 변경된 입력은 다음과 같습니다.

AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64582c%n%50065c%n

 

입력값을 넣어 보도록 하겠습니다.

다행이도 쉘이 떴습니다.

계정명이 clear네요.


패스워드는 i will come in a minute입니다.

 

이렇게 모든 ftz 문제를 다 풀었네요.

작년 1223일쯤 시작했으니거의 1년 넘게 걸렸네요. 도중에 반년 정도 쉰적도 있고.. 레벨 11부터 20까지 푸는데에는 10일 정도 걸렸네요.

 

이제 FTZ는 끝났으니 다른 문제로 돌아오겠습니다.

 

수고 많으셨습니다.

+ Recent posts