1. 程式人生 > 其它 >繞過kernel模組版本校驗檢測(轉)

繞過kernel模組版本校驗檢測(轉)

轉自:https://www.cnblogs.com/super-king/p/3296347.html?utm_source=tuicool

kernel module version check bypass
1、 舉例說明
2、 核心是怎麼實現的
3、 怎樣去突破
4、 總結

1、 舉例說明
Linux核心版本很多,升級很快,2個小核心版本中核心函式的定義可能都不一樣,為了確保不一致的驅動程式導致kernel oops,
開發者加入了模組驗證機制。它在載入核心模組的時候對模組進行校驗, 如果模組與主機的一些環境不一致,就會載入不成功。
看下面一個例子,它簡單的輸出當期系統中的模組列表:
-------------------------------------------------------------------
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/string.h>
#include <linux/list.h>

MODULE_LICENSE("GPL");
struct module *m = THIS_MODULE;

int print_module_test(void)
{
struct module *mod;
list_for_each_entry(mod, &m->list, list) {
printk("%s\n", mod->name);
}
return NULL;
}

static int list_print_init(void)
{
printk("load list_print module.\n");
print_module_test();
return 0;
}
static void list_print_exit(void)
{
printk("unload list_print module.\n");
}
module_init(list_print_init);
module_exit(list_print_exit);複製程式碼
我們在Asianux3.0環境中編譯一下:
[root@localhost list]# uname -a
Linux localhost.localdomain 2.6.18-8.10AX #1 SMP Wed Jan 21 10:44:23 EST 2007 i686 i686 i386 GNU/Linux
然後拷貝到另一臺主機上Asianux3.0sp2:
[root@localhost list]#uname -a
Linux localhost.localdomain 2.6.18-128.7AXS3 #1 SMP Wed Jan 21 10:44:23 EST 2007 i686 i686 i386 GNU/Linux
用insmod載入:
[root@localhost ~]# insmod list.ko
insmod: error inserting 'list.ko': -1 Invalid module format
報錯了,在看下dmesg的資訊:
[root@localhost ~]# dmesg|tail -n 1
list: disagrees about version of symbol struct_module

先不管這是什麼, 總之我們的模組在另一臺2.6.18的主機中載入失敗。 通常的做法是要在主機中對原始碼進行編譯,
然後才能載入成功, 但是如果主機中缺少核心編譯環境的話, 我們的模組就不能編譯, 也不能安裝在主機之中,這是多麼尷尬的事情。
沒錯, 這就是linux kernel開發的特點。


2、核心是怎麼實現的
我們去看看核心在載入模組的時候都幹了什麼。
grep下dmesg裡的關鍵字, 看看它在哪個檔案中:
[root@localhost linux-2.6.18]# grep -r -i 'disagrees about' kernel/
kernel/module.c: printk("%s: disagrees about version of symbol %s\n",


2.6.18/kernel/module.c:
insmod呼叫了sys_init_module這個系統呼叫, 然後進入load_module這個主函式,它解析elf格式的ko檔案,然後載入
到核心中:

/* Allocate and load the module: note that size of section 0 is always
zero, and we rely on this for optional sections. */
static struct module *load_module(void __user *umod, unsigned long len, const char __user *uargs)
{
...
if (!check_modstruct_version(sechdrs, versindex, mod)) {
err = -ENOEXEC;
goto free_hdr;
}

modmagic = get_modinfo(sechdrs, infoindex, "vermagic");
/* This is allowed: modprobe --force will invalidate it. */
if (!modmagic) {
add_taint_module(mod,TAINT_FORCED_MODULE);
printk(KERN_WARNING "%s: no version magic, tainting kernel.\n",
mod->name);
} else if (!same_magic(modmagic, vermagic)) {
printk(KERN_ERR "%s: version magic '%s' should be '%s'\n", mod->name, modmagic, vermagic);
err = -ENOEXEC;
goto free_hdr;
}
...
}
check_modstruct_version就是用來計算模組符號的一些crc值,不相同就會出現我們在dmesg裡看到的“disagrees about version of symbol”資訊。
get_modinfo取得了核心本身的vermagic值,然後用same_magic函式和核心的vermagic去比較,不同也會使核心載入失敗。
所以在這裡,我們看到核心對模組驗證的時候採用了2層驗證的方法:模組crc值和vermagic檢查。

繼續跟蹤check_modstruct_version, 現在的核心預設的都開啟了CONFIG_MODVERSIONS, 如果沒有指定這個選項,
函式為空,我們的目的是要在Asianux 3.0sp2下安裝模組,系統當然開了MODVERSIONS選項。
static inline int check_modstruct_version(Elf_Shdr *sechdrs, unsigned int versindex, struct module *mod)
{
const unsigned long *crc;
struct module *owner;

if (!__find_symbol("struct_module", &owner, &crc, 1))
BUG();

return check_version(sechdrs, versindex, "struct_module", mod, crc);
}
__find_symbol找到了struct_module這個符號的crc值,然後呼叫check_version去校驗:

static int check_version(Elf_Shdr *sechdrs, unsigned int versindex, const char *symname, struct module *mod, const unsigned long *crc)
{
unsigned int i, num_versions;
struct modversion_info *versions;

/* Exporting module didn't supply crcs? OK, we're already tainted. */
if (!crc)
return 1;

versions = (void *) sechdrs[versindex].sh_addr;
num_versions = sechdrs[versindex].sh_size / sizeof(struct modversion_info);

for (i = 0; i < num_versions; i++) {
if (strcmp(versions[i].name, symname) != 0)
continue;

if (versions[i].crc == *crc)
return 1;

printk("%s: disagrees about version of symbol %s\n", mod->name, symname);
DEBUGP("Found checksum %lX vs module %lX\n", *crc, versions[i].crc);
return 0;
}

/* Not in module's version table. OK, but that taints the kernel. */

if (!(tainted & TAINT_FORCED_MODULE)) {
printk("%s: no version for \"%s\" found: kernel tainted.\n", mod->name, symname);
add_taint(TAINT_FORCED_MODULE);
}
return 1;
}

它搜尋elf的versions小節, 迴圈遍歷陣列中的每個符號表,找到struct_module這個符號,然後去比較crc的值。
現在有個疑問, versions小節是怎麼連結到模組的elf檔案中去的呢? 在看下編譯後的生成檔案, 有一個list.mod.c

[root@localhost list]# cat list.mod.c
#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>

MODULE_INFO(vermagic, VERMAGIC_STRING);

struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
};

static const struct modversion_info ____versions[]
__attribute_used__
__attribute__((section("__versions"))) = {
{ 0x89e24b9c, "struct_module" },
{ 0x1b7d4074, "printk" },
};

static const char __module_depends[]
__attribute_used__
__attribute__((section(".modinfo"))) =
"depends=";

MODULE_INFO(srcversion, "26DB52D8A56205333D414B9");
這個檔案是模組在編譯的時候,呼叫了linux-2.6.18/scripts/modpost這個檔案生成的。
裡面增加了2個小節.gnu.linkonce.this_module和__versions。 __versions小節的內容就是一些字串和值組成的陣列,
check_version就是解析這個小節去做驗證。 這裡還有一個MODULE_INFO巨集用來生成模組的magic字串,這個在以後的vermagic中要做驗證。

先看下vermagic的格式:
[root@localhost list]# modinfo list.ko
filename: list.ko
license: GPL
srcversion: 26DB52D8A56205333D414B9
depends:
vermagic: 2.6.18-128.el5 SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1複製程式碼
這裡可以看到vermagic跟核心版本,smp,gcc版本,核心堆疊大小都有關。

3、怎樣去突破
知道了核心是怎麼實現的了, 下面開始想辦法繞過這些驗證。
3.1 怎麼突破crc驗證:
在仔細看下程式碼:
for (i = 0; i < num_versions; i++) {
if (strcmp(versions[i].name, symname) != 0)
continue;

if (versions[i].crc == *crc)
return 1;

printk("%s: disagrees about version of symbol %s\n", mod->name, symname);
DEBUGP("Found checksum %lX vs module %lX\n", *crc, versions[i].crc);
return 0;
}
/* Not in module's version table. OK, but that taints the kernel. */
if (!(tainted & TAINT_FORCED_MODULE)) {
printk("%s: no version for \"%s\" found: kernel tainted.\n",
mod->name, symname);
add_taint(TAINT_FORCED_MODULE);
}
return 1;

check_version在迴圈中只是在尋找struct_module符號, 如果沒找到呢? 它會直接返回1! 沒錯, 這是一個
邏輯bug,在正常情況下,module必會有一個struct_module的符號, 這是modpost生成的。如果我們修改elf檔案,
把struct_module這個符號改名,豈不是就可以繞過crc驗證了嗎? 先做個實驗看下:
.mod.c是由modpost這個工具生成的, 它在linux-2.6.18/scripts/Makefile.modpost檔案中被呼叫, 去看下:
PHONY += __modpost
__modpost: $(wildcard vmlinux) $(modules:.ko=.o) FORCE
$(call cmd,modpost)
@sleep 30 // add line

我們用一個很土的方法, 就是在編譯模組的時候,modpost生成.mod.c檔案後, 暫停下編譯,sleep 30秒吧,我們用
這個時間去改寫下.mod.c, 把struct_module換個名字。

隨便將struct_module改個名:

[root@localhost list]# cat list.mod.c
...
static const struct modversion_info ____versions[]
__attribute_used__
__attribute__((section("__versions"))) = {
{ 0x89e24b9c, "stauct_module" }, //modify line
{ 0x1b7d4074, "printk" },
};
...
我們是在AS3下編譯的, 然後拷貝到AS3SP1下, 在執行下insmod看下:
[root@localhost ~]# insmod list.ko
[root@localhost ~]# dmesg|tail
ata_piix
libata
sd_mod
scsi_mod
ext3
jbd
ehci_hcd
ohci_hcd
uhci_hcd
成功了! 這跟我們預期的一樣, 我們用這個邏輯bug繞過了模組的crc驗證! 這個bug直到2.6.31版本中才得到修正。
我們可以用這種方法在Asianux or redhat主機中任意安裝模組了。 那麼怎樣繞過在2.6.31以後的核心呢?
看下它是怎麼修補的:

for (i = 0; i < num_versions; i++) {
if (strcmp(versions[i].name, symname) != 0)
continue;

if (versions[i].crc == *crc)
return 1;
DEBUGP("Found checksum %lX vs module %lX\n", *crc, versions[i].crc);
goto bad_version;
}

printk(KERN_WARNING "%s: no symbol version for %s\n", mod->name, symname);
return 0;

bad_version:
printk("%s: disagrees about version of symbol %s\n", mod->name, symname);
return 0;

如果沒找到struct_module也會返回0, 這樣我們就必須將struct_module的值改為正確後, 才能繼續安裝。
如何找到模組符號的crc值呢? 我們可以去找目標主機中那些已被系統載入的模組的crc值,如ext3檔案系統的模組,
或者自己寫個程式去解析elf檔案, 就可以得到某些符號的crc值了。
還有沒有更簡單的方法呢?去/boot目錄下看看,symvers-2.6.18-128.7AXS3.gz貌似和crc有關,gunzip解壓後看看:
[root@localhost boot]# grep 'struct_module' symvers-2.6.18-128.7AXS3
0x89e24b9c struct_module vmlinux EXPORT_SYMBOL複製程式碼
原來核心中所有符號的crc值都儲存在這個檔案中。如何改寫struct_module的值呢,可以用上面那個土方法,
或者自己寫程式去解析elf檔案, 然後改寫其值。

3.2 如何突破vermagic驗證:
如果我們用list.mod.c中的做法, 用MODULE_INFO巨集來生成一個與目標主機相同的vermagic呢? 答案是
否定的,gcc連結的時候會把modinfo小節連結在最後,載入模組的時候還是會讀取第一個.modinfo小節。
我們可以用上面那種很土的方法, 先用modinfo命令得到目標主機中某個模組的資訊:
[root@localhost list]# modinfo /lib/modules/2.6.18-128.7AXS3/kernel/fs/ext3/ext3.ko
filename: /lib/modules/2.6.18-128.7AXS3/kernel/fs/ext3/ext3.ko
license: GPL
description: Second Extended Filesystem with journaling extensions
author: Remy Card, Stephen Tweedie, Andrew Morton, Andreas Dilger, Theodore Ts'o and others
srcversion: B048AC103E5034604A721C5
depends: jbd
vermagic: 2.6.18-128.7AXS3 SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1
然後在用那個很土的方面, 將.mod.c中vermagic值進行修改。
...
MODULE_INFO(vermagic, "2.6.18-128.7AXS3 SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1"); //modify line
...
4、總結
前面有一點沒有提到, 就是某些核心版本的相同介面的函式程式碼可能已經變化, 這樣在使用這項技術的時候,
最好在同一個大核心版本使用。本人在ubuntu 9.04 上編譯然後放入AXS3SP1中#insmod list.ko模組插入沒有問題,
但是沒有打印出任何資訊,這就是kernel版本差別大的結果。