首页
社区
课程
招聘
[原创] Android 内核加载未签名驱动的一次实践
发表于: 20小时前 193

[原创] Android 内核加载未签名驱动的一次实践

20小时前
193

前言

测试机型是 Honor 50 SE 内核版本是 4.14.186 已解锁 BootLoader 通过 Magisk 27 管理 root

前段时间需要测试 4 系内核的一个外设驱动,打算用这台机器,编译好了驱动才发现有签名校验.网上一搜几乎都是小米和一加的教程,大意就是,先用 find_load_module.exe 在内核中搜索 load_module 函数位置,然后用 ida 修补了其中的几行汇编就搞定了.无奈我手中的的二进制看上去和教程中的完全不一样,压根无从改起,只好自己动手,这才有了这篇文档

一 提取内核文件

  1. 查看当前系统使用的槽位,我的是 b
    getprop ro.boot.slot_suffix
    
  2. 找到内核文件所在分区,在我这里 boot_b 对应的是 /dev/block/sdc64
    ls -al /dev/block/by-name/ | grep boot
    
  3. 复制分区镜像到当前目录下
    dd if=/dev/block/sdc64 of=./boot.img
    
  4. 在系统里找到 magiskboot 文件用于解包 boot.img, 我的是在 /data/adb/magisk/magiskboot, 如果是 apk 文件就从中提取 libmagiskboot.so
    ./magiskboot unpack ./boot.img
    # 或
    ./libmagiskboot.so unpack ./boot.img
    
  5. 得到的 kernel 文件就是本篇的主角

二 对照内核源代码修补

  1. 官网搜索对应的机型下载源码,解压后进到 Code_Opensource/kernel 找到 kernel/module.c, load_module 函数的相关代码基本上都在这里,下面贴一小段主要逻辑

    static int load_module(struct load_info *info, const char __user *uargs,
                           int flags)
    {
            struct module *mod;
            long err;
            char *after_dashes;
    
            err = module_sig_check(info, flags);
            if (err)
                    goto free_copy;
    
            err = elf_header_check(info);
            if (err)
                    goto free_copy;
    
            /* Figure out module layout, and allocate all the memory. */
            mod = layout_and_allocate(info, flags);
            if (IS_ERR(mod)) {
                    err = PTR_ERR(mod);
                    goto free_copy;
            }
    
            audit_log_kern_module(mod->name);
    
            /* Reserve our place in the list. */
            err = add_unformed_module(mod);
            if (err)
                    goto free_module;
    
    #ifdef CONFIG_MODULE_SIG
            mod->sig_ok = info->sig_ok;
            if (!mod->sig_ok) {
                    pr_notice_once("%s: module verification failed: signature "
                                   "and/or required key missing - tainting "
                                   "kernel\n", mod->name);
                    add_taint_module(mod, TAINT_UNSIGNED_MODULE, LOCKDEP_STILL_OK);
            }
    #endif
    
            ... 以下省略 ...
    }
    
    static int module_sig_check(struct load_info *info, int flags)
    {
            int err = -ENOKEY;
            const unsigned long markerlen = sizeof(MODULE_SIG_STRING) - 1;
            const void *mod = info->hdr;
    
            /* 
             * Require flags == 0, as a module with version information
             * removed is no longer the module that was signed
             */ 
            if (flags == 0 &&
                info->len > markerlen &&
                memcmp(mod + info->len - markerlen, MODULE_SIG_STRING, markerlen) == 0) {
                    /* We truncate the module to discard the signature */
                    info->len -= markerlen;
                    err = mod_verify_sig(mod, &info->len);
            }
    
            if (!err) {
                    info->sig_ok = true;
                    return 0;
            }
    
    #ifdef CONFIG_HUAWEI_PROC_CHECK_ROOT
            saudit_log(MOD_SIGN, STP_RISK, 0, "result=%d,", err);
    #endif
    
            /* Not having a signature is only an error if we're strict. */
            if (err == -ENOKEY && !sig_enforce)
                    err = 0;
    
            return err;
    }
    

    可以看到签名校验部分都在 module_sig_check 中,我一看这还不手拿把掐嘛, nop 掉一个 bl 指令就能收工啦

  2. 打开 ida 后尝试搜索了几个字符串,很快就定为到了 load_module, 部分反汇编代码如下

      v6 = a1;
      v575 = &off_0;
      v8 = (const char *)(a1 + 8);
      v7 = *(_QWORD *)(a1 + 8);
      if ( a3
        || (v10 = (const char **)(v6 + 16), v9 = *(_QWORD *)(v6 + 16), v9 < 0x1D)
        || *(_QWORD *)(v7 + v9 - 28) ^ 0x20656C75646F4D7ELL
         | *(_QWORD *)(v7 + v9 - 20) ^ 0x727574616E676973LL
         | *(_QWORD *)(v7 + v9 - 12) ^ 0x646E657070612065LL
         | *(unsigned int *)(v7 + v9 - 4) ^ 0xA7E6465LL )
      {
        v11 = -126;
    LABEL_5:
        sub_68939C(10, 1, 0, "result=%d,", v11);
        v12 = v11;
        goto LABEL_6;
      }
      v4 = a2;
      v3 = v5;
      *v10 = (const char *)(v9 - 28);
      v11 = sub_2B82E0();
      if ( v11 )
        goto LABEL_5;
      v15 = *(_QWORD *)(v6 + 16);
      *(_BYTE *)(v6 + 76) = 1;
      if ( v15 < 0x40
        || (v16 = *(_DWORD **)v8, **(_DWORD **)v8 != 1179403647)
        || *((_WORD *)v16 + 8) != 1
        || *((_WORD *)v16 + 9) != 183
        || *((_WORD *)v16 + 29) != 64
        || (v17 = *((_QWORD *)v16 + 5), v18 = v15 > v17, v19 = v15 - v17, !v18)
        || v19 < (unsigned __int64)*((unsigned __int16 *)v16 + 30) << 6 )
      {
        v12 = -8;
        goto LABEL_6;
      }
    

    emm...这种情况怎么说呢,算不上好但也不是无解,我尝试了直接跳转当然是失败了哈哈.编译内核时 module_sig_check 甚至 mod_verify_sig 都被内联进了 load_module, 没有函数调用就没有弹栈寄存器恢复现场,所以只好手动分析汇编,看看哪些寄存器会被后面的代码用到

  3. 机器码如下

    好在编译器没给我更上强度,在一番试错后也是终于顺利进入系统,只需做如下修改

    文件偏移 原指令 原机器码 新指令 新机器码 作用
    0x2B267C CBNZ W2,loc_2B26FC 02 04 00 35 NOP 1F 20 03 D5 绕过 flags 校验
    0x2B2688 CMP X8,#0x1D 1F 75 00 F1 MOV X22,X1 F6 03 01 AA 保存必要寄存器值
    0x2B268C B.CC loc_2B26FC 83 03 00 54 MOV X20,X30 F4 03 1E AA 同上
    0x2B2690 ADD X9,X0,X8 09 00 08 8B B 0x2B2780 3C 00 00 14 跳过验证函数

三 回刷内核

  1. 保持 kernel 文件名不变放回原来的位置打包
    ./magiskboot repack ./boot.img 
    
  2. 得到 new-boot.img 后写入原分区
    dd if=./new-boot.img of=/dev/block/sdc64
    
  3. 重启手机,如果失败了进不去系统,那就得用 fastboot 刷回原镜像
    fastboot flash boot boot.img
    

四 编译内核驱动

这部分和其他的 4 系内核差不多,简单说明一下,除了上面用到的内核源代码,还得下载 gcc 和 clang 工具链,地址如下

  • gcc: 43bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6x3K9h3&6W2j5h3N6W2e0#2y4Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6g2X3M7s2u0W2j5Y4g2A6L8s2c8K6i4K6g2X3k6$3y4U0i4K6g2X3L8r3W2F1N6i4S2Q4x3X3c8^5z5o6k6Q4y4h3k6S2j5i4u0U0K9o6j5@1i4K6g2X3j5h3q4J5j5$3R3$3y4q4)9J5k6r3I4A6L8Y4g2^5i4K6u0V1j5h3&6V1M7X3!0A6k6q4)9J5k6o6c8Q4x3X3f1&6
  • clang: 356K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0W2k6$3!0G2k6$3I4W2M7$3!0#2M7X3y4W2i4K6u0W2j5$3!0E0i4K6u0r3M7r3I4S2N6r3k6G2M7X3#2Q4x3V1k6H3M7X3g2T1N6h3W2D9N6s2y4Q4x3V1k6U0L8r3q4F1k6#2)9J5c8X3S2G2M7%4c8Q4x3V1k6D9K9h3&6#2P5q4)9J5k6s2R3^5y4W2)9J5c8W2)9J5b7X3q4J5j5$3S2A6N6X3g2Q4x3V1k6J5k6h3k6K6i4K6u0r3K9r3g2S2k6s2y4Q4x3V1k6S2L8X3c8J5L8$3W2V1x3e0u0Q4x3X3c8J5k6h3I4W2j5i4y4W2i4K6u0r3j5$3I4S2L8X3N6Q4x3X3c8J5x3K6R3K6z5e0l9J5i4K6u0W2N6r3q4J5i4K6u0W2k6%4Z5`.

编译时最好是用 Ubuntu 20.04 x64 系统,我没有用机器自带的 /proc/config.gz 也成功了,只编译内核模块的话应该没那么多讲究

MAKE_ARGS='ARCH=arm64 SUBARCH=arm64 CLANG_TRIPLE=aarch64-linux-gnu- CC=clang LD=ld.lld CROSS_COMPILE=aarch64-linux-android- O=out'
export PATH="/home/ubuntu/clang/bin:/home/ubuntu/gcc/bin:$PATH"
cd Code_Opensource/kernel/
make $MAKE_ARGS merge_full_k6877v1_64_defconfig
make $MAKE_ARGS modules_prepare

上面的脚本 PATHdefconfig 按需修改就行,测试代码如下

Makefile

obj-m += hello.o
KDIR := /home/ubuntu/Code_Opensource/kernel/out
MAKE_ARGS := ARCH=arm64 CC=clang LD=ld.lld CLANG_TRIPLE=aarch64-linux-gnu- CROSS_COMPILE=aarch64-linux-android-

all:
    $(MAKE) -C $(KDIR) M=$(PWD) $(MAKE_ARGS) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) $(MAKE_ARGS) clean

hello.c

#include &lt;linux/module.h&gt;
static int __init hello_init(void) {
    pr_info("Test: Hello, world!\n");
    return 0;
}
static void __exit hello_exit(void) {
    pr_info("Test: Goodbye, world!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

KDIR 是编译好的头文件目录,如果是新开的终端要重新添加 PATH, 将以上两个文件放到同一个目录下 make 即可

完事收工!


[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

最后于 20小时前 被拉不利多编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (1)
雪    币: 1538
活跃值: (4123)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
2
学到了哈哈
9小时前
0
游客
登录 | 注册 方可回帖
返回