litemars/EDRHookDetector

GitHub: litemars/EDRHookDetector

一款多架构 Linux Hook 检测器,通过审计用户态和内核态多种拦截机制来发现 EDR 产品或 Rootkit 的 hook 行为。

Stars: 4 | Forks: 0

# 多架构 EDR 与 Rootkit Hook 检测器 这是一个单一的 Linux 二进制程序,用于审计实时系统中最常见的 EDR 产品(或 rootkit)拦截活动的方式: | 层级 | 检查内容 | 真值来源 | |---|---|---| | 用户态内联补丁 | `libc`, `libssl`, `libcrypto`, `libpthread`, `libdl`, `libpam`, `libaudit` 中一组精选的热点函数 | 磁盘上的 `.so` 字节对比 `/proc/PID/mem` | | GOT / PLT 劫持 | 同样精选的函数,但通过每个已加载模块的 GOT 导入 —— 即使函数体未被修改也能捕获 | ELF `JUMP_SLOT`/`GLOB_DAT` 重定位对比 `/proc/PID/mem` 中的实时槽值 | | 环境变量预加载 | `LD_PRELOAD`, `/etc/ld.so.preload` | 文件系统 / 环境变量 | | eBPF 程序 | 每个具备 hook 能力的 BPF 程序 (KPROBE / TRACING / LSM / TRACEPOINT / PERF_EVENT / SYSCALL) | `bpf()` syscall, BTF, `BPF_TASK_FD_QUERY`, `BPF_LINK_GET_NEXT_ID` | | 非 BPF kprobes | 每个 kprobe/kretprobe,包括来自 SystemTap 或自定义 LKM 的 | `/sys/kernel/debug/kprobes/list` | | uprobes | 每个 uprobe,无论其附加方式如何 | `tracefs/uprobe_events` | | ftrace hook | 通过带有自定义 trampoline 的 ftrace hook 的函数(rootkit 滥用 ftrace)+ 全局 `current_tracer` 状态 | `tracefs/enabled_functions`, `tracefs/current_tracer` | | VDSO 篡改 | 内容偏离同架构大多数进程的特定进程 `[vdso]` 页(原地 vdso 补丁没有磁盘基线) | 对 `/proc/PID/mem` 中的 `[vdso]` 字节进行跨进程比较 | | 活动的 LSM | 已加载 LSM 的列表和顺序;未知名称会被标记 | `/sys/kernel/security/lsm` | | 受污染的内核模块 | 带有 `O` (树外)、`E` (未签名) 或 `F` (强制加载) 标志的已加载模块 | `/proc/modules` | 支持 **ARM64 (AArch64)** 和 **x86-64** 二进制文件。架构是通过每个 ELF 的 `e_machine` 按库检测的,因此单个扫描器二进制文件可以 处理混合环境。CLI 输出人类可读的文本或 单个有效的 JSON 文档以供机器使用。 ## 快速开始 ``` make sudo ./edr_hooks_check # full system scan ./edr_hooks_check --self # current process only, no root needed sudo ./edr_hooks_check --json # machine-readable output ``` ### CLI 选项 | 标志 | 含义 | |---|---| | `-p, --pid ` | 仅扫描给定的进程 | | `-l, --lib ` | 将用户态检查限制在特定的库 | | `-s, --self` | 自扫描(仅当前 PID);无需 root 权限 | | `-v, --verbose` | 按来源列出详细信息(kprobe 地址、函数名等)。使用两次获取更多细节 | | `-x, --hexdump` | 显示每个检测到的用户态 hook 的磁盘与内存指令字节对比 | | `-j, --json` | 将整个报告输出为单个 JSON 对象 | | `-h, --help` | 显示用法并退出 | ### 退出代码 - `0` — 未检测到用户态 hook 和内核侧 hook 信号 - `1` — 至少检测到以下之一:用户态补丁函数、GOT/PLT 劫持、eBPF hook 程序、kprobe、uprobe、ftrace trampoline 或 VDSO 异常 (未知的 LSM 和受污染的模块会被报告,但它们本身**不会**将退出代码设为 `1` —— 它们在大多数实际系统中仅供参考。) ## 检测机制 ### 用户态内联补丁 对于每个被扫描进程加载的受监控库,扫描器会: 1. 读取磁盘上的 `.so` 并解析 ELF 动态符号表,以定位每个受监控函数的虚拟地址。 2. 分别从文件(`pread`)和 `/proc/PID/mem` 中的 `base_addr + (vaddr − preferred_base)` 处读取固定大小的窗口。 - **ARM64**: 8 条定长指令(32 字节)。 - **x86-64 / i386**: 64 字节(≈10–15 条变长指令),由内置的长度解码器解码。 3. 如果字节不同,则运行特定架构的评分启发式算法,过滤已知的良性模式 —— PLT stub、syscall trampoline、尾调用、函数结语、IFUNC 分发、≤ 32 字节的轻量级包装器 —— 并将其余的分类为 **低 / 中 / 高** 置信度。评分系统还能识别以无相对分支开头的全范围 trampoline:x86-64 上的 `push imm; ret` 和 `mov r64, imm64; jmp r64`,以及 ARM64 上的 `movz/movk…; br Xn`。 ### GOT / PLT 劫持 内联补丁并不是拦截调用的唯一方式:覆盖全局偏移表(GOT)条目会将每个调用点通过 PLT 重新路由,而不会触碰目标函数的任何一个字节,因此上面的内联检查无法察觉。对于每个加载的模块(主可执行文件和每个 `.so`),扫描器会: 1. 解析模块的 `JUMP_SLOT` / `GLOB_DAT` 重定位(x86-64/ARM64 上为 RELA,i386 上为 REL),并保留符号为受监控函数的那些重定位。 2. 计算每个 GOT slot 的运行时地址(`base + (r_offset − preferred_base)`),并从 `/proc/PID/mem` 读取实时指针。 3. 当该指针**没有**落入合法的可执行映射中时,标记该条目。延迟绑定的槽指向模块自身的 PLT,而真正的插入式拦截器(包括 `LD_PRELOAD`)会解析为磁盘上的 `.so` 文件 —— 这两者都不会触发检查。该信号专门是指向**匿名 / 注入**内存(或未映射地址)的指针,这是 ptrace 注入 trampoline 的特征。 ### eBPF 内核 hook 系统级运行,直接与 `bpf()` 通信。对于每个已加载的 BPF 程序: 1. `BPF_PROG_GET_NEXT_ID` 循环直到遇到 `ENOENT`。 2. `BPF_PROG_GET_FD_BY_ID` + `BPF_OBJ_GET_INFO_BY_FD` 检索类型、名称、UID 和 `attach_btf_id`。 3. 附加目标通过三个独立的路径进行解析: - **vmlinux BTF**(对 ID 1 执行 `BPF_BTF_GET_FD_BY_ID`,解析类型区段)—— 这是 fentry/fexit/LSM 所需的,它们的目标存储为 BTF 类型 ID。 - **`BPF_TASK_FD_QUERY`** 遍历 `/proc/*/fd` —— 捕获 BCC 风格的 perf_event 附加,此时程序可以通过打开的文件描述符访问,但没有 `bpf_link` 对象。 - **`BPF_LINK_GET_NEXT_ID`** + `BPF_OBJ_GET_INFO_BY_FD` —— 捕获被固定的 link(包括具有具体附加点的 raw_tracepoint / perf_event 变体)。 仅报告具备 hook 能力的类型:`KPROBE`, `TRACEPOINT`, `RAW_TRACEPOINT`, `RAW_TP_WRITABLE`, `PERF_EVENT`, `TRACING`, `LSM`, `SYSCALL`。纯网络类型(XDP, SOCKET_FILTER, SCHED_CLS, …)被有意忽略。 ### 非 BPF kprobes `/sys/kernel/debug/kprobes/list` 枚举了内核中每个活动的 kprobe/kretprobe —— 包括由非 BPF 工具(如 SystemTap)或自定义内核模块注册的。每一行都被解析为 `{address, type (k/r/p), symbol+offset, active, optimized, ftrace_based}`。需要 root 权限 + 挂载 debugfs。 ### uprobes `tracefs/uprobe_events` 列出了每个 uprobe,无论它是如何被附加的(BPF、perf、手动写入)。在 `/sys/kernel/tracing/`(现代)和 `/sys/kernel/debug/tracing/`(传统 debugfs 挂载)下都进行了尝试。 ### ftrace hook `tracefs/enabled_functions` 列出了当前通过 ftrace 被 hook 的每个内核函数。包含 `tramp:` 的行表示真正的代码重定向(rootkit 滥用 ftrace 是一种流行的 LKM hooking 技术,因为它绕过了直接的函数修补);没有包含 `tramp:` 的行则是被动的 tracer。只有带有 trampoline 的条目才会计入 hook 总数。 `tracefs/current_tracer` 会单独读取 —— 如果它是 `nop` 以外的任何内容,扫描器就会发出一个警告条目,因为在生产系统上开启全局函数追踪是不寻常的。 ### VDSO 篡改 内核将相同的、位置无关的 `[vdso]` 镜像(`gettimeofday`, `clock_gettime`, `getcpu`)映射到每个进程中,因此其字节在给定架构的所有进程中都是相同的。修补某个进程 vdso 的 rootkit 会破坏写时复制 (COW),并使该进程拥有一个私有的、被修改过的页面,该页面**没有磁盘基线**可供对比。扫描器通过 `/proc/PID/mem` 读取每个可读的 `[vdso]`,根据 `(length, FNV-1a hash)` 对它们进行分组,并标记任何在**相同长度**(相同长度 ⇒ 相同架构,因此内容不同即为篡改信号;长度不同仅仅是 32 位与 64 位进程的区别,属于预期情况)的 vdso 中占绝对少数的任何变体。 局限性:在 COW 之前应用的*全局*补丁会同样地改变每个进程,从而不会留下任何少数派供标记 —— 检测该情况需要可信的基线,且超出了本文的讨论范围。CRIU 恢复的进程可以合理地拥有一个存在差异的 vdso 代理,因此应将命中视为“需要调查”,而不是确凿证据。 ### 活动的 LSM `/sys/kernel/security/lsm` 是一个以逗号分隔的已激活 LSM 列表(按加载顺序排列)。已知的良好名称会被静默接受;其他任何内容都会被标记。在 JSON 模式下总是会输出完整的列表,以便机器消费者验证顺序。 ### 受污染的内核模块 `/proc/modules` 被解析以获取尾部括号内的污染标志。扫描器将每个模块归类为以下内容的某种组合: - `O` = 树外模块 - `E` = 未签名 - `F` = 强制加载 - `P` = 专有 树外 + 未签名 + 强制加载的模块会被标记为值得调查(恶意的 LKM 几乎总是同时具备这三者)。仅具有专有标志是信息性的(涵盖了合法的供应商驱动程序)。 ## 输出 ### 文本模式 (使用 `-v`) ``` ======================================================== Multi-Arch EDR Hook Detector (ARM64 + x86 / x86-64) ======================================================== [+] No /etc/ld.so.preload [*] Scanning eBPF kernel hooks... security_file_open [TRACING] prog=falco_filopen do_unlinkat [KPROBE] prog=kp_unlink 2 kernel hook(s) found (out of 38 eBPF program(s) seen) [*] Scanning kprobes... ffffffff8108a2c0 k __x64_sys_open+0x0 [FTRACE] ffffffff810b5450 r __x64_sys_kill+0x0 2 kprobe(s) registered (2 active) [*] Scanning uprobes... [+] No uprobes registered [*] Scanning ftrace hooks... 14 ftrace hook(s), 0 with custom trampoline — run with -v for the list [*] Active LSMs... capability yama apparmor bpf [*] Scanning kernel modules... nvidia taint=[OE] out-of-tree UNSIGNED vboxdrv taint=[OE] out-of-tree UNSIGNED 187 module(s) loaded, 2 flagged (out-of-tree / unsigned / forced) [*] Checking VDSO consistency... [+] VDSO consistent across 42 process(es) (1 variant) [*] Scanning GOT/PLT for hijacks... [+] No GOT/PLT hijacks detected Scanning processes... [!] PID 1234 (sshd): 2 hook(s) ======================================================== SUMMARY ======================================================== Processes scanned: 42 Processes w/ userland hooks: 1 Userspace hooks: 2 GOT/PLT hijacks: 0 eBPF kernel hooks: 2 Active kprobes: 2 uprobes: 0 ftrace trampoline hooks: 0 Unknown LSMs: 0 Out-of-tree/unsigned mods: 2 VDSO anomalies: 0 -------------------------------------------------------- Total signals: 8 [!] Suspicious activity found — investigate above Run with -v for the full per-source listings Run with -x to see userland instruction hexdumps ======================================================== ```
标签:Docker镜像, EDR, Hakrawler, Hook检测, Linux内核, 动态分析, 客户端加密, 脆弱性评估