CVE-2014-6271으로 알려진 ShellShock이 이슈화 되고 있는데, 간단한 내용을 알아보고자 합니다.


다음의 URL을 먼저 방문하시면 도움이 되실 것 같습니다.

http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-6271

https://securityblog.redhat.com/2014/09/24/bash-specially-crafted-environment-variables-code-injection-attack/


테스트는 CentOS 6.3 64bit machine에서 진행하였습니다.


[그림 1] 취약한 bash 버전의 테스트 화면


위 테스트에서 기본적으로 다음과 같은 구문이 문제시 되는 것 같습니다.

env x='() { : ; } ; echo vulnerable'


쉘 스크립트 언어의 Syntax 측면에서 파싱 구조를 살펴보았을 때 '변수 x'에 할당하고 있는 값은 ' (single quotes) 가 시작해서 끝나는 지점까지를 해당 변수의 값으로 할당하여야 하지만, 특수 문자 파싱의 오류로 인하여 ';' 문자 뒤의 echo 구문이 쉘 스크립트의 실행 구문으로 실행되어 문제가 발생하는 것 같습니다.

즉, "echo vulnerable" 문자열은 콘솔 상 출력되지 않아야 할 "변수 x"의 값이어야 하나, 해당 문자열이 쉘 스크립트의 실행 영역으로 수행되면서 악의적인 명령어의 삽입이 가능합니다.


조금 더 자세한 구조를 알아보기 위해 ltrace를 사용해 보았습니다.

 

[그림 2] 변수 할당 영역을 벗어나 구문 실행 파싱 영역으로 수행되는 테스트 코드


[그림 3] 쉘 스크립트의 실행 구문 파싱 영역


해당 취약점은 패키지 업데이트로 해결할 수 있습니다.


[그림 4] bash 패키지 업데이트


현재 TeamCR@K은 더 상세한 내용을 위해 분석 중에 있으며, 분석이 완료되면 해당 내용을 다시 게시하도록 하겠습니다.


루팅된 Android 환경에서 특정 모바일 애플리케이션을 조작하는 방법은 크게 세가지 정도로 구분할 수 있습니다.


1. ptrace() 시스템 콜 사용

2. LD_PRELOAD 환경변수 사용

3. LKM (Loadable Kernel Module) 사용


다른 방법이 더 존재할지는 모르겠지만 크게 위와 같은 맥락에서 움직이고 있는 것 같습니다.

위 방법들 중에서 오늘은 LD_PRELOAD에 대해 조금 말씀드릴까 합니다.


Android 환경에서 애플리케이션이 실행될때에는 zygote라는 프로세스에 의해 apk파일이 로드되며 실행되는데, 이러한 zygote 프로세스는 전체적으로 애플리케이션의 권한 할당이나 프로세스 생성에 관여하게 됩니다.

zygote 프로세스는 애플리케이션이 실행되는 가장 기본적인 환경이므로 init (pid 1번 프로세스) 프로세스에 의해 관리되고 있으며, init 프로세스는 zygote 프로세스가 정상적인 동작을 하지 않거나 외부환경에 의해 동작을 멈추면 즉시 재시작하도록 되어있습니다.


init 프로세스는 부팅 초기에 init.rc 파일을 읽어들여 환경설정과 함께 zygote 프로세스를 생성하도록 수행합니다.

다음은 init.rc 파일의 일부분입니다.

...

> snip <

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-sys

    class main

    socket zygote stream 660 root system

    onrestart write /sys/android_power/request_state wake

    onrestart write /sys/power/state on

    onrestart restart media

    onrestart restart netd

> snip <

...


zygote의 실제 실행프로그램은 /system/bin/app_process 이며, 옵션에 따라 zygote 프로세스 역할을 수행하는 것으로 보입니다.

zygote프로세스가 실행될 때 LD_PRELOAD 환경변수를 추가하여 실행되도록 하면 특정 라이브러리 함수들을 후킹하여 변조하는 기법이 존재하는데, 대부분 이를 위해 init.rc 파일을 수정하게 됩니다.

그러나 init.rc 파일을 수정하기 위해서는 boot 이미지파일의 압축해제, 수정 및 변조, 재 압축, boot 이미지파일 overwrite 등의 과정을 거쳐야 하므로, 귀찮은 작업이 될 수도 있습니다.

그래서 다른 방법이 없을까 고민해 보다가 다음과 같이 환경변수를 injection 하는 방법을 구상해보았습니다.


[그림 1] LD_PRELOAD injection


"zygote가 정상적인 실행상태가 아니면 init 프로세스에서 zygote 프로세스를 재 시작한다"는 점에 착안하여, 먼저 init 프로세스를 ptrace로 감지하면서 기존에 실행되고 있던 zygote 프로세스에 SIGKILL 시그널을 보냅니다.

그러면 zygote의 부모프로세스인 init 프로세스에서는 SIGCHLD 시그널을 받게 되고, zombie 프로세스가 되지 않도록 zygote가 사용하고 있던 리소스들을 kernel에 반환 후 zygote 프로세스의 재 시작 작업을 수행합니다.

zygote 프로세스의 재시작은 fork 시스템 콜을 수행하여 새로운 프로세스를 생성 한 후, execve 시스템 콜을 사용하여 zygote 바이너리를 실행하는 구조로 되어 있습니다.

바이너리 실행에 사용되는 execve 시스템 콜의 원형은 다음과 같습니다.


int execve(const char *filename, char *const argv[], char *const envp[]);


execve 시스템 콜의 3번째 인자가 envp라고 되어 있는데 바로 이 envp 인자가 실행되는 프로그램의 환경변수를 결정하게 됩니다.

따라서 zygote를 실행하기 위한 execve 시스템 콜 실행 직전에 LD_PRELOAD 환경변수를 injection한다면 zygote는 변조된 환경변수를 가진 상태에서 실행될 수 있습니다.

다음은 이러한 시나리오를 가지고 만든 injector 소스코드의 일부입니다.


/*

* god_daewoong.c

*

* Version: v1.0

* Date: 2014.09.10

*

* You can compile using gcc with -D option: -D_SO_PATH=\"${SO_PATH}\"

*/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <ctype.h>

#include <errno.h>

#include <sys/wait.h>

#include <sys/ptrace.h>

#include <linux/user.h>

#include <linux/ptrace.h>


#define EXTRA_ENVIRON   "LD_PRELOAD="_SO_PATH

#define ZYGOTE_PATH     "/system/bin/app_process"


long internal_ptrace(int, pid_t, void *, void *);

#define PTRACE(r,p,a,d) internal_ptrace(r,p,a,d)

#define SYSCALL_REGISTER(r) r.ARM_r7

#define IP_REGISTER(r) r.ARM_ip

#define RESULT_REGISTER(r) r.ARM_r0

#define ENV_REGISTER(r) r.ARM_r2


#define MAX_BUFFER_SIZE 1024

#define MAX_PROC_PID    65536


int init_pid = 1;


void sigint(int signo)

{

        fprintf(stdout, "[!] Received Ctrl+C signal.\n");

        if(init_pid != 0) {

                if(PTRACE(PTRACE_DETACH, init_pid, (void*)1, 0) < 0)

                        fprintf(stderr, "[!] PTRACE_DETACH error\n");

        }

        exit(0);

}


long internal_ptrace(int request, pid_t pid, void *addr, void *data)

{

        int ret, stat;


        errno = 0;


        while(1) {

                stat = 0;

                ret = waitpid(pid, &stat, WNOHANG);

                if((ret == pid && WIFEXITED(stat)) || (WIFSTOPPED(stat) && !WSTOPSIG(stat))) {

                        fprintf(stderr, "[!] Killed Process: %d\n", pid);

                        return -1;

                }

                if((ret = ptrace(request, pid, addr, data)) == -1) {

                        switch(request) {

                        case PTRACE_DETACH:

                        case PTRACE_SYSCALL:

                        case PTRACE_KILL:

                                return 0;

                        default:

                        break;

                        }

                } else {

                        break;

                }

        }

        return ret;

}

int doPunchLine(void)
{
        struct pt_regs regs;
        siginfo_t sig;
        int zygote_path_check = 0;
        char buf[128];
        int i, ret = -1;
        int zygote = 0;

        fprintf(stdout, "[*] Get the zygote pid..... ");
        if((zygote = get_zygote_pid()) < 0) {
                fprintf(stdout, "failed\n");
                return -1;
        }
        fprintf(stdout, "%d\n", zygote);

        if(PTRACE(PTRACE_ATTACH, init_pid, (void*)1, 0) < 0) {
                fprintf(stderr, "[!] PTRACE_ATTACH error\n");
                return -1;
        }

        fprintf(stdout, "[*] Attached the init process successfully: %d\n", init_pid);

        kill(zygote, SIGKILL);
        fprintf(stdout, "[*] Sending a SIGKILL signal to zygote\n");

        // XXX: tracing...
        while(1) {
....
> snip <
...
        }

failed:
        if(PTRACE(PTRACE_DETACH, init_pid, (void*)1, 0) < 0) {
                fprintf(stderr, "[!] PTRACE_DETACH error\n");
        }

        return ret;
}

int get_zygote_pid(void)
{
        char fname[64];
        char status[64];
        int i, zygote_pid;
        FILE *fp = NULL;

        zygote_pid = -1;

        for(i = 0; i < MAX_PROC_PID; i++) {
                snprintf(fname, sizeof(fname), "/proc/%d/status", i);
                if((fp = fopen(fname, "r")) == NULL)
                        continue;
                if(fgets(status, sizeof(status), fp) != NULL) {
                        if(strstr(status, "zygote") != NULL) {
                                zygote_pid = i;
                                fclose(fp);
                                break;
                        }
                }
                fclose(fp);
        }
        return zygote_pid;
}

int HerpoongIs0Per(int pid)
{
        void *environ, *mem, *sp, *init_sp, *data;
        int text, j, newdata, empty = 0;
        const char *add = EXTRA_ENVIRON;
        struct pt_regs regs;

        if(PTRACE(PTRACE_ATTACH, pid, (void*)1, 0) < 0) {
                fprintf(stderr, "[!] PTRACE_ATTACH error\n");
                return -1;
        }

        // XXX: Finding execve() syscall
        while(1) {
                if(PTRACE(PTRACE_GETREGS, pid, 0, &regs) < 0) {
                        fprintf(stderr, "[!] PTRACE_GETREGS error\n");
                        goto failed;
                }

                // XXX: execve() sys-call number is '11'
                if(SYSCALL_REGISTER(regs) == 11 && IP_REGISTER(regs) == 0) {
                        environ = (void*)ENV_REGISTER(regs);
                        fprintf(stdout, "[*] Environ: %p\n", environ);
                        break;
                }
                if(PTRACE(PTRACE_SYSCALL, pid, (void*)1, 0) < 0) {
                        fprintf(stderr, "[!] PTRACE_SYSCALL error\n");
                        goto failed;
                }

        }
...
> snip <
...
        if(PTRACE(PTRACE_DETACH, pid, NULL, NULL) < 0) {
                fprintf(stderr, "[!] PTRACE_DETACH error\n");
        }
failed:
        return 0;
}


int main(void)
{
        int new_zygote;

        signal(SIGINT, sigint);

        fprintf(stdout,
                "*****************************************************\n"
                "************ Android LD_PRELOAD Injector ************\n"
                "*****************************************************\n"
                "                Implemented by TeamCR@K in A3Security\n\n"
                "  Greetz to: 1ndr4, bash205, blpark, fr33p13, alrogia\n"
                "                              kgyoun4, maz3, rhaps0dy\n\n");

        if((new_zygote = doPunchLine()) != -1) {
                HerpoongIs0Per(new_zygote);
        }
        fprintf(stdout, "[*] Done\n");
        return 0;
}


악의적으로 사용될 수 있는 우려로 인해 전체 소스코드를 올리지 못하는 점 양해 부탁드립니다.

위 코드를 컴파일 하여 실행하면 다음과 같이 재시작된 zygote의 실행환경에서 특정 라이브러리가 적재되어 실행되는것을 볼 수 있습니다.


[그림 2] Injector 실행 전 zygote의 기본 환경변수


[그림 3] Injector 실행 후 재 시작된 zygote 프로세스에 적용된 LD_PRELOAD 환경변수


[그림 4] LD_PRELOAD에 의해 적재된 libopen.so 라이브러리가 정상적으로 동작한 화면


LD_PRELOAD로 로딩한 Shared Object의 소스코드는 다음과 같습니다.


/* libopen.c */

#include <stdio.h>

#include <stdlib.h>

#include <stdarg.h>

#include <unistd.h>

#include <dlfcn.h>


#define LIBC_PATH       "/system/lib/libc.so"

#define LOG_PATH        "/data/test/openhook.log"


static int (*orig_open)(const char *f, ...) = NULL;


int open(const char *f, ...)

{

        int fd = 0, flags = 0, mode = 0;

        void *dl = NULL;

        va_list args;

        FILE *fp = fopen(LOG_PATH, "a+");


        if(fp == NULL) fp = stdout;


        fprintf(fp, "[HOOK-LIB] Executed open() system call: %s\n", f);

        if((dl = dlopen(LIBC_PATH, RTLD_LAZY)) == NULL) {

                fprintf(fp, "[!] dlopen() function error.\n");

                return -1;

        }

        orig_open = dlsym(dl, "open");

        if(orig_open == NULL) {

                fprintf(fp, "[HOOK-LIB] dlsym() function error.\n");

                return -1;

        }

        va_start(args, f);

        flags = va_arg(args, int); // 2nd argument of open() syscall (flags)

        mode = va_arg(args, int); // 3rd argument of open() syscall (mode)

        va_end(args);

        if((fd = orig_open(f, flags, mode)) < 0) {

                fprintf(fp, "[HOOK-LIB] orig_open() function error: %s\n", f);

                return -1;

        }

        dlclose(dl);

        fclose(fp);

        return fd;

}


zygote 프로세스가 실행되는 시점에 환경변수를 추가하여 실행하는 형태의 인젝션 방식은 init.rc 파일을 손상시킬 우려가 없으며, 모바일 기기를 재시작하면 본래의 시스템 환경으로 동작하게 됩니다.

injector의 Full Source code를 올려드리지는 못하지만 특정 환경변수를 추가하여 zygote를 실행하도록 되어 있는 pre-compiled binary를 대신 올려드립니다.


god_daewoong-poc


위 바이너리를 실행하면 다음과 같이 특정이름의 환경변수가 추가되어 동작하는 zygote를 확인 하실 수 있습니다.


[그림 5] 테스트 injector 동작 화면


본 injector 프로그램은 루팅된 Android 버전에서 테스트 되었으며, 다음과 같은 디바이스 정보를 가진 기기에서 테스트 되었습니다.


[그림 6] 테스트 환경


감사합니다.

지난 9월 4일 목요일 저희 회사인 에이쓰리시큐리티에서 보안 전략 세미나 SMS 2014를 진행하였습니다.

너무나 많은 분들이 자리를 빛내주신 점에 대해 이 자리를 빌어 다시 한번 감사의 말씀을 드리고자 합니다.


저희 TeamCR@K에서는 "모바일 금융거래 애플리케이션의 보안대책 우회 기법"이라는 주제로 시연을 진행했었고, 이에 대해 기술적인 부분에서 조금 더 많은 내용을 공유해 볼까 합니다.


시연의 주제는 모바일 애플리케이션의 실행 흐름 조작을 통한 보안대책들을 우회하는 기법으로써 다음과 같은 내용을 준비하였습니다.


[그림 1] 임의의 시스템 콜을 사용한 모바일 앱 실행 플로우 변조



[그림 2] 임의의 함수 후킹을 통한 모바일 앱 실행 플로우 변조


임의의 시스템 콜을 사용하는 방법은 ptrace() 시스템 콜을 사용하여 특정 시스템 콜의 실행 전 인자 정보 조작이나 실행 후 반환 값 조작을 통해 모바일 앱 실행에 조작을 가하는 방법을 의미합니다.

임의의 함수 후킹을 통한 방법으로는 LD_PRELOAD 환경 변수를 init 프로세스에 적용하여 특정 라이브러리의 함수 로직에 대해 조작을 가하는 방법입니다.

일반적으로 시스템 콜이라는 단어와 함수라는 단어를 혼재하여 사용하기도 하지만 개인적으로 시스템 콜이란 kernel에 직접 interrupt를 일으키는 구조이고 함수라는건 그보다 상위에 포진되어 라이브러리 형태로 존재하는 형태라고 생각하기에 구분지어 이야기 하도록 하겠습니다. 양해 부탁드립니다.


LD_PRELOAD에 대해서는 별도의 Shared Object를 제작하고 SSL-Strip을 구현하는 방법으로 시연하였습니다.

해당 환경변수를 이용한 프로그램 실행 로직의 조작방법은 이미 많이 알려져 있기에 별도로 추가적인 내용은 말씀드리도록 하지 않겠습니다.


ptrace() 시스템 콜을 사용하는 공격 기법의 경우 처음에는 "프로그램 및 디바이스 변조 탐지"를 우회하기 위한 목적으로 진행했던 내부 프로젝트였습니다.

그러나 시간이 흐르고 점점 프로젝트의 성향도 많은 케이스의 우회 방안을 포함하다 보니 툴 제작까지 하게 되었습니다.

아래는 해당 툴의 실행 모습입니다.


[그림 3] 안드로이드 애플리케이션 실행 변조 툴 (A.K.A ardb)


프로젝트 시작점 자체가 "프로그램 및 디바이스 변조 탐지"를 우회하기 위한 목적으로 시작되어서 툴 이름도 "Android Rooting Detection By Pass Tool" 입니다.

툴은 -p 옵션과 -f 옵션을 사용하도록 되어 있습니다.

-p 옵션은 package 이름을 인자로 받게 되며, -f 옵션은 일반 console 상에서 실행 할 프로그램 경로 정보를 인자로 받게 됩니다.

위의 경우 com.testbank 프로그램이 실행되면 이를 추적하고, fork() 시스템 콜 실행을 탐지하여 그 결과 값을 -1로 조작하도록 한 화면입니다.

루팅 탐지 케이스의 경우 특정 경로의 파일이 존재하거나 실행되는 경우를 루팅되었다고 판별하기도 하고, 탐지 루틴을 Shared Object 안에 구현 후 해당 Object의 내부 함수를 실행하여 판별하기도 합니다.

저희가 모의해킹 할 때에 이러한 여러 케이스들에 대항하기 위해 아예 변조시킬 Rule을 설정파일로 만들어 관리하기로 하였습니다.

다음은 설정파일의 내용입니다.


[그림 4] 툴 설정파일

설정파일의 지시자는 FUNCTION과 VETO 두 가지가 존재합니다.

FUNCTION 지시자의 경우 시스템 콜을 기준으로 변조 여부를 결정하며, VETO 지시자의 경우 문자열을 기준으로 처리합니다.

우선 첫번째 FUNCTION 지시자 설정은 fork 시스템 콜 실행 후  결과 레지스터를 -1로 변경한다  는 설정이고, 두번째 FUNCTION 지시자의 경우, execve 시스템 콜 실행 전 첫번째 인자의 주소값을 변경한다 는 설정입니다.

VETO 지시자의 경우는 인자 값에 /system/xbin/su 문자열을 예외처리 하는 내용들로 되어 있습니다.

앞으로 더 많은 모바일 애플리케이션 케이스를 진단하다 보면 Rule도 지속적으로 늘어 날 것 같습니다.

다음은 실행하며 남기는 로그 화면입니다.


[그림 5] 로그파일의 내용


툴을 실행할 때의 콘솔화면에서는 간략하게 탐지 된 내용만 출력되게 하고 실제 탐지되는 모든 내용은 로그파일에 남도록 구현하였습니다.

로깅 루틴을 구현하다 보니 strace 개발자가 존경스러워 집니다.

본 툴은 기본적으로 zygote 프로세스에서 fork() 시스템 콜을 지속적으로 추적하도록 되어있는데, 아래와 같은 형태로 구현이 되어 있습니다.


/*

* - Name: Zygote_Trace.c

* - Date: 2014.09.06

* Implemented by TeamCR@K in A3Security

*/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <ctype.h>

#include <errno.h>

#include <getopt.h>

#include <sys/wait.h>

#include <sys/ptrace.h>

#include <linux/user.h>

#include <linux/ptrace.h>


long internal_ptrace(int, pid_t, void *, void *);


#define ZYGOTE_NAME     "zygote"

#define MAX_PROC_PID    65536

#define PTRACE(r,p,a,d) internal_ptrace(r,p,a,d)

#define SYSCALL_REGISTER(r) r.ARM_r7

#define IP_REGISTER(r) r.ARM_ip

#define RESULT_REGISTER(r) r.ARM_r0


int zygote_pid;


void sigint(int signo)

{

        fprintf(stdout, "[!] Received INTERRUPT signal.\n");

        if(zygote_pid != 0) {

                if(PTRACE(PTRACE_DETACH, zygote_pid, (void*)1, 0) < 0)

                        fprintf(stderr, "[!] PTRACE_DETACH error\n");

        }

        exit(0);

}


int get_zygote_pid(void)

{

        char fname[64];

        char status[64];

        int i;

        FILE *fp = NULL;


        zygote_pid = -1;


        for(i = 0; i < MAX_PROC_PID; i++) {

                snprintf(fname, sizeof(fname), "/proc/%d/status", i);

                if((fp = fopen(fname, "r")) == NULL)

                        continue;

                if(fgets(status, sizeof(status), fp) != NULL) {

                        if(strstr(status, ZYGOTE_NAME) != NULL) {

                                zygote_pid = i;

                                fclose(fp);

                                break;

                        }

                }

                fclose(fp);

        }

        return zygote_pid;

}


long internal_ptrace(int request, pid_t pid, void *addr, void *data)

{

        int ret, stat;


        errno = 0;


        while(1) {

                ret = waitpid(pid, &stat, WNOHANG|WUNTRACED);

                if((ret == pid && WIFEXITED(stat)) || (WIFSTOPPED(stat) && !WSTOPSIG(stat))) {

                        fprintf(stderr, "[!] Killed Process: %d\n", pid);

                        return -1;

                }

                if((ret = ptrace(request, pid, addr, data)) == -1) {

                        switch(request) {

                        case PTRACE_DETACH:

                        case PTRACE_SYSCALL:

                        case PTRACE_KILL:

                                return 0;

                        default:

                        break;

                        }

                } else {

                        break;

                }

        }

        return ret;

}


int do_trace_zygote(void)

{

        struct pt_regs regs;


        if(PTRACE(PTRACE_ATTACH, zygote_pid, (void*)1, 0) < 0) {

                fprintf(stderr, "[!] PTRACE_ATTACH error\n");

                return -1;

        }


        fprintf(stdout, "[*] Attached the zygote process successfully: %d\n", zygote_pid);


        // XXX: tracing...

        while(1) {

                if(PTRACE(PTRACE_GETREGS, zygote_pid, 0, &regs) < 0) {

                        fprintf(stderr, "[!] PTRACE_GETREGS error\n");

                        goto failed;

                }


                // XXX: fork()'s system call number is '2'

                if(SYSCALL_REGISTER(regs) == 2 && IP_REGISTER(regs) == 1) {

                        fprintf(stdout, "[*] Created a new process from zygote successfully: %ld\n", RESULT_REGISTER(regs));

                }

                if(PTRACE(PTRACE_SYSCALL, zygote_pid, (void*)1, 0) < 0) {

                        fprintf(stderr, "[!] PTRACE_SYSCALL error\n");

                        goto failed;

                }

        }


failed:

        if(PTRACE(PTRACE_DETACH, zygote_pid, (void*)1, 0) < 0) {

                fprintf(stderr, "[!] PTRACE_DETACH error\n");

        }


        return -1; // it always return -1

}


int main(void)

{

        signal(SIGINT, sigint);


        fprintf(stdout, "[- ZYGOTE TRACING -]\n");

        if(get_zygote_pid() < 0) {

                fprintf(stderr, "[!] Could not find process id of zygote\n");

                return -1;

        }

        do_trace_zygote();

        fprintf(stdout, "[*] Done\n");

        return 0;

}


위 소스코드를 컴파일 한 후 실행하면 아래와 같이 zygote에서 fork 되는 프로세스 정보를 실시간으로 얻어올 수 있습니다.


[그림 6] Zygote에서 실행되는 fork를 실시간으로 탐지하는 예제 프로그램


처음 "루팅 탐지 우회"에서 출발한 툴 제작이 조금 비대해진 느낌입니다.

GCC가 GNU C Compiler 였다가 이제는 GNU Compiler Collection이라고 명칭을 바꾼 것 처럼 이제는 저희가 제작한 툴 이름도 바꿔야 될 것 같기도 합니다....

이상 안드로이드 스마트 폰 삽질기였습니다..


읽어주셔서 감사합니다..

모의해킹에 대한 단상

CR@K 이야기 2014. 7. 29. 15:59 Posted by TEAMCR@K

찌는 듯한 여름이 지속되고 있습니다~

비가 와도 그다지 시원하지는 않고 눅눅하기만 한 상태가 지속되고 있네요...

하지만 TeamCR@K은 이런 외압(?)에 굴하지 않고 열심히 모의해킹 프로젝트에 매진하고 있습니다.


요즘에는 모의해킹과 취약점 점검/진단이라는 단어가 서로 뒤죽박죽 되는 형국입니다.

취약점 점검 및 진단은 말 그대로 정해진 대상에 대해 점검을 하는 형태이지만, 모의해킹은 조금 더 넓은 개념으로 보고 있습니다.

쉽게 이야기 해서 특정 취약점을 점검 할 수 있는 몇 가지 케이스를 기준으로 취약점 존재 여부를 판별하는 것은 점검 및 진단이며,

모의해킹은 특정 시나리오를 기반으로 출발하여 취약점 공격 패턴에 대해 차단되는 공격을 우회하는 등의 행위들을 포함합니다. 


이러한 모의해킹 프로젝트에 투입되어 일을 하다 보면 인터넷에 퍼져있는 형태의 작업이 아닌 개인 스킬이 요구되는 작업들이 필요할 때가 있습니다.


모의해킹 프로젝트 수행 중 어떤 분께서 이런 말씀을 하셨습니다.


"실제 해커가 공격하는 형태로 진행을 해 주셨으면 좋겠다.."




네...

이런 부탁..

저희 참 좋아합니다.. ^^;;

TeamCR@K은 모의해킹 팀이고 그런 것들이 좋아서 지금 이 자리에 있는 것 아닐까요? ^^


사내에서 사용하고 있는 백신 프로그램이 있고, 또한 매 분기마다 보안에 대한 교육을 실시하고 있지만 내부 보안관리에 대해 지속적으로 걱정하시는 것 같았습니다.

어떤 방식이 제일 좋을까 생각하던 도중 기존 C&C 공격을 약간 응용해 보기로 하였습니다.





그리하여 다음과 같은 프로그램을 제작하였습니다.


(1) 임의의 명령어 정보 얻기


(2) 명령어 실행 결과 기록 (Local PC)


(3) 명령어 실행 결과 전송



이렇게 만들어 놓고 보니 개인용 백신의 일반적인 악성코드 검사에서는 탐지가 불가능합니다.


많이 알려진 악성코드의 경우 시간이 지날수록 수집된 데이터에 따라 악성코드로 분류될 확률이 높아지겠지만

특정 타겟을 위한 APT 공격용 악성코드나 알려지지 않은 악성코드 패턴이 존재 할 수도 있기 때문에 인터넷 사용자들은 자신의 PC를 안전하게 지키기 위해 더욱 주의를 기울여야 할 것 같습니다.


한 여름에 건강 잘 챙기시고 자신의 PC의 보안도 잘 챙기시는 블로그 구독자 여러분이 되셨으면 좋겠습니다!~












I. 분석 개요

 

분석 대상

OpenSSL Library 1.0.0l

문서 작성일

2014.04.11

문서 버전

V0.3

 

OpenSSL프로젝트는 정해진 규격의 네트워크 보안 프로토콜을 범용 라이브러리로 구현하기 위한 목적을 가지고 오픈소스 프로젝트로 시작하였으며, 현재 세계 개발자들이 유지보수에 참여하고 있다.

OpenSSL라이브러리는 OpenSSL 프로젝트의 결과물로써, 데이터 통신 사용되는 네트워크 암호화 프로토콜인 TLS(Transport Layer Security) SSL(Secure Sockets Layer)기능을 구현한 라이브러리이다.

이러한 OpenSSL라이브러리는 네트워크 보안 프로토콜에 사용되는 소프트웨어나 네트워크 하드웨어 장비 등에 사용되고 있는데, 라이브러리와 관련하여 지난 2014 4 8 “HeartBleed”라는 이름의 보안 취약점이 공개 되었다.

“HeartBleed” 취약점은 OpenSSL 라이브러리를 사용하는 서버와 통신 조작된 TLS 패킷 전송 과정을 적절하게 검증하지 않음으로 발생한다. SSL 사용한 네트워크 통신 과정에는 서버와 클라이언트 연결을 안정적으로 지속시키기 위해 연결 지속 신호를 주고 받도록 되어 있는HeartBeat라는 과정이 존재한다. 취약한 버전의OpenSSL 라이브러리는HeartBeat 통신 과정을 수행할 특정 변수의 경계 값을 적절히 검증하지 않으며, 이로 인해 서버 메모리 데이터가 노출되는 취약점이 존재한다.

해당 취약점을 통해 개인 , 비밀 키와 서비스를 이용 중인 사용자 세션의 획득이 가능하고 서버와 클라이언트의 암호화 트래픽을 통해 정보를 획득할 있다.

 

-      OpenSSL TLS heartbeat관련 URL

https://www.openssl.org/news/secadv_20140407.txt

http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-0160

http://heartbleed.com/

https://bugzilla.redhat.com/show_bug.cgi?id=1084875

 

-      취약한 버전 정보

openssl:1.0.1f

openssl:1.0.1

openssl:1.0.1:beta1

openssl:1.0.1:beta2

openssl:1.0.1:beta3

openssl:1.0.1a

openssl:1.0.1b

openssl:1.0.1c

openssl:1.0.1d

openssl:1.0.1e

 

 

II. 대상 세부 분석

1.    Memory Read Overrun

“Heartbleed” 취약점은 OpenSSL라이브러리 TLS 패킷을 처리하는 과정 수행되는 tls1_process_heartbeat(), dtls1_process_heartbeat() 함수에서 payload 변수의 경계 값을 적절히 검증하지 않아 발생한다.

 

              

  

                                   [그림 1] d1_both.c 소스코드의 취약한 함수

 

 

 

 

 [그림 2] t1_lib.c 소스코드의 취약한 함수

 

 

TLS 패킷 처리 과정에서 payload값을 설정하여 서버에 요청 패킷을 보내면, 서버로 보내진 payload 값이 그대로 서버 응답 패킷에 포함되어 클라이언트로 전달된다.

공격자는 이러한 통신 과정 수행 payload 데이터는 명시하지 않고, 단순히payload 사이즈 값만을 조작한 패킷을 서버로 전송 있다. 서버는 payload사이즈 여부만 검증하여 payload 데이터 영역에 내부 특정 메모리 영역의 데이터를 복사하여 클라이언트로 응답 패킷을 보내게 되며, 공격자는 이에 따라 최대 64KB 임의의 데이터를 획득할 있다.

 

 

 

[그림 3] 조작된 TLS 패킷 전송

 

 

HeartBeat 허용 요청 패킷이 전송된 것을 모니터링 도구를 통해 확인 있다.

 

[그림 4] TLS 패킷 확인

 

 

클라이언트에서 HeartBeat 통신 과정 payload 사이즈 값을 조작하여 서버에 요청한다.

 

[그림 5] heartbeat 패킷 전송

 

 

취약점이 발생하는 부분을 OpenSSL 라이브러리의 소스코드를 참조하여 쉽게 알 수 있다.

 

[그림 6] tls1_process_heartbeat() 함수에 인자로 전달되는 SSL 구조체

 

 

 

[그림 7] SSL 구조체를 참조하는 과정과 취약점이 패치된 부분

 

해당 취약점을 근거로, 다음과 같은 테스트베드를 구축하여 취약점의 심각성을 재현하였다.

* 시나리오 : OpenSSL 구성된 Apache 서버의 사용자 권한 획득

* 서버 : Apache 2.2.15-29 + php 5.3.3 + openssl-1.0.1e-16

* 클라이언트 : Windows XP + Chrome 34.0.1847.116 m

 

사이트에 로그인 사용자의 화면이다. 

 

 

[그림 8] 로그인 된 사용자

 

 

로그인 사용자의 세션 정보이다.

 

[그림 9] 로그인 된 사용자 세션

 

조작된 heartbeat 패킷을 통해 서버의 특정 메모리 영역에 대한 데이터를 전달 받을 있다.

 

[그림 10] heartbeat 패킷 응답 값

 

 

다음과 같이 전달된 데이터를 통해 메모리에 저장된 로그인 사용자의 세션 정보 사용자 계정 정보를 확인할 있다.

[그림 11] 메모리에 저장된 사용자 세션 정보

 

 

 

2.    위험 분석

취약점에 노출 경우 악의적인 외부 사용자는 서비스 보안 장비, 또는 네트워크를 사용하는 사용자들에 대해 세션이나 개인키, 비밀 키에 대한 획득이 가능하다.

사용자 관리자의 세션이 탈취 경우, 홈페이지에 대한 위험이 발생할 있으며 나아가 서버 내부 네트워크 침투에 대한 위험을 초래할 있다.

 

취약점으로 인해 노출되는 위협에는 다음과 같은 사례들이 존재한다.

 

1. 포털 커뮤니티 사이트의 로그인 정보 유출

2. VPN 서버의 Private Key 유출

3. 네트워크 장비 보안 장비의 Private Key 로그인 정보 유출

4. 취약한 OpenSSL 라이브러리로 구성되어 통신하고 있는 애플리케이션의 로그인 정보 유출

 

 

  해당 취약점은 특정 OS 환경에 국한되는 취약점이 아닌 OpenSSL 라이브러리를 사용하고 있는 VPN 라우터와 같은 네트워크 장비나 하드웨어 보안장비에도 영향을 미칠 있음.

 

 

III. 대응 방안

1.    Memory Read Overrun

1.    OpenSSL 업데이트

OpenSSL 공식 홈페이지를 통해 패치 버전으로 업데이트 있도록 권고함.

 

2.    OpenSSL 컴파일

OpenSSL 업데이트 불가피할 경우, 소스코드를 "-D OPENSSL_NO_HEARTBEAT" 통해 컴파일 있도록 권고함.

 

 

 

※ 현재 ㈜에이쓰리시큐리티에서 테스트 및 분석 중에 있으며, 이 문서는 계속 업데이트될 것입니다. 본 문서는 보안취약점으로 인한 피해를 최소화하는 데 도움이 되고자 작성되었으나, 본 문서에 포함된 대응방안의 유효성이나 기타 예상치 못한 시스템의 오작동 발생에 대하여서는 ㈜에이쓰리시큐리티에서는 일체의 책임을 지지 아니합니다.