基于内核秘钥保留服务的ELF文件签名程序和验签模块

基于内核秘钥保留服务的ELF文件签名程序和验签模块

https://download.csdn.net/download/qq_42584874/85011973
解决方案的核心思想是,以 二进制文件处理程序 的形式,实现一个 ELF 签名验证模块 (binfmt_elf_signature_verification),并将该模块注册到链表中 ELF 处理模块 (binfmt_elf) 之前。在该模块中,实现签名验证的逻辑。如果签名验证通过,则返回 -ENOEXEC 错误码,使得内核能够继续遍历链表,直到调用真正的 ELF 处理模块;如果签名验证不通过,则直接返回其它错误码,使得内核不再继续调用真正的 ELF 处理模块。

在这个 ELF 数字签名验证模块中,需要进行的工作主要有三点:

  1. 格式检查 - 判断该二进制文件是否符合 ELF 格式,跳过所有非 ELF 格式的文件
  2. 自身签名验证 - 如果文件符合 ELF 格式,则按 ELF 格式取出其中附带的数字签名并验证
  3. 依赖签名验证 - 如果 ELF 文件依赖其它 (共享对象) 文件,则对其所有的依赖进行数字签名验证

用户可以在内核启动后,通过 insmod 命令动态装载这个模块,通过 rmmod 命令动态卸载这个模块。当用户准备装载该模块时,内核中的二进制处理模块链表已经初始化完成了。由于模块被插入链表的方式只有从链表头插入和从链表尾插入,而插入到链表尾部会导致该模块的 load_binary() 函数无法在系统内置的 binfmt_elf 模块之前被执行。因此只能在链表的头部插入这个处理模块,即使用 insert_binfmt()。模块装载后,内核的二进制处理模块链表如下图所示:

ELF 签名验证模块被内核装载

ELF 签名验证模块可能返回的错误原因:

  • 内核中缺少用于签名验证的密钥
  • ELF 文件中没有附带数字签名
  • ELF 文件中附带了数字签名,但其中的内容无法通过签名验证
  • ELF 文件格式正确但内容损坏
  • ELF 文件所依赖共享对象的签名验证失败
  • 内核运行时的一些不可预知错误 (如动态分配内存失败)

非 ELF 格式的二进制文件 (如 shell 脚本) 将无法通过 binfmt_elf_signature_verification 模块的 ELF 格式检查,从而返回 -ENOEXEC,由之后其它的二进制文件处理模块进行处理。

ELF签名

首先,需要安装签名程序依赖的库,发行版的LInux可以通过外网源安装

$ sudo apt install libssl-dev

这是elf签名程序编译前的所有程序extra_system_key.crt是用于签名的公钥,extra_system_key.pem是私钥

image-20220320171051741

由于自行构建得到的 elf-sign 也是一个 ELF 程序,因此,在它可以用于对其它 ELF 文件进行签名之前,其自身必须先被签名,否则内核将拒绝执行这个 ELF 程序。通过执行build.sh可以快速安装并对elf-signed进行签名。

image-20220320171457672

签名程序的使用方法

elf-sign 的参数含义:

  1. hash-algo - 摘要算法 (可以选用其它 内核支持的摘要算法
  2. key - 存放用于签名的私钥的文件路径
  3. x509 - 存放用于签名的公钥证书的文件路径
  4. elf-file - 待签名的目标 ELF 文件
  5. dest-file (可选) - 签名后的输出文件名
 ./elf-signed sha256 extra_system_key.pem extra_system_key.crt  test/hello  hellosign

image-20220320171932389

检验是否签名成功

readefl -S对边签名前后的程序可以看到签名后sectiond中多了text_sign

image-20220320171649948

image-20220320171702431

验签模块

Linux 密钥保留服务

Linux 密钥保留服务 (Linux key retention service) 是在 Linux 2.6 中引入的,它的主要意图是在 Linux 内核中缓存身份验证数据。远程文件系统和其他内核服务可以使用这个服务来管理密码学、身份验证标记、跨域用户映射和其他安全问题。它还使 Linux 内核能够快速访问所需的密钥,并可以用来将密钥操作(比如添加、更新和删除)委托给用户空间。

Root 用户可以用过 proc 文件系统查看内核中的密钥:

$ cat /proc/keys
125ce30c I------     1 perm 1f030000     0     0 keyring   .dns_resolver: empty
155ea96d I------     1 perm 1f030000     0     0 keyring   .id_resolver: empty
178570f1 I------     1 perm 1f0b0000     0     0 keyring   .builtin_trusted_keys: 1
1ab86c77 I------     1 perm 1f030000     0     0 asymmetri sforshee: 00b28ddf47aef9cea7: X509.rsa []
1abf10d6 I--Q---     1 perm 1f3f0000     0 65534 keyring   _uid_ses.0: 1
390c087a I------     1 perm 1f0b0000     0     0 keyring   .builtin_regdb_keys: 1
3e099031 I--Q---     2 perm 1f3f0000     0 65534 keyring   _uid.0: empty
3e22b50c I------     1 perm 1f030000     0     0 asymmetri WatchDog: ELF verification: 7e0e1ac946e5350460497ba611a475534c9c3ec4: X509.rsa 4c9c3ec4 []

上述信息显示了公钥的序号 (Serial number)、类型 (key-ring/asymmetry/…)、状态、过期时间、描述等信息。

在编译内核时通过配置 CONFIG_SYSTEM_TRUSTED_KEYS 选项,引用 PEM 格式的证书文件,就能够在内核的系统密钥环 (.system_keyring) 上添加额外的 X.509 公钥证书。关于该编译选项的说明如下:

  config SYSTEM_TRUSTED_KEYS
	string "Additional X.509 keys for default system keyring"
	depends on SYSTEM_TRUSTED_KEYRING
	help
	  If set, this option should be the filename of a PEM-formatted file
	  containing trusted X.509 certificates to be included in the default
	  system keyring. Any certificate used for module signing is implicitly
	  also trusted.NOTE: If you previously provided keys for the system keyring in the
  form of DER-encoded *.x509 files in the top-level build directory,
  those are no longer used. You will need to set this option instead.

访问系统内置密钥进行签名验证

首先,我们对 Linux 内核中已有的 内核模块签名 验证机制的代码进行了分析。在内核源代码目录 certs/system_keyring.c 中,定义了内核内置的受信密钥:

static struct key *builtin_trusted_keys;

但由于这个变量没有被声明为 extern,因此无法在其它内核代码中直接引用这个变量。但是在这个源文件中,开放了 verify_pkcs7_signature() 函数,使得其它内核代码能够通过这个函数,间接使用内置密钥环的签名验证功能:

/**
 * verify_pkcs7_signature - Verify a PKCS#7-based signature on system data.
 * @data: The data to be verified (NULL if expecting internal data).
 * @len: Size of @data.
 * @raw_pkcs7: The PKCS#7 message that is the signature.
 * @pkcs7_len: The size of @raw_pkcs7.
 * @trusted_keys: Trusted keys to use (NULL for builtin trusted keys only,
 *					(void *)1UL for all trusted keys).
 * @usage: The use to which the key is being put.
 * @view_content: Callback to gain access to content.
 * @ctx: Context for callback.
 */
int verify_pkcs7_signature(const void *data, size_t len,
			   const void *raw_pkcs7, size_t pkcs7_len,
			   struct key *trusted_keys,
			   enum key_being_used_for usage,
			   int (*view_content)(void *ctx,
					       const void *data, size_t len,
					       size_t asn1hdrlen),
			   void *ctx)
{
...

在内核代码中,通过 #include <linux/verification.h> 使用该函数时,输入 签名数据被签名数据缓冲区内存地址缓冲区长度,就能够使用内置密钥完成签名认证。因此,ELF 签名验证模块 只要能够从 ELF 文件中正确提取 PKCS #7 格式的签名数据,以及签名保护的目标数据,就可以通过这个函数验证数字签名是否正确。

验签模块的编译使用

首先使用make命令编译模块,然后insmod将模块加载到内核中

kylin@local:~/桌面/sign/signelf/elfsign-modules$ ls
binfmt_elf_signature_verification.c  Makefile
kylin@local:~/桌面/sign/signelf/elfsign-modules$ make 
make -C /lib/modules/5.4.159/build M=/home/kylin/桌面/sign/signelf/elfsign-modules modules
make[1]: 进入目录“/home/kylin/kernel/build/linux-stable”
  CC [M]  /home/kylin/桌面/sign/signelf/elfsign-modules/binfmt_elf_signature_verification.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC [M]  /home/kylin/桌面/sign/signelf/elfsign-modules/binfmt_elf_signature_verification.mod.o
  LD [M]  /home/kylin/桌面/sign/signelf/elfsign-modules/binfmt_elf_signature_verification.ko
make[1]: 离开目录“/home/kylin/kernel/build/linux-stable”
kylin@local:~/桌面/sign/signelf/elfsign-modules$ sudo  insmod binfmt_elf_signature_verification.ko 
[sudo] kylin 的密码:
kylin@local:~/桌面/sign/signelf/elfsign-modules$ lsmod  |grep binfmt
binfmt_elf_signature_verification    16384  0
binfmt_misc            24576  1

实现效果

为了避免对系统产生影响,跳过了特定目录下的elf程序和动态库的签名验证具体如下:

image-20220320172512632

在加载模块后如果程序运行失败message中会有以下保存

image-20220320172818534

程序中会有以下提示

image-20220320172754601

签名成功的则正常执行程序

image-20220320173352786

生成秘钥方法

秘钥依赖与openssl需要安装libssl-dev

$ sudo apt install libssl-dev

openssl.cnf

[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
prompt = no
string_mask = utf8only
x509_extensions = myexts

[ req_distinguished_name ]
O = CCCC
CN = THE NEW SING KEY
emailAddress = 493466243@qq.com

[ myexts ]
basicConstraints=critical,CA:FALSE
keyUsage=digitalSignature
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid

创建公钥和私钥

openssl req -new -newkey rsa:2048         -sha256 -nodes         -config openssl.cnf         -keyout extra_system_key.pem      -out extra_system_key.csr

需要用/proc/keys下的一把秘钥对其进行签名

    openssl x509 -req -in extra_system_key.csr         -CA system_key.pem          -CAkey system_key.pem         -set_serial 1 -days 3650         -extfile openssl.cnf -extensions myexts         -out extra_system_key.crt

内核安全特性的秘钥生成机制有几种

  • 在可重编内核的前提下,修改CONFIG_SYSTEM_TRUSTED_KEYS指定的key文件的内容,将更多的system trusted key级联到该key文件中即可。

  • 在不可重编内核的前提下,借用CONFIG_SYSTEM_EXTRA_CERTIFICATE或CONFIG_SECONDARY_TRUSTED_KEYRING可以添加额外的system trusted key。第一种方法要求内核在build时要事先保留足够大的空间。如果该空间的总容量本身就很小,那只能精简要导入的X.509证书的内容,删除掉不再使用的system trusted key;第二种方法是从用户空间直接向secondary trusted keyring中导入额外的system trusted key,不过这个key必须事先由已经存在于builtin trusted keyring或secondary trusted keyring中的某一把key签过的才可以导入。

  • 内核引导时通过nsert-sys-cert向秘钥保留区打入秘钥

我使用的是重新编译内核

首先拷贝原来内核的config文件修改

$ cp /boot/config-5.4.18-35-generic ./.config
vim ./.config修改以下两个参数
CONFIG_SYSTEM_TRUSTED_KEYRING=y
CONFIG_SYSTEM_TRUSTED_KEYS="<PATH_TO_CERT>/kernel_key.pem"
$ make menuconfig 点击load 再save 再exit
$ make-kpkg --initrd -j4 kernel_image kernel_headers

image-20220320181346436

在..下生成2个文件
linux-headers-5.4.159_5.4_amd64.debb
linux-image-5.4.159_5.4_amd64.deb
$ dpkg -i *.deb 安装内核
重启选择该内核进入系统

image-20220320181743794

cat /proc/keys 
 WatchDog: ELF verification就是刚刚加入内核的秘钥

image-20220320180707005

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ELF文件(Executable and Linkable Format,可执行和可链接格式)是一种能够在不同操作系统上共享和执行的二进制文件格式。ELF文件的头部和程序头表是ELF文件中的两个关键部分。 ELF文件的头部位于文件的开头,包含了描述整个ELF文件的基本信息。具体包括标识字段、目标机器体系结构、文件类型、入口点地址、程序头表偏移地址等重要信息。标识字段用来识别文件ELF标志和版本。目标机器体系结构字段标识了目标操作系统的硬件要求,例如x86、ARM等。文件类型字段表示了ELF文件的类型,如可执行文件、共享目标文件、动态链接库等。入口点地址标识了程序运行的起始地址。程序头表偏移地址则指向ELF文件中的程序头表的位置, 程序头表包含了更加详细的段信息。 程序头表位于ELF文件的头部之后,包含了多个描述ELF文件中各个段(section)的表项。每个表项包含了段的类型、段在文件中的偏移地址、内存中的虚拟地址、段的大小等信息。段是ELF文件的基本组成单元,如代码段、数据段、bss段。这些段在ELF文件中包含了可执行代码、全局变量、静态数据等。程序头表通过这些表项的信息告诉操作系统如何加载和运行ELF文件。 通过解析ELF文件的头部和程序头表,我们可以获取关于ELF文件的基本信息和段的详细信息。这些信息对于调试、加载、执行ELF文件都非常重要。因此,深入理解ELF文件的头部和程序头表对于理解操作系统和二进制文件的运行机制具有重要意义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值