文章总结: 本文记录了在Honor50SE手机上绕过内核驱动签名校验的完整实践过程,作者通过提取boot分区镜像、对照内核源码分析load_module函数、使用IDA定位并修补内联的签名检查汇编指令,最终成功加载未签名驱动,并提供了编译内核驱动的具体方法。 综合评分: 85 文章分类: 逆向分析,实战经验,安全开发,移动安全
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
|| (v10 = (constchar **)(v6 + 16), v9 = *(_QWORD *)(v6 + 16), v9 < 0x1D)
|| *(_QWORD *)(v7 + v9 - 28) ^ 0x20656C75646F4D7ELL
| *(_QWORD *)(v7 + v9 - 20) ^ 0x727574616E676973LL
| *(_QWORD *)(v7 + v9 - 12) ^ 0x646E657070612065LL
| *(unsignedint *)(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 = (constchar *)(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, 没有函数调用就没有弹栈寄存器恢复现场,所以只好手动分析汇编,看看哪些寄存器会被后面的代码用到。
机器码如下:
好在编译器没给我更上强度,在一番试错后也是终于顺利进入系统,只需做如下修改。
三、回刷内核
保持kernel文件名不变放回原来的位置打包
./magiskboot repack ./boot.img
得到new-boot.img后写入原分区
dd 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 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
上面的脚本PATH和defconfig按需修改就行,测试代码如下
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 <linux/module.h>
staticint __init hello_init(void) {
pr_info("Test: Hello, world!\n");
return 0;
}
staticvoid __exit hello_exit(void) {
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 内核加载未签名驱动的一次实践》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论