본문으로 바로가기

해커스쿨 LOB nightmare 풀이, write-up

category Wargame/LOB 2018. 10. 23. 17:33

nightmare --- beg for me



문제 풀기 전에 아래의 명령어를 반드시 쳐야한다.



$export SHELL=/bin/bash2

$/bin/bash2



우선 nightmare의 디렉터리를 확인한다. 





xavius라는 의심스러운 파일이 존재한다.

xavius는 xavius의 권한을 가지고 있고, setuid가 걸려있는 파일이다.



xavius.c를 가지고 xavius elf 파일을 만들었을 가능성이 있기 때문에, xavius 파일을 본 뒤 어떤 취약점을 가지고 있는지 확인해보자.






지역 변수를 선언한다.



1
2
char buffer[40];                                                                    
char *ret_addr;
cs



buffer에 256 byte의 입력을 저장하고 buffer를 출력한다.



1
2
fgets(buffer, 256, stdin);                                                            
printf("%s\n", buffer);
cs



*(buffer+47) == '\xbf' 이면 "stack retbayed you!\n"를 출력하고 종료한다.



1
2
3
4
5
if(*(buffer+47== '\xbf')
{
    printf("stack retbayed you!\n");                                                
    exit(0);
}
cs



*(buffer+47) == '\x08'이면 "binary image retbayed you, too!!\n"를 출력하고 종료한다.



1
2
3
4
5
if(*(buffer+47== '\x08')
{
    printf("binary image retbayed you, too!!\n");                                        
    exit(0);
}
cs



buffer+44가 가리키는 곳부터 4 byte 만큼 &ret_addr에 저장한다.



1
memcpy(&ret_addr, buffer+444);                                                    
cs



memcpy 함수


void *memcpy(void *destination, const void *source, size_t num);

source가 가리키는 곳부터 num byte 만큼 destination이 가리키는 곳으로 복사한다.

return 값은 destination의 포인터를 return 한다.


"\x90\x90"을 ret_addr에 2 byte 씩 저장한다.

만일 *ret_addr == '\xc9'이고, *(ret_addr+1) == '\xc3'이면 

"You cannot use library function!\n"을 출력하고 종료한다.

ret_addr++



1
2
3
4
5
6
7
8
9
10
while(memcmp(ret_addr, "\x90\x90"2!= 0)            // end point of function        
{
    if(*ret_addr == '\xc9' ){                        // leave
            if(*(ret_addr+1== '\xc3'){            // ret
                printf("You cannot use library function!\n");
                exit(0);    
        }
    }
    ret_addr++;
}
cs



buffer를 0으로 초기화하고, buffer+48~0xbfffffff까지 stack 영역을 0으로 초기화한다.



1
2
3
// stack destroyer
memset(buffer, 044);
memset(buffer+4800xbfffffff - (int)(buffer+48));                                
cs



buffer의 스텍 윗 부분을 40 byte를 제외하고 모두 0으로 초기화 한다.



1
2
3
// LD_* eraser
// 40 : extra space for memset function
memset(buffer-300003000-40);                                                    
cs



xavius의 메모리 구조를 예상해보면 아래와 같다.






실제 변수가 할당될 때, gcc가 최적화를 위해 자동적으로 dummy를 생성할 수 있다.



gdb를 이용해 xavius를 분석해보자. xavius를 gdb로 실행하는 도중 permission denied가 발생하면 cp 명령을 이용하여 파일을 복사하면 된다. 현재 user의 권한으로 똑같은 파일을 가질 수 있기 때문이다.





위와 같이 xavius가 nightmare의 권한으로 복사된 것을 확인할 수 있다.



dummy가 생성되었는지 gdb를 통해 확인할 수 있다.





dummy가 추가적으로 생성되지 않고 정확히 44(0x2C) byte가 생성된 것을 확인할 수 잇다.



문제의 제한으로 인하여, stack 영역, library 영역, code 영역 모두 사용할 수 없다.

그렇다면 우리가 사용할 수 있는 공간은 어디일까??

소스코드를 잘 살펴보면 stdin을 이용하는 것을 확인할 수 있다.


stdin은 표준입력 파일 스트림으로, 우리가 입력한 문자열을 가지고 있는 버퍼이다.

그렇다면 이곳을 이용할 수 있지 않을까???


gdb를 통해 stdin의 주소를 찾아 분석해보자.



fgets(buffer, 256, stdin); 형식으로 사용했다.

즉, fgets를 호출하기전에 제일 먼저 push하는 값이 stdin의 주소이다.





stdin의 주소는 0x401068c0라는것을 알 수 있다.

더 자세히 알아보면 아래와 같다.




위와 같이 stdin(*0x8049a3c)가 가리키는 값도 확인 가능하다.





start of ptr = 0x40015000이다. start of ptr + 16(A*16) + 1(\n)를 통해 end of ptr을 유추할 수 있다.

end of ptr = 0x40015011이다.



stdin 구조체를 공부하고 조사해보면, stdin에서 실행이 가능하다.

따라서 우리의 payload는 stdin에서 실행하게 작성할 것이다.


nop(20) + shellcode(24) + ret(4) 로 구성된다.





다음과 같이 공격에 성공할 수 있다.


일반적으로 stdin(표준입력)이 EOF(End of File)을 돌려주기 때문에 단순 쉘 코드를 작성하면 쉘이 종료된다.

따라서 이러한 문제를 없에기 위해서 cat(표준출력)을 이용한다.