본문 바로가기
스터디/wargames

[USE-Wargames] IO SmashTheStack Level2 문제풀이

by 깝태 2011. 8. 12.

level2@io:~$ cd /levels

level2@io:/levels$ ./level02
source code is available in level02.c

이번에는 프로그램을 실행하니 소스를 확인하라고 합니다. 
소스에는 바로바로 주석을 달아가겠습니다. 

level2@io:/levels$ cat level02.c
//a little fun brought to you by bla

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>

void catcher(int a) // 쉘 획득
{
        setresuid(geteuid(),geteuid(),geteuid());
        printf("WIN!\n");
        system("/bin/sh");
        exit(0);
}

int main(int argc, char **argv)
{
        puts("source code is available in level02.c\n");

        if (argc != 3 || !atoi(argv[2])) // 인자의 개수가 3개거 아니거나 || argv[2] 를 정수로 변환했을때 0이 아닌경우
/*  atoi 함수란?

atoi() 함수를 호출할 떄 먼저 문자열의 주소를 넘겨주게 됩니다.
그리고 atoi() 함수 안에서는 그 주소부터 시작해서 한 문자씩 읽어갑니다.
문자열의 끝, 널문자를 만나거나 숫자가 아닌 문자를 만날때까지 계속 읽어갑니다.
그리고 읽어들인 숫자들만 int형 정수로 리턴해줍니다.

ex) 3454x2341 를 받아들이면 3454 까지만 받아들이게 됩니다.
2341을 받고싶다면 2341의 시작주소 2이 저장된 문자열 주소를 atoi 함수에 넘겨줘야 합니다.
*/ 
                return 1; // 프로그램을 종료시켜 버립니다.
        signal(SIGFPE, catcher); // 시그널함수를 호출해 SIGFPE 신호가 전달되면 cather 함수 호출 
        return atoi(argv[1]) / atoi(argv[2]); // argv[1] 을 정수로 받은것과 argv[2] 를 정수로 받은것과 나눈값을 리턴 
}

먼저 쉘이 뜨는 부분은 signal(SIGFPE, catcher); 이 부분 입니다. 시그널에 대해 자세히 알아보겠습니다.
해석해보면 SIGNAL 함수를 호출하는데 SIGFPE 에 해당하는 예외상황을 발생시키면 특정 신호가 전달되고 함수로 지정된
해당 시그널 핸들러인 catcher 함수가 호출됩니다.

시그널이란 우리가 사용하는 프로세스에 예외상황, 에러가 났을때 발생하는 특정한 어떤 신호를 시그널이라고 부른다.
그리고 시그널에는 여러가지 종류가 있다.

level2@io:/levels$ kill -l
 1) SIGHUP                  2) SIGINT                 3) SIGQUIT                  4) SIGILL               5) SIGTRAP
 6) SIGABRT           7) SIGBUS         8) SIGFPE                  9) SIGKILL         10) SIGUSR1
11) SIGSEGV         12) SIGUSR2         13) SIGPIPE         14) SIGALRM         15) SIGTERM
16) SIGSTKFLT         17) SIGCHLD         18) SIGCONT         19) SIGSTOP         20) SIGTSTP
21) SIGTTIN         22) SIGTTOU         23) SIGURG         24) SIGXCPU         25) SIGXFSZ
26) SIGVTALRM         27) SIGPROF       28) SIGWINCH         29) SIGIO                 30) SIGPWR
31) SIGSYS         34) SIGRTMIN         35) SIGRTMIN+1         36) SIGRTMIN+2         37) SIGRTMIN+3
38) SIGRTMIN+4         39) SIGRTMIN+5       40) SIGRTMIN+6       41) SIGRTMIN+7         42) SIGRTMIN+8
43) SIGRTMIN+9         44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

가장 기본적인 시그널은 Ctrl + C 입니다. 컨트롤 + C 를 누르면 SIGINT 시그널이 호출합니다.
여기서 우리가 호출해야하는 시그널은 8번 시그널, SIGFPE 입니다.

SIGFPE 가 시그널이 호출되는 이유는 고정소수점 예외상황 발생시 발생되며 또 정수 오버/언더플로우 때와 0 으로 나뉘어질때
발생하는 신호입니다. 그러니깐 여기서 사용할 수 있는 방법은 정수 오버플로우를 일으키는 방법입니다.
여기서 예외상황을 발생시킬수 있는 취약점이 존재하는곳은  return atoi(argv[1]) / atoi(argv[2]); 이곳입니다.
atoi 함수를 이용해 나누고 리턴할때 오버플로우 취약점을 일으킬 수 있습니다.

int 형 정수 자료형의 범위를 보면 -2147483648 ~ 2147483647 까지 입니다. 그리고 저 수와 다른 수를 나눈값을 리턴하는데
최소범위인 -2147483648 을 가지고 꼼수를 쓸 수 있습니다. 왜, 저렇게 굳이 범위내의 숫자를 쓰나면 atoi 함수는 범위 외 숫자를
사용할경우 나온값을 65565 로 나눠서 리턴한다는 특이한점이 존재하기 때문입니다.

과연 어떻게 꼼수를 써서 문제를 풀까요?

A / B => int 형 정수 자료형의 범위를 넘어서야 한다.

저는 이렇게 풀었습니다, -2147483648 / -1 => 2147483648 으로 범위를 넘어가게된다.
생각해보면 안될것같지만 정답이 됩니다. -214747483648 을 -1 로 나누면 2147483648 이 되는데 이는 범위를 넘어가게되죠,
그래서 정수 오버플로우가 일어나 SIGFPE 신호를 호출하고 SIGNAL 함수에서 신호를 받아 catcher 함수를 호출합니다.

그러면 입력해보겠습니다.

level2@io:/levels$ ./level02 -2147483648 -1
source code is available in level02.c

WIN!
sh-4.1$ id 
uid=1003(level3) gid=1002(level2) groups=1003(level3),1002(level2),1029(nosu)
 
sh-4.1$ cat /home/level3/.pass
f9esfdy8T6Hd

성공적으로 문제가 풀어졌습니다.