370rokas/disk-analysis

GitHub: 370rokas/disk-analysis

一款基于 The Sleuth Kit 构建的磁盘镜像分析与文件提取 CLI 工具,支持多种输出格式和 Lua 脚本自动化。

Stars: 1 | Forks: 0

# disk-analysis (da) 一个用于磁盘镜像分析和文件提取的终端工具。基于 [The Sleuth Kit (TSK)](https://www.sleuthkit.org/) 构建,支持人类可读格式、CSV 和 JSON 输出,并带有用于自动化工作流的 Lua 脚本引擎。 ## 快速开始 ``` # 列出磁盘镜像上的分区 disk-analysis disk.img ls # 显示 partition 1 的文件树 disk-analysis disk.img tree 1 # 从 partition 1 提取文件 disk-analysis disk.img extract 1 /etc/passwd ./passwd.txt # 对镜像运行 Lua 脚本 disk-analysis disk.img script ./scripts/largest_files.lua ``` ## 用法 ``` disk-analysis [options] [args] ``` `` 是磁盘镜像文件(`.img`、`.bin`、`.dd` 等)或物理设备节点的路径。它必须存在且可读。 ### 全局选项 | Flag | Description | |---|---| | `-j, --json` | 以 JSON 格式输出 | | `-c, --csv` | 以 CSV 格式输出 | | `-l, --log ` | 将日志消息写入文件 | | `--lc, --console` | 将日志消息打印到控制台 | `--json` 和 `--csv` 是互斥的。默认输出为人类可读的文本。 ### `ls` — 列出分区 ``` disk-analysis [options] ls ``` 列出镜像中找到的每个非元数据分区。如果没有检测到分区表(例如,格式化为单个文件系统的原始 U 盘),则整个磁盘将作为 ID 为 `0` 的单个条目显示。 **人类可读输出:** ``` ID Name Byte Offset Byte Length Has Filesystem Filesystem Type 1 Linux (0x83) 1048576 10737418240 Y ext4 2 Linux swap 10738466816 2147483648 N None ``` **JSON 输出** (`--json`): ``` [ { "id": 1, "name": "Linux (0x83)", "byte_offset": 1048576, "byte_length": 10737418240, "has_filesystem": true, "fs_type": "ext4" }, { "id": 2, "name": "Linux swap", "byte_offset": 10738466816, "byte_length": 2147483648, "has_filesystem": false, "fs_type": "None" } ] ``` **CSV 输出** (`--csv`): ``` id,name,byte_offset,byte_length,has_filesystem,fs_type 1,"Linux (0x83)",1048576,10737418240,true,"ext4" 2,"Linux swap",10738466816,2147483648,false,"None" ``` ### `tree` — 显示文件系统树 ``` disk-analysis [options] tree ``` 递归遍历 `` 上的文件系统,并打印每个文件和目录。请使用 `ls` 获取的 ID 作为 ``。 未分配的条目、`.`、`..` 以及 NTFS 的 `$OrphanFiles` 将被排除。硬链接的 inode 只显示一次;后续的出现将显示为指向首次出现的链接。 **人类可读输出:** ``` / (size: 0, dir) bin (size: 4096, dir) bash (size: 1234576, file) ls (size: 147912, file) etc (size: 4096, dir) hostname (size: 12, file) passwd (size: 2048, file) usr (size: 4096, dir) bin (size: 4096, dir) → [link: /bin] ``` **JSON 输出** (`--json`): ``` { "name": "", "size": 0, "is_directory": true, "children": [ { "name": "etc", "size": 4096, "is_directory": true, "children": [ { "name": "passwd", "size": 2048, "is_directory": false } ] }, { "name": "lib", "size": 0, "is_directory": true, "is_link": true, "link_target": "/usr/lib" } ] } ``` **CSV 输出** (`--csv`) — `name` 列包含完整路径: ``` name,size,is_directory,linksTo "/etc",4096,true,null "/etc/passwd",2048,false,null "/lib",0,true,/usr/lib ``` ### `extract` — 提取文件 ``` disk-analysis extract ``` 从 `` 中的 ``(文件系统内的绝对路径)提取单个文件,并将其写入主机上的 ``。输出格式标志对此子命令无效。 ``` # 从 partition 1 提取 /etc/shadow disk-analysis disk.img extract 1 /etc/shadow ./shadow.txt # 从 NTFS 镜像中提取二进制文件 disk-analysis disk.img extract 2 /Windows/System32/cmd.exe ./cmd.exe ``` ### `script` — 运行 Lua 脚本 ``` disk-analysis script [settings] ``` 对加载的镜像执行 Lua 脚本。可选的 `settings` 字符串将原样通过 `da.settings` 传递给脚本,可以携带任意配置(路径、标志等)。 ``` # 运行脚本 disk-analysis disk.img script ./scripts/carve_images.lua # 使用设置字符串运行脚本 disk-analysis disk.img script ./scripts/export.lua "out=/tmp/export,ext=jpg" ``` ## Lua 脚本 脚本可以访问 `da` 全局表,该表公开了镜像和 TSK 文件系统 API。 ### 全局:`da` | Name | Type | Description | |---|---|---| | `da.image_path` | string | 在命令行传递的磁盘镜像路径 | | `da.settings` | string | 在脚本路径之后传递的可选设置参数 | | `da.list_partitions()` | function | 返回一个包含 `PartitionInfo` 对象的表(数组) | | `da.open_fs(id)` | function | 打开分区 `id` 上的文件系统,返回一个 `FileSystem` | | `da.log_info(msg)` | function | 在 INFO 级别记录日志 | | `da.log_warn(msg)` | function | 在 WARN 级别记录日志 | | `da.log_error(msg)` | function | 在 ERROR 级别记录日志 | ### `PartitionInfo` 字段为只读。通过 `da.list_partitions()` 获取实例。 | Field | Type | Description | |---|---|---| | `.id` | integer | 分区 ID(与 `da.open_fs` 和 `tree`/`extract` 配合使用) | | `.name` | string | 分区表中对该分区的描述 | | `.byte_offset` | integer | 分区的起始字节位置 | | `.byte_length` | integer | 分区的大小(以字节为单位) | | `.has_filesystem` | boolean | 是否找到了可识别的文件系统 | | `.fs_type` | string | 文件系统类型名称(例如 `"ext4"`、`"ntfs"`、`"fat32"`) | ### `FileSystem` 通过 `da.open_fs(id)` 获取。 | Method | Returns | Description | |---|---|---| | `fs:root()` | `FSEntry` | 文件系统的根目录条目 | | `fs:extract(src, dest)` | boolean | 将 `src`(绝对文件系统路径)处的文件提取到主机上的 `dest` | ### `FSEntry` | Method | Returns | Description | |---|---|---| | `entry:name()` | string | 文件或目录名 | | `entry:full_path()` | string | 文件系统内的绝对路径(例如 `/etc/passwd`) | | `entry:size()` | integer | 大小(以字节为单位,目录为 0) | | `entry:is_directory()` | boolean | | | `entry:is_valid()` | boolean | 如果此条目是硬链接占位符,则为 `false` | | `entry:is_link()` | boolean | 对于首次出现之后的硬链接 inode,为 `true` | | `entry:link_target()` | string | 首次出现的路径(仅在 `is_link()` 为 true 时有意义) | | `entry:load_all_descendants()` | — | 递归加载所有子项到内存中 | | `entry:children()` | table | `{ name → FSEntry }` 直接子项的映射(触发延迟加载) | ### 示例脚本 ``` -- Export all .jpg files from every partition to /tmp/export/ local partitions = da.list_partitions() for _, p in ipairs(partitions) do if not p.has_filesystem then goto continue end da.log_info(string.format("Scanning partition %d (%s)", p.id, p.fs_type)) local ok, fs = pcall(da.open_fs, p.id) if not ok then goto continue end local root = fs:root() root:load_all_descendants() local function walk(entry) if entry:is_link() or not entry:is_valid() then return end if entry:is_directory() then for _, child in pairs(entry:children()) do walk(child) end elseif entry:name():match("%.jpg$") then local dest = "/tmp/export" .. entry:full_path() if fs:extract(entry:full_path(), dest) then da.log_info("Exported: " .. entry:full_path()) else da.log_warn("Failed: " .. entry:full_path()) end end end walk(root) ::continue:: end ``` ## 日志 日志默认关闭。使用任一标志启用它 — 两者可以同时使用: ``` # 记录到文件 disk-analysis -l analysis.log disk.img tree 1 # 记录到控制台 disk-analysis --console disk.img ls # 同时记录到两者 disk-analysis -l analysis.log --console disk.img script carve.lua ``` ## 前置条件 在构建之前,请确保已安装以下内容: * **构建工具:** [CMake](https://cmake.org/download/) (3.10+) 和 C++20 编译器。 * **包管理器:** [VCPKG](https://learn.microsoft.com/en-us/vcpkg/get_started/overview)。 * **库:** **SleuthKit (TSK)** * **Linux:** `sudo apt install libtsk-dev` * **macOS:** `brew install sleuthkit` * **Windows:** 请遵循[编译说明](https://github.com/sleuthkit/sleuthkit/blob/develop/INSTALL.txt)。 ## 构建 ``` # 1. Clone the repository git clone https://github.com/370rokas/disk-analysis.git cd disk-analysis # 2. 安装 vcpkg 依赖 vcpkg install # 3. 配置并构建 cmake -B build cmake --build build # 4. 运行 ./build/disk-analysis --help ``` 要在启用 AddressSanitizer 的情况下构建(仅限开发环境): ``` cmake -B build -DENABLE_ASAN=ON cmake --build build ``` ## 项目结构 ``` src/ ├── main.cpp # Entry point — parses CLI and dispatches to actions ├── defs.hpp # ActionType enum ├── core/ # TSK wrappers and domain types │ ├── disk.hpp # Disk image (TSK_IMG_INFO) │ ├── volume.hpp # Partition table (TSK_VS_INFO) + PartitionInfo │ ├── filesystem.hpp # Filesystem handle (TSK_FS_INFO) │ ├── fsEntry.hpp/cpp # File/directory entry with lazy child loading │ ├── context.hpp # Global singleton — config, disk handle, inode map │ └── logger.hpp # spdlog initialisation ├── actions/ # Business logic │ ├── partitions.hpp/cpp # Partition listing and filesystem access │ └── extract.hpp/cpp # File extraction (shared by CLI and Lua) ├── ui/cli/ # Command-line interface │ ├── cli.hpp # CLI11 parser + CliConfig struct │ └── wrappers.hpp # Output formatters for each subcommand └── scripting/ # Lua integration ├── lua.hpp/cpp # sol2 bindings and script runner scripts/ # Example Lua scripts ``` ## TODO - [X] 实现基本的 TSK 包装器。 - [X] 实现对不同输出格式的支持(人类可读、CSV、JSON)。 - [X] 实现 LUA 脚本支持(`da.list_partitions`、`da.open_fs`、`da.extract`、日志绑定)。 - [X] 实现基本子命令: - [X] `ls`:列出分区。 - [X] `tree`:显示文件系统树。 - [X] `extract`:提取特定文件。 - [X] 创建文档和使用示例。 - [ ] 核心功能的自动化测试。 - [X] 自动化构建和发布。 - [ ] (想法):用于 AI 代理的 MCP 服务器。 - [ ] (想法):带有实时文件系统浏览的交互式 TUI。 - [ ] (想法):支持 Windows 注册表配置单元和其他非文件系统数据结构。 ## 许可证 本项目基于 MIT 许可证授权 - 有关详细信息,请参阅 [LICENSE](LICENSE) 文件。
标签:Bash脚本, Linux安全, Lua, rizin, The Sleuth Kit, 数字取证, 数据提取, 磁盘分析, 自动化脚本