
Zero-dependency Linux memory forensics, leveraging kernel-embedded BTF and kallsyms for type-aware memory analysis without external debug info.
mquire, a play on the memory and inquire words, is a memory querying tool inspired by osquery.
mquire can analyze Linux kernel memory snapshots without requiring external debug symbols.
Everything needed for analysis is already embedded in the memory dump itself. This means you can analyze:
Kernel version requirements:
scripts/kallsyms.c format)mquire analyzes kernel memory by reading two types of information that are embedded in modern Linux kernels:
/proc/kallsyms)By combining type information with symbol locations, mquire can find and read complex kernel data structures like:
This makes it possible to extract files directly from the kernel's file cache, even if they've been deleted from disk.
The Kallsyms scanner depends on the data format from scripts/kallsyms.c in the kernel source. If future kernel versions change this format, the scanner heuristics may need updates.
mquire provides SQL tables to query different aspects of the system or the state of the tool itself.
mquire is not a database. Each query reconstructs kernel data structures by scanning memory and following pointers. There are no precomputed indexes or cached results: every table access is a traversal of kernel data. Use
AS MATERIALIZEDto avoid redundant scans (see Query Optimization), and provide constraints liketaskwhen querying per-process tables liketask_open_filesandmemory_mappingsto limit the scan to a single process.
Design principle: virtual addresses as join keys. Tables use
virtual_address(the kernel address of the underlying data structure) as the canonical join key: notpidor other user-visible identifiers. This is intentional, because the same PID can appear multiple times across different discovery sources and root tasks, while a virtual address uniquely identifies a specific kernel object. Both the SQL tables and the underlyingLinuxOperatingSystemAPI are built around this convention.
/proc/kallsyms)dmesg command)task constraint for targeted analysis, or query all tasks at once)task constraint for targeted analysis, or query all tasks at once)task constraint, so join it against tasks/processes (e.g. JOIN task_capabilities c ON c.task = p.virtual_address).task constraint.mem entries from a kernel module object. Requires a kernel_module constraint, so join it against kernel_modules (e.g. JOIN kernel_module_mem_entries r ON r.kernel_module = m.virtual_address).struct ftrace_ops nodes, walked from the ftrace_ops_list symbol by default. Constrain virtual_address to read a single node, or start_vaddr (optionally bounded by end_vaddr) to walk from an arbitrary node.mquire provides three main commands:
mquire shell - Start an interactive SQL shell to query memory snapshotsmquire query - Execute a single SQL query and output results (supports JSON or table format)mquire command - Execute custom commands on memory snapshots (e.g., .task_tree, .system_version, .dump)mquire provides special commands prefixed with a dot (.) to distinguish them from SQL queries.
These commands work in the interactive shell and with mquire query:
.tables - List all available tables.schema - Show schema for all tables.schema <table> - Show schema for a specific table.commands - List all available custom commands.exit - Exit the interactive shell (shell only)These commands work in the interactive shell and with mquire command:
Use --help with any command to see available options and usage information. For example: .task_tree --help
.system_versionDisplay the operating system version information.
This is a convenience command equivalent to SELECT * FROM os_version, but with formatted output.
.task_treeDisplay a hierarchical tree of running processes and threads, similar to the pstree command on Linux.
Options:
--show-threads - Include threads in addition to processes. When enabled, displays both TGID and TID for each entry.--use-real-parent - Use the real_parent field instead of parent for building the tree structure. The real_parent field shows the original parent process before any reparenting (useful for tracking process creation chains even after parent processes exit).Notes:
[TGID TID] when showing threads, or [TGID] when threads are hidden. TGID (Thread Group ID) is what's commonly called PID. For main threads (where TGID == TID), both values will be the same..carveCarve a region of virtual memory to disk. This command extracts raw memory content from a specific virtual address range using a given page table, useful for extracting process memory, heap contents, or other memory regions.
Arguments:
ROOT_PAGE_TABLE - The physical address of the root page table (hex string with optional 0x prefix). This determines the address space to use for translation.VIRTUAL_ADDRESS - The virtual address to start carving from (hex string with optional 0x prefix).SIZE - Number of bytes to carve.DESTINATION_PATH - Output file path where the carved memory will be written.Notes:
.dumpExtract files from the kernel's file cache to recover files directly from memory. This command iterates through all tasks and their open file descriptors, extracting file contents from the page cache.
Arguments:
OUTPUT - Output directory for extracted files. Files are organized by TGID (e.g., tgid_1234/path/to/file).Notes:
mquire is designed for:
Pre-built packages are available as artifacts from CI runs. You can download them from the Actions tab by selecting a successful workflow run and downloading the artifacts. The following package formats are available:
.deb package.rpm package.tar.gz archivemquire is written in Rust. To build it:
# Clone the repository
git clone https://github.com/trailofbits/mquire
cd mquire
# Build the project
cargo build --release
# The binary will be in target/release/
# - mquire: Unified tool with shell, query, and command modes
mquire supports the following memory snapshot formats, detected by file extension:
We recommend AVML for acquiring memory snapshots from live Linux systems. LiME was previously suggested but is no longer actively maintained.
sudo avml output.lime
Important: Do not use
--compresswhen acquiring snapshots for mquire. mquire does not support compressed AVML snapshots. If you have a compressed snapshot, useavml-convertto decompress it first:avml-convert compressed.lime uncompressed.lime
See the AVML documentation for additional options.
For libvirt/KVM virtual machines, use virsh dump to produce an ELF core dump:
virsh -c qemu:///system dump <domain> output.elf --memory-only --format elf
The VM is paused during the dump and resumed after. Add --live to avoid pausing, at the cost of snapshot consistency.
Once you have a memory snapshot, you can interact with it using SQL queries and custom commands. mquire provides three ways to interact with snapshots:
Start an interactive SQL shell:
mquire shell /path/to/memory.raw
This opens a prompt where you can run both SQL queries and commands interactively:
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
Execute a single SQL query or built-in command from the command line:
# 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"
Run custom commands for specialized analysis:
# List all available commands (default behavior)
mquire command /path/to/memory.raw
# Display system version
mquire command /path/to/memory.raw ".system_version"
# Show process tree
mquire command /path/to/memory.raw ".task_tree"
# Show process tree with threads
mquire command /path/to/memory.raw ".task_tree --show-threads"
# Get help for a command
mquire command /path/to/memory.raw ".task_tree --help"
mquire automatically loads and executes SQL files from $HOME/.config/trailofbits/mquire/autostart/ on startup. Files are organized by operating system and architecture:
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
Files within each directory are sorted alphabetically. Directories are scanned in the order shown above.
Features:
.sql extensionmquire_diagnostics but don't block executionmquire shell and mquire query commandsmquire ships reusable SQL views in the sql/views/ directory. Install them with just install-views. See the views README for the full list, numbering convention, and directory structure.
Linux views (sql/views/linux/common/):
000_processes.sql - Deduplicated process list across all discovery sources, filtered to user-space process leaders. Query with SELECT * FROM processes.100_process_network_connections.sql - Maps network connections to owning processes by joining through file descriptors. Query with SELECT * FROM process_network_connections WHERE comm = 'sshd'.120_process_capabilities.sql - Quick-peek capability overview, one row per process with each set as a compact list (ALL, '', or cap names; NULL if unreadable). Query with SELECT * FROM process_capabilities.mquire queries require reconstructing kernel data structures from virtual memory by dereferencing pointers using embedded type information and debug symbols. This processing can be expensive, so use query optimization techniques to improve performance dramatically.
AS MATERIALIZEDUse the AS MATERIALIZED hint to cache table results when tables are used in JOINs or accessed multiple times.
When to materialize:
tasks requires walking linked lists of process structures, dereferencing multiple pointers per process)Example:
-- 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;
Note: The task_open_files and memory_mappings tables use the task column as a generator input. When joined with the tasks table, SQLite automatically passes the constraint via nested loop joins, making direct JOINs efficient.
Performance impact: Materialization can provide significant speedup for queries with JOINs (typically 2-5x faster)
Example benchmark results:
Test performed on an Ubuntu 24.04 snapshot (kernel 6.8.0-63), 351 processes, 50 connections, 2142 open files. Performance will vary based on snapshot size, kernel version, and hardware.
| Method | Real Time | User Time | Speedup |
|---|---|---|---|
| WITHOUT materialization | 12.067s | 16.373s | baseline |
| WITH materialization | 3.171s | 8.786s | 3.8x faster |
Start with the smallest table and JOIN toward larger tables to minimize rows processed early in the query pipeline.
Typical table sizes:
network_connections: smallest - only processes with network activitytasks: medium - all processestask_open_files: largest - all open file descriptorsOptimal order:
Start with the filtered tasks table and join toward larger tables:
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
Use EXPLAIN QUERY PLAN to see how SQLite executes your query:
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;
Look for:
EXPLAIN QUERY PLAN for complex queriesSELECT * in production - specify only needed columnsExtract files from memory to disk:
mquire command /path/to/memory.raw ".dump /output/directory"
All queries use standard SQL syntax.
$ 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"
$ 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:"<null>"
name:"qrtr" state:"live" src_version:"473C5AB47E04ECEA0106681" parameters:"<null>"
name:"virtio_rng" state:"live" src_version:"0852940240D554836D22CB2" parameters:"<null>"
name:"intel_rapl_msr" state:"live" src_version:"34853C4F5EB8FCAD28ACFB3" parameters:"<null>"
$ 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"
Find network connections for a specific process by joining tasks, task_open_files, and network_connections.
$ 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:"<null>" remote_port:"<null>" state:"listen"
tgid:"1134" comm:"sshd" protocol:"tcp" local_address:"::" local_port:"22" remote_address:"<null>" remote_port:"<null>" state:"listen"
List open files for specific processes by joining tasks with task_open_files:
$ 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"
}
]
$ 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
$ 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
Note: When multiple task_struct entries exist with the same TID (Thread ID, which can occur due to memory corruption or snapshot timing), duplicate entries are displayed with the continuation symbol ╎ ↳ indented under the primary entry. The format is [TGID] (virtual_address) name when threads are hidden, or [TGID TID] when showing threads (where TGID is the Thread Group ID, commonly known as PID).
$ 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)
...
Use the --debug flag to enable verbose debug messages during initialization and analysis:
mquire --debug command /path/to/memory.raw ".system_version"
shell and query modes: Debug messages are stored in the mquire_diagnostics SQL table. Query them with SELECT * FROM mquire_diagnostics;command mode: Debug messages are printed directly to stderr.For initialization issues that prevent mquire from successfully loading the snapshot, using command mode with a simple command like .system_version is recommended, as it prints debug output to stderr immediately without needing to query the mquire_diagnostics table.
mquire can be configured via a TOML file at $HOME/.config/trailofbits/mquire/config.toml. If the file does not exist, default values are used.
[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
This project uses just as a command runner. Run just to see available commands:
These tests verify mquire produces correct output for queries against memory snapshots.
just integration-test - Run tests and compare output to expected JSON filesjust integration-update - Update expected JSON files with actual output (use when changing table schemas)After running integration-update, review the git diff to ensure changes match your expectations before committing.
Adding new tests: Create a .sql file and matching .json file in the appropriate snapshot directory, then run just integration-update to populate the expected output.
Contributions are welcome! When contributing, please follow these guidelines:
init_task virtual addressThis project is licensed under the Apache License 2.0. See the LICENSE file for details.
| Format | Extension | Description |
|---|
| Raw | .raw | Flat physical memory dump (byte-for-byte copy of physical address space) |
| LiME | .lime | Linux Memory Extractor format with address range headers |
| ELF core | .elf | ELF core dump with PT_LOAD segments (as produced by virsh dump or QEMU dump-guest-memory) |
130_process_ptrace_flags.sqlPT_*''SELECT * FROM process_ptrace_flags200_tasklist_pidns_differences.sql - Detects processes visible in one discovery source but missing from another, useful for rootkit detection. Query with SELECT * FROM tasklist_pidns_differences.230_unbacked_ftrace_ops.sql - Registered ftrace callbacks pointing outside the kernel text and every module listed in kernel_modules. Query with SELECT * FROM unbacked_ftrace_ops.| Command | Description |
|---|
just check | Run all checks (cargo check, cargo clippy, cargo fmt, ruff, mypy) |
just test | Run unit tests |
just format | Format code (cargo fmt, ruff) |
just integration-test | Run SQL query integration tests |
just integration-update | Update expected test output |
just package | Build release packages |