<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從0開始

    VSole2021-12-10 13:42:20

    簡介

    網上一大堆教編譯內核的,但很多教程看得特別迷糊。第一次編譯內核時,沒設置好參數,直接把虛擬機編譯炸開了。所以就想著能不能先做個一鍵獲取內核源碼和相關vmlinux以及bzImage的腳本,先試試題,后期再深入探究編譯內核,加入debug符號,所以就有了這個一鍵腳本。

    這個直接看我的項目就好了,我是直接拖官方的docker,然后把編譯所需要的環境都重新安裝了一遍,基本可以適配所有環境,安裝各個版本的內核,外加調試信息也可以配置。

    PIG-007/kernelAll (github.com)

    (https://github.com/PIG-007/kernelAll)

    前置環境,前置知識啥的在上面已經足夠了,如果還是感覺有點迷糊可以再去搜搜其他教程。看雪的鈔sir師傅和csdn上的ha1vk師傅就很不錯啊,還有安全客上的ERROR404師傅。

    鈔sir師傅:Ta的論壇 (pediy.com)(https://bbs.pediy.com/user-818602.htm)

    ha1vk師傅:kernel- CSDN搜索(https://so.csdn.net/so/search?q=kernel&t=blog&u=seaaseesa)

    error404師傅:Kernel Pwn 學習之路(一) - 安全客,安全資訊平臺 (anquanke.com)(https://www.anquanke.com/post/id/201043)

    這個系列記錄新的kernel解析,旨在從源碼題目編寫,不同內核版本來進行各式各樣的出題套路解析和exp的解析。另外內核的pwn基本都是基于某個特定版本的內核來進行模塊開發,而出漏洞地方就是這個模塊,我們可以借助這個模塊來攻破內核,所以我們進行內核pwn的時候,最應該先學習的就是一些簡單內核驅動模塊的開發。

    例子編寫

    首先最簡單和經典的的Hello world。

    //注釋頭 //由于基本都是用下載的內核編譯,所以這里的頭文件直接放到正常的編譯器中可能找不到對應的頭文件。//在自己下載的編譯好的內核中自己找對應的,然后Makefile中來設置內核源碼路徑#include #include #include MODULE_LICENSE("Dual BSD/GPL");static int __init hello_init(void){    printk("PIG007:Hello world!");    return 0;}static void __exit hello_exit(void){    printk("PIG007:Bye,world");}module_init(hello_init);module_exit(hello_exit);
    

    1、頭文件簡介

    module.h:包含可裝載模塊需要的大量符號和函數定義。

    init.h:指定初始化模塊方面和清除函數。

    另外大部分模塊還包括moduleparam.h頭文件,這樣就可以在裝載的時候向模塊傳遞參數。而我們常常用的函數_copy_from_user則來自頭文件uaccess.h。

    2、模塊許可證

    //注釋頭 MODULE_LICENSE("Dual BSD/GPL");
    

    這個就是模塊許可證,具體有啥用不太清楚,如有大佬懇請告知。可以通過下列命令查詢。

    grep "MODULE_LICENSE" -B 27 /usr/src/linux-headers-`uname -r`/include/linux/module.h
    

    或者網址Linux內核許可規則 — The Linux Kernel documentation

    (https://www.kernel.org/doc/html/latest/translations/zh_CN/process/license-rules.html)

    3、模塊加載卸載

    加載

    一般以 __init標識聲明,返回值為0表示加載成功,為負數表示加載失敗。用來初始化,定義之類的。

    static int __init hello_init(void)
    

    在整個模塊的最后加上:

    module_init(hello_init);
    

    來通過這個init函數加載模塊。

    卸載

    一般以 __exit標識聲明,用來釋放空間,清除一些東西的。

    static void __exit hello_exit(void)
    

    同樣的模塊最后加上以下代碼來卸載。

    module_exit(hello_exit);
    

    ▲其實加載和卸載有點類似于面向對象里的構造函數和析構函數。

    以上是一個最簡單的例子,下面講講實際題目的編寫,實際的題目一般涉及驅動的裝載。

    題目編寫

    由于模塊裝載是在內核啟動時完成的(root下也可以設置再insmod裝載),所以一般需要安裝驅動,通過驅動來啟動模塊中的代碼功能。而驅動類型也一般有兩種,一種是字符型設備驅動,一種是globalmem虛擬設備驅動。

    1、字符型設備驅動

    (1)安裝套路

    首先了解一下驅動設備的結構體:

    ///linux/cdev.h   kernel 5.14.8 struct cdev {    struct kobject kobj;    // 內嵌的kobject對象    struct module *owner;    // 所屬模塊    const struct file_operations *ops;    // 文件操作結構體,用來進行交互    struct list_head list;    dev_t dev;                // 設備號    unsigned int count;} __randomize_layout;
    

    然后就是套路編寫:

    // 設備結構體struct xxx_dev_t {    struct cdev cdev;} xxx_dev; // 設備驅動模塊加載函數static int __init xxx_init(void){    // 初始化cdev    cdev_init(&xxx_dev.cdev, &xxx_fops);    xxx_dev.cdev.owner = THIS_MODULE;     // 獲取字符設備號    if (xxx_major) {         //register_chrdev_region用于已知起始設備的設備號的情況        register_chrdev_region(xxx_dev_no, 1, DEV_NAME);    } else {        alloc_chrdev_region(&xxx_dev_no, 1, DEV_NAME);    }    //申請設備號常用alloc_chrdev_region,表動態申請設備號,起始設備設備號位置。     // 注冊設備    ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); }// 設備驅動模塊卸載函數static void __exit xxx_exit(void){    // 釋放占用的設備號    unregister_chrdev_region(xxx_dev_no, 1);    cdev_del(&xxx_dev.cdev);}
    

    這樣簡單的驅動就安裝完了,安裝完了之后,我們想要使用這個驅動的話,還需要進行交互,向驅動設備傳遞數據,所以上面的xxx_fops,即file_operations這個結構體就起到了這個功能。

    有的時候安裝注冊設備驅動需要用到class來創建注冊,原因未知:

    static int __init xxx_init(void){    buffer_var=kmalloc(100,GFP_DMA);    printk(KERN_INFO "[i] Module xxx registered");    if (alloc_chrdev_region(&dev_no, 0, 1, "xxx") < 0)    {        return -1;    }    if ((devClass = class_create(THIS_MODULE, "chardrv")) == NULL)    {        unregister_chrdev_region(dev_no, 1);        return -1;    }    if (device_create(devClass, NULL, dev_no, NULL, "xxx") == NULL)    {        printk(KERN_INFO "[i] Module xxx error");        class_destroy(devClass);        unregister_chrdev_region(dev_no, 1);        return -1;    }    cdev_init(&cdev, &xxx_fops);    if (cdev_add(&cdev, dev_no, 1) == -1)    {        device_destroy(devClass, dev_no);        class_destroy(devClass);        unregister_chrdev_region(dev_no, 1);        return -1;    }     printk(KERN_INFO "[i] : <%d, %d>", MAJOR(dev_no), MINOR(dev_no));    return 0; }
    

    (2)交互套路

    安裝完成之后還需要交互,用到file_operations結構體中的成員函數,首先了解下這個結構體。

    ///linux/fs.h     kernel 5.14.8 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 (*read_iter) (struct kiocb *, struct iov_iter *);    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);    int (*iopoll)(struct kiocb *kiocb, bool spin);    int (*iterate) (struct file *, struct dir_context *);    int (*iterate_shared) (struct file *, struct dir_context *);    __poll_t (*poll) (struct file *, struct poll_table_struct *);    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);    int (*mmap) (struct file *, struct vm_area_struct *);    unsigned long mmap_supported_flags;    int (*open) (struct inode *, struct file *);    int (*flush) (struct file *, fl_owner_t id);    int (*release) (struct inode *, struct file *);    int (*fsync) (struct file *, loff_t, loff_t, int datasync);    int (*fasync) (int, struct file *, int);    int (*lock) (struct file *, int, struct file_lock *);    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);    int (*check_flags)(int);    int (*flock) (struct file *, int, struct file_lock *);    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);    int (*setlease)(struct file *, long, struct file_lock **, void **);    long (*fallocate)(struct file *file, int mode, loff_t offset,              loff_t len);    void (*show_fdinfo)(struct seq_file *m, struct file *f);#ifndef CONFIG_MMU    unsigned (*mmap_capabilities)(struct file *);#endif    ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,            loff_t, size_t, unsigned int);    loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,                   struct file *file_out, loff_t pos_out,                   loff_t len, unsigned int remap_flags);    int (*fadvise)(struct file *, loff_t, loff_t, int);} __randomize_layout;
    

    其中常用的就是read,write等函數。

    之后也是正常的調用函數,套路編寫。

    // 讀設備ssize_t xxx_read(struct file *filp, char __user *buf, size_t count,                loff_t *f_pos){    ...    copy_to_user(buf, ..., ...); // 內核空間到用戶空間緩沖區的復制    ...}// 寫設備ssize_t xxx_write(struct file *filp, const char __user *buf,                 size_t count, loff_t *f_pos){    ...    copy_from_user(..., buf, ...); // 用戶空間緩沖區到內核空間的復制    ...} // ioctl函數命令控制long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){    ...    switch (cmd) {    case XXX_CMD1:        ...        break;    case XXX_CMD2:        ...        break;    default:        // 不支持的命令        return -ENOTTY;    }    return 0;}
    

    然后需要file_operations結構體中的函數來重寫用戶空間的write,open,read等函數:

    static struct file_operations xxx_fops =        {                .owner = THIS_MODULE,                // .open = xxx_open,                // .release = xxx_close,                .write = xxx_write,                .read = xxxx_read        };
    

    這樣當用戶空間打開該設備,調用該設備的write函數,就能通過.write進入到xxx_write函數中。

    ▲這樣一些常規kernel題的編寫模板就總結出來了。

    (3)具體的題目

    原題:https://github.com/black-bunny/LinKern-x86_64-bypass-SMEP-KASLR-kptr_restric

    ①代碼和簡單的解析

    #include #include #include #include #include #include #include #include #include #include  //正常的設置了dev_t和cdev,但是這里使用的class這個模板來創建設備驅動static dev_t first; // Global variable for the first device numberstatic struct cdev c_dev; // Global variable for the character device structurestatic struct class *cl; // Global variable for the device classstatic char *buffer_var; //打開關閉設備的消息提示函數static int vuln_open(struct inode *i, struct file *f){  printk(KERN_INFO "[i] Module vuln: open()");  return 0;}static int vuln_close(struct inode *i, struct file *f){  printk(KERN_INFO "[i] Module vuln: close()");  return 0;} //從buffer_var中讀取數據static ssize_t vuln_read(struct file *f, char __user *buf, size_t len, loff_t *off){  if(strlen(buffer_var)>0) {    printk(KERN_INFO "[i] Module vuln read: %s", buffer_var);    kfree(buffer_var);    buffer_var=kmalloc(100,GFP_DMA);    return 0;  } else {    return 1;  }}//向buffer中寫入數據,然后拷貝給buffer_var,這里就是漏洞存在點。//由于len和buf都是我們可以控制的,而buffer是棧上的數據,長度為100。//所以我們可以通過len和buf,將數據復制給buffer從而進行棧溢出。static ssize_t vuln_write(struct file *f, const char __user *buf,size_t len, loff_t *off){  char buffer[100]={0};  if (_copy_from_user(buffer, buf, len))    return -EFAULT;  buffer[len-1]='\0';  printk("[i] Module vuln write: %s", buffer);  strncpy(buffer_var,buffer,len);  return len;} //file_operations結構體初始化static struct file_operations pugs_fops ={  .owner = THIS_MODULE,  .open = vuln_open,  .release = vuln_close,  .write = vuln_write,  .read = vuln_read}; //驅動設備加載函數static int __init vuln_init(void) /* Constructor */{  buffer_var=kmalloc(100,GFP_DMA);  printk(KERN_INFO "[i] Module vuln registered");  if (alloc_chrdev_region(&first, 0, 1, "vuln") < 0)  {    return -1;  }  if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)  {    unregister_chrdev_region(first, 1);    return -1;  }  if (device_create(cl, NULL, first, NULL, "vuln") == NULL)  {    printk(KERN_INFO "[i] Module vuln error");    class_destroy(cl);    unregister_chrdev_region(first, 1);    return -1;  }  cdev_init(&c_dev, &pugs_fops);  if (cdev_add(&c_dev, first, 1) == -1)  {    device_destroy(cl, first);    class_destroy(cl);    unregister_chrdev_region(first, 1);    return -1;  }   printk(KERN_INFO "[i] : <%d, %d>", MAJOR(first), MINOR(first));  return 0;} //驅動設備卸載函數static void __exit vuln_exit(void) /* Destructor */{    unregister_chrdev_region(first, 3);    printk(KERN_INFO "Module vuln unregistered");} module_init(vuln_init);module_exit(vuln_exit); MODULE_LICENSE("GPL");MODULE_AUTHOR("blackndoor");MODULE_DESCRIPTION("Module vuln overflow");
    

    ②內核函數解析

    printk
    printk(日志級別 "消息文本");
    

    其中日志級別定義如下:

    #defineKERN_EMERG "<0>"/*緊急事件消息,系統崩潰之前提示,表示系統不可用*/#defineKERN_ALERT "<1>"/*報告消息,表示必須立即采取措施*/#defineKERN_CRIT "<2>"/*臨界條件,通常涉及嚴重的硬件或軟件操作失敗*/#define KERN_ERR "<3>"/*錯誤條件,驅動程序常用KERN_ERR來報告硬件的錯誤*/#define KERN_WARNING "<4>"/*警告條件,對可能出現問題的情況進行警告*/#define KERN_NOTICE "<5>"/*正常但又重要的條件,用于提醒。常用于與安全相關的消息*/#define KERN_INFO "<6>"/*提示信息,如驅動程序啟動時,打印硬件信息*/#define KERN_DEBUG "<7>"/*調試級別的消息*/
    
    kmalloc
    static inline void *kmalloc(size_t size, gfp_t flags)
    

    其中flags一般設置為GFP_KERNEL或者GFP_DMA,在堆題中一般就是GFP_KERNEL模式,如下:

     

     |– 進程上下文,可以睡眠 GFP_KERNEL

     |– 進程上下文,不可以睡眠 GFP_ATOMIC

     | |– 中斷處理程序 GFP_ATOMIC

     | |– 軟中斷 GFP_ATOMIC

     | |– Tasklet GFP_ATOMIC

     |– 用于DMA的內存,可以睡眠 GFP_DMA | GFP_KERNEL

     |– 用于DMA的內存,不可以睡眠 GFP_DMA |GFP_ATOMIC

     

    具體可以看:

    Linux內核空間內存申請函數kmalloc、kzalloc、vmalloc的區別【轉】 - sky-heaven - 博客園 (cnblogs.com)

    (https://www.cnblogs.com/sky-heaven/p/7390370.html)
    kfree

    這個就不多說了,就是簡單的釋放。

    copy_from_user
    copy_from_user(void *to, const void __user *from, unsigned long n)
    
    copy_to_user
    copy_to_user(void __user *to, const void *from, unsigned long n)
    

    這兩個就不講了,顧名思義。

    注冊函數

    剩下的好多就是常見的注冊函數了。

    alloc_chrdev_region(&t_dev, 0, 1, "xxx");unregister_chrdev_region(t_dev, 1);xxx_class = class_create(THIS_MODULE, "xxx");device_create(xxx_class, NULL, devno, NULL, "xxx");cdev_init(&c_dev, &pugs_fops);cdev_add(&c_dev, t_dev, 1)
    

    2、globalmem虛擬設備驅動

    這個不太清楚,題目見的也少,先忽略。

    printk()函數的總結 - 深藍工作室 - 博客園 (cnblogs.com)

    https://www.cnblogs.com/king-77024128/articles/2262023.html

    Linux kernel pwn notes(內核漏洞利用學習) - hac425 - 博客園 (cnblogs.com)

    https://www.cnblogs.com/hac425/p/9416886.html

    Linux設備驅動(二)字符設備驅動 | BruceFan's Blog (pwn4.fun)

    http://pwn4.fun/2016/10/21/Linux%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%EF%BC%88%E4%BA%8C%EF%BC%89%E5%AD%97%E7%AC%A6%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8/

    infodev
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    在 Kerberos 身份驗證中,客戶端必須在 KDC為其提供票證授予票證 之前執行“預驗證”,該票證隨后可用于獲取服務票證。使用時間戳而不是靜態值有助于防止重放攻擊。客戶端有一個公私鑰對,并用他們的私鑰對預認證數據進行加密,KDC 用客戶端的公鑰對其進行解密。該屬性的值是 Key Credentials這種信任模型消除了使用無密碼身份驗證為每個人頒發客戶端證書的需要。PKINIT 允許 WHfB 用戶或更傳統的智能卡用戶執行 Kerberos 身份驗證并獲得 TGT。
    BEServer - BattlEye服務器,收集上傳的信息,并判定作弊行為。本次分析的是BEDaisy,也就是BE內核驅動中的各種檢測。上傳的內容主要是一些黑名單特征,這些特征應該是從服務器下發的,因此可以在不重新編譯驅動的情況下,動態調整檢測的特征。沒有給定偏移量的特征,BE在檢測時會按子串匹配的方式嘗試所有位置進行匹配。
    敏感信息泄露對于學校站點的信息搜集,一般來說外網能拿直接權限的點已經很少了,web應用大多是放在vpn后面,因此能弄到一個vpn賬號可以說是事半功倍,這時候可以通過語法對此類信息進行挖掘常用命令如下:#google語法。弱口令默認口令對于部分站點,在搭建完成后可能沒有更改默認賬號密碼,這時候可以嘗試使用默認賬密登錄下面列舉一些常見的web站點默認口令賬號:。對于一些應用廣泛的系統,可以通過google語法搜索其默認密碼這里通過sysadmin/1?
    顯示機器的處理器架構。/proc/cpuinfo 顯示CPU info的信息。/proc/swaps 顯示哪些swap被使用。/proc/net/dev 顯示網絡適配器及統計。/proc/mounts 顯示已加載的文件系統。顯示2007年的日歷表。設置日期和時間 - 月日時分年.秒。將時間修改保存到 BIOS. 取消按預定時間關閉系統。的目錄并同時刪除其內容。f -atime +100 搜索在過去100天內未被使用過的執行文件。結尾的文件并定義其權限。halt 顯示一個二進制文件或可執行文件的完整路徑。file.iso /mnt/cdrom 掛載一個文件或ISO鏡像文件
    敏感信息泄露對于學校站點的信息搜集,一般來說外網能拿直接權限的點已經很少了,web應用大多是放在vpn后面,因此能弄到一個vpn賬號可以說是事半功倍,這時候可以通過語法對此類信息進行挖掘常用命令如下:#google語法。弱口令默認口令對于部分站點,在搭建完成后可能沒有更改默認賬號密碼,這時候可以嘗試使用默認賬密登錄下面列舉一些常見的web站點默認口令賬號:。對于一些應用廣泛的系統,可以通過google語法搜索其默認密碼這里通過sysadmin/1?
    編譯make x86_64_defconfig # 加載默認configmake menuconfig # 自定義config. 編譯選項添加調試信息, 需要以下幾行[*] Compile the kernel with debug info [*] Generate dwarf4 debuginfo [*] Provide GDB scripts for kernel debugging. 由于本虛擬機是只有很基本的環境,在調試漏洞之前還需要做一些操作, 創建/etc/passwd,?漏洞原理在調試之前首先根據補丁來簡單了解一下漏洞造成的原因。Exp分析根據exp分析漏洞利用的細節,刪除了部分檢測利用條件、備份密碼等漏洞利用不相關代碼。const unsigned pipe_size = fcntl; static char buffer[4096];for { unsigned n = r > sizeof ?int main() { const char *const path = "/etc/passwd";const int fd = open; if { perror; return EXIT_FAILU
    影響版本:Linux v5.10-rc1 ~ v5.14.15。v5.14.16已修補。高危,可導致遠程提權,評分9.8。 默認不加載,需用戶配置。
    上述操作均在gitlab默認配置情況下,若漏洞利用無法復現可留言,一起討論研究。
    稍后將會基于linux 2.4.x內核環境,演示這種感染技術。不過,由于內核模塊是elf格式的,所以閱讀后續內容之前,先要熟悉elf格式,并且要重點理解其符號表,之后才好明白,向正常內核模塊注入感染代碼的原理。----[ 2.1 - The .symtab section.symtab節區(符號表)的內容,是一個供鏈接器使用的Elf32_Sym結構數組,Elf32_Sym結構定義在內核的/usr/include/elf.h頭文件:typedef struct
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类