绕过CONFIG_MODVERSIONS

最近买了个手机乐视1pro,没有内核源码,想插入一些自己编译的模块来扩展内核,于是找到了硬件相近的一加2的内核源码,把编译得到的模块拷到手机上,发现无法加载。经过摸索,发现原因是内核开启了CONFIG_MODVERSIONS,但是没开启CONFIG_MODULE_FORCE_LOAD. 内核symbol有crc校验,不同源码树编译出来的内核,symbol的crc可能不一样。
要想编译出crc一致的模块,需要用到编译内核时生成的Module.symvers这个文件,厂商没有开放内核源码,更不可能提供这个文件给你了。不过,既然内核会对模块引用到的symbol验证crc,说明内核本身就保存有一份crc,我们只要提取出来就行了。
我们只要编写如下模块,就可完成提取工作:
symcrc.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/slab.h>

static char symcrc[256];
static ssize_t symcrc_write(struct class *cls, struct class_attribute *attr, const char *_buf, size_t _count)
{
    const struct kernel_symbol *ksymbol;
    char *symname;
    struct module *owner;
    const unsigned long *crc = NULL;
    bool gplok = true;
    bool warn = true;
    symname = kstrndup(_buf,strlen(_buf)-1,GFP_KERNEL);
    ksymbol = find_symbol (symname, &owner, &crc, gplok, warn);
    if (ksymbol != NULL && crc != NULL)
	snprintf (symcrc, ARRAY_SIZE(symcrc), "0x%lx\t%s\tvmlinux\tEXPORT_SYMBOL\n", *crc, ksymbol->name);
    else{
        symcrc[0] = '\0';
    }
    kfree(symname);
    return _count;
}

static ssize_t symcrc_read(struct class *cls, struct class_attribute *attr, char *_buf)
{
    return sprintf(_buf, "%s", symcrc);
}

static struct class *symcrc_class = NULL;
static CLASS_ATTR(writesym, 0220, NULL, symcrc_write);
static CLASS_ATTR(getcrc, 0440, symcrc_read, NULL);
int __init find_symbol_init (void)
{
    int ret;
    symcrc_class = class_create(THIS_MODULE, "symcrc");
     if (IS_ERR(symcrc_class)) 
    {   
        printk("Create class symcrc_class failed.\n");
        return -ENOMEM;
    }
    ret =  class_create_file(symcrc_class, &class_attr_getcrc);
    ret =  class_create_file(symcrc_class, &class_attr_writesym);
    return 0;
}

void __exit
find_symbol_exit (void)
{
  class_remove_file(symcrc_class, &class_attr_getcrc);
  class_remove_file(symcrc_class, &class_attr_writesym);
  class_destroy(symcrc_class);
  symcrc_class = NULL;
}

module_init (find_symbol_init);
module_exit (find_symbol_exit);


MODULE_AUTHOR("Mr Pang");
MODULE_LICENSE ("GPL");

编译这个模块,可以看到自动生成的symcrc.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
	.arch = MODULE_ARCH_INIT,
};

static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
	{ 0x48f7bf25, __VMLINUX_SYMBOL_STR(module_layout) },
	{ 0xeb6cd1a5, __VMLINUX_SYMBOL_STR(class_destroy) },
	{ 0xe8efe986, __VMLINUX_SYMBOL_STR(class_remove_file) },
	{ 0x276c87ab, __VMLINUX_SYMBOL_STR(class_create_file) },
	{ 0x27e1a049, __VMLINUX_SYMBOL_STR(printk) },
	{ 0x8637fc77, __VMLINUX_SYMBOL_STR(__class_create) },
	{ 0x91715312, __VMLINUX_SYMBOL_STR(sprintf) },
	{ 0x37a0cba, __VMLINUX_SYMBOL_STR(kfree) },
	{ 0x28318305, __VMLINUX_SYMBOL_STR(snprintf) },
	{ 0x8b58b0d, __VMLINUX_SYMBOL_STR(find_symbol) },
	{ 0xaf6ae696, __VMLINUX_SYMBOL_STR(kstrndup) },
	{ 0x98cf60b3, __VMLINUX_SYMBOL_STR(strlen) },
};

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

里面的static const struct modversion_info ____versions[]列举出了本模块引用到的symbol以及对应的crc。因为这个模块使用的是一加内核源码树编译的,所以部分crc与1pro内核的不一致,以至于无法加载。
为了解决这个问题,我们另外写一个模块,尽可能少的直接引用内核的symbol,
firststep.c

#include <linux/module.h>
#include <linux/init.h>


const struct kernel_symbol *(*find_symbol1)(const char *name, struct module **owner, const unsigned long **crc, bool gplok, bool warn) = (const struct kernel_symbol *(*)(const char *name, struct module **owner, const unsigned long **crc, bool gplok, bool warn))0xffffffc00027cab4;

int (*printk1)(const char *fmt, ...) = (int (*)(const char *fmt, ...))0xffffffc000be3e94;

static const char *sym_array[] = {
  "module_layout",
  "class_destroy",
  "class_remove_file",
  "class_create_file",
    "printk",
  "__class_create",
  "sprintf",
  "kfree",
  "up",
  "snprintf",
  "down",
    "find_symbol",
  "kstrndup",
  "strlen",
};


int __init
find_symbol_init (void)
{
  const char *name;
  int index;
  const struct kernel_symbol *ksymbol;
  struct module *owner;
  const unsigned long *crc;
  bool gplok = true;
  bool warn = true;
  for (index = 0; index < ARRAY_SIZE (sym_array); index++)
    {
      name = sym_array[index];
      ksymbol = find_symbol1 (name, &owner, &crc, gplok, warn);
      if (ksymbol != NULL)
	printk1 (" %s", ksymbol->name);
      else
	printk1 ("<0>Failed to find symbol %s\n", name);
      if (crc != NULL)
	printk1 ("*crc : 0x%lx\n", *crc);
      else
	printk1 ("\n");
    }
  return 0;
}

void __exit
find_symbol_exit (void)
{
}

module_init (find_symbol_init);
module_exit (find_symbol_exit);

MODULE_LICENSE ("GPL");
MODULE_AUTHOR("Mr Pang");

这个模块引用到的两个symbol是printk和find_symbol,我们通过其绝对地址来调用,就可以避免crc的问题了。绝对地址可以查看/proc/kallsyms获得,如果/proc/kallsyms地址全是0,可以echo 0 > /proc/sys/kernel/kptr_restrict
在我的手机上,find_symbol地址是0xffffffc00027cab4,printk地址是0xffffffc000be3e94.
static const char *sym_array[] 保存的是前一个模块symcrc引用到的symbol,可以查看symcrc.mod.c获得。
编译这个模块,查看自动生成的firststep.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
	.arch = MODULE_ARCH_INIT,
};

static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
	{ 0x48f7bf25, __VMLINUX_SYMBOL_STR(module_layout) },
};

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

发现这个模块依然引用了一个symbol–“module_layout”.这个symbol在所有模块上都可以查到,所以并不要紧,只需要提取手机上的任意一个模块,通过命令modprobe --show-modversions xxx.ko就可以找到module_layout的crc。人工修改firststep.mod.c文件,然后人工编译firststep.mod.c并人工链接firststep.ko。具体命令可以在编译模块时指定V=1查看。就两行长长的命令,复制粘贴而已。
此时生成的firststep.ko就是可以加载的了。加载之后通过dmesg查看打印出的crc,同样人工修改symcrc.mod.c里面的crc,再人工编译出symcrc.ko.
插入symcrc.ko之后,可以通过以下命令提取内核的Module.symvers:

for i in `cat /proc/kallsyms|awk '{print $3}'`;do
echo $i > /sys/class/symcrc/writesym;
cat /sys/class/symcrc/getcrc >> /sdcard/Module.symvers;
done

拿到Module.symvers之后编译模块就方便多了。
首先要make clean
然后make nconfig选择需要编译的模块
然后通过make modules KBUILD_EXTRA_SYMBOLS=/path/to/Module.symvers
编译模块,生成的模块就是带有正确crc的模块,可以直接载入。

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值