再谈Linux内核模块注入(没有kernel header也能hack kernel)

在前面的一篇文章中,我简单描述了一种Linux内核模块注入的方法:
https://blog.csdn.net/dog250/article/details/105978803

本文,我们抛开Rootkit这个刺眼的字样,看看这种注入机制还有什么新的玩法。

我们知道,编写Linux内核模块必须要有对应版本的kernel header,并且版本号必须100%匹配,一个字都不能差,这对Linux内核模块的移植和分发带来了极大的不便,这一切的规则只是因为:

  • Linux内核的API是不稳定的!

即便你心里明确知道两个版本之间你使用的API没有任何变化,也不能公用一套kernel header,这是规定,规定!

但规则往往是建立给遵守它的人的! 人们为了避开一种规则不得不建立另一种规则,简而言之,牢笼终不可破。比如为了让内核模块的代码可跨版本移植,下面的链接是可观摩的:
http://vger.kernel.org/bpfconf2019_talks/bpf-core.pdf
但毫无疑问,CO-RE(Compile Once – Run Everywhere)本身也是一套复杂的规则。

在被规则戏弄的时候,我们要换一种思路,用一些小把戏摆脱规则。

让我们开始,来吧!

简单的需求,我没有kernel header,但我希望写一个简单的功能:

char *p = vmalloc(128);
printk("p is at %p\n", p);
vfree(p);

显然,我无法编写内核模块,那我该怎么办?

简单啊,随便找一个已经存在的模块,把上面的代码编译成重定位.o文件,注入进既有模块即可咯。让我们看看怎么做。

首先,我们给出一个主内核模块,以模拟既有的内核模块,但其实,你应该从下面的目录下找的:

/lib/modules/`uname -r`/kernel/

我们的模拟内核模块代码如下:

// simple.c
#include <linux/module.h>
#include <linux/vmalloc.h>

int __init hotfix_init(void)
{
	char *pp = vmalloc(128); // 使用一下vmalloc,以便raw.o可以extern注入
	printk("i am original init, and p is at :%p\n", pp);
	vfree(pp);
	return 0;
}

void __exit hotfix_exit(void)
{
}
module_init(hotfix_init);
module_exit(hotfix_exit);
MODULE_LICENSE("GPL");

我们编译它,加载它,正常应该输出:

[root@localhost test]# insmod ./simple.ko
[root@localhost test]# dmesg
[15138.718371] i am original init, and p is at :ffffc900001fe000

接下来,让我们用标准C编写一个stub,编译为raw.o:

#include <stdlib.h>

extern int hotfix_init(void);
extern int printk(const char *fmt, ...);
extern char * vmalloc(unsigned int size);
extern char * vfree(char *p);

// 注意,section属性希望将test_stub和simple的初始化代码放在一个section!
int  test_stub(void) __attribute__((section(".init.text")));
int  test_stub(void)
{
	char *p = NULL;

	p = vmalloc(128);
	printk("i am a stub, and p is at %p\n", p);
	vfree(p);
	hotfix_init();
	return -1;
}

以上代码并不依赖任何内核的东西,它就是一个普通的C文件,编译如下:

# mcmodel选项还是需要的,毕竟这是要在内核态运行的。
[root@localhost test]# gcc -mcmodel=kernel -c raw.c -o raw.o

OK,现在开始注入:

[root@localhost test]# ld -r ./simple.ko ./raw.o -o ./new.ko
[root@localhost test]# ./modsym ./new.ko hotfix_init test_stub
###   @@@@@@@@ init func  :init_module  1  1
[root@localhost test]# dmesg -c
# 由于我们的stub返回了-1,当然加载不成功。
[root@localhost test]# insmod ./new.ko # 
insmod: ERROR: could not insert module ./new.ko: Operation not permitted
# 成功打印出我们预期的结果
[root@localhost test]# dmesg
[15450.735419] i am a stub, and p is at ffffc900001fe000
[15450.735423] i am original init, and p is at :ffffc90000701000

是不是很有趣呢?

行文至此,我的目的相信已经暴露了。我想说的是,如果你想注入一些Rootkit在系统既有的内核模块中,你不必拥有编译内核模块所需的完整工具链,你不需要make,不需要kernel header,你只需要:

  • 一个gcc或者类似的编译器。
  • 对内核函数足够熟悉。
  • 实在不熟悉内核函数,还可以用二进制直接hack呢。

你可以在别的机器上编译出.o文件,然后携带着.o和modsym程序登录经理的机器,只需要不到一分钟,就能将代码注入到多个既有内核模块,当然了,千万不要return !0哦,一定要return orig…,不然就要吃大亏了。

我知道,这一切还是有可以硬杠硬怼的地方,说什么经理万一做模块签名怎么办,经理万一做inotify发现内核模块文件被改变怎么办,经理滑倒了怎么办,…等等一切怎么办,但事情总要一步一步来,这些都不是本文要关注的重点,至于别的那些,以后再说吧。

经理的皮鞋很高档,但领带看起来不行。


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

猜你喜欢

转载自blog.csdn.net/dog250/article/details/105984089