Android内核加载未签名驱动的一次实践

admin 2026-03-30 00:03:13 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文记录了在Honor50SE手机上绕过内核驱动签名校验的完整实践过程,作者通过提取boot分区镜像、对照内核源码分析load_module函数、使用IDA定位并修补内联的签名检查汇编指令,最终成功加载未签名驱动,并提供了编译内核驱动的具体方法。 综合评分: 85 文章分类: 逆向分析,实战经验,安全开发,移动安全


cover_image

Android 内核加载未签名驱动的一次实践

拉不利多 拉不利多

看雪学苑

2026年3月26日 17:59 上海

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

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

一、提取内核文件

查看当前系统使用的槽位,我的是b

getprop ro.boot.slot_suffix

找到内核文件所在分区,在我这里boot_b对应的是/dev/block/sdc64

ls -al /dev/block/by-name/ | grep boot

复制分区镜像到当前目录下

dd if=/dev/block/sdc64 of=./boot.img

在系统里找到magiskboot文件用于解包boot.img, 我的是在/data/adb/magisk/magiskboot,如果是apk文件就从中提取libmagiskboot.so

./magiskboot unpack ./boot.img
# 或
./libmagiskboot.so unpack ./boot.img

得到的kernel文件就是本篇的主角。

二、对照内核源代码修补

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

staticintload_module(struct load_info *info, constchar __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

        ... 以下省略 ...
}
staticintmodule_sig_check(struct load_info *info, int flags){
int err = -ENOKEY;
constunsignedlong markerlen = sizeof(MODULE_SIG_STRING) - 1;
constvoid *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指令就能收工啦。

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

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

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

机器码如下:

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

三、回刷内核

保持kernel文件名不变放回原来的位置打包

./magiskboot repack ./boot.img

得到new-boot.img后写入原分区

dd&nbsp;if=./new-boot.img of=/dev/block/sdc64

重启手机,如果失败了进不去系统,那就得用fastboot刷回原镜像

fastboot flash boot boot.img

#

四、编译内核驱动

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

  • gcc: https://github.com/LineageOS/android_prebuilts_gcc_linux-x86_aarch64_aarch64-linux-android-4.9
  • clang: https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+archive/refs/heads/android12-release/clang-r383902.tar.gz

编译时最好是用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&nbsp;PATH="/home/ubuntu/clang/bin:/home/ubuntu/gcc/bin:$PATH"
cd&nbsp;Code_Opensource/kernel/
make&nbsp;$MAKE_ARGS&nbsp;merge_full_k6877v1_64_defconfig
make&nbsp;$MAKE_ARGS&nbsp;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)&nbsp;-C&nbsp;$(KDIR)&nbsp;M=$(PWD)&nbsp;$(MAKE_ARGS)&nbsp;modules

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

hello.c

#include&nbsp;<linux/module.h>
staticint&nbsp;__init&nbsp;hello_init(void)&nbsp;{
pr_info("Test: Hello, world!\n");
return&nbsp;0;
}
staticvoid&nbsp;__exit&nbsp;hello_exit(void)&nbsp;{
pr_info("Test: Goodbye, world!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

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

#

#

看雪ID:拉不利多

https://bbs.kanxue.com/user-home-1066062.htm

*本文为看雪论坛优秀文章,由 拉不利多 原创,转载请注明来自看雪社区

往期推荐

安卓逆向基础知识之frida Hook

2025 强网杯和强网拟态部分题解

在逆向分析方面-unidbg真的适合 MCP 吗?

AI静态分析,内核模块隐藏 Frida 特征,绕过linker私有结构遍历崩溃链

某安全so库深度解析

球分享

球点赞

球在看

点击阅读原文查看更多


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:看雪学苑 拉不利多 拉不利多《Android 内核加载未签名驱动的一次实践》

评论:0   参与:  0