为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权限太大了!

唉…


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

原文链接: https://blog.csdn.net/dog250/article/details/105512598

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

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

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/406023

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午9:34
下一篇 2023年4月26日 上午9:34

相关推荐