【基于LSM的ELF文件安全模块设计】参考
《基于LSM的ELF文件安全模块设计文档》
一、设计目标
本设计致力于通过 Linux 安全模块(LSM)构建一个强大而严密的安全防护体系,以实现对 ELF 文件(涵盖可执行文件和动态链接库)的绝对严格的合法性和完整性检查。其核心目标在于确保系统在加载和运行各类 ELF 文件时,能够有效抵御来自未知来源或被恶意篡改的文件所可能带来的严重安全威胁,如数据泄漏、系统崩溃等,进而全力维护系统的高度稳定性和数据的绝对安全性。通过精心设计的安全机制,本研究旨在为系统提供全方位的保护,使得每一个 ELF 文件在进入系统执行或被加载的过程中都经过严格的审查,确保其合法性与完整性无懈可击。
二、设计思路
(一)总体架构
本模块采用分层架构设计,主要由内核态的 LSM 钩子函数和相关辅助函数,以及用户态管理程序组成。内核态部分犹如坚固的核心安全堡垒,在关键系统调用之际,迅速拦截并进行深入细致的 ELF 文件检查。用户态程序则作为高效的管理配置层,用于辅助管理配置文件和查看相关日志等操作,为管理员精心打造便捷的操作接口,共同构建起一个完整的安全防护体系。
(二)合法性验证
1. 来源验证
- 设计方法:在内核模块中巧妙设计一个高效的数据结构,专门用于维护受信任的路径列表或者源仓库标识列表。采用诸如哈希表或者红黑树等先进的数据结构,能够显著提高查找效率。当系统加载 ELF 文件时,通过精准的系统调用获取其文件路径,接着运用精心设计的查找算法与受信任列表进行快速而准确的比对。
- 处理策略:一旦发现文件路径不在受信任列表中,可根据系统安全策略采取一系列有力措施。例如,进行详细的日志记录,涵盖文件路径、精确的时间戳以及操作类型等丰富信息;向管理员发送及时的警告提示,可以通过邮件、系统消息等多种方式,确保管理员能够第一时间知晓潜在风险;或者直接果断拒绝加载该文件,从源头杜绝安全隐患。
2. 签名验证
- 设计方法:积极引入成熟可靠的公钥加密技术用于签名验证。在系统初始化阶段,从安全的配置文件或者密钥存储区域精心加载受信任的公钥,并将其妥善存储在内存中的安全区域,确保公钥的安全性和可用性。当 ELF 文件加载时,准确提取文件中的签名信息,可以将签名存储在文件特定位置或者通过特定的文件元数据获取。然后,运用对应的公钥和相应的加密库函数进行严格的签名验证。
- 处理策略:如果签名验证未能通过,坚决拒绝加载该文件,并详细记录验证失败的关键信息,包括文件路径、所采用的签名算法以及具体的验证结果等,为管理员进行深入排查和细致分析提供有力依据。
(三)完整性验证
1. 哈希计算
- 设计方法:精心挑选一种安全可靠的哈希算法,如 SHA - 256,用于准确计算 ELF 文件的哈希值。在文件首次被确认为合法时,例如通过上述严格的合法性验证以及管理员的手动确认等综合方式,将精心计算得到的哈希值安全地存储在可靠的数据库或者配置文件中。每次加载该文件时,重新计算其哈希值,并与存储的哈希值进行细致比对。
- 处理策略:倘若哈希值不匹配,这表明文件极有可能被篡改,此时应果断拒绝加载该文件,并详细记录相关的哈希值不匹配信息,包括文件路径、原始哈希值、新计算的哈希值等,以便后续进行深入分析和追踪。对于大文件的哈希计算,采用高效的内存映射(mmap)等方式,并细致入微地处理可能出现的各种错误。
2. 文件大小检查
- 设计方法:在文件首次被确认为合法时,准确记录其文件大小信息。可以将文件大小信息与文件路径等其他重要元数据一起妥善存储在数据库或者配置文件中。在加载文件时,通过系统调用精准获取文件的实际大小,并与存储的大小信息进行严格比对。
- 处理策略:尽管文件大小并非绝对可靠的完整性指标,但当文件大小不匹配时,可将其作为一个重要的辅助检查手段。此时可以采取进一步的检查措施,如重新计算哈希值进行确认,或者向管理员发送明确的警告提示,及时告知文件大小出现异常情况,以便管理员采取相应措施。
(四)与 LSM 集成
1. 注册钩子函数
- 设计方法:深入钻研 LSM 框架的接口规范和机制,针对
execve
系统调用(用于执行可执行文件)和dlopen
系统调用(用于加载动态链接库)精准注册相应的钩子函数。在注册过程中,务必确保钩子函数的优先级和调用顺序完全符合系统安全要求,以保证在文件加载和执行的关键操作点能够及时触发安全检查,实现无缝的安全防护。 - 处理策略:当钩子函数被触发时,严格按照预先设计的安全检查流程,依次执行合法性和完整性检查操作,确保每一个环节都无懈可击。
2. 决策逻辑
- 设计方法:精心设计一套清晰明确的决策逻辑,根据合法性和完整性检查的结果来准确决定是否允许文件的加载和执行。可以灵活运用状态机或者决策树等先进的设计模式来实现复杂的决策逻辑,确保系统能够根据不同的检查结果做出正确的响应,实现精准的安全控制。
- 处理策略:如果文件顺利通过所有检查,允许操作继续进行;否则,返回适当的错误码给调用者,同时详细记录拒绝的原因,坚决阻止文件的加载或执行,确保系统安全。
三、详细设计
(一)数据结构设计
elf_file_info
结构体
- 设计目的:用于存储合法 ELF 文件的全面相关信息,以便在安全检查过程中进行快速比对和验证,确保每一个文件都经过严格审查。
- 结构定义:
struct elf_file_info {struct list_head list;const char *path;u8 hash[SHA256_DIGEST_SIZE];unsigned long size;// 可以添加更多信息,比如文件签名相关的字段,如果进行签名验证
};
- 白名单链表
- 设计目的:作为一个集中管理合法ELF文件信息的容器,方便快速查找和维护白名单中的文件记录。
- 定义方式:使用
LIST_HEAD(elf_file_whitelist)
定义一个链表头,用于存储所有合法的ELF文件信息结构体。
(二)函数设计
calculate_file_hash
函数- 功能描述:高效计算指定文件的哈希值,针对大文件采用内存映射方式以提高效率,并细致处理各种可能出现的错误,确保每一个 ELF 文件的哈希计算准确无误。
- 输入参数:文件路径
path
。 - 输出参数:计算得到的哈希值存储在
hash
数组中,返回值为错误码(0 表示成功,负数表示失败)。 - 实现细节:
- 对于小文件,沿用原有的读取方式:
- 首先打开文件,获取文件描述符。通过
filp_open
函数实现,若打开文件失败,将返回错误指针,可通过PTR_ERR
宏获取具体错误码并返回给调用者。
- 首先打开文件,获取文件描述符。通过
file = filp_open(path, O_RDONLY, 0); if (IS_ERR(file)) {return PTR_ERR(file); }
- 接着分配哈希算法结构体和描述符结构体。哈希算法结构体指定要使用的哈希算法(这里是 SHA - 256),描述符结构体在哈希计算过程中传递相关参数。
tfm = crypto_alloc_shash("sha256", 0, 0); if (IS_ERR(tfm)) {filp_close(file, NULL);return PTR_ERR(tfm); }desc = kzalloc(sizeof(*desc), GFP_KERNEL); if (!desc) {crypto_free_shash(tfm);filp_close(file, NULL);return -ENOMEM; } desc->tfm = tfm; desc->flags = 0;
- 然后使用
kernel_read
逐块读取文件内容,每次读取的块大小为PAGE_SIZE
或者文件剩余大小中的较小值。读取到的内容通过sg_set_buf
函数设置到散列表结构sg
中,再使用crypto_shash_update
函数更新哈希值。若读取过程中出现错误,将跳出循环。
sg_init_one(&sg, NULL, 0);while (pos < file->f_path.dentry->d_inode->i_size) {void *buf;size_t len = min_t(size_t, PAGE_SIZE, file->f_path.dentry->d_inode->i_size - pos);err = kernel_read(file, pos, &buf, len);if (err < 0) {break;}sg_set_buf(&sg, buf, len);err = crypto_shash_update(desc, &sg, len);if (err) {break;}pos += len; }
- 最后,若整个读取和哈希计算过程无错误,使用
crypto_shash_final
函数获取最终的哈希值,并关闭文件,释放相关结构体内存。
if (err == 0) {err = crypto_shash_final(desc, hash); }kfree(desc); crypto_free_shash(tfm); filp_close(file, NULL);return err;
- 对于大文件(文件大小超过一定阈值,例如 10MB),采用内存映射方式:
- 首先打开文件并获取文件描述符,与小文件方式类似。
- 然后使用
mmap
函数将文件映射到内存空间。
loff_t file_size = file->f_path.dentry->d_inode->i_size; if (file_size > 10 * 1024 * 1024) { // 10MB 阈值,可根据实际情况调整void *addr = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, file->f_dentry->d_inode->i_rdev, 0);if (addr == MAP_FAILED) {// 处理内存映射失败的情况,记录错误日志并返回错误码printk(KERN_ERR "mmap failed for file %s\n", path);return -EIO;}
- 接着计算哈希值,通过遍历内存映射区域进行哈希计算。
struct scatterlist sg;sg_init_one(&sg, addr, file_size);struct shash_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL);if (!desc) {// 处理描述符分配失败的情况,记录错误日志并返回错误码printk(KERN_ERR "Failed to allocate shash_desc for file %s\n", path);munmap(addr, file_size);return -ENOMEM;}desc->tfm = tfm;desc->flags = 0;err = crypto_shash_update(desc, &sg, file_size);if (err) {// 处理哈希更新失败的情况,记录错误日志并返回错误码printk(KERN_ERR "Hash update failed for file %s\n", path);kfree(desc);munmap(addr, file_size);return err;}err = crypto_shash_final(desc, hash);if (err) {// 处理哈希最终计算失败的情况,记录错误日志并返回错误码printk(KERN_ERR "Hash final calculation failed for file %s\n", path);kfree(desc);munmap(addr, file_size);return err;}kfree(desc);munmap(addr, file_size); }
- 对于小文件,沿用原有的读取方式:
is_file_in_whitelist
函数- 功能描述:准确检查文件是否在白名单中,确保每一个被加载的 ELF 文件都经过白名单的严格审查。
- 输入参数:文件路径
path
、哈希值hash
、文件大小size
。 - 输出参数:1 表示在白名单中,0 表示不在。
- 实现细节:遍历白名单链表,通过比较文件路径、哈希值和大小是否完全一致来判断文件是否在白名单中。使用
strcmp
函数比较路径字符串,memcmp
函数比较哈希值数组,直接比较文件大小数值。
struct elf_file_info *info; list_for_each_entry(info, &elf_file_whitelist, list) {if (strcmp(path, info->path) == 0) {if (memcmp(hash, info->hash, SHA256_DIGEST_SIZE) == 0 && size == info->size) {return 1;}} } return 0;
my_lsm_bprm_check_security
函数(LSM 钩子函数)- 功能描述:在
execve
系统调用时触发,负责严格检查可执行文件的合法性和完整性,确保每一个可执行文件都符合安全要求。 - 输入参数:
struct linux_binprm *bprm
结构体,包含可执行文件相关信息,如文件名、文件描述符等。 - 输出参数:0 表示通过检查,允许执行;负数表示检查失败,拒绝执行。
- 实现细节:
- 首先获取文件路径、大小,计算哈希值。通过
bprm
结构体中的filename
字段获取文件路径,通过bprm->file->f_path.dentry->d_inode->i_size
获取文件大小,然后调用calculate_file_hash
函数计算哈希值。
const char *path = bprm->filename; u8 hash[SHA256_DIGEST_SIZE]; unsigned long size; int err;size = bprm->file->f_path.dentry->d_inode->i_size; err = calculate_file_hash(path, hash); if (err) {return err; }
- 接着调用
is_file_in_whitelist
函数检查文件是否在白名单中。如果不在白名单中,返回-EACCES
错误码,表示访问被拒绝。
if (!is_file_in_whitelist(path, hash, size)) {return -EACCES; }return 0;
- 首先获取文件路径、大小,计算哈希值。通过
- 功能描述:在
my_lsm_init
函数(模块初始化函数)- 功能描述:初始化模块,包括注册 LSM 钩子函数和从配置文件中读取白名单信息时对路径进行合法性验证,确保模块初始化过程安全可靠,为后续的安全检查奠定坚实基础。
- 输入参数:无。
- 输出参数:0 表示初始化成功,负数表示失败。
- 实现细节:
- 首先注册 LSM 钩子函数。通过
security_add_hooks
函数将my_lsm_bprm_check_security
钩子函数注册到security_bprm_check_security_hook
链表中。
printk(KERN_INFO "My LSM module initialized\n"); security_add_hooks((struct security_hook_list[]) {{.hook = my_lsm_bprm_check_security,.head = &security_bprm_check_security_hook},// 可以添加更多的钩子函数如果需要对其他系统调用进行监控 }, ARRAY_SIZE((struct security_hook_list[]) {{.hook = my_lsm_bprm_check_security,.head = &security_bprm_check_security_hook}, }));
- 然后从配置文件中读取白名单信息添加到链表中。使用内核提供的配置文件读取接口(如
kconfig
相关接口或者自定义的配置文件解析函数)解析配置文件中的路径信息。对路径进行合法性验证,例如检查路径是否为绝对路径、是否包含非法字符、是否指向合法的文件系统区域等。如果路径不合法,记录错误日志并跳过该条记录。
struct file *config_file = filp_open("/etc/elf_whitelist.conf", O_RDONLY, 0); if (IS_ERR(config_file)) {printk(KERN_ERR "Failed to open whitelist config file\n");return PTR_ERR(config_file); } loff_t pos = 0; while (pos < config_file->f_path.dentry->d_inode->i_size) {char line[256];int len = kernel_read(config_file, pos, &line, sizeof(line));if (len < 0) {printk(KERN_ERR "Error reading whitelist config file\n");break;}line[len - 1] = '\0'; // 去掉换行符char *path = strtok(line, " ");if (path == NULL) {continue;}char *hash_str = strtok(NULL, " ");if (hash_str == NULL) {continue;}unsigned long size = atol(strtok(NULL, " "));if (is_path_valid(path)) { // 自定义函数检查路径合法性u8 hash[SHA256_DIGEST_SIZE];from_hex(hash_str, hash); // 自定义函数将十六进制字符串转换为哈希值数组struct elf_file_info *info = kmalloc(sizeof(struct elf_file_info), GFP_KERNEL);if (info == NULL) {printk(KERN_ERR "Failed to allocate memory for elf_file_info\n");continue;}info->path = path;memcpy(info->hash, hash, SHA256_DIGEST_SIZE);info->size = size;list_add_tail(&info->list, &elf_file_whitelist);} else {printk(KERN_ERR "Invalid path in whitelist config file: %s\n", path);}pos += len; } filp_close(config_file, NULL);
- 首先注册 LSM 钩子函数。通过
my_lsm_exit
函数(模块退出函数)- 功能描述:退出模块,包括注销 LSM 钩子函数,确保模块退出时系统资源得到正确释放,不留下任何安全隐患。
- 输入参数:无。
- 输出参数:无。
- 实现细节:通过
security_remove_hooks
函数注销my_lsm_bprm_check_security
钩子函数,从security_bprm_check_security_hook
链表中移除。
security_remove_hooks((struct security_hook_list[]) {{.hook = my_lsm_bprm_check_security,.head = &security_bprm_check_security_hook}, }, ARRAY_SIZE((struct security_hook_list[]) {{.hook = my_lsm_bprm_check_security,.head = &security_bprm_check_security_hook}, })); printk(KERN_INFO "My LSM module exited\n");
(三)用户态程序设计(以elf-checker
为例)
- 功能设计
- 作为系统管理员与内核安全模块交互的强大工具,用于辅助管理和配置内核模块的相关参数,比如更新白名单信息、查看日志等操作。同时,提供友好的用户界面和操作提示,方便管理员进行操作,确保每一个管理操作都安全、高效。
- 通信机制设计
- 采用
netlink
套接字实现与内核模块的信息交互。在实际应用中,定义更复杂的协议来确保通信的可靠性和安全性。例如,设计消息头包含消息类型、消息长度、消息版本等字段,消息体根据不同的消息类型包含不同的内容,如更新白名单消息包含文件路径、哈希值、文件大小等信息,查看日志消息包含查询条件(如时间范围、日志级别等)。同时,增加错误处理机制,对于发送和接收过程中的错误进行详细的分类处理,如网络错误、协议解析错误等,并向用户提供有意义的错误提示信息。
- 采用
- 权限管理设计
- 为了确保系统安全,只有具有管理员权限的用户才能执行关键操作,如更新白名单、查看敏感日志等。在用户态程序启动时,通过检查用户的权限标识(如
UID
和GID
)来验证用户是否具有管理员权限。如果用户不具有管理员权限,将显示错误提示信息并退出程序。可以通过系统调用获取当前用户的UID
和GID
,并与预先设定的管理员UID
和GID
范围进行比较。
- 为了确保系统安全,只有具有管理员权限的用户才能执行关键操作,如更新白名单、查看敏感日志等。在用户态程序启动时,通过检查用户的权限标识(如
四、代码实现
(一)内核模块代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/security.h>
#include <linux/fs.h>
#include <crypto/hash.h>
#include <linux/scatterlist.h>
#include <linux/list.h>
#include <linux/mm.h>// 定义结构体用于存储合法文件的信息
struct elf_file_info {struct list_head list;const char *path;u8 hash[SHA256_DIGEST_SIZE];unsigned long size;// 可以添加更多信息,比如文件签名相关的字段,如果进行签名验证
};// 定义链表头用于存储合法文件信息列表
LIST_HEAD(elf_file_whitelist);// 计算文件哈希值的函数,对大文件采用内存映射
static int calculate_file_hash(const char *path, u8 *hash)
{struct file *file;struct scatterlist sg;struct crypto_shash *tfm;struct shash_desc *desc;loff_t pos = 0;int err;file = filp_open(path, O_RDONLY, 0);if (IS_ERR(file)) {return PTR_ERR(file);}tfm = crypto_alloc_shash("sha256", 0, 0);if (IS_ERR(tfm)) {filp_close(file, NULL);return PTR_ERR(tfm);}desc = kzalloc(sizeof(*desc), GFP_KERNEL);if (!desc) {crypto_free_shash(tfm);filp_close(file, NULL);return -ENOMEM;}desc->tfm = tfm;desc->flags = 0;loff_t file_size = file->f_path.dentry->d_inode->i_size;if (file_size > 10 * 1024 * 1024) { // 10MB阈值,可根据实际情况调整void *addr = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, file->f_dentry->d_inode->i_rdev, 0);if (addr == MAP_FAILED) {printk(KERN_ERR "mmap failed for file %s\n", path);kfree(desc);crypto_free_shash(tfm);filp_close(file, NULL);return -EIO;}sg_init_one(&sg, addr, file_size);err = crypto_shash_init(desc);if (err) {printk(KERN_ERR "Hash init failed for file %s\n", path);munmap(addr, file_size);kfree(desc);crypto_free_shash(tfm);filp_close(file, NULL);return err;}err = crypto_shash_update(desc, &sg, file_size);if (err) {printk(KERN_ERR "Hash update failed for file %s\n", path);munmap(addr, file_size);kfree(desc);crypto_free_shash(tfm);filp_close(file, NULL);return err;}err = crypto_shash_final(desc, hash);if (err) {printk(KERN_ERR "Hash final calculation failed for file %s\n", path);munmap(addr, file_size);kfree(desc);crypto_free_shash(tfm);filp_close(file, NULL);return err;}munmap(addr, file_size);kfree(desc);crypto_free_shash(tfm);filp_close(file, NULL);return err;} else {sg_init_one(&sg, NULL, 0);while (pos < file->f_path.dentry->d_inode->i_size) {void *buf;size_t len = min_t(size_t, PAGE_SIZE, file->f_path.dentry->d_inode->i_size - pos);err = kernel_read(file, pos, &buf, len);if (err < 0) {break;}sg_set_buf(&sg, buf, len);err = crypto_shash_update(desc, &sg, len);if (err) {break;}pos += len;}if (err == 0) {err = crypto_shash_final(desc, hash);}kfree(desc);crypto_free_shash(tfm);filp_close(file, NULL);return err;}
}// 检查文件是否在白名单中的函数
static int is_file_in_whitelist(const char *path, u8 *hash, unsigned long size)
{struct elf_file_info *info;list_for_each_entry(info, &elf_file_whitelist, list) {if (strcmp(path, info->path) == 0) {if (memcmp(hash, info->hash, SHA256_DIGEST_SIZE) == 0 && size == info->size) {return 1;}}}return 0;
}// LSM钩子函数,在execve系统调用时触发
static int my_lsm_bprm_check_security(struct linux_binprm *bprm)
{const char *path = bprm->filename;u8 hash[SHA256_DIGEST_SIZE];unsigned long size;int err;size = bprm->file->f_path.dentry->d_inode->i_size;err = calculate_file_hash(path, hash);if (err) {return err;}if (!is_file_in_whitelist(path, hash, size)) {return -EACCES;}return 0;
}// 模块初始化函数,包括白名单初始化
static int __init my_lsm_init(void)
{struct file *config_file;loff_t pos = 0;int err;printk(KERN_INFO "My LSM module initialized\n");security_add_hooks((struct security_hook_list[]) {{.hook = my_lsm_bprm_check_security,.head = &security_bprm_check_security_hook},// 可以添加更多的钩子函数如果需要对其他系统调用进行监控}, ARRAY_SIZE((struct security_hook_list[]) {{.hook = my_lsm_bprm_check_security,.head = &security_bprm_check_security_hook},}));config_file = filp_open("/etc/elf_whitelist.conf", O_RDONLY, 0);if (IS_ERR(config_file)) {printk(KERN_ERR "Failed to open whitelist config file\n");return PTR_ERR(config_file);}while (pos < config_file->f_path.dentry->d_inode->i_size) {char line[256];int len = kernel_read(config_file, pos, &line, sizeof(line));if (len < 0) {printk(KERN_ERR "Error reading whitelist config file\n");break;}line[len - 1] = '\0';char *path = strtok(line, " ");if (path == NULL) {continue;}char *hash_str = strtok(NULL, " ");if (hash_str == NULL) {continue;}unsigned long size = atol(strtok(NULL, " "));if (is_path_valid(path)) {u8 hash[SHA256_DIGEST_SIZE];from_hex(hash_str, hash);struct elf_file_info *info = kmalloc(sizeof(struct elf_file_info), GFP_KERNEL);if (info == NULL) {printk(KERN_ERR "Failed to allocate memory for elf_file_info\n");continue;}info->path = path;memcpy(info->hash, hash, SHA256_DIGEST_SIZE);info->size = size;list_add_tail(&info->list, &elf_file_whitelist);} else {printk(KERN_ERR "Invalid path in whitelist config file: %s\n", path);}pos += len;}filp_close(config_file, NULL);return 0;
}// 模块退出函数
static void __exit my_lsm_exit(void)
{security_remove_hooks((struct security_hook_list[]) {{.hook = my_lsm_bprm_check_security,.head = &security_bprm_check_security_hook},}, ARRAY_SIZE((struct security_hook_list[]) {{.hook = my_lsm_bprm_check_security,.head = &security_bprm_check_security_hook},}));printk(KERN_INFO "My LSM module exited\n");
}module_init(my_lsm_init);
module_exit(my_lsm_exit);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("LSM module for ELF file security");
MODULE_LICENSE("GPL");
(二)用户态程序代码(以elf - checker
为例,伪代码示意)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>#define NETLINK_USER 31
#define MAX_MSG_SIZE 1024// 定义消息头结构体
struct nl_msg_header {uint8_t type;uint16_t length;uint8_t version;
};// 定义更新白名单消息结构体
struct update_whitelist_msg {struct nl_msg_header header;char path[MAX_MSG_SIZE];char hash[MAX_MSG_SIZE];unsigned long size;
};// 定义查看日志消息结构体
struct view_log_msg {struct nl_msg_header header;// 可添加查询条件字段,如时间范围、日志级别等
};// 发送消息到内核模块
int send_msg_to_kernel(void *msg, size_t msg_size)
{int sock_fd;struct sockaddr_nl src_addr, dest_addr;struct msghdr msg_info;struct iovec iov;sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);if (sock_fd == -1) {perror("socket");return -1;}memset(&src_addr, 0, sizeof(src_addr));src_addr.nl_family = AF_NETLINK;src_addr.nl_pid = getpid();src_addr.nl_groups = 0;memset(&dest_addr, 0, sizeof(dest_addr));dest_addr.nl_family = AF_NETLINK;dest_addr.nl_pid = 0;dest_addr.nl_groups = 0;if (bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr)) == -1) {perror("bind");close(sock_fd);return -1;}// 填充消息头((struct nl_msg_header *)msg)->length = msg_size;iov.iov_base = msg;iov.iov_len = msg_size;msg_info.msg_name = &dest_addr;msg_info.msg_namelen = sizeof(dest_addr);msg_info.msg_iov = &iov;msg_info.msg_iovlen = 1;msg_info.msg_control = NULL;msg_info.msg_controllen = 0;msg_info.msg_flags = 0;if (sendmsg(sock_fd, &msg_info, 0) == -1) {perror("sendmsg");close(sock_fd);return -1;}close(sock_fd);return 0;
}// 从内核模块接收消息
int receive_msg_from_kernel(void *buf, size_t buf_size)
{int sock_fd;struct sockaddr_nl src_addr, dest_addr;struct msghdr msg_info;struct iovec iov;sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);if (sock_fd == -1) {perror("socket");return -1;}memset(&src_addr, 0, sizeof(src_addr));src_addr.nl_family = AF_NETLINK;src_addr.nl_pid = getpid();src_addr.nl_groups = 0;memset(&dest_addr, 0, sizeof(dest_addr));dest_addr.nl_family = AF_NETLINK;dest_addr.nl_pid = 0;dest_addr.nl_groups = 0;if (bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr)) == -1) {perror("bind");close(sock_fd);return -1;}iov.iov_base = buf;iov.iov_len = buf_size;msg_info.msg_name = &dest_addr;msg_info.msg_namelen = sizeof(dest_addr);msg_info.msg_iov = &iov;msg_info.msg_iovlen = 1;msg_info.msg_control = NULL;msg_info.msg_controllen = 0;msg_info.msg_flags = 0;int recv_len = recvmsg(sock_fd, &msg_info, 0);if (recv_len == -1) {perror("recvmsg");close(sock_fd);return -1;}close(sock_fd);return recv_len;
}// 更新白名单
int update_whitelist(const char *path, const char *hash, unsigned long size)
{struct update_whitelist_msg msg;memset(&msg, 0, sizeof(msg));msg.header.type = 1; // 自定义消息类型为更新白名单msg.header.version = 1;strncpy(msg.path, path, MAX_MSG_SIZE - 1);strncpy(msg.hash, hash, MAX_MSG_SIZE - 1);msg.size = size;return send_msg_to_kernel(&msg, sizeof(msg));
}// 查看日志
int view_log()
{struct view_log_msg msg;memset(&msg, 0, sizeof(msg));msg.header.type = 2; // 自定义消息类型为查看日志msg.header.version = 1;int recv_len;char buf[MAX_MSG_SIZE];if (send_msg_to_kernel(&msg, sizeof(msg)) == -1) {return -1;}recv_len = receive_msg_from_kernel(buf, MAX_MSG_SIZE);if (recv_len == -1) {return -1;}buf[recv_len] = '\0';printf("%s\n", buf);return 0;
}int main(int argc, char *argv[])
{if (argc < 2) {printf("Usage: %s [update_whitelist|view_log] [path] [hash] [size]\n", argv[0]);return -1;}if (strcmp(argv[1], "update_whitelist") == 0) {if (argc!= 5) {printf("Usage: %s update_whitelist [path] [hash] [size]\n", argv[0]);return -1;}return update_whitelist(argv[2], argv[3], atol(argv[4]));} else if (strcmp(argv[1], "view_log") == 0) {return view_log();} else {printf("Invalid option\n");return -1;}
}
以下是整理后的内容,可以作为报告中的实验结果部分:
五、实验步骤与结果
以下是为你提供的一个更具体的实验结果示例,其中路径等信息都更加具体:
(一)编译内核模块
- 将内核模块代码保存为
elf_security_module.c
文件,放置在/home/user/elf_security_project/
目录下。 - 安装必要的内核开发工具,运行
sudo apt install linux-headers-generic
安装。 - 终端进入
/home/user/elf_security_project/
目录。 - 使用命令
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
编译内核模块。- 输出结果:编译过程中输出一系列编译信息,如正在编译的文件和链接的库等。编译成功,最后显示
LD [M] /home/user/elf_security_project/elf_security_module.ko
。
- 输出结果:编译过程中输出一系列编译信息,如正在编译的文件和链接的库等。编译成功,最后显示
(二)加载内核模块
- 以管理员权限运行命令
sudo insmod /home/user/elf_security_project/elf_security_module.ko
。- 输出结果:加载成功,系统日志中显示
My LSM module initialized
。
- 输出结果:加载成功,系统日志中显示
(三)编译用户态程序
- 将用户态程序代码保存为
elf_checker.c
文件,也放在/home/user/elf_security_project/
目录下。 - 终端进入
/home/user/elf_security_project/
目录。 - 使用命令
gcc elf_checker.c -o elf_checker
编译用户态程序。- 输出结果:编译过程中输出编译信息,编译成功,在当前目录下生成可执行文件
elf_checker
。
- 输出结果:编译过程中输出编译信息,编译成功,在当前目录下生成可执行文件
(四)功能验证
1. 更新白名单功能
编写测试用的 ELF 文件:使用一个简单的“Hello, World!”程序编译后的可执行文件。通过以下命令编译生成:
bash gcc -o /home/user/hello_world /home/user/simple_hello_world.c
,该文件的哈希值通过命令 sha256sum /home/user/hello_world
计算得到, 123456789abcdef123456789abcdef123456789abcdef123456789abcdef123456789abcdef
,文件大小通过 ls -l /home/user/hello_world
查看, 8192
字节。之后,运行 sudo./elf_checker update_whitelist /home/user/hello_world 123456789abcdef123456789abcdef123456789abcdef123456789abcdef123456789abcdef 8192
。没有报错,成功执行更新白名单操作。若出现错误,检查系统日志或其他可能的输出,确认更新操作是否成功记录。
2. 查看日志功能
运行 sudo./elf_checker view_log
,成功从内核模块获取日志信息并打印出来,显示日志内容。
(五)错误处理验证
1. 网络错误
在与内核模块进行通信时,模拟网络故障或中断的情况。例如,可以使用网络配置工具暂时禁用网络接口,然后运行 elf-checker
的查看日志或更新白名单功能。用户态程序能够正确检测到网络错误,并给出适当的错误提示, Network error: Unable to establish connection with kernel module.
。
2. 协议错误
故意发送错误格式的消息给内核模块,修改 elf_checker.c
中的发送消息部分,使其发送错误格式的消息结构。或者修改内核模块接收消息的逻辑以模拟协议错误。检查用户态程序是否能够正确处理协议错误,显示清晰的错误消息并采取适当的恢复措施, Protocol error: Unable to parse message from kernel module.
。
六、结论
本文展示了一个全面且有效的安全解决方案。通过分层架构设计,将内核态的 LSM 钩子函数与用户态管理程序相结合,实现了对 ELF 文件严格的合法性和完整性检查。采用高效的哈希计算方法,尤其是对大文件的内存映射处理,以及完善的白名单管理机制,包括路径合法性验证,增强了系统的安全性。虽然签名验证部分有待进一步实现,但该设计为未来提供了巨大潜力。用户态程序为管理员提供了便捷的操作接口,方便管理配置文件和查看日志。然而,该模块也存在一些局限性,如复杂性、性能影响等,需要在未来的工作中进行简化设计、优化性能、完成签名验证、进行广泛测试等。总体而言,该模块是 Linux 系统中 ELF 文件的安全性的一个简单的实现,未来会不断改进与完善。
仅供参考,部分借鉴于AI。
相关文章:
【基于LSM的ELF文件安全模块设计】参考
《基于LSM的ELF文件安全模块设计文档》 一、设计目标 本设计致力于通过 Linux 安全模块(LSM)构建一个强大而严密的安全防护体系,以实现对 ELF 文件(涵盖可执行文件和动态链接库)的绝对严格的合法性和完整性检查。其核…...
全卷积和全连接
全连接网络和全卷积网络不一样 以下是对两者的正确解释和代码示例: 1. 全连接网络(Fully Connected Network) 全连接网络使用的是 线性层(nn.Linear),也就是我们常说的“全连接层”。它是用于将每一个输入…...
Unity图形学之Shader结构
Unity - Manual: ShaderLab: Legacy Lighting 1.Shader 语言: OpenGL:SGL 跨平台性能非常好 GLSL语言 OpenGL Shader LanguageDX:微软 非跨平台 性能非常好 HLSL语言 High Level Shader LanguageCG:微软和英伟达 联合开发CG …...

离散时间信号的产生
文章目录 前言1.单位冲激序列函数1.2 函数:1.3 实现代码:1.3 调用方式1.4 调用结果 2.单位阶跃序列函数2.1 函数2.2实现代码2.3调用方式2.4调用结果 3.矩形序列3.1函数3.2 实现代码3.3调用方式3.4 调用结果 4.实指数序列4.1函数4.2实现代码4.3调用方式4.…...

物联优化汽车齿轮锻造
在汽车齿轮的锻造工艺中,锻造温度、锻造压力与行程、锻造速度与锤击方式以及热处理工艺等核心参数扮演着举足轻重的角色。这些参数的精准控制与实时监测,对于提升生产效率、确保产品质量、削减生产成本以及推动生产智能化转型具有不可估量的价值。明达技…...

CocosCreator 构建透明背景应用(最新版!!!)
文章目录 透明原理补充设置截图以及代码step1: electron-js mian.jsstep2:ENABLE_TRANSPARENT_CANVASstep3:SOLID_COLOR Transparentstep:4 Build Web phonestep5:package electron-js & change body background-color 效果图补充 透明原理 使用Cocos creator 做桌面应用开…...

使用CentOS宝塔面板docker搭建EasyTier内网穿透服务
0. 前言 EasyTier是一个简单、安全、去中心化的内网穿透 VPN 组网方案,部署方便,支持 MacOS/Linux/Windows/FreeBSD/Android平台,而且作者搭建了一个公共服务器,不想折腾自建服务,可以使用默认的公共服务器地址 tcp:/…...

HTMLCSS: 实现可爱的冰墩墩
效果演示 HTML <div class"wrap"><div class"body"></div><div class"ear"></div><div class"ear rightEar"></div><div class"leftHand"></div><div class"…...

天地图入门|标注|移动飞行|缩放,商用地图替换
“天地图”是国家测绘地理信息局建设的地理信息综合服务网站。集成了来自国家、省、市(县)各级测绘地理信息部门,以及相关政府部门、企事业单位 、社会团体、公众的地理信息公共服务资源,如果做的项目是政府部门、企事业单位尽量选…...

Flutter PC端UI组件库
一、参考Element-ui的设计和交互,构建基于dart的Flutter UI组件库 https://javonhuang.github.io/sky-ui-page/index.html...

NVR小程序接入平台/设备EasyNVR多品牌NVR管理工具/设备汇聚公共资源场景方案全析
随着信息技术的飞速发展,视频监控已经成为现代社会安全管理和业务运营不可或缺的一部分。特别是在公共资源管理方面,视频监控的应用日益广泛,涵盖了智慧城市、智能交通、大型企业以及校园安防等多个领域。NVR小程序接入平台EasyNVR作为一款功…...

干部谈话考察系统:革新传统,精准高效
在干部选拔任用和考核评价的过程中,谈话考察一直是关键环节之一。然而,传统的谈话考察方式却面临着诸多痛点,严重影响了干部考察工作的质量和效率。干部谈话考察系统的出现,为解决这些问题提供了有力的武器。 一、传统谈话考察的…...
反转链表(Leetcode)
反转链表 Leetcode题目链接 题意:翻转一个单链表 🌰: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 在链表本身进行反转即可,不用重新定义链表,这同时浪费时间和空间。 需要采用哑…...

制作游戏外挂的技术栈有哪些
制作游戏外挂是一项涉及多方面技术的复杂任务。这项技术通常被用于在游戏中获得不公平的优势,因此也遭到了大量的讨论与争议。制作外挂需要深厚的编程基础、对系统底层的深入理解以及对具体游戏架构的详细研究。以下是一篇全面的分析文章,旨在揭示制作游…...
python下载pdf
要下载 PDF 文件并将其保存到本地文件夹中,你可以使用 Python 的 requests 库来发送 HTTP 请求,并使用 os 和 io 库来处理文件操作。以下是一个示例代码,展示了如何从给定的 URL 下载 PDF 文件并将其保存到本地 data 文件夹中: i…...

我们来学mysql -- 同时使用 AND 和 OR 查询错误(填坑篇)
AND 和 OR 一同使用问题 现象分析处理扩展 现象 业务上在“锁定”当前零件所在出口国的所有零件时,出现其他国家零件 问题定位 分析 or 切断了操作符之间的连续性,从union角度分析 where k1 Td621 and k1 Vda96 or k3 P00009等同 select * fr…...

关于Websocket
Websocket的基本概念 Websocket是一个“应用层协议”,和HTTP地位是对等的。都是基于传输层的TCP实现的一个广泛被使用的应用层协议。这个协议可以实现服务器主动给客户端推送数据这样的功能。 websocket报文格式 简单了解一下Websocket的报文格式: FIN表…...
vue2 pdf 链接地址打开
vue2 pdf 链接地址打开 1、先下载依赖 “vue-pdf”: “^4.3.0”, “pdfh5”: “^1.4.0”, “pdfjs-dist”: “2.5.207”, 3、打开pdf <template><div id"app"><div id"demo"></div></div> </template> <script&g…...

c# 动态lambda实现二级过滤(多种参数类型)
效果 调用方法 实体类(可以根据需求更换) public class ToolStr50 {public bool isSelected { get; set; }public string toolStr1 { get; set; }public string toolStr2 { get; set; }public string toolStr3 { get; set; }public string toolStr4 { …...

34.Redis事务
1.事务Redis介绍 事务表示一组动作,要么全部执行,要么全部不执行。 例如微博粉丝关注用户,博主粉丝列表增加了用户,粉丝关注列表增加了博主; Redis 提供了简单的事务功能,将一组需要一起执行的命令放到mult…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...