ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 0x02.Local Privilege Escalation(LPE)
    0x.Pwnable 2019. 2. 13. 21:46

    0x02.Local Privilege Escalation(LPE)


    Kernel Exploit 문제를 풀거나 공부를 하면서 하나씩 정리하는 노트


    Basic Vector - 1


    LPE를 할 때 자주 사용하는 공격 벡터 중 하나는 commit_cred(prepare_kernel_cred(0)) 를 호출하는 방식이다.


     commit_cred(prepare_kernel_cred(0))


    cred란 구조체의 형태를 띄고 있는데 uid, gid 등의 정보가 포함되어 있다.


    struct cred {
          atomic_t        usage;
           #ifdef CONFIG_DEBUG_CREDENTIALS
          atomic_t        subscribers;    /* number of processes subscribed */
          void            *put_addr;
          unsigned        magic;
           #define CRED_MAGIC     0x43736564
           #define CRED_MAGIC_DEAD 0x44656144
           #endif
          uid_t           uid;            /* real UID of the task */
          gid_t           gid;            /* real GID of the task */
          uid_t           suid;           /* saved UID of the task */
          gid_t           sgid;           /* saved GID of the task */
          uid_t           euid;           /* effective UID of the task */
          gid_t           egid;           /* effective GID of the task */
          uid_t           fsuid;          /* UID for VFS ops */
          gid_t           fsgid;          /* GID for VFS ops */
          unsigned        securebits;     /* SUID-less security management */
          kernel_cap_t    cap_inheritable; /* caps our children can inherit */
          kernel_cap_t    cap_permitted;  /* caps we're permitted */
          kernel_cap_t    cap_effective;  /* caps we can actually use */
          kernel_cap_t    cap_bset;       /* capability bounding set */


    prepare_kernel_cred를 호출하면 new cred를 만드는데, 0을 인자로 주면 uid, gid 등이 0으로 설정된 cred가 생성이 된다. 즉 prepare_kernel_cred(0) 은 credential structure를 0으로 초기화해서 생성해주는 역할을 한다.


    commit_cred는 credential structure를 인자로 받아서 해당 cred로 재설정을 해주는 역할을 하기에, 0으로 초기화된 cred를 넘겨주면 uid, gid 등이 결국 0으로 재설정되어 lpe가 가능한 것이다.


    Method 1. Syscall Overwrite


    특정 바이너리에 syscall을 덮을 수 있는 취약점이 존재할 때 사용한다.


    /usr/include/arm-linux-gnueabihf/asm/unistd.h를 보면 syscall 넘버를 확인한 뒤, /proc/kallsyms에서 cred 관련 함수의 주소를 got overwrite 와 비슷한 방식으로 syscall에 덮어씌우는 방식으로 진행한다.


    syscall을 덮어씌운 뒤에, 해당 syscall chaining을 통한 root 획득 가능.


    Method 2. f_op->aio_read Overwrite


    readv 함수 내의 포인터를 덮을 수 있는 취약점이 존재할 때 사용 가능.


    readv(fd, iovec, cnt) 라는 read를 수행하는 함수가 호출되면 내부적으로 다음이 연쇄적으로 호출이 됨


    readv -> vfs_readv -> do_readv_writev -> do_sync_readv_writev -> fn


    aio_read 함수 포인터를 덮은 뒤, readv로 fn을 호출하는데 해당 함수 안의 file_operations 구조체 안에 존재하는 덮어씌운 aio_read 함수 포인터를 트리거 하여 root 획득 가능.


    /dev/ptmx에서 사용하는 ptmx_fops 주소를 /proc/kallsyms에서 알아낸 뒤 offset을 더해주면 aio_read의 주소를 알 수 있고, 해당 주소를 commit_cred(prepare_kernel_cred(0))로 덮어씌우면 된다.


    struct file_operations {
       struct module *owner;
       loff_t (*llseek) (struct file *, loff_t, int);
       ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
       ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
       ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);


    참고로 iovec 구조체의 모양은 다음과 같다.


    struct iovec
    {
       ptr_t iov_base;
       size_t lov_len;
    };


    Basic Vector - 2


    include/linux/sched.h에 task_struct라는 구조체가 정의되어 있으며, 해당 구조체는 특정 프로세스에 관한 정보들을 저장하고 있는 형태를 띄고 있다.


    struct task_struct {
    #ifdef CONFIG_THREAD_INFO_IN_TASK
    /*
    * For reasons of header soup (see current_thread_info()), this
    * must be the first element of task_struct.
    */
    struct thread_info thread_info;
    #endif
    /* -1 unrunnable, 0 runnable, >0 stopped: */
    volatile long state;

    /*
    * This begins the randomizable portion of task_struct. Only
    * scheduling-critical items should be added above here.
    */
    randomized_struct_fields_start

    void *stack;
    atomic_t usage;
    /* Per task flags (PF_*), defined further below: */
    unsigned int flags;
    unsigned int ptrace;


    구조체 내에는 당연히 cred에 대한 내용도 관리가 된다.


    /* Process credentials: */

    /* Tracer's credentials at attach: */
    const struct cred __rcu *ptracer_cred;

    /* Objective and real subjective task credentials (COW): */
    const struct cred __rcu *real_cred;

    /* Effective (overridable) subjective task credentials (COW): */
    const struct cred __rcu *cred;

    /*
    * executable name, excluding path.
    *
    * - normally initialized setup_new_exec()
    * - access it with [gs]et_task_comm()
    * - lock it with task_lock()
    */
    char comm[TASK_COMM_LEN];

    struct nameidata *nameidata;


    real_cred는 해당 프로세스에 대한 접근 권한 정보를, cred는 해당 프로세스가 다른 작업을 진행할 시에 가지는 권한을 가리킨다. 커널의 특정 위치에 값을 write할 수 있다면 해당 부분의 값을 0으로 바꿔서 lpe를 할 수 있다.


    해당 주소를 알아내기 위해서는 mem leak 취약점이 존재하여야 하는데, cred 포인터 다음에 위치하는 comm 부분에 해당 프로세스의 이름이 들어가 있기 때문에 해당 부분을 찾으면 위의 cred에 대한 주소를 알아낼 수 있다.


    #include <stdio.h>
    #include <sys/prctl.h>

    int main()
    {
       prctl(PR_SET_NAME, "process_name");
    return 0;  
    }


    위의 예제와 같이 prctl로 PR_SET_NAME으로 임의로 comm 부분의 값을 변경한 뒤, 해당 부분의 문자열을 read하여 위치를 알아내고 위로 접근하면 real_cred와 cred에 대한 주소를 알아낼 수 있다. 해당 값을 0으로 변경하여 권한 상승을 시도하면 된다.

    '0x.Pwnable' 카테고리의 다른 글

    0x03.Browser Exploit Reference  (0) 2019.02.22
    0x01.버퍼 오버플로우(Buffer Overflow)  (0) 2017.07.07
    0x00.파이프라인(Pipeline)  (0) 2017.02.26

    댓글

Designed by Tistory.