对内核监控方式评测

admin 2024年7月29日20:26:18评论14 views字数 6772阅读22分34秒阅读模式

背景

监控进程创建是系统管理和安全监控中的一个关键方面。本文将介绍四种常见的进程监控技术:So preload、Netlink Connector、Audit 和 Syscall hook的实现方式和适用场景。

So preload

在 Linux 系统中,通过设置动态链接库预加载(LD_PRELOAD),可以在程序启动时优先加载指定的库。这使得用户可以覆盖库中已有的函数实现,从而实现对某些关键函数(如execve)的监控。

编写一个demo示例,如hook.c

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>

typedef int (*orig_execve_func)(const char *filename, char *const argv[], char *const envp[]);
static orig_execve_func orig_execve = NULL;

// 我们的自定义 execve 函数
int execve(const char *filename, char *const argv[], char *const envp[]) {
// 打印将要执行的程序名称
printf("Intercepted execve call: %sn", filename);

// 动态获取原始的 execve 函数指针
orig_execve = (orig_execve_func)dlsym(RTLD_NEXT, "execve");

// 调用原始的 execve 函数
return orig_execve(filename, argv, envp);
}

然后执行以下命令进行编译

gcc -shared -fPIC hook.c -o hook.so -ldl

编译完成之后,将上面生成的动态链接库注册成 preload

echo "/usr/lib/hook.so" | sudo tee -a /etc/ld.so.preload

在这个示例中,我们通过定义一个新的execve函数,先打印出被执行的命令,然后调用原始的 execve函数。通过设置 LD_PRELOAD环境变量,我们的库会在程序启动时被加载,从而允许我们的 execve覆盖标准库中的同名函数。

退出当前 shell 并重新登录(下面会讲原因),执行命令即可看到我们编写的代码已被执行:

对内核监控方式评测

优缺点

  • 优点:轻量级,不需修改内核代码。

  • 缺点:不能监控静态链接的程序;通过系统调用(如 int80h)可绕过。

使用条件

该方法没有什么条件限制,只需有 root 权限即可(做入侵监控程序 root 权限是必需的,后面的几种方法默认也都是在 root 权限下)

适用场景

适用于需要监控动态链接应用程序的创建过程,不涉及内核修改,适用于权限受限的环境。

Netlink Connector

Netlink Connector 是一种特殊的基于 Netlink 协议的通信机制,它构建在 Linux 内核中,用于内核与用户空间应用之间的通信。Netlink 本身是一种灵活的IPC(进程间通信)机制,主要用于网络配置和管理,但其使用范围已经扩展到了各种系统事件的通知。Netlink Connector 则专门用于传递事件和消息,包括进程事件,如进程创建和终止。

Netlink Connector 使用标准的 Netlink 套接字和通信机制,但它专注于事件的传递。它允许内核组件注册事件源,并将这些事件广播给订阅这些事件的用户空间应用程序。

关键组件

  1. 连接器模块(Connector Module)

    • 内核模块,负责管理事件的注册和广播。

    • 它处理来自用户空间的订阅请求,并在相应的事件发生时向订阅者广播通知。

  2. 用户空间应用程序

    • 通过创建 Netlink 套接字并绑定到特定的 Netlink 协议(如 NETLINK_CONNECTOR)来与内核模块通信。

    • 应用程序发送订阅请求到内核,并监听来自内核的事件通知。

实现步骤

  1. 内核端配置

    • 确保内核配置中启用了 Netlink Connector 支持(通常是 CONFIG_CONNECTOR 和 CONFIG_PROC_EVENTS)。

  2. 用户空间监听程序

    • 创建一个 Netlink 套接字。

    • 绑定到 Netlink Connector 协议。

    • 发送订阅消息给内核,表明它希望接收特定类型的事件(例如,进程创建和终止)。

    • 监听套接字并处理接收到的事件通知。

代码示例

下面是一个简单的用户空间程序示例,通过 Netlink Connector 接口监听内核发出的进程事件:

// 引入必要的头文件
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/connector.h>
#include <linux/cn_proc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

// 定义Netlink协议中使用的CONNECTOR类型
#define NETLINK_CONNECTOR 11

int main() {
// 套接字描述符
int sock;
// Netlink套接字地址结构
struct sockaddr_nl sa;
// 数据接收缓冲区
char buffer[8192];
// 接收函数的返回值
int ret;

// 创建一个Netlink套接字,使用NETLINK_CONNECTOR协议
sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
if (sock == -1) {
    perror("socket");  // 如果创建套接字失败,则打印错误信息
    return 1;          // 并退出程序
}

// 初始化sockaddr_nl结构体
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;  // 设置地址类型为AF_NETLINK
sa.nl_groups = CN_IDX_PROC; // 加入到进程事件组,监听进程相关事件
sa.nl_pid = getpid();       // 设置Netlink套接字的端口号为当前进程ID

// 将套接字绑定到刚刚设置的地址
if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
    perror("bind");  // 如果绑定失败,打印错误信息
    close(sock);     // 关闭套接字
    return 1;        // 并退出程序
}

// 循环接收消息
while (1) {
    ret = recv(sock, buffer, sizeof(buffer), 0); // 从套接字接收数据
    if (ret == -1) {
        perror("recv");  // 如果接收失败,打印错误信息
        close(sock);     // 关闭套接字
        return 1;        // 并退出程序
    }

    // 打印接收到的消息提示
    printf("Received process eventn");
}

// 关闭套接字,虽然由于无限循环,实际上这行代码不会被执行
close(sock);
return 0;
}

这段代码主要通过 Netlink Connector 来接收来自内核的进程事件通知。它首先创建了一个 Netlink 套接字,然后将其绑定到一个特定的地址,该地址订阅了进程事件。通过无限循环,程序不断接收并处理来自内核的消息。每当接收到一个消息时,程序会输出一条信息表示已接收到进程事件。这个简单的示例没有对接收到的数据进行解析,只是简单地通知已接收到数据。

对内核监控方式评测

优缺点

  • 优点:实现简单,直接从内核获取信息。

  • 缺点:获取的信息有限,可能需要额外步骤来完整获取进程信息。

适用场景

非常适合需要实时监控进程活动且对性能要求不是非常高的场合。

Audit

Linux Audit 系统是一个内核集成的审计系统,能够记录系统中发生的详细安全相关事件,包括系统调用、文件访问和网络活动。

具体架构如下 :

对内核监控方式评测

  • 用户通过用户态的管理进程配置规则(例如图中的 go-audit ,也可替换为常用的 auditd ),并通过 Netlink 套接字通知给内核。

  • 内核中的 kauditd 通过 Netlink 获取到规则并加载。

  • 应用程序在调用系统调用和系统调用返回时都会经过 kauditd ,kauditd 会将这些事件记录下来并通过 Netlink 回传给用户态进程。

  • 用户态进程解析事件日志并输出。

从上面的架构图可知,整个框架分为用户态和内核态两部分,内核空间的 kauditd 是不可变的,用户态的程序是可以定制的,目前最常用的用户态程序就是 auditd ,除此之外知名的 osquery在底层也是通过与 Audit 交互来获取进程事件。下面我们就简单介绍一下如何通过 auditd 来监控进程创建。

apt update && apt install auditd
systemctl start auditd && systemctl status auditd
sudo auditctl -a always,exit -F arch=b64 -S execve -k exec-monitor //创建一个对execve这个系统调用的监控

再通过 auditd 软件包中的 ausearch来检索 auditd 产生的日志:

sudo ausearch -k exec-monitor

这里使用auditctl工具来添加一个监控所有 execve调用的审计规则。-k exec-monitor是为这个规则设置的一个关键字,使得查找这个规则生成的日志更容易。通过 ausearch工具,我们可以根据关键字 exec-monitor搜索相关的审计日志。至于其他的使用方法可以通过 man auditd和 man auditctl来查看。

使用条件

内核开启 Audit

  • cat/boot/config-$(uname-r)|grep^CONFIG_AUDIT

优缺点

优点

  • 组件完善,使用 auditd 软件包中的工具即可满足大部分需求,无需额外开发代码。

  • 相比于 Netlink Connector ,获取的信息更为全面,不仅仅是 pid 。

缺点

  • 性能消耗随着进程数量提升有所上升,需要通过添加白名单等配置来限制其资源占用。

关于性能消耗:

开启了osquery的审计功能之后,会在两个方面存在性能损耗:

  1. 当开启了内核中审计功能之后并且存在审计规则,那么内核每次都会比对审计规则和实际产生的审计事件。

  2. 这个audit consumer(在本例中是osquery)将会从内核netlink socket中接受数据,然后将数据解析为了其内部定义的表的格式(socket_eventsprocess_events)并保存在早RocksDB中。最后一旦这个数据被查询,那么这个数据就会被写入到文件或者是通过日志插件发送。

每一次在内核中产生系统调用,就会产生相应的审计事件。如果这样的系统调用越多,那么内核产生这些系统调用的审计事件的工作量就越大,同时osquery解析这些审计事件并且保存在数据库中的工作量也越大。

Syscall hook

上面的 Netlink Connector 和 Audit 都是 Linux 本身提供的监控系统调用的方法,如果我们想拥有更大程度的可定制化,我们就需要通过安装内核模块的方式来对系统调用进行 hook 。

目前常用的 hook 方法是通过修改 sys_call_table( Linux 系统调用表)来实现,具体原理就是系统在执行系统调用时是通过系统调用号在 sys_call_table中找到相应的函数进行调用,所以只要将 sys_call_table中 execve对应的地址改为我们安装的内核模块中的函数地址即可。

这里贴出文章里的一张图方便大家对整个流程有个直观地了解:

代码示例

修改sys_call_table来挂钩 execve系统调用。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/kallsyms.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");

void **sys_call_table;

asmlinkage int (*original_execve)(const char __user *filename, const char __user *const __user *argv, const char __user *const __user *envp);

asmlinkage int hook_execve(const char __user *filename, const char __user *const __user *argv, const char __user *const __user *envp) {
printk(KERN_INFO "Execve hooked: %sn", filename);
return original_execve(filename, argv, envp);
}

static int __init hook_init(void) {
// Find the system call table address
sys_call_table = (void **)kallsyms_lookup_name("sys_call_table");

// Store the original pointer of execve
original_execve = sys_call_table[__NR_execve];

// Change the page protection settings so we can write to the table
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_execve] = hook_execve;
write_cr0(read_cr0() | 0x10000);

printk(KERN_INFO "Module loaded: execve hookedn");
return 0;
}

static void __exit hook_exit(void) {
// Restore the original execve function in the syscall table
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_execve] = original_execve;
write_cr0(read_cr0() | 0x10000);

printk(KERN_INFO "Module unloaded: execve restoredn");
}

module_init(hook_init);
module_exit(hook_exit);

这个代码通过修改系统调用表来挂钩 execve系统调用。模块初始化时,我们查找系统调用表的地址,并替换掉 execve系统调用的入口点,使其指向我们自定义的函数 hook_execve。当任何进程尝试执行 execve时,都会先调用 hook_execve,在这里我们仅打印出被执行文件的名称。

编译内核模块

创建一个makefile

obj-m += syscall_hook.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

加载模块

sudo insmod syscall_hook.ko

查看消息

dmesg

使用条件

  • 可以安装内核模块。

  • 需针对不同 Linux 发行版和内核版本进行定制。

优缺点

优点

  • 高定制化,从系统调用层面获取完整信息。

缺点

  • 开发难度大。

  • 兼容性差,需针对不同发行版和内核版本进行定制和测试。

总结

So preload :Hook 库函数,不与内核交互,轻量但易被绕过。
Netlink Connector :从内核获取数据,监控系统调用,轻量,仅能直接获取 pid ,其他信息需要通过读取 proc/<pid>/来补全。
Audit :从内核获取数据,监控系统调用,功能多,不只监控进程创建,获取的信息相对全面。
Syscall hook :从内核获取数据,监控系统调用,最接近实际系统调用,定制度高,兼容性差。

单纯地看监控进程创建这方面,更推荐使用 Netlink Connector 的方式,这种方式在保证从内核获取数据的前提下又足够轻量,方便进行定制化开发。如果是想要进行全方面的监控包括进程、网络和文件,Audit 是一个不错的选择。

原文始发于微信公众号(德斯克安全小课堂):对内核监控方式评测

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月29日20:26:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   对内核监控方式评测https://cn-sec.com/archives/3011761.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息