为Linux内核text段计算SHA1摘要以检测篡改

在上文中,左右手互搏,最终成功将二进制stub函数注入到了Linux内核的text段本身,逃过了jmp/call的越界检测:
https://blog.csdn.net/dog250/article/details/105496996

真把二进制注入到Linux内核的text自身就很难检测了,任何检测机制均类似免疫系统一样,如此一来很难分清敌我。

如果把采用越界call的rootkit看作是病毒入侵,那么直接注入text段的rootkit就像癌细胞!

最直接的方法就是为内核的text计算摘要了,定期执行计算,只要发现有变化,就dump出代码,xed解析并和基准进行diff:

// scanner.stp
%{
#include <linux/module.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>

#define SHA1_LENGTH     20
%}

function scan_text (type:long)
%{
	int i ,ret;
	unsigned int size;
	unsigned char *__text;
	unsigned char *__etext;
	unsigned char *plaintext = NULL;
    unsigned char hashtext[SHA1_LENGTH];
	struct scatterlist sg;
    struct hash_desc desc;

	__text = (void *)kallsyms_lookup_name("_text");
	__etext = (void *)kallsyms_lookup_name("_etext");
	__text ++;
	size = __etext - __text;

	if (STAP_ARG_type == 0) {
		STAP_PRINTF("90 ");
	} else {
    	plaintext = (unsigned char *)vzalloc(size);
		if (!plaintext) {
			return;
		}
	}

	for (i = 0; i < size; i++) {
		if (STAP_ARG_type == 0) {
			STAP_PRINTF("%x ", __text[i]);
		} else {
			plaintext[i] = __text[i];
		}
	}

	if (STAP_ARG_type == 0) {
		return;
	}

    memset(hashtext, 0, SHA1_LENGTH);

    sg_init_one(&sg, plaintext, size);
    desc.tfm = crypto_alloc_hash("sha1", 0, CRYPTO_ALG_ASYNC);
    desc.flags = 0;

	ret = crypto_hash_init(&desc);
    if (ret) {
		vfree(plaintext);
		return;
    }
    ret = crypto_hash_update(&desc, &sg, size);
    if (ret) {
		vfree(plaintext);
		return;
    }
    ret = crypto_hash_final(&desc, hashtext);
    if (ret) {
		vfree(plaintext);
		return;
    }
    crypto_free_hash(desc.tfm);

    for (i = 0; i < 20; i++) {
        STAP_PRINTF("%02x ", hashtext[i]&0xff);
    }
    STAP_PRINTF("\n");

	vfree(plaintext);
%}

probe begin
{
	// type 为0为dump,type为1为摘要
	scan_text($1);
	exit(); // oneshot模式
}

我们先算一下没有加载drop模块时的text段摘要,并且dump出基准的code:

[root@localhost obj]# stap -g ./scanner.stp 1
08 b2 05 20 7f a6 fe 4f c0 70 b7 71 25 6c a0 e8 80 d9 d3 61
[root@localhost obj]# stap -g ./scanner.stp 0 >./hex1

现在加载drop模块,执行注入,再次计算摘要:

[root@localhost obj]# insmod ./drop.ko
[root@localhost obj]# stap -g ./scanner.stp 1
da 9b e5 a7 1b f9 24 39 3f 8d a5 ca 16 f1 9f a7 72 33 a1 34

变了,变了!到底什么变了?再次dump出code,并用xed解析:

[root@localhost obj]# stap -g ./scanner.stp 0 >./hex2
[root@localhost obj]# ./xed -ih ./hex1 -64 >./code1
[root@localhost obj]# ./xed -ih ./hex2 -64 >./code2

来吧,揪出真凶来:

[root@localhost obj]# diff code1 code2
113c113
< XDIS 1d0: CALL      BASE       E8DE67FFFF               call 0xffffffffffff69b3
---
> XDIS 1d0: CALL      BASE       E8CB845500               call 0x5586a0
116c116
< XDIS 1da: BINARY    BASE       FF042580E20AA0           inc dword ptr [0xffffffffa00ae280]
---
> XDIS 1da: BINARY    BASE       FF042580620AA0           inc dword ptr [0xffffffffa00a6280]
1452815c1452815
< XDIS 561ebd: CALL      BASE       E8DE67FFFF               call 0x5586a0
---
> XDIS 561ebd: CALL      BASE       E80EE3A9FF               call 0x1d0
1691134c1691134
< #Total DECODE cycles:        2294206070
---
> #Total DECODE cycles:        2293072802
1691136c1691136
< #Total tail DECODE cycles:        2298560197
---
> #Total tail DECODE cycles:        2293479218
1691138,1691139c1691138,1691139
< #Total cycles/instruction DECODE: 1356.65
< #Total tail cycles/instruction DECODE: 1359.19
---
> #Total cycles/instruction DECODE: 1355.98
> #Total tail cycles/instruction DECODE: 1356.18

不光揪出来了ip_local_deliver被篡改,就连注入到stext的nop序列中的内容都被揪出来了:

inc dword ptr [0xffffffffa00ae280]

哦,增加一个计数器的值,还好,只是做了个统计,没有偷经理的皮鞋👞。

要做到摘要校验的有效性,必须在系统初启尚未有机会被注入的时候,第一时间拿到干净的摘要,并且保证该摘要本身不会被篡改,以此作为后续比较的基准。

并不建议0号线程的cpu_idle_loop中做摘要计算,因为内核函数本身就可能后续被篡改,我建议摘要的计算作为1号进程systemd/init的启动钩子比较妥当。

内核被rootkit篡改,事实上只要有root权限,就都不是事儿,而无论是注入代码还是读写/dev/mem,均需要内核模块机制,一般而言/dev/mem是不可写甚至不可访问的,你需要systemtap先hook掉devmem_is_allowed:

stap -g -e 'probe kernel.function("devmem_is_allowed").return { $return = 1 }'

而stap的底层也是靠内核模块机制起作用的。

为了让这一切的门槛更高一些,内核模块签名就显得重要!即便你有root权限,你没有签名私钥也是白搭,至少签名私钥的保管要严格得多,攻破签名服务器本身就很不容易!

换句话说,Unix/Linux的root权限太大了!

唉…


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

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值