当前位置: 首页 > backend >正文

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文件的执行请求,完成以下核心工作:

  1. ELF文件筛选与跳过
    先判断文件是否为ELF格式(校验ELF魔数、类型、架构),同时跳过系统关键路径的ELF(如/bin//usr/下的系统程序)——这是为了避免模块安装后,因系统自带ELF未签名导致系统崩溃,是“安全性与可用性”的权衡设计。

  2. 关键Section签名验证
    仅针对ELF的核心Section(当前为.text代码段,可扩展至.data数据段)验证签名:ELF需额外包含对应“签名Section”(如.text_sig),模块会加载原始Section数据与签名数据,通过内核加密接口验证完整性。

  3. 动态依赖库递归验证
    若ELF是动态链接程序(依赖.so库),模块会读取系统的/etc/ld.so.cache(动态链接缓存),找到依赖库的绝对路径,再对每个.so库重复上述签名验证流程,确保“主程序+依赖库”的全链路可信。

  4. 验证结果闭环

    • 验证通过:返回-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内核中的密钥管理机制,用于安全存储加密密钥、证书等敏感数据。密钥环分为不同命名空间(如usersystemkernel),模块使用的“内置密钥”存储在kernel命名空间,仅内核可访问,确保验证用的根证书不被用户空间篡改,是“可信根”的核心保障。

5. PKCS#7签名标准

一种密码消息语法(Cryptographic Message Syntax),常用于数字签名、数据加密等场景。内核的verify_pkcs7_signature函数支持验证PKCS#7格式的签名,确保数据完整性与来源合法性——模块基于该标准,实现跨平台、标准化的签名验证。

六、总结与扩展方向

该ELF签名验证模块通过“内核拦截+分层校验+全链路可信”的设计,为Linux系统构建了一道“ELF执行安全闸门”,尤其适合嵌入式设备、安全敏感服务器等场景(需强制控制可执行文件来源)。

现有局限性

  1. 仅验证.text section,未覆盖.data.rodata等关键数据段;
  2. 依赖ld.so.cache,无法处理缓存外的.so库(如自定义路径的.so);
  3. 跳过大量系统ELF,安全性未覆盖全系统(需后续为系统文件添加签名)。

可扩展方向

  1. 扩展校验范围:添加.data.rodata等section的验证,提升完整性保障;
  2. 支持自定义密钥:允许用户通过内核接口动态导入根证书,而非硬编码内置;
  3. 集成SELinux/AppArmor:与现有强制访问控制机制联动,实现“签名验证+权限控制”的双重安全;
  4. 支持增量验证:对ELF文件的修改部分仅重新签名,无需全文件验证,提升效率。

通过该模块的设计与实现,我们可以看到:Linux内核安全功能的开发,往往需要在“安全性、可用性、效率”之间找到平衡,同时最大化复用内核现有机制,以降低复杂度与安全风险。

http://www.xdnf.cn/news/18636.html

相关文章:

  • 源滚滚React消息通知框架v1.0.2使用教程
  • 《支付回调状态异常的溯源与架构级修复》
  • 【RAGFlow代码详解-3】核心服务
  • Linux驱动之DMA(三)
  • ubuntu中网卡的 IP 及网关配置设置为永久生效
  • Maxwell学习笔记
  • 8月精选!Windows 11 25H2 【版本号:26200.5733】
  • 从技术精英到“芯”途末路:一位工程师的沉沦与救赎
  • IC验证 APB 项目(二)——框架结构(总)
  • 项目编译 --- 基于cmake ninja编译 rtos项目
  • COSMIC智能化编写工具:革命性提升软件文档生成效率
  • 20.13 ChatGLM3 QLoRA微调实战:3步实现高效低资源训练
  • Shell Case 条件语句详解
  • 数据挖掘 4.8 评估泛化能力
  • k8s原理及操作
  • Go语言环境安装
  • Spring面试题及详细答案 125道(16-25) -- 核心概念与基础2
  • Jwt令牌设置介绍
  • c++基础知识入门
  • Https之(三)TLS双向认证
  • 打响“A+H”双重上市突围战,云天励飞实力如何?
  • 云原生俱乐部-RH294知识点归纳(3)
  • [滑动窗口]1493. 删掉一个元素以后全为 1 的最长子数组
  • 今天学习计算机网格技术的TCP,UDP以及OSPF
  • 【AI智能体】Dify 搭建业务单据差异核对助手实战详解
  • 【Spring Cloud 微服务】3.智能路由器——深入理解与配置负载均衡
  • 【数据结构】从基础到实战:全面解析归并排序与计数排序
  • 在 Docker 容器中查看 Python 版本
  • SpringBoot的学生学习笔记共享系统设计与实现
  • SO_REUSEADDR