糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > 构建/dev/kmem枚举所有Linux内核模块(包括隐藏的)

构建/dev/kmem枚举所有Linux内核模块(包括隐藏的)

时间:2021-12-08 05:48:18

相关推荐

构建/dev/kmem枚举所有Linux内核模块(包括隐藏的)

Linux系统不是可以有lsmod枚举所有内核模块吗?procfs不香吗?干嘛还要费事从/dev/kmem里去枚举?

其实,Linux是后来的事了,在最初的UNIX时代,像ps之类的枚举进程的,都是从/dev/kmem里扫描出来的,这就是一切皆文件,后来的Linux内核很不恰当地拓展了procfs,将乱八七糟的东西都往里面塞,像modules,filesystems,vmallocinfo之类,这些明明不是进程,全部扔进去了,这并不合适,但就是因为这是Linux内核,所以什么都是对的!

当然,Linux也保留了/dev/mem,/dev/kmem这两个极其特殊且好玩的文件:

/dev/mem:映射系统所有的物理内存。/dev/kmem:映射系统所有的内核态虚拟内存。

再后来由于/dev/kmem暴露的权限过大,存在安全隐患,所以一般的内核都封堵了这个字符设备,仅仅保留了/dev/mem,并且还是在受限的情况下:

# CONFIG_DEVKMEM is not setCONFIG_STRICT_DEVMEM=y

本文我们就展示一下如何通过扫描/dev/kmem来枚举所有的内核模块。

什么?不是说CONFIG_DEVKMEM被禁用了吗?好办!重新移植过来便是了,并且,我重新移植的版本还能支持vmalloc空间的映射呢。

代码如下:

// kmem.c#include <linux/module.h>#include <linux/mm.h>#include <linux/kallsyms.h>#include <linux/cdev.h>#include <linux/fs.h>pgprot_t (*_phys_mem_access_prot)(struct file *, unsigned long, unsigned long, pgprot_t);phys_addr_t (*_slow_virt_to_phys)(void *);pte_t *(*_lookup_address)(unsigned long, unsigned int *);static const struct vm_operations_struct mmap_mem_ops = {.access = generic_access_phys};static int mmap_kmem(struct file *file, struct vm_area_struct *vma){unsigned long pfn;pte_t *pte;unsigned int level = 0;size_t size;// 这个是我添加的,由于包含了vmalloc空间,要防止去未映射page的虚拟地址取值造成crash。pte = _lookup_address((u64)vma->vm_pgoff << PAGE_SHIFT, &level);if (!pte || !pte_present(*pte))return -EIO;// 通过通用的方法获得pfn,而不再仅仅考虑线性映射。pfn = _slow_virt_to_phys((void *)(vma->vm_pgoff << PAGE_SHIFT)) >> PAGE_SHIFT;if (!pfn_valid(pfn))return -EIO;vma->vm_pgoff = pfn;size = vma->vm_end - vma->vm_start;vma->vm_page_prot = _phys_mem_access_prot(file, vma->vm_pgoff,size,vma->vm_page_prot);vma->vm_ops = &mmap_mem_ops;if (remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,size,vma->vm_page_prot)) {return -EAGAIN;}return 0;}static const struct file_operations kmem_fops = {.mmap= mmap_kmem,};dev_t dev = 0;static struct cdev kmem_cdev;static int __init devkmem_init(void){_phys_mem_access_prot = (void *)kallsyms_lookup_name("phys_mem_access_prot");_slow_virt_to_phys = (void *)kallsyms_lookup_name("slow_virt_to_phys");_lookup_address = (void *)kallsyms_lookup_name("lookup_address");if((alloc_chrdev_region(&dev, 0, 1, "test_dev")) <0){printk("alloc failed\n");return -1;}printk("major=%d minor=%d \n",MAJOR(dev), MINOR(dev));cdev_init(&kmem_cdev, &kmem_fops);if ((cdev_add(&kmem_cdev, dev, 1)) < 0) {printk("add failed\n");goto out;}return 0;out:unregister_chrdev_region(dev,1);return -1;}void __exit devkmem_exit(void){cdev_del(&kmem_cdev);unregister_chrdev_region(dev, 1);}module_init(devkmem_init);module_exit(devkmem_exit);MODULE_LICENSE("GPL");

OK,我们编译加载之,并且创建字符设备:

insmod ./kmem.komknod /dev/kmem c 248 0

然后,下面的脚本展示了如何枚举所有模块。由于我是一个地址一个地址去映射解析的,并且我的bash水平很low,python,go也不会,所以脚本的效率非常低,运行非常慢,如果一下子映射从0xffffffffa0000000到0xffffffffff000000的所有内存,那就快多了,虽然慢,但是绝对足够详细,看吧:

#!/bin/bash# mlist.shstart=''end=''base=''moktype=$(cat /proc/kallsyms|grep module_ktype|awk '{print $1}')# 用于模式匹配moktype=$(echo $moktype|tr 'a-z' 'A-Z')for line in $(cat /proc/vmallocinfo |grep 0xffffffffa|awk '{print $1}')dostart=$(echo $line|awk -F '-' '{print $1}'|awk -F '0x' '{print $2}')start=$(echo $start|tr 'a-z' 'A-Z')end=$(echo $line|awk -F '-' '{print $2}'|awk -F '0x' '{print $2}')end=$(echo $end|tr 'a-z' 'A-Z')base=$startnext=$basewhile true; doval=$(./a.out $next);if [ $? -ne 0 ]; thenbreak;fiif [ $val == $base ]; thenmod=$(echo "ibase=16;$next-138"|bc)mod=$(echo "obase=16;$mod"|bc)state=$(./a.out $mod)if [ $? -ne 0 ] || [ $state != '0' ]; thennext=$(echo "ibase=16;$next+8"|bc)next=$(echo "obase=16;$next"|bc)continue;fiktype=$(echo "ibase=16;$mod+78"|bc)ktype=$(echo "obase=16;$ktype"|bc)type=$(./a.out $ktype)if [ $? -ne 0 ] || [ $type != $moktype ]; thennext=$(echo "ibase=16;$next+8"|bc)next=$(echo "obase=16;$next"|bc)continue;finamea=$(echo "ibase=16;$mod+18"|bc)namea=$(echo "obase=16;$namea"|bc)name=$(./a.out $namea)# 仅仅截断名字的前8个字符name=$(echo -n $name|sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf)name=$(echo $name|rev 2>/dev/null)if [ $? -eq 0 ]; thenecho name-- $namefifinext=$(echo "ibase=16;$next+8"|bc)next=$(echo "obase=16;$next"|bc)donedone;

解析的结果如下:

[root@localhost test]# ./mlist.shname-- dm_modname-- dm_regioname-- serio_raname-- dm_logname-- dm_mirroname-- libataname-- ahciname-- ata_genename-- crct10diname-- e1000name-- pata_acpname-- cdromname-- sr_modname-- crc_t10dname-- sd_modname-- ablk_helname-- libcrc32name-- ip_tablename-- videoname-- i2c_piixname-- parportname-- cryptdname-- parport_name-- ata_piixname-- kmemname-- i2c_core...

下面简单解释一下。

我们知道,模块的地址映射空间是从0xffffffffa0000000到0xffffffffff000000的,所以我们需要在这个空间里找到它们,只要模块是通过正规途径init_module系统调用加载的,那么它就一定在这个空间里,所以我们只需要扫描这个空间的内存即可,配合关键的两个特征匹配:

module结构体module_core的自指特征。module的kobject的ktype特征。

OK,接下来我们来看看如何在这个地址空间里找到被隐藏的模块,也就是摘链的模块:

扫描空隙呗!!

来吧:

#!/bin/bashstart=''end=''base=''moktype=$(cat /proc/kallsyms|grep module_ktype|awk '{print $1}')moktype=$(echo $moktype|tr 'a-z' 'A-Z')for line in $(cat /proc/vmallocinfo |grep 0xffffffffa|awk '{print $1}')dostart=$(echo $line|awk -F '-' '{print $1}'|awk -F '0x' '{print $2}')start=$(echo $start|tr 'a-z' 'A-Z')if [ $start == 'FFFFFFFFA0000000' ]; thenend=$(echo $line|awk -F '-' '{print $2}'|awk -F '0x' '{print $2}')end=$(echo $end|tr 'a-z' 'A-Z')continue;fiif [ $start == $end ];thenend=$(echo $line|awk -F '-' '{print $2}'|awk -F '0x' '{print $2}')end=$(echo $end|tr 'a-z' 'A-Z')continue;fibase=$endnext=$baseend=$(echo $line|awk -F '-' '{print $2}'|awk -F '0x' '{print $2}')end=$(echo $end|tr 'a-z' 'A-Z')while true; doval=$(./a.out $next);if [ $? -ne 0 ]; thenbreak;fiif [ $val == $base ]; thenmod=$(echo "ibase=16;$next-138"|bc)mod=$(echo "obase=16;$mod"|bc)state=$(./a.out $mod)if [ $? -ne 0 ] || [ $state != '0' ]; thennext=$(echo "ibase=16;$next+8"|bc)next=$(echo "obase=16;$next"|bc)continue;fiktype=$(echo "ibase=16;$mod+78"|bc)ktype=$(echo "obase=16;$ktype"|bc)type=$(./a.out $ktype)if [ $? -ne 0 ] || [ $type != $moktype ]; thennext=$(echo "ibase=16;$next+8"|bc)next=$(echo "obase=16;$next"|bc)continue;finamea=$(echo "ibase=16;$mod+18"|bc)namea=$(echo "obase=16;$namea"|bc)name=$(./a.out $namea)name=$(echo -n $name|sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf)name=$(echo $name|rev 2>/dev/null)if [ $? -eq 0 ]; thenecho name-- $namefifinext=$(echo "ibase=16;$next+8"|bc)next=$(echo "obase=16;$next"|bc)donedone;

试试看,只要模块是通过insmod命令加载后动的手脚,很容易就把隐藏模块找出来了,只是时间久一些。

时间久是因为我这个脚本每个地址折腾一番,效率很低,事实上如果每个page一次映射,那就会好很多,但因为我不会编程,所以只能先这样了。

前面说,只要通过insmod命令,即init_module系统调用加载的模块,都能被找出来。那如果不通过这种正规途径加载模块呢?

其实,你想想看,进入内核的入口,特别是代码进入内核的入口,有多少:

init_moduleptraceftraceeBPF…

本来就不多,init_module是最常用最容易的,如果不用init_module,还能用什么呢?

浙江温州皮鞋湿,下雨进水不会胖。

如果觉得《构建/dev/kmem枚举所有Linux内核模块(包括隐藏的)》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。