Linux内核ELF文件签名验证机制的设计与实现(C/C++代码实现)
一、引言:为何需要ELF签名验证?
在Linux系统中,ELF(Executable and Linkable Format)是最核心的可执行文件与共享库格式。默认情况下,内核仅校验ELF文件的格式合法性(如魔数、架构匹配),不验证文件的完整性与来源合法性——这意味着攻击者可通过篡改ELF文件(如修改代码段)、植入恶意代码,或运行未授权的ELF程序,突破系统安全防线。
为解决这一问题,本文解析的ELF签名验证内核模块(binfmt_elf_signature_verification
),通过内核层的“前置拦截+签名校验”机制,强制验证ELF文件及其依赖共享库(.so)的签名完整性。只有通过验证的文件才能被内核执行,从根源上阻断恶意ELF的运行,构建Linux系统的“可信执行入口”。
二、核心功能:模块到底做了什么?
该模块本质是一个Linux内核二进制格式(binfmt)处理器,通过注册到内核的binfmt机制,拦截所有ELF文件的执行请求,完成以下核心工作:
-
ELF文件筛选与跳过
先判断文件是否为ELF格式(校验ELF魔数、类型、架构),同时跳过系统关键路径的ELF(如/bin/
、/usr/
下的系统程序)——这是为了避免模块安装后,因系统自带ELF未签名导致系统崩溃,是“安全性与可用性”的权衡设计。 -
关键Section签名验证
仅针对ELF的核心Section(当前为.text
代码段,可扩展至.data
数据段)验证签名:ELF需额外包含对应“签名Section”(如.text_sig
),模块会加载原始Section数据与签名数据,通过内核加密接口验证完整性。 -
动态依赖库递归验证
若ELF是动态链接程序(依赖.so
库),模块会读取系统的/etc/ld.so.cache
(动态链接缓存),找到依赖库的绝对路径,再对每个.so
库重复上述签名验证流程,确保“主程序+依赖库”的全链路可信。 -
验证结果闭环
- 验证通过:返回
-ENOEXEC
,告知内核“当前模块不处理执行,交由原生ELF处理器(binfmt_elf
)完成后续加载”; - 验证失败(无签名/签名无效/依赖库未通过):返回错误码,内核直接阻断执行。
- 验证通过:返回
三、实现原理:从拦截到验证的全流程
模块的工作流程围绕“内核拦截→分层校验→可信链延伸”展开,无需修改内核原有ELF处理逻辑,仅通过复用系统机制实现轻量化集成:
1. 内核拦截:基于binfmt机制的前置接入
Linux内核通过binfmt(二进制格式)框架管理不同类型的可执行文件(如ELF、脚本、a.out)。每个格式对应一个struct linux_binfmt
结构体,其中load_binary
函数是“文件处理入口”。
模块的核心接入逻辑:
- 初始化时,通过
insert_binfmt
注册自定义的elf_signature_verification_format
结构体,将load_binary
指向模块的验证入口函数; - 当用户执行ELF文件时,内核会遍历所有binfmt handler,优先调用该模块的
load_binary
——实现“执行前先验证”的拦截效果; - 验证通过后,返回
-ENOEXEC
让内核继续遍历,最终由原生binfmt_elf
处理执行(如加载代码段、初始化进程空间)。
2. 分层校验:从格式到签名的层层过滤
模块采用“先过滤无效文件,再深度验证”的策略,减少不必要的计算开销:
(1)第一关:ELF格式合法性校验
先通过elf_format_validation
函数排除非目标文件:
- 校验ELF魔数(
ELFMAG
,即0x7f + "ELF"
),确认是ELF文件; - 校验ELF类型(仅处理
ET_EXEC
可执行文件、ET_DYN
共享库)、架构(elf_check_arch
,确保与内核架构匹配); - 校验关键字段(如
e_shstrndx
不能为SHN_UNDEF
,避免section信息缺失)。
(2)第二关:ELF Section与签名解析
通过ELF头部(elfhdr
)加载关键数据结构,为签名验证做准备:
- 加载Section头部表(
elf_shdr
):通过load_elf_shdrs
读取ELF的section描述信息(如section名称、偏移、大小); - 加载Section字符串表(
shstrtab
):存储所有section的名称(如.text
、.text_sig
),用于匹配“原始section与签名section”; - 匹配签名section:通过
scn_name_match
函数,找到名称后缀为_sig
的section(如.text
对应.text_sig
),确保两者前缀一致、长度差等于_sig
的长度。
(3)第三关:PKCS#7签名验证
模块复用内核原生的verify_pkcs7_signature
函数(内核自带的PKCS#7签名验证接口),完成核心安全校验:
- 加载原始section数据(如
.text
的代码数据)与签名section数据(如.text_sig
的PKCS#7签名数据); - 调用内核接口,基于内置内核密钥环(Key Ring)中的根证书验证签名——密钥环存储在kernel space,无法被用户空间篡改,确保“验证根”的可信性;
- 若验证通过,更新“section校验清单”(
scn_checklist
),标记该section已通过验证。
(4)第四关:依赖库递归验证
动态链接的ELF需验证所有依赖.so
,模块通过复用系统缓存提升效率:
- 加载动态链接信息:从
.dynamic
section(存储动态链接参数)和.dynstr
section(存储依赖库名称,如libcrypto.so.1.1
)中提取依赖库列表; - 复用
ld.so.cache
:通过init_so_caches
加载系统的/etc/ld.so.cache
(动态链接器的缓存文件,存储.so
名称与绝对路径的映射),避免遍历文件系统查找.so
,减少IO开销; - 递归验证:对每个依赖库,创建新的
linux_binprm
结构体(复用ELF验证逻辑),重复上述所有校验步骤,确保依赖链无篡改。
3. 结果处理:可信则放行,不可信则阻断
验证流程结束后,模块通过两个关键检查确保安全性:
- 清单检查:通过
lookup_checklist
确认所有需验证的section(如.text
)均已通过签名校验,避免遗漏; - 依赖链检查:确保所有
.so
库均通过验证,无未授权依赖。
最终返回结果:
- 全量通过:返回
-ENOEXEC
,内核调用原生ELF处理器执行文件; - 任一失败:返回错误码(如
-ENODATA
无签名、-EBADMSG
签名无效),内核直接终止执行,并通过dmesg
输出日志供排查。
四、设计思路:权衡与复用的艺术
struct scn_checklist {unsigned char s_name[8]; int s_nlen; int s_check;
};struct ld_cache_header {char magic[sizeof(LD_CACHE_MAGIC_OLD) - 1];unsigned int n_libs;
};struct ld_cache_entry {int e_flags; unsigned int e_key; unsigned e_value;
};struct ld_so_cache {char *l_buf; loff_t l_len; unsigned int l_entrynum; struct ld_cache_entry *l_entries; char *l_strtab;
};
...
int init_so_caches(struct ld_so_cache *so_cache);
char *get_so_file_path(struct ld_so_cache *so_cache, char *so_key);
unsigned char *load_elf_sdata(struct elf_shdr *elf_shdata,struct file *elf_file);
int verify_scn_signature(unsigned char *scn_data,int scn_data_len, unsigned char *sig_scn_data,int sig_scn_data_len);
int elf_signature_verification(struct linux_binprm *bprm,struct ld_so_cache *so_cache);
int elf_format_validation(struct linux_binprm *bprm);
int load_elf_signature_verification_binary(struct linux_binprm *bprm);static struct linux_binfmt elf_signature_verification_format = {.module = THIS_MODULE,.load_binary = load_elf_signature_verification_binary,
};static int __init init_elf_signature_verification_binfmt(void)
{insert_binfmt(&elf_signature_verification_format);return 0;
}static void __exit exit_elf_signature_verification_binfmt(void)
{unregister_binfmt(&elf_signature_verification_format);
}module_init(init_elf_signature_verification_binfmt);
module_exit(exit_elf_signature_verification_binfmt);
...
If you need the complete source code, please add the WeChat number (c17865354792)
默认情况下,Makefile中的KDIR值指向当前正在运行的内核的源代码目录,内核模块将安装到该目录下。
KDIR := /lib/modules/$(shell uname -r)/build
此外,您可以通过覆盖KDIR变量,在另一个内核上为某个内核构建模块。假设您的目录是linux-kernel-elf-sig-verify下的一个子模块,其目录结构类似于linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module,那么您可以将KDIR修改为:
KDIR := …/
然后,使用make
命令构建内核模块:
$ make
make -C /lib/modules/5.3.0-53-generic/build M=/home/mrdrivingduck/Desktop/linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module modules
make[1]: Entering directory '/usr/src/linux-headers-5.3.0-53-generic'CC [M] /home/mrdrivingduck/Desktop/linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module/binfmt_elf_signature_verification.oBuilding modules, stage 2.MODPOST 1 modulesCC /home/mrdrivingduck/Desktop/linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module/binfmt_elf_signature_verification.mod.oLD [M] /home/mrdrivingduck/Desktop/linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module/binfmt_elf_signature_verification.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.3.0-53-generic'
binfmt_elf_signature_verification.ko是内核模块。您可以验证此模块的基本信息:
$ modinfo binfmt_elf_signature_verification.ko
filename: /home/mrdrivingduck/Desktop/linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module/binfmt_elf_signature_verification.ko
alias: fs-binfmt_elf_signature_verification
version: 1.0
description: Binary handler for verifying signature in ELF section
...
license: Dual MIT/GPL
srcversion: 24C778301DE1DD13C1BB3CF
depends:
retpoline: Y
name: binfmt_elf_signature_verification
vermagic: 5.3.0-53-generic SMP mod_unload
通过insmod命令安装模块:
$ sudo insmod binfmt_elf_signature_verification.ko
使用rmmod命令移除模块:
$ sudo rmmod binfmt_elf_signature_verification
如果模块安装成功,您将无法再运行未签名的ELF文件。通过dmesg命令,您可以查看更多信息。
自己生成密钥, 并使用openssl工具生成密钥:
$ cd certs
$ openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 \-config x509.genkey -outform PEM-out kernel_key.pem -keyout kernel_key.pem
Generating a RSA private key
........+++++
........................................+++++
writing new private key to 'kernel_key.pem'
-----
$ cd ..
该模块的设计核心是“最小侵入性、高兼容性、平衡安全与效率”,具体体现在以下几点:
1. 基于内核现有机制,避免重复造轮子
模块没有修改内核原生ELF处理逻辑,而是通过binfmt机制“外挂”验证功能——这种设计的优势在于:
- 兼容性强:可适配不同Linux内核版本(只要binfmt接口不变),无需重新编译内核;
- 维护成本低:内核ELF处理逻辑更新时,模块无需同步修改;
- 安全可信:复用内核的
verify_pkcs7_signature
和密钥环机制,避免自定义加密逻辑引入安全漏洞。
2. 分阶段验证:效率与安全的平衡
模块没有对“整个ELF文件”进行签名验证,而是采用“格式→关键section→依赖库”的分层策略:
- 先过滤非ELF文件,减少无效计算;
- 仅验证关键section(如
.text
),而非整个文件——既保证代码完整性(.text是执行核心),又减少数据加载与校验的开销; - 复用
ld.so.cache
查找依赖库,避免IO密集型的路径搜索,提升验证效率。
3. 可用性优先:系统关键ELF的跳过机制
模块默认跳过/bin/
、/usr/
等系统路径的ELF,这是“安全性与可用性”的必要权衡:
- 多数Linux发行版的系统程序未内置签名,若强制验证会导致系统无法启动;
- 该设计允许模块“即插即用”,用户可根据需求扩展验证范围(如后续为系统ELF签名后,移除跳过逻辑)。
4. 可扩展性设计:支持更多场景
模块预留了明确的扩展接口:
- 校验section可扩展:
scn_checklist
结构体可新增.data
、.rodata
等section,只需添加一行配置; - 签名算法可扩展:基于内核
verify_pkcs7_signature
,若需支持其他签名标准(如ED25519),仅需扩展内核加密接口适配; - 依赖查找可扩展:当前依赖
ld.so.cache
,未来可支持缓存外的.so
查找(如遍历LD_LIBRARY_PATH
)。
五、相关领域知识点:理解模块的技术背景
要深入掌握该模块,需了解以下Linux内核与安全领域的核心概念:
1. Linux内核binfmt机制
内核通过struct linux_binfmt
管理可执行文件格式,每个格式对应一个“处理器”。执行文件时,内核调用search_binary_handler
遍历所有处理器,直到找到能处理该格式的load_binary
函数——这是模块能“拦截ELF执行”的核心原理。
2. ELF文件核心结构
ELF文件的“签名验证”依赖其结构化设计:
- ELF头部(elfhdr):存储文件类型(可执行/共享库)、架构、section头部表偏移等全局信息;
- Section头部表(elf_shdr):每个entry对应一个section的描述(名称索引、类型、偏移、大小);
- 关键Section:
.text
:代码段,存储可执行指令,是签名验证的核心对象;.dynamic
:动态链接信息,存储依赖库列表(DT_NEEDED
);.dynstr
:动态链接字符串表,存储依赖库名称(如libc.so.6
);_sig
后缀Section:自定义签名段,存储对应section的PKCS#7签名数据。
3. 动态链接与ld.so.cache
- 动态链接器(ld.so):负责在程序启动时加载依赖的
.so
库,解析符号; - ld.so.cache:ld.so的缓存文件,由
ldconfig
生成,存储.so
名称与绝对路径的映射——模块复用该缓存,避免重复查找文件系统,提升依赖库定位效率。
4. 内核密钥环(Key Ring)
Linux内核中的密钥管理机制,用于安全存储加密密钥、证书等敏感数据。密钥环分为不同命名空间(如user
、system
、kernel
),模块使用的“内置密钥”存储在kernel
命名空间,仅内核可访问,确保验证用的根证书不被用户空间篡改,是“可信根”的核心保障。
5. PKCS#7签名标准
一种密码消息语法(Cryptographic Message Syntax),常用于数字签名、数据加密等场景。内核的verify_pkcs7_signature
函数支持验证PKCS#7格式的签名,确保数据完整性与来源合法性——模块基于该标准,实现跨平台、标准化的签名验证。
六、总结与扩展方向
该ELF签名验证模块通过“内核拦截+分层校验+全链路可信”的设计,为Linux系统构建了一道“ELF执行安全闸门”,尤其适合嵌入式设备、安全敏感服务器等场景(需强制控制可执行文件来源)。
现有局限性
- 仅验证
.text
section,未覆盖.data
、.rodata
等关键数据段; - 依赖
ld.so.cache
,无法处理缓存外的.so
库(如自定义路径的.so
); - 跳过大量系统ELF,安全性未覆盖全系统(需后续为系统文件添加签名)。
可扩展方向
- 扩展校验范围:添加
.data
、.rodata
等section的验证,提升完整性保障; - 支持自定义密钥:允许用户通过内核接口动态导入根证书,而非硬编码内置;
- 集成SELinux/AppArmor:与现有强制访问控制机制联动,实现“签名验证+权限控制”的双重安全;
- 支持增量验证:对ELF文件的修改部分仅重新签名,无需全文件验证,提升效率。
通过该模块的设计与实现,我们可以看到:Linux内核安全功能的开发,往往需要在“安全性、可用性、效率”之间找到平衡,同时最大化复用内核现有机制,以降低复杂度与安全风险。