零依赖的Linux内存取证,利用内核嵌入的BTF和kallsyms进行类型感知的内存分析,无需外部调试信息。
mquire,由 memory 和 inquire 组合而成,是一款受 osquery 启发而开发的内存查询工具。
mquire 可以在不需要外部调试符号的情况下分析 Linux 内核内存快照。
分析所需的一切都已嵌入内存转储本身。这意味着你可以分析:
内核版本要求:
scripts/kallsyms.c 格式的变更)mquire 通过读取现代 Linux 内核中嵌入的两种信息来分析内核内存:
/proc/kallsyms 使用的数据相同)通过结合类型信息和符号位置,mquire 可以查找和读取复杂的内核数据结构,例如:
这使得直接从内核的文件缓存中提取文件成为可能,即使这些文件已从磁盘中删除。
Kallsyms 扫描器依赖于内核源码中 scripts/kallsyms.c 的数据格式。如果未来内核版本更改了此格式,扫描器的启发式方法可能需要更新。
mquire 提供了 SQL 表来查询系统的不同方面或工具自身的状态。
mquire 不是一个数据库。 每次查询都通过扫描内存并跟随指针来重建内核数据结构。没有预计算的索引或缓存结果:每次表访问都是对内核数据的遍历。使用
AS MATERIALIZED可避免冗余扫描(请参阅查询优化),并在查询每个进程的表(如task_open_files和memory_mappings)时提供task等约束,以将扫描限制为单个进程。
设计原则:虚拟地址作为连接键。 表使用
virtual_address(底层数据结构的内核地址)作为规范的连接键,而不是pid或其他用户可见的标识符。这是有意为之,因为相同的 PID 可能在不同的发现源和根任务中出现多次,而虚拟地址唯一标识一个特定的内核对象。SQL 表和底层的LinuxOperatingSystemAPI 都围绕这一约定构建。
/proc/kallsyms 相同的数据)dmesg 命令相同的数据)task 约束进行针对性分析,或一次性查询所有任务)task 约束进行针对性分析,或一次性查询所有任务)task 约束,因此请与 tasks/processes 进行连接(例如 JOIN task_capabilities c ON c.task = p.virtual_address)。task 约束。mem 条目。需要 kernel_module 约束,因此请与 kernel_modules 进行连接(例如 JOIN kernel_module_mem_entries r ON r.kernel_module = m.virtual_address)。ftrace_ops_list 符号遍历的 struct ftrace_ops 节点列表。约束 virtual_address 以读取单个节点,或约束 start_vaddr(可选地通过 end_vaddr 限制范围)以从任意节点开始遍历。mquire 提供三个主要命令:
mquire shell - 启动交互式 SQL shell 以查询内存快照mquire query - 执行单个 SQL 查询并输出结果(支持 JSON 或表格格式)mquire command - 对内存快照执行自定义命令(例如 .task_tree、.system_version、.dump)mquire 提供了以点 (.) 为前缀的特殊命令,以区别于 SQL 查询。
这些命令可在交互式 shell 和 mquire query 中使用:
.tables - 列出所有可用表.schema - 显示所有表的模式.schema <table> - 显示特定表的模式.commands - 列出所有可用的自定义命令.exit - 退出交互式 shell(仅限 shell)这些命令可在交互式 shell 和 mquire command 中使用:
对任何命令使用 --help 可查看可用选项和用法信息。例如:.task_tree --help
.system_version显示操作系统版本信息。
这是一个便捷命令,等同于 SELECT * FROM os_version,但输出格式更美观。
.task_tree显示运行中进程和线程的层次结构树,类似于 Linux 上的 pstree 命令。
选项:
--show-threads - 除进程外还包括线程。启用时,显示每个条目的 TGID 和 TID。--use-real-parent - 使用 real_parent 字段而不是 parent 来构建树结构。real_parent 字段显示任何重新父进程之前的原始父进程(即使在父进程退出后,也对于跟踪进程创建链很有用)。注意:
[TGID TID](显示线程时)或 [TGID](隐藏线程时)。TGID(线程组 ID)通常称为 PID。对于主线程(TGID == TID),两个值相同。.carve将虚拟内存区域提取到磁盘。此命令使用给定的页表从特定的虚拟地址范围提取原始内存内容,适用于提取进程内存、堆内容或其他内存区域。
参数:
ROOT_PAGE_TABLE - 根页表的物理地址(十六进制字符串,可带有 0x 前缀)。这决定了用于地址转换的地址空间。VIRTUAL_ADDRESS - 开始提取的虚拟地址(十六进制字符串,可带有 0x 前缀)。SIZE - 要提取的字节数。DESTINATION_PATH - 输出文件路径,提取的内存将写入此路径。注意:
.dump从内核的文件缓存中提取文件,以直接从内存中恢复文件。此命令遍历所有任务及其打开的文件描述符,从页缓存中提取文件内容。
参数:
OUTPUT - 提取文件的输出目录。文件按 TGID 组织(例如 tgid_1234/path/to/file)。注意:
mquire 专为以下场景设计:
预构建包可作为 CI 运行的构件提供。您可以从 Actions 选项卡 下载,方法是选择一次成功的 workflow 运行并下载构件。支持以下包格式:
.deb 包.rpm 包.tar.gz 存档mquire 使用 Rust 编写。要构建它:```bash
git clone https://github.com/trailofbits/mquire cd mquire
cargo build --release
## 支持的快照格式
mquire 支持以下内存快照格式,通过文件扩展名检测:
| 格式 | 扩展名 | 描述 |
|--------|-----------|-------------|
| 原始 | `.raw` | 平坦物理内存转储(物理地址空间的逐字节副本) |
| LiME | `.lime` | [Linux 内存提取器](https://github.com/504ensicsLabs/LiME) 格式,带有地址范围头部 |
| ELF 内核 | `.elf` | 包含 PT_LOAD 段的 ELF 内核转储(由 `virsh dump` 或 QEMU `dump-guest-memory` 生成) |
## 获取内存快照
### 使用 AVML(推荐)
我们推荐使用 [AVML](https://github.com/microsoft/avml) 从运行中的 Linux 系统获取内存快照。之前曾推荐过 [LiME](https://github.com/504ensicsLabs/LiME),但它已不再积极维护。```bash
sudo avml output.lime
重要提示: 在获取用于 mquire 的快照时,不要使用
--compress。mquire 不支持压缩的 AVML 快照。如果您有压缩的快照,请先使用avml-convert解压缩:avml-convert compressed.lime uncompressed.lime
有关其他选项,请参阅 AVML 文档。
对于 libvirt/KVM 虚拟机,请使用 virsh dump 生成 ELF 核心转储:```bash
virsh -c qemu:///system dump output.elf --memory-only --format elf
VM 在转储期间暂停,之后恢复。添加 `--live` 可避免暂停,但会牺牲快照一致性。
## 入门
获得内存快照后,你可以使用 SQL 查询和自定义命令与之交互。mquire 提供了三种与快照交互的方式:
### 交互式 shell
启动交互式 SQL shell:```bash
mquire shell /path/to/memory.raw
这会打开一个提示符,您可以在其中交互式地运行SQL查询和命令:```bash mquire> .tables # List all available tables mquire> .schema tasks # Show schema for a specific table mquire> SELECT * FROM tasks; # Run SQL queries mquire> .task_tree --show-threads # Run custom commands mquire> .exit # Exit the shell
### 一次性SQL查询
从命令行执行单个SQL查询或内置命令:```bash
# Output as JSON (default)
mquire query /path/to/memory.raw "SELECT * FROM os_version"
# Output as table format
mquire query /path/to/memory.raw "SELECT * FROM tasks" --format table
# Built-in commands work too
mquire query /path/to/memory.raw ".tables"
mquire query /path/to/memory.raw ".schema tasks"
运行自定义命令进行专门分析:```bash
mquire command /path/to/memory.raw
mquire command /path/to/memory.raw ".system_version"
mquire command /path/to/memory.raw ".task_tree"
mquire command /path/to/memory.raw ".task_tree --show-threads"
mquire command /path/to/memory.raw ".task_tree --help"
## Autostart SQL Files
mquire 在启动时会自动加载并执行 `$HOME/.config/trailofbits/mquire/autostart/` 目录下的 SQL 文件。文件按操作系统和架构进行组织:```
autostart/
common/common/ # All platforms and architectures
common/{arch}/ # All platforms, specific architecture
{os}/common/ # Specific platform, all architectures
{os}/{arch}/ # Specific platform and architecture
每个目录内的文件按字母顺序排序。目录按上述顺序扫描。
特性:
.sql 扩展名mquire_diagnostics,但不会阻止执行mquire shell 和 mquire query 命令mquire 在 sql/views/ 目录中附带可复用的 SQL 视图。使用 just install-views 安装它们。完整列表、编号约定和目录结构请参阅 views README。
Linux 视图 (sql/views/linux/common/):
000_processes.sql — 跨所有发现源的去重进程列表,过滤为用户空间进程领导者。使用 SELECT * FROM processes 查询。100_process_network_connections.sql — 通过文件描述符关联,将网络连接映射到所属进程。使用 SELECT * FROM process_network_connections WHERE comm = 'sshd' 查询。120_process_capabilities.sql — 快速查看能力概览,每进程一行,每个集合以紧凑列表形式呈现(ALL、'' 或能力名称;若不可读则为 NULL)。使用 SELECT * FROM process_capabilities 查询。130_process_ptrace_flags.sql — 快速查看 ptrace 概览,每进程一行,以紧凑列表形式呈现 标志集合(未跟踪时为 )以及原始值。使用 查询。mquire 查询需要通过使用嵌入的类型信息和调试符号解引用指针,从虚拟内存重建内核数据结构。此处理过程可能开销较大,因此使用查询优化技术可显著提升性能。
AS MATERIALIZED 进行物化当表用于 JOIN 或多次访问时,使用 AS MATERIALIZED 提示来缓存表结果。
何时物化:
tasks 需要遍历进程结构的链表,为每个进程解引用多个指针)示例:```sql -- Find network connections for a specific process using materialization WITH target_tasks AS MATERIALIZED ( SELECT * FROM tasks WHERE comm = 'sshd' AND type = 'thread_group_leader' ),
network_connections_mat AS MATERIALIZED ( SELECT * FROM network_connections )
SELECT t.tgid, t.comm, nc.local_address, nc.local_port, nc.remote_address, nc.remote_port, nc.state, nc.protocol FROM target_tasks t JOIN task_open_files tof ON tof.task = t.virtual_address JOIN network_connections_mat nc ON nc.inode = tof.inode;
**注意:** `task_open_files` 和 `memory_mappings` 表使用 `task` 列作为生成器输入。当与 `tasks` 表连接时,SQLite 通过嵌套循环连接自动传递约束条件,使得直接 JOIN 更高效。
**性能影响:** 物化可为含有 JOIN 的查询提供显著的加速(通常快 2-5 倍)
**示例基准测试结果:**
*测试在 Ubuntu 24.04 快照(内核 6.8.0-63)上执行,含 351 个进程、50 个连接、2142 个打开文件。性能会因快照大小、内核版本和硬件而异。*
| 方法 | 实时时间 | 用户时间 | 加速比 |
|--------|-----------|-----------|---------|
| 无物化 | 12.067s | 16.373s | 基准线 |
| 有物化 | 3.171s | 8.786s | **快 3.8 倍** |
### JOIN 顺序优化
**从最小的表开始,朝着较大的表进行 JOIN**,以最小化查询管道早期处理的行数。
**典型表大小:**
- `network_connections`:最小——仅包含有网络活动的进程
- `tasks`:中等——所有进程
- `task_open_files`:最大——所有打开的文件描述符
**最佳顺序:**
从经过筛选的 tasks 表开始,然后向较大的表进行 JOIN:```sql
FROM target_tasks t -- filtered tasks
JOIN task_open_files tof ON tof.task = t.virtual_address -- open files
JOIN network_connections_mat nc ON nc.inode = tof.inode -- matching connections
使用 EXPLAIN QUERY PLAN 查看 SQLite 如何执行你的查询:```sql
EXPLAIN QUERY PLAN
SELECT ...
FROM target_tasks t
JOIN task_open_files tof ON tof.task = t.virtual_address
JOIN network_connections_mat nc ON nc.inode = tof.inode;
寻找:
- **布隆过滤器**:SQLite 对大型 JOIN 的优化
- **自动覆盖索引**:为查找创建的临时索引
- **扫描**:全表扫描(驱动表预期方式)
- **搜索**:基于索引的查找(高效)
### 最佳实践
1. **始终物化 JOIN 中开销大的表**
2. **从最小的表开始**作为驱动表
3. **在脚本中使用多行 SQL** 以提高可读性
4. **使用 `EXPLAIN QUERY PLAN` 检查查询计划** 对于复杂查询
5. **避免在生产中使用 `SELECT *`** - 仅指定需要的列
### 文件提取
将文件从内存提取到磁盘:```bash
mquire command /path/to/memory.raw ".dump /output/directory"
所有查询均使用标准 SQL 语法。
$ mquire shell ubuntu2404_6.14.0-37-generic.lime mquire> SELECT * FROM os_version; arch:"x86_64" kernel_version:"6.14.0-37-generic" system_version:"#37~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Nov 20 10:25:38 UTC 2"
#### 系统信息```bash
$ mquire shell ubuntu2404_6.14.0-37-generic.lime
mquire> SELECT * FROM system_info;
domain:"(none)" hostname:"ubuntu2404"
$ mquire shell ubuntu2404_6.14.0-37-generic.lime mquire> SELECT name, state, src_version, parameters FROM kernel_modules LIMIT 5; name:"snd_seq_dummy" state:"live" src_version:"7A40E0FD47A0746D1C9CD85" parameters:"ump (perm: 0o444), duplex (perm: 0o444), ports (perm: 0o444)" name:"snd_hrtimer" state:"live" src_version:"81EE6D58896E2C2E63E252D" parameters:"" name:"qrtr" state:"live" src_version:"473C5AB47E04ECEA0106681" parameters:"" name:"virtio_rng" state:"live" src_version:"0852940240D554836D22CB2" parameters:"" name:"intel_rapl_msr" state:"live" src_version:"34853C4F5EB8FCAD28ACFB3" parameters:""
#### 运行任务```bash
$ mquire shell ubuntu2404_6.14.0-37-generic.lime
mquire> SELECT comm, binary_path, command_line FROM tasks WHERE command_line NOT NULL AND comm LIKE "%systemd%";
comm:"systemd" binary_path:"/usr/lib/systemd/systemd" command_line:"/sbin/init splash"
comm:"systemd-oomd" binary_path:"/usr/lib/systemd/systemd-oomd" command_line:"/usr/lib/systemd/systemd-oomd"
comm:"systemd-resolve" binary_path:"/usr/lib/systemd/systemd-resolved" command_line:"/usr/lib/systemd/systemd-resolved"
comm:"systemd-udevd" binary_path:"/usr/bin/udevadm" command_line:"/usr/lib/systemd/systemd-udevd"
comm:"systemd" binary_path:"/usr/lib/systemd/systemd" command_line:"/usr/lib/systemd/systemd --user"
comm:"systemd-logind" binary_path:"/usr/lib/systemd/systemd-logind" command_line:"/usr/lib/systemd/systemd-logind"
comm:"systemd-journal" binary_path:"/usr/lib/systemd/systemd-journald" command_line:"/usr/lib/systemd/systemd-journald"
comm:"systemd-timesyn" binary_path:"/usr/lib/systemd/systemd-timesyncd" command_line:"/usr/lib/systemd/systemd-timesyncd"
通过连接 tasks、task_open_files 和 network_connections,查找特定进程的网络连接。```bash $ mquire shell ubuntu2404_6.14.0-37-generic.lime mquire> SELECT t.tgid, t.comm, nc.protocol, nc.local_address, nc.local_port, nc.remote_address, nc.remote_port, nc.state FROM tasks t JOIN task_open_files tof ON tof.task = t.virtual_address JOIN network_connections nc ON nc.inode = tof.inode WHERE t.comm = 'sshd'; tgid:"1134" comm:"sshd" protocol:"tcp" local_address:"0.0.0.0" local_port:"22" remote_address:"" remote_port:"" state:"listen" tgid:"1134" comm:"sshd" protocol:"tcp" local_address:"::" local_port:"22" remote_address:"" remote_port:"" state:"listen"
#### 任务打开文件
通过连接 `tasks` 与 `task_open_files`,列出特定进程的打开文件:```bash
$ mquire shell ubuntu2404_6.14.0-37-generic.lime
mquire> SELECT t.comm, tof.path
FROM tasks t
JOIN task_open_files tof ON tof.task = t.virtual_address
WHERE t.comm LIKE '%systemd%'
LIMIT 10;
comm:"systemd" path:"/null"
comm:"systemd" path:"/null"
comm:"systemd" path:"/null"
comm:"systemd" path:"/kmsg"
comm:"systemd" path:"[eventpoll]"
comm:"systemd" path:"[signalfd]"
comm:"systemd" path:"inotify"
comm:"systemd" path:"/"
comm:"systemd" path:"[timerfd]"
comm:"systemd" path:"/usr/lib/systemd/systemd-executor"
$ mquire query --format=json ubuntu2404_6.14.0-37-generic.lime "SELECT * FROM os_version" [ { "arch": "x86_64", "kernel_version": "6.14.0-37-generic", "system_version": "#37~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Nov 20 10:25:38 UTC 2" } ]
##### 表格输出```bash
$ mquire query --format=table ubuntu2404_6.14.0-37-generic.lime "SELECT * FROM os_version"
arch:"x86_64" kernel_version:"6.14.0-37-generic" system_version:"#37~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Nov 20 10:25:38 UTC 2"
$ mquire command ubuntu2404_6.14.0-37-generic.lime Available commands: .carve Carve a region of virtual memory to disk .dump Dump all open files from tasks to disk .system_version Display the operating system version .task_tree Display a hierarchical task tree
##### 显示系统版本```bash
$ mquire command ubuntu2404_6.14.0-37-generic.lime ".system_version"
System Version: #37~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Nov 20 10:25:38 UTC 2
Kernel Version: 6.14.0-37-generic
Architecture: x86_64
$ mquire command ubuntu2404_6.8.0-63-generic.lime .task_tree | head -n 10 Parent: task_struct::parent Threads: Disabled Page Table: paddr(0x0000000001a60000)
└─ [0] (ffffffff90c0fcc0) swapper/0 ╎ ↳ [0] (ffff982a00e33518) \xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd,)\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\x0e ├─ [1] (ffff982a0084a8c0) systemd │ ├─ [430] (ffff982a0d27a8c0) systemd-journal │ ├─ [495] (ffff982a08a88000) systemd-udevd │ ├─ [786] (ffff982a07a60000) systemd-oomd
**注意:** 当存在多个拥有相同 TID(线程 ID,可能由内存损坏或快照时机导致)的 `task_struct` 条目时,重复条目会以续行符 `╎ ↳` 缩进显示在主条目下方。格式为 `[TGID] (virtual_address) name`(当隐藏线程时),或 `[TGID TID]`(当显示线程时,其中 TGID 即通常所说的 PID)。
#### 从内存中提取文件```bash
$ mquire command ubuntu2404_6.14.0-37-generic.lime ".dump ./extracted_files"
Legend: SK = skipped, OK = all good, ER = errored
Summary:
Total files processed: 1234
Successfully dumped: 1156
Skipped: 45
Errors: 33
File Status:
OK /usr/lib/systemd/systemd (TGID 1)
OK /etc/passwd (TGID 1)
SK /dev/null (TGID 1)
...
使用 --debug 标志可在初始化和分析期间启用详细的调试消息:```bash
mquire --debug command /path/to/memory.raw ".system_version"
- **`shell` 和 `query` 模式**:调试消息存储在 `mquire_diagnostics` SQL 表中。使用 `SELECT * FROM mquire_diagnostics;` 查询。
- **`command` 模式**:调试消息直接输出到 stderr。
对于阻止 mquire 成功加载快照的初始化问题,建议使用 `command` 模式并执行一个简单的命令(如 `.system_version`),因为它会立即将调试输出打印到 stderr,无需查询 `mquire_diagnostics` 表。
## 配置
mquire 可以通过位于 `$HOME/.config/trailofbits/mquire/config.toml` 的 TOML 文件进行配置。如果该文件不存在,则使用默认值。
### 可用选项和默认值```toml
[database]
# Maximum number of entries retained in the mquire_diagnostics table.
# During initialization, all entries are kept regardless of this limit.
# Once initialization completes, new log entries trigger eviction of the
# oldest entries when the total exceeds this value.
mquire_diagnostics_max_entries = 1000
本项目使用 just 作为命令运行器。运行 just 可查看可用命令:
这些测试验证 mquire 针对内存快照的查询是否能产生正确输出。
just integration-test - 运行测试并将输出与预期的 JSON 文件进行比较just integration-update - 用实际输出更新预期的 JSON 文件(在更改表模式时使用)运行 integration-update 后,请检查 git diff,确保更改符合预期,然后再提交。
添加新测试: 在相应的快照目录中创建 .sql 文件和对应的 .json 文件,然后运行 just integration-update 以填充预期的输出。
欢迎贡献!贡献时请遵循以下指南:
init_task 虚拟地址本项目采用 Apache License 2.0 许可。详情请参阅 LICENSE 文件。
PT_*''SELECT * FROM process_ptrace_flags200_tasklist_pidns_differences.sql — 检测在一个发现源中可见但在另一个中缺失的进程,适用于 rootkit 检测。使用 SELECT * FROM tasklist_pidns_differences 查询。230_unbacked_ftrace_ops.sql — 已注册的 ftrace 回调,指向内核文本和 kernel_modules 中列出的每个模块之外。使用 SELECT * FROM unbacked_ftrace_ops 查询。| 命令 | 描述 |
|---|
just check | 运行所有检查(cargo check、cargo clippy、cargo fmt、ruff、mypy) |
just test | 运行单元测试 |
just format | 格式化代码(cargo fmt、ruff) |
just integration-test | 运行SQL查询集成测试 |
just integration-update | 更新预期测试输出 |
just package | 构建发布包 |