루팅된 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이라고 명칭을 바꾼 것 처럼 이제는 저희가 제작한 툴 이름도 바꿔야 될 것 같기도 합니다....

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


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

 

    

얼마 전 TeamCR@K 팀원 스마트폰으로 한 통의 문자 메시지가 왔습니다.

누가 봐도 문자 메시지 피싱(SMS phsing, 스미싱) 공격이라는 것을 알 수 있는 쿠폰 문자 메시지가 한 통 도착 하였습니다.

          

[그림1] 스미싱 공격 문자메시지 수신

 

URL을 접속하면 해당 apk 파일을 다운로드 하게 됩니다.

 

해당 apk 파일을 APKinspector를 이용하여 어떤 동작을 하는지 살펴보도록 하겠습니다.

APKinspector는 python으로 작성된 The Honeynet Project중 하나인 GUI 툴입니다.

APKinspector Download: https://github.com/honeynet/apkinspector/

우선 설치를 위해서 Ubuntu 11.10 버전을 설치합니다.

[그림2] 설치 시 버전 요구

 

Ubuntu 11.10에서 APKinspector를 다운로드 후 install.sh로 필요한 python 패키지를 설치합니다.

[그림3] 패키지 설치

 

APKinspector 실행 시 다음과 같은 에러메시지를 볼 수 있습니다.

$ python startQT.py

Traceback (most recent call last):

File "startQT.py", line 18, in <module>

from GetMethods import *

File "/home/dev/Desktop/apkinspector/GetMethods.py", line 20, in <module>

import androguard, analysis, androlyze

File "/home/dev/Desktop/apkinspector/androguard/androlyze.py", line 35, in <module>

import IPython.ipapi

ImportError: No module named ipapi

 

iPython 패키지가 업데이트 되면서 모듈위치가 바뀌어서 위와 같은 에러메시지가 나오게 됩니다.

버전에 맞도록 아래 사이트에서 iPython 모듈을 다운로드 후 설치합니다.

iPython Download: http://archive.ipython.org/release/0.10.2/

 

설치가 완료되었다면 python startQT.py 를 실행하여 APKinspector를 실행합니다.

[그림4] 악성 어플리케이션 오픈

 

[그림5] 디바이스 권한 확인

프로그램 실행 시 해당 어플리케이션이 문자메시지 권한을 요청하는 경고 창을 볼 수 있습니다.

통화핸들링 접근이나 혹은 녹음기능 권한을 요청하는 어플리케이션을 불러올 시 같은 경고 창을 볼 수 있습니다.


[그림6] Proguard 난독화 적용 확인

Proguard 적용으로 난독화 되어있는 것을 확인할 수 있습니다.

원하는 메소드를 더블클릭 하게 되면 그래픽컬한 화면을 볼 수 있습니다.


[그림7] CFG 확인

Dalvik byte code단위로 흐름도를 한눈에 볼 수 있습니다.

해당 코드를 선택 후 Space bar를 누르면 Dalvik byte code로 이동하게 됩니다.


[그림8] Dalvik byte code 확인

반대로 오른쪽 마우스버튼으로 goto CFG 메뉴로 흐름도를 볼 수 있습니다.


[그림8] Permission 확인

주요 권한을 요청 시 어느 Method에서 호출하는지 확인 할 수 있습니다.


[그림9] 어플리케이션 설치 시 권한 확인

어플리케이션 설치 시 요청하는 권한들을 살펴보면 다음과 같습니다.

주요 요청 권한을 살펴보면 스마트폰 시작 시 백그라운드 서비스로 동작되며 문자 메시지 전송, 확인 등 해당 어플리케이션에서 사용하는 권한들은 아래 사이트에서 살펴 볼 수 있습니다.

권한확인: http://developer.android.com/reference/android/Manifest.permission.html

 

어플리케이션의 동작을 smali 코드 단위로 살펴보면 유포자의 서버를 확인 할 수 있습니다.

[그림10] smali code 확인

 

전송되는 서버 주소를 확인 할 수 있습니다.

[그림11] 서버주소 확인


현재 해당 서버가 운영 중에 있음을 확인하였습니다.

[그림12] 서버주소 decoding

 

토르 브라우저를 이용하여 해당 서버를 접속하였습니다.

[그림13] 공격자 서버 접속

 

이상 APKinspector 를 이용하여 APK 동작부분을 간단히 살펴 보았습니다.

APKinspector를 이용하여 악성 어플리케이션으로 의심되는 어플리케이션의 동작을 간단하게 살펴 볼 수 있습니다.

 



본 문서는 안드로이드(Android) 기반에서의 APK 동적 디버깅 기법에 대해 작성하였습니다. 해당 내용은 아래와 같이 2개의 주제로 나눠 연재하겠습니다.

 

1. '안드로이드 기반 동적 디버깅 환경 구축'
2. '안드로이드 APP Code Patch 기법'



안드로이드 동적 디버깅 환경 구축


1. 디버깅 환경
Android 동적 디버깅 환경에서 필요한 Tool은 APKTools와 Netbeans IDE, Android개발환경, APK Sign환경이 구축되어 있어야 합니다. APKTools를 이용하여 APK파일을 디버그 모드로 디코딩 하면 Class단위로 java파일이 생성되며 java파일안에는 smali코드가 주석처리 되어 있습니다.
Netbeans는 IDE중 주석문에도 breakpoint를 설정할 수 있기 때문에 동적 디버깅 시 이용하며, APK Sign환경은 APK 수정 후 배포 전에 사인할 때 사용합니다.

동적 디버깅 시 필요한 Tool들의 역할입니다.



2.동적 디버깅 절차
동적 디버깅 절차는 다음과 같습니다.
1.    APK Tools를 이용하여 디버깅 모드로 덤프 (-d 옵션사용)
2.    APK Tools를 이용하여 디버깅 모드로 덤프한 것을 디버깅모드로 다시 패키징 (동적 디버깅을 위함)
3.    디버깅 모드로 패키징 한 APK파일을 sign하여 AVD에 실행
4.    Netbeans를 이용하여 1번에서 덤프했던 코드를 프로젝트 추가
5.    DDMS를 이용하여 대상 APP에 대한 포트를 열어줌.
6.    Netbeans 메뉴에서 Debug -> Attach Debugger -> Select JPDA 를 통해 host와 port를 설정하여 원격 디버깅 연결
7.    분석할 부분에 bp설정
8.    에뮬레이터에서 이벤트를 줘서 bp설정부분의 라인이 실행하게 유도
9.    Line by Line으로 동적 디버깅 시작

APK Tools를 이용하여 디버그모드로 APK파일을 디코딩 합니다. 디코딩 후 파일을 확인해 보면 확장자는 .java 이지만 파일의 내용에는 smali코드가 주석으로 처리 되어 있는 것을 확인 할 수 있습니다.

                                                             [그림 1] 디버그 모드로 디코딩



out폴더에 결과 파일들이 생성된 것을 확인 할 수 있으며, java파일이 생성되었지만 함수 몸체는 smali 코드로 구성되어 있는 것을 확인 할 수 있습니다.

                                                                       [그림 2] 디코딩 결과


동적 디버깅을 위하여 디코딩 된 파일을 다시 디버깅 모드로 패키징 합니다.

                                                              [그림 3] 디버깅 모드로 패키징


패키징 결과 out폴더 및에 dist라는 폴더가 생성되며 그 안에 새로운 APK파일이 생성된 것을 확인 할 수 있습니다.

                                                                 [그림 4] 패키징 결과



디버깅 모드로 패키징 된 APK파일을 설치하고 실행하기 위하여 APK Sign Tool을 이용하여 사인합니다.

                                                                      [그림 5] 사인하기


사인이 완료되어 새로운 파일이 생성된 것을 확인 할 수 있습니다.

                                                                        [그림 6] 사인 결과


사인이 완료 되면 AVD에 설치 후 실행합니다.

                                                                        [그림 7] AVD에 설치(Install)


설치 후 실행하여 제대로 설치 되었는지 확인합니다.

                                                                [그림 8] AVD설치 확인


디버그 모드로 디코딩 된 파일을 Netbeans에서 프로젝트로 추가합니다. 프로젝트에 추가 할 시에는 메뉴아이콘에서 두 번째 위치한 버튼을 이용하여 프로젝트를 추가합니다. [New Project] -> [java] -> [Java Project with Existing Source]를 선택하여 Next를 클릭합니다.

                                                      [그림 9] New Project 선택


다음은 덤프 했었던 경로를 설정하여 Next를 클릭합니다.

                                                  [그림 10] 프로젝트 경로 설정


프로젝트 경로를 설정한 후 코드가 있는 경로를 설정하여 코드를 프로젝트에 추가합니다.
 

                                                   [그림 11] 소스코드 추가


프로젝트 추가 후 Android의 버전에 맞게 android.jar파일을 추가해 주어야 합니다. Android.jar파일은 Android관련 API를 호출 할 때 참조되는 Library이므로 꼭 추가가 필요합니다. 추가 시 실행하였던 AVD의 버전에 맞추어 추가를 합니다.
 

                                                     [그림 12] Library 추가

                                                  [그림 13] android.jar추가



android.jar와 코드를 프로젝트에 추가하였으니 제대로 추가되었는지 확인합니다.

                                                                 [그림 14] 프로젝트 확인


DDMS에서 현재 동작하고 있는 APP중 방금 디버그 모드로 패킹되어 설치된 APP를 선택하여 열린 PORT번호를 확인합니다. 화면에서는 8700번이 open되 있는 것을 확인 할 수 있습니다.

                                                     [그림 15] 디버깅 대상 원격 포트확인


포트가 확인 되었으면 Netbeans에서 원격 디버깅을 위한 세팅을 합니다.

                                                   [그림 16] Attach Debbuger 메뉴 선택

                                                   [그림 17] JPDA선택


디버거 선택 후 host와 port정보를 설정해야 합니다. local에서 디버깅을 할 것이기 때문에 host에는 127.0.0.1, port에는 앞에서 확인한 8700으로 설정합니다.

                                                         [그림 18] 원격 디버깅 환경 설정


원격 디버깅을 위한 설정이 끝나면 디버깅관련 기능을 사용할 수 있습니다. 원격 디버깅을 위한 연결이 맺어 졌는 지 확인합니다. DDMS에서는 버그 모양이 앞에 표시 되며, Netbeans에서는 대상 프로그램을 pause시킬 수 있으므로 이 기능을 이용하여 연결이 정상적인지 확인 가능합니다.

                                                 [그림 19] DDMS에서 디버깅연결 확인


디버깅을 위한 연결이 맺어 졌기 때문에 bp설정을 하여 분석을 시작 할 수 있습니다. 다른 IDE같은 경우 주석문에 bp설정을 할 수 없지만 Netbeans에서는 Alt + Shift + F8번을 이용하여 line break point를 이용하여 bp 설정이 가능합니다.
원격 디버깅으로 중간에 디버거에 Attach 되었기 때문에 처음부터 프로그램의 분석이 필요하다면 bp를 첫 부분에 설정한 후 프로그램을 다시 시작하여 처음 부분부터 분석이 가능합니다.


                                               [그림 20] break point 설정


                                                               [그림 21] break point 설정

이렇게 디버거 연결이 완료 됩니다.

이제 안드로이드 에물레이터에서 실행을 하면서 각 단계마다 변하는 값들을 조작할 수 있으며, 이 내용은 "제 2부 안드로이드 APP Code Patch" 를 통해 알아가도록 하겠습니다.

참고 자료
[1] http://code.google.com/p/android-apktool/wiki/SmaliDebugging
[2] http://www.youtube.com/watch?v=P_Zyf7jFbx4
[3] http://code.google.com/p/android-apktool/issues/detail?id=88&colspec=ID Stars Type Status Priority Milestone Owner Summary
[4] http://pastebin.com/ge39wRZS


Android 악성코드 위협 연구 (수정본)

스마트폰 2010. 7. 5. 22:15 Posted by TEAMCR@K

Android 악성코드 위협 연구

By Dear.TOM(
bdr@a3security.com)
By k3rz(kerz@a3security.com)
By kyh1026(kyh1026@a3security.com)
편집 : 니키(ngnicky@a3security.com)




1. 개요
스마트폰(iphone 등)이2009년 4분기부터 국내 유입을 통해서 국내 스마트폰 신흥 시장은 지속적으로 발전되고 있으며, 세계적으로도 향후 5년간(현재 2010년) 스마트폰 매출의 주요로 자리잡으며 성장 될 것으로 예측되고 있습니다. 이와 같이 사용자가 증가함으로써 스마트폰 보안 위협이 지속적인 관심을 받고 있습니다.
스마트폰에 대한 위협 및 취약점을 연구 분석 하였으며, 이에 관련된 공격 시나리오를 구성 및 시연 함으로써 스마트폰 공격 가능성을 확인하는데 목적이 있습니다.



2. 시나리오
본 시나리오는 악성코드에 감염된 스마트폰에 저장된 개인 정보를 유출하는 시나리오입니다.
스마트폰의 특성상 개인과 아주 밀접한 기기 이기 때문에 스마트폰에는 민감한 개인정보가 많이 저장되어 있습니다. Android스마트폰의 어플리케이션이 마켓 뿐만 아니라 웹 상에 자유롭게 배포 될 수 있기 때문에 사용자의 무분별한 다운로드에 의해 악성코드가 전파될 수 있다는 점에 초점을 두었으며 연구환경은 안드로이드 에뮬레이터에서 진행되었습니다.

가. 스마트폰내 개인 정보획득 가능성
나. 스팸 문자(SMSBomb) 발송 가능성
다. 파일전송 등의 기능을 이용하여 악성코드에 감염된 Android의 정보 획득 가능성


3. 상세내역
일반적으로 Android 스마트폰의 IP주소를 알기 힘들기 때문에 공격자가 직접적으로 Android 스마트폰에 접속하기는 힘듭니다. 그러므로 반대로 Android 스마트폰에서 공격자에게 접속하는 Reverse Connectiong 형태로 공격을 시도하며, Android 스마트폰 어플리케이션이 마켓뿐만 아니라 웹 상에 자유롭게 배포 될 수 있기 때문에 사용자 무분별한 다운로드에 의해 악성코드에 감염될 수 있는 가능성이 존재합니다.
이런 가능성을 전제로 악성코드에 감염된 스마트폰의 피해는 악성코드의 기능에 따라 무궁무진합니다.

* 본 연구 문서는 신규 취약점 또는 보안 기술 우회 기법에 관한 문서로써 악용될 우려가 있는 상세 공격 코드 및 내용을 제한 합니다.

1. 스마트폰내 개인 정보획득 가능성
Android 스마트폰 사용자가 정상적인 어플리케이션으로 위장한 악성코드를 다운받아 실행하면 Android 스마트폰이 공격자의 서버에 접속하면서 사용자의 정보를 서버에 전송하게 됩니다. 이 정보에는 스마트폰의 전화번호, USIM카드번호, 네트워크지역등 스마트폰 단말기에 대한 정보가 포함됩니다. 이를 통해 사용자의 국가, 전화번호, IP주소 등의 정보를 획득할 수 있습니다.

다음은 공격자의 서버로 악성코드에 감염된 스마트폰의 정보가 수집되어 있는 화면입니다.

획득한 전화번호를 통해 광고 문자 혹은 스팸문자를 발송하거나 악의적인 공격이 가능하리라 예상됩니다.


2. 스팸 문자(SMSBomb) 발송 가능성
악성코드에 감염된 스마트폰에 공격자가 명령을 내려 다른곳으로 대량의 문자를 발송시켜 해당 스마트폰 사용자에게 과금의 패해와 함께 스팸문자 발송의 근원지로 만들 수 있습니다.

다음은 공격자가 스마트폰으로 스팸문자를 보내라는 명령을 내리는 화면입니다.


이 명령을 통해 스마트폰이 자신의 전화번호부에 등록되어 있는 전화번호로 혹은 공격자에게 전송받은 전화번호로 대량의 스팸문자를 발송하게 됩니다.

다음은 공격자에게서 문자 전송명령을 받고 문자를 전송하는 화면입니다.


이런 과정을 통해 악성코드에 감염된 스마트폰 사용자는 문자 발송에 대한 과금피해를 입을 수 있으며, 더 나아가 악성코드에 다른 기능들을 구현해 나간다면 개인정보가 노출 될 수 있으며, DDoS공격에 활용될 수 있는 가능성이 있습니다.



3. 
파일전송등의 기능을 이용하여 악성코드에 감염된 Android 스마트폰의 정보를 획득 가능성
스마트폰에는 다양한 파일들이 존재합니다. 스마트폰의 사진촬영기능, 동영상 재생기능, 음원 재생기능, 모바일 오피스 등 여러가지 기능들이 존재 하며 이에 대한 미디어, 문서파일들이 스마트폰 내에 존재하게 됩니다.
이런 파일들이 외부로 노출된다면 사생활의 노출, 업무 비밀정보의 노출 등 심각한 피해를 입을 수 있습니다.

다음은 공격자가 악성코드에 감염된 스마트폰에 있는 파일을 보내라는 명령을 내리는 화면입니다.



이 명령을 통해 스마트폰에 있는 파일을 공격자에게 전송시킬 수 있으며, 이를 통해 스마트폰 사용자의 사생활 정보 등을 얻어 낼 수 있습니다.

다음은 스마트폰으로부터 전송받은 파일을 확인하는 화면입니다.




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