【kernel exploit】CVE-2022-0995 堆溢出1比特置1漏洞利用

影響版本:Linux 5.8~5.17-rc7 5.17-rc8已修補(bǔ) / 5.16.15已修補(bǔ)挤安。評(píng)分只有 7.1 分。

測(cè)試版本:Linux-5.16.14(利用失斖裣荨)改用v5.11.22 exploit及測(cè)試環(huán)境下載地址https://github.com/bsauce/kernel-exploit-factory

編譯選項(xiàng)CONFIG_WATCH_QUEUE

在編譯時(shí)將.config中的CONFIG_E1000CONFIG_E1000E,變更為=y官研。參考

$ wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.16.14.tar.xz
$ tar -xvf linux-5.16.20.tar.xz
# KASAN: 設(shè)置 make menuconfig 設(shè)置"Kernel hacking" ->"Memory Debugging" -> "KASan: runtime memory debugger"秽澳。
$ make -j32
$ make all
$ make modules
# 編譯出的bzImage目錄:/arch/x86/boot/bzImage。

漏洞描述watch_queue 事件通知子系統(tǒng)存在堆溢出阀参,漏洞函數(shù)是watch_queue_set_filter()肝集。內(nèi)核會(huì)對(duì)用戶傳入的 watch_notification_type_filter 類型的 filter 進(jìn)行兩次有效性檢查,第1次檢查是為了確定分配的內(nèi)存大小蛛壳,第2次是為了將用戶filter 存入該內(nèi)存杏瞻。但是兩次檢查不一致,導(dǎo)致分配空間過(guò)小衙荐,可溢出存入更多的 filter捞挥。可以利用第2次溢出忧吟,對(duì)相鄰的堆塊特定bit位置1砌函,接下來(lái)的利用方法和 CVE-2021-22555 一樣。

補(bǔ)丁patch 修改了判斷條件溜族,兩處都判斷type是否大于等于2讹俊。

@@ -320,7 +319,7 @@ long watch_queue_set_filter(struct pipe_inode_info *pipe,
            tf[i].info_mask & WATCH_INFO_LENGTH)
            goto err_filter;
        /* Ignore any unknown types */
-       if (tf[i].type >= sizeof(wfilter->type_filter) * 8)
+       if (tf[i].type >= WATCH_TYPE__NR)
            continue;
        nr_filter++;
    }
@@ -336,7 +335,7 @@ long watch_queue_set_filter(struct pipe_inode_info *pipe,
 
    q = wfilter->filters;
    for (i = 0; i < filter.nr_filters; i++) {
-       if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG)
+       if (tf[i].type >= WATCH_TYPE__NR)
            continue;
 
        q->type         = tf[i].type;

保護(hù)機(jī)制:KASLR / SMEP / SMAP / KPTI

利用總結(jié):用的方法和 CVE-2021-22555 的方法一樣。

1. 背景知識(shí)

內(nèi)核通知機(jī)制:參見 watch_queue官方說(shuō)明煌抒,內(nèi)核的通用通知機(jī)制是基于pipe的仍劈,可以將內(nèi)核的通知消息拼接到用戶打開的管道中(編譯時(shí)開啟CONFIG_WATCH_QUEUE)。采用特殊mode打開pipe寡壮,即可啟用該機(jī)制贩疙;內(nèi)核生成的消息會(huì)被保存到 pipe_buffer 結(jié)構(gòu)的 ring buffer 中;可調(diào)用 read() 來(lái)讀取該消息况既。pipe的所有者應(yīng)該告訴內(nèi)核这溅,哪些資源需要通過(guò)該管道進(jìn)行觀察,只有連接到該管道上的資源才會(huì)往里邊插入消息棒仍,需要注意的是一個(gè)資源可能會(huì)與多個(gè)管道綁定并同時(shí)將消息插入所有管道悲靴。

用戶管理 watch queuewatch queue是應(yīng)用程序分配的一段緩沖區(qū),用來(lái)記錄通知降狠,其實(shí)現(xiàn)代碼都在pipe驅(qū)動(dòng)中对竣,用戶可以通過(guò)兩個(gè)API 引用和丟棄引用 pipe文件描述符fd中的緩沖區(qū)對(duì)應(yīng)的 watch queue庇楞。分別是 struct watch_queue *get_watch_queue(int fd) / void put_watch_queue(struct watch_queue *wqueue)

event filter:創(chuàng)建好 watch queue 之后否纬,用戶可以創(chuàng)建 filter 來(lái)限制接收的事件吕晌。用戶可傳入 watch_notification_filter -> watch_notification_type_filter 結(jié)構(gòu),這樣在內(nèi)核中就會(huì)創(chuàng)建相應(yīng)的filter临燃,漏洞就出在創(chuàng)建 filter 過(guò)程的代碼中睛驳。結(jié)構(gòu)中的成員含義可見以下的漏洞分析部分。

2. 漏洞分析

用戶調(diào)用 ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter) 來(lái)設(shè)置filter時(shí)會(huì)觸發(fā)漏洞膜廊。

調(diào)用關(guān)系ioctl -> vfs_ioctl() -> pipe_ioctl() -> watch_queue_set_filter() -> __set_bit()

注意vfs_ioctl() 中會(huì)調(diào)用 filp->f_op->unlocked_ioctl() 乏沸,該函數(shù)表的創(chuàng)建流程是 do_pipe2() -> __do_pipe_flags() -> create_pipe_files() -> alloc_file_pseudo() -> alloc_file()alloc_file() 分配一個(gè) file 結(jié)構(gòu)體并將其函數(shù)表設(shè)為上層調(diào)用傳入的函數(shù)表爪瓜,而在 create_pipe_files() 中傳入的函數(shù)表為 pipefifo_fops蹬跃。

long watch_queue_set_filter(struct pipe_inode_info *pipe,
                struct watch_notification_filter __user *_filter)
{
    struct watch_notification_type_filter *tf;
    struct watch_notification_filter filter;
    struct watch_type_filter *q;
    struct watch_filter *wfilter;
    struct watch_queue *wqueue = pipe->watch_queue;
    int ret, nr_filter = 0, i;
    ...
    if (copy_from_user(&filter, _filter, sizeof(filter)) != 0)      // [1] 拷貝用戶傳入的 watch_notification_filter 結(jié)構(gòu)
        return -EFAULT;
    if (filter.nr_filters == 0 ||
        filter.nr_filters > 16 ||
        filter.__reserved != 0)
        return -EINVAL;

    tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf)); // [2] 分配臨時(shí)空間并拷貝用戶傳入的 filter
    ...
    for (i = 0; i < filter.nr_filters; i++) {
        if ((tf[i].info_filter & ~tf[i].info_mask) ||
            tf[i].info_mask & WATCH_INFO_LENGTH)
            goto err_filter;
        /* Ignore any unknown types */
        if (tf[i].type >= sizeof(wfilter->type_filter) * 8) // [3] 只計(jì)入 type 值小于 0x10*8 的數(shù)量, 后續(xù)根據(jù) nr_filter 分配空間
            continue;
        nr_filter++;
    }

    ... 
    wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL);    // [4] 根據(jù) nr_filter 值來(lái)分配空間,存儲(chǔ)filter
    ...
    wfilter->nr_filters = nr_filter;

    q = wfilter->filters;
    for (i = 0; i < filter.nr_filters; i++) {               // [5] 填充 wfilter->filters[]
        if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG) // [6] 漏洞點(diǎn), 這里只要 type < 0x10*64 (0x400) 就會(huì)存入, 之前判斷時(shí)是 0x80
            continue;

        q->type         = tf[i].type;                       // [7] 溢出點(diǎn)1
        q->info_filter      = tf[i].info_filter;
        q->info_mask        = tf[i].info_mask;
        q->subtype_filter[0]    = tf[i].subtype_filter[0];
        __set_bit(q->type, wfilter->type_filter);           // [8] 溢出點(diǎn)2, 將wfilter->type_filter偏移q->type的bit位置為1, 可以溢出篡改指定bit
        q++;
    }
    ...
}

#define BITS_PER_LONG 64
#define BIT_MASK(nr)        (UL(1) << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr)        ((nr) / BITS_PER_LONG)
static inline void __set_bit(int nr, volatile unsigned long *addr)
{
    unsigned long mask = BIT_MASK(nr);               // 1 左移 (nr % 64)
    unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);  // nr 除以 64bit, 但p是指向8字節(jié), 所以將修改偏移 (nr/64*8 = n/8) 處的字節(jié)

    *p  |= mask;
}

漏洞:內(nèi)核會(huì)對(duì)用戶傳入的 watch_notification_type_filter 類型的 filter 進(jìn)行兩次有效性檢查铆铆,第1次檢查是為了確定分配的內(nèi)存大械骸(見[3]),第2次是為了將用戶filter 存入該內(nèi)存(見[6])薄货。問(wèn)題在于兩次檢查不一致翁都,第1次是計(jì)算type小于0x80的個(gè)數(shù),第2次卻是將type小于0x400的filter存入該內(nèi)存谅猾。所以當(dāng)type位于 0x80~0x400 之間時(shí)柄慰,實(shí)際存入的filter個(gè)數(shù)會(huì)大于分配的內(nèi)存,導(dǎo)致 [7][8] 都會(huì)溢出税娜。漏洞利用時(shí)采用的是第2處溢出坐搔,越界將指定的bit置1(將 wfilter->type_filter 偏移 q->type 的bit位置為1,而 wfilter->type_filter 位于 watch_filter 結(jié)構(gòu)的開頭敬矩,所以只要將 q->type 設(shè)置為固定的值薯蝎,就能將相鄰塊的固定偏移位 置為1)。

結(jié)構(gòu)關(guān)系

// (1) 用戶參數(shù)結(jié)構(gòu)體
struct watch_notification_filter {
    __u32   nr_filters;     /* Number of filters */
    __u32   __reserved;     /* Must be 0 */
    struct watch_notification_type_filter filters[];    // <---------
};

struct watch_notification_type_filter {
    __u32   type;           // 要過(guò)濾的事件類型, eg, WATCH_TYPE_KEY_NOTIFY
    __u32   info_filter;        /* Filter on watch_notification::info */
    __u32   info_mask;      /* Mask of relevant bits in info_filter */
    __u32   subtype_filter[8];  /* Bitmask of subtypes to filter on */
};

// (2) 內(nèi)核結(jié)構(gòu)體
struct watch_filter {
    union {
        struct rcu_head rcu;
        unsigned long   type_filter[2]; /* Bitmask of accepted types */
    };
    u32         nr_filters; /* Number of filters */
    struct watch_type_filter filters[];
};

struct watch_type_filter {      // size: 0x10
    enum watch_notification_type type;
    __u32       subtype_filter[1];  /* Bitmask of subtypes to filter on */
    __u32       info_filter;        /* Filter on watch_notification::info */
    __u32       info_mask;      /* Mask of relevant bits in info_filter */
};

3. 漏洞利用

漏洞利用:其實(shí)有兩處溢出谤绳,但作者用到了第2處溢出。作者傳入4個(gè)filter袒哥,其中3個(gè)有效缩筛,則在 [4] 處會(huì)申請(qǐng) 0x18+0x30 的內(nèi)存,實(shí)際申請(qǐng)到 kmalloc-96堡称。當(dāng)type值為 0x30a 時(shí)(96*8+0xa)瞎抛,會(huì)將相鄰 kmalloc-96 的第10bit 置為1,也即將 0x0000 修改為 0x0400却紧。

好處:一是只需要溢出1次桐臊,也即堆噴布置1次胎撤,提高利用成功率;二是可以直接采用CVE-2021-22555 的利用方法断凶,篡改 msg_msg->m_list.next伤提。

利用過(guò)程

  • (1)堆布局:堆噴4096個(gè) msg_msg,主消息和輔助消息 kmalloc-96 <-> kmalloc-1024认烁;
  • (2)觸發(fā)OOB
    • 釋放第0 / 1024 / 2048 / 3072 個(gè)主消息肿男;
    • 觸發(fā)OOB溢出,漏洞對(duì)象位于kmalloc-96却嗡,可能將某個(gè) msg_msg->m_list.next 的最低兩字節(jié)從 0x0000 修改為 0x0400舶沛;
    • 找到 msg_msg->m_list.next 被修改的msg_msg,下標(biāo)記為 victim_qid 窗价,指向的 msg_msg 下標(biāo)記為 real_qid如庭;
  • (3)構(gòu)造UAF:釋放下標(biāo)為 real_qid 的輔助消息B2(將下標(biāo) victim_qid 的輔助消息記為A2,下標(biāo) real_qid 的輔助消息記為B2)撼港;
  • (4)泄露UAF消息B2的地址
    • 堆噴 16*128 個(gè) sk_buff 占據(jù)剛才釋放的B2坪它,偽造A2的 msg_msg->m_ts = 0xfd0
    • 利用A2進(jìn)行OOB read餐胀,泄露相鄰消息的 msg_msg->m_list.prev (記為C1哟楷,相鄰輔助消息對(duì)應(yīng)的主消息地址);
    • 釋放 sk_buff 后再次堆噴 sk_buff 否灾,偽造A2的 msg_msg->m_ts = 0x1fc8 / msg_msg->next = C1卖擅;
    • 利用A2泄露 C1 處的 msg_msg->m_list.next (記為C2,相鄰輔助消息的地址)墨技,C2-0x400 即為B2的地址惩阶;
  • (5)泄露內(nèi)核基址
    • 釋放 sk_buff 后再次堆噴 sk_buff ,偽造A2
      • msg_msg->m_list.next = msg_msg->m_list.prev = B+0x800扣汪;
      • msg_msg->type = 0x1337断楷;
      • msg_msg->m_ts = 0xfd0
      • msg_msg->next = msg_msg->security = 0崭别;
    • 釋放下標(biāo) victim_qid 的輔助消息A2冬筒;
    • 堆噴256個(gè) pipe_buffer 占據(jù)A2;
    • 讀取 sk_buff 泄露 pipe_buffer->ops 即可泄露內(nèi)核基址茅主;
  • (6)劫持控制流
    • 堆噴 sk_buff 舞痰,篡改pipe_buffer->ops 指向 A2+0x290
    • 并偽造 release 指針為 pivot gadget诀姚,剩下的ROP chain放在 pipe_buffer 上响牛,完成提權(quán)。

問(wèn)題:在內(nèi)核版本 5.16.14 上始終沒(méi)有辦法使漏洞對(duì)象和 kmalloc-96 大小的 msg_msg 相鄰,跟之前調(diào)試 CVE-2022-0185 遇到的問(wèn)題一樣(希望之后能弄明白呀打,難道是account標(biāo)志導(dǎo)致矢赁?)。無(wú)奈贬丛,只能用 5.11.22 版本上提權(quán)了撩银。

succeed.png

參考

【kernel exploit】CVE-2021-22555 2字節(jié)堆溢出寫0漏洞提權(quán)分析

exploit

【CVE.0x08】CVE-2022-0995 漏洞復(fù)現(xiàn)及簡(jiǎn)要分析

CVE-2022-0995分析(內(nèi)核越界 watch_queue_set_filter)

[漏洞分析] CVE-2022-0995 watch_queue 1bit “溢出“內(nèi)核提權(quán)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瘫寝,隨后出現(xiàn)的幾起案子蜒蕾,更是在濱河造成了極大的恐慌,老刑警劉巖焕阿,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咪啡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡暮屡,警方通過(guò)查閱死者的電腦和手機(jī)撤摸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)褒纲,“玉大人准夷,你說(shuō)我怎么就攤上這事≥郝樱” “怎么了衫嵌?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)彻秆。 經(jīng)常有香客問(wèn)我楔绞,道長(zhǎng),這世上最難降的妖魔是什么唇兑? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任酒朵,我火速辦了婚禮,結(jié)果婚禮上扎附,老公的妹妹穿的比我還像新娘蔫耽。我一直安慰自己,他們只是感情好留夜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布匙铡。 她就那樣靜靜地躺著,像睡著了一般碍粥。 火紅的嫁衣襯著肌膚如雪慰枕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天即纲,我揣著相機(jī)與錄音,去河邊找鬼博肋。 笑死低斋,一個(gè)胖子當(dāng)著我的面吹牛蜂厅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播膊畴,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼掘猿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了唇跨?” 一聲冷哼從身側(cè)響起稠通,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎买猖,沒(méi)想到半個(gè)月后改橘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玉控,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年飞主,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片高诺。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碌识,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出虱而,到底是詐尸還是另有隱情筏餐,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布牡拇,位于F島的核電站魁瞪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诅迷。R本人自食惡果不足惜佩番,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望罢杉。 院中可真熱鬧趟畏,春花似錦、人聲如沸滩租。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)律想。三九已至猎莲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間技即,已是汗流浹背著洼。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人身笤。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓豹悬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親液荸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瞻佛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容