mnrkbys/fjta
GitHub: mnrkbys/fjta
FJTA 是一款通过解析 Linux 文件系统(ext4、XFS)底层日志来重建操作时间线并检测文件删除或时间戳篡改的取证分析工具。
Stars: 104 | Forks: 9
# FJTA - Forensic Journal Timeline Analyzer
FJTA (Forensic Journal Timeline Analyzer) 是一个用于分析 Linux 文件系统(ext4、XFS)日志(而非 systemd-journald 日志)、生成时间线并检测可疑活动的工具。
## 功能
- **日志分析**:扫描 ext4 和 XFS 日志以可视化修改历史。
- **时间线生成**:按时间顺序组织日志中的事件。
- **可疑活动检测**:识别已删除的文件和可能被篡改的操作。
- **跨平台**:使用 Python 编写,允许在任何操作系统上进行分析。
## 文件系统日志中支持的产物
| 产物 | ext4 | XFS |
| -------------------------------------- | :----: | :-: |
| inode | ✅ | ✅ |
| 包含少量条目的目录 | ✅ | ✅ |
| 包含大量条目的目录 | ✅[^1] | ✅ |
| 短符号链接目标名称 | ✅ | ✅ |
| 长符号链接目标名称[^2] | ✅ | ❌ |
| 短扩展属性 | ✅ | ✅ |
| 长扩展属性[^3] | ✅[^4] | ❌ |
| 非常规文件(例如块设备) | ✅ | ✅ |
| 2038 年问题 | ✅ | ✅ |
| 导出的日志 | ✅ | ✅ |
[^1]: 目前,只能解析线性目录。未来版本将添加对哈希树目录的支持。
[^2]: 存储在 inode 外部的符号链接目标名称。
[^3]: 存储在 inode 外部的扩展属性。
[^4]: 仅识别分配给扩展属性的第一个数据块。不支持 EXT4_FEATURE_INCOMPAT_EA_INODE 标志。
## 可检测的活动
| 活动 | ext4 | XFS |
| ------------------------------------- | :--: | :-: |
| 创建文件 | ✅ | ✅ |
| 删除文件 | ✅ | ✅ |
| 修改扩展属性 | ✅ | ✅ |
| 时间戳伪造 (Timestomping) | ✅ | ✅ |
| 其他 inode 元数据更改[^5] | ✅ | ✅ |
[^5]: “其他 inode 元数据更改”包括对 MACB 时间戳(mtime、atime、ctime 和 crtime)的更新、文件大小更改以及设置文件标志等。
## 环境要求
已在以下软件和库上进行测试:
- [Python](https://www.python.org/) 3.12
- [The Sleuth Kit](https://github.com/sleuthkit/sleuthkit) 4.14.0
- [pytsk3](https://github.com/py4n6/pytsk) 20250729
- [Construct](https://github.com/construct/construct) 2.10.70
- [python-magic](https://github.com/ahupp/python-magic) 0.4.27
- [libewf-python](https://pypi.org/project/libewf-python/) 20240506
- [libvmdk-python](https://pypi.org/project/libvmdk-python/) 20240510
- [libvhdi-python](https://pypi.org/project/libvhdi-python/) 20240509
- [yaspin](https://github.com/pavdmyt/yaspin) 3.4.0
## 从源码安装
编译并安装 TSK。
```
wget https://github.com/sleuthkit/sleuthkit/releases/download/sleuthkit-4.14.0/sleuthkit-4.14.0.tar.gz
tar xvzf sleuthkit-4.14.0.tar.gz
cd sleuthkit-4.14.0
./configure
make
sudo make install
sudo echo /usr/local/lib > /etc/ld.so.conf.d/local-lib.conf
sudo ldconfig
```
然后,克隆 FJTA。
```
git clone https://github.com/mnrkbys/fjta.git
cd fjta
```
最后,安装所需的 Python 包。
```
python3 -m venv .venv
source .venv/bin/activate
pip install pytsk3 construct python-magic libewf-python libvmdk-python libvhdi-python yaspin
```
## 从包安装
从您使用的 Linux 发行版安装 TSK 包。
```
sudo apt install sleuthkit python3-tsk libewf2 libvmdk1 libvhdi1 python3-libewf python3-libvmdk python3-libvhdi
```
然后,克隆 FJTA。
```
git clone https://github.com/mnrkbys/fjta.git
cd fjta
```
最后,安装所需的 Python 包。
```
python3 -m venv .venv
source .venv/bin/activate
pip install construct python-magic yaspin
```
## 使用方法
### 基础用法
```
python ./fjta.py -i ~/ext4.img | jq
```
### 将时间线保存到文件
```
python ./fjta.py -i ~/ext4.img --output timeline.ndjson
```
当指定 `--output` 时,默认会显示时间线生成进度指示器。
使用 `--no-progress` 隐藏它们。
### 使用 inode 编号进行过滤
```
python ./fjta.py -s 0 -i ~/xfs.img | jq 'select(.inode == 101040435)' | less
```
### 使用 crtime 进行过滤
to_epoch() 函数定义在 helper.sh 文件中,因此在执行以下命令之前需要导入它。
```
source scripts/helper.sh
python ./fjta.py -s 0 -i ~/xfs.img | jq --argjson threshold $(to_epoch "2025-06-23 07:33:20.123456789") 'select(.crtime >= $threshold)'
```
### 使用文件名进行过滤
```
python ./fjta.py -s 0 -i ~/xfs.img | jq 'select(.names? and ([.names[][]] | index("backdoor.c")))'
```
### 使用字符串进行过滤
```
python ./fjta.py -s 0 -i ~/xfs.img | jq 'select(.info | contains("Added EA: security.selinux"))'
```
### 使用正则表达式模式进行过滤
```
python ./fjta.py -s 0 -i ~/xfs.img | jq 'select(.info | test("added ea: security\\.selinux"; "i"))'
```
## 示例输出(时间戳伪造)
```
...
{
"transaction_id": 3,
"action": "CREATE_INODE|CREATE_HARDLINK",
"inode": 12,
"file_type": "REGULAR_FILE",
"names": {
"2": [
"test.txt"
]
},
"mode": 420,
"uid": 0,
"gid": 0,
"size": 0,
"atime": 1729038807.9101748,
"ctime": 1729038807.9101748,
"mtime": 1729038807.9101748,
"crtime": 1729038807.9101748,
"dtime": 0.0,
"flags": 524288,
"link_count": 1,
"symlink_target": "",
"extended_attributes": [],
"device_number": {
"major": 0,
"minor": 0
},
"info": "Crtime: 2024-10-16 00:33:27.910174879 UTC|Link Count: 1"
}
...
{
"transaction_id": 23,
"action": "CREATE_INODE|ACCESS|CHANGE|MODIFY|TIMESTOMP",
"inode": 12,
"file_type": "REGULAR_FILE",
"names": {
"2": [
"test.txt"
]
},
"mode": 420,
"uid": 0,
"gid": 0,
"size": 0,
"atime": 978312225.8287878,
"ctime": 978312225.8287878,
"mtime": 978312225.8287878,
"crtime": 978312225.8287878,
"dtime": 0.0,
"flags": 524288,
"link_count": 1,
"symlink_target": "",
"extended_attributes": [],
"device_number": {
"major": 0,
"minor": 0
},
"info": "Atime: 2024-10-18 08:25:51.385837319 UTC -> 2001-01-01 01:23:45.828787850 UTC (Timestomp)|Ctime: 2024-10-18 08:25:51.385837319 UTC -> 2001-01-01 01:23:45.828787850 UTC (Timestomp)|Mtime: 2024-10-18 08:25:51.385837319 UTC -> 2001-01-01 01:23:45.828787850 UTC (Timestomp)|Crtime: 2024-10-16 00:33:27.910174879 UTC -> 2001-01-01 01:23:45.828787850 UTC (Timestomp)"
}
...
```
## 输出规范 (JSON Lines)
FJTA 每行向 stdout 写入一个 JSON 对象(JSONL/NDJSON)。
### 事件对象
每一行都是带有以下字段的时间线事件。
- `transaction_id` (integer):日志顺序中的事务标识符。
- `action` (string):由 `|` 连接的一个或多个操作标志。
- `inode` (integer):inode 编号。
- `file_type` (string):`REGULAR_FILE`、`DIRECTORY`、`SYMBOLIC_LINK` 等。
- `names` (object):目录 inode 到名称列表的映射。例如:`{"2": ["test.txt"]}`。
- `mode` (integer):十进制文件模式。
- `uid`, `gid` (integer):所有者和组 ID。
- `size` (integer):文件大小(字节)。
- `atime`, `ctime`, `mtime`, `crtime`, `dtime` (number):具有亚秒级精度的 Epoch 时间戳。
- `flags` (integer):特定于文件系统的标志位掩码。
- `link_count` (integer):硬链接计数。
- `symlink_target` (string):符号链接目标(如果可用)。
- `extended_attributes` (array):`{name, value}` 对象的列表。
- `device_number` (object):设备文件的 `{major, minor}`。
- `info` (string):用于取证解读的人类可读详细信息。
### `action` 语义
- `action` 是一种位标志表示法,通过 `|` 连接名称进行序列化。
- 不保证令牌顺序。通过在 `|` 上拆分并匹配令牌来解析。
- 常见值包括 `CREATE_INODE`、`DELETE_INODE`、`CREATE_HARDLINK`、`DELETE_HARDLINK`、`ACCESS`、`CHANGE`、`MODIFY`、`TIMESTOMP`、`MOVE` 以及元数据更改操作。
### 兼容性策略
- 现有文档化的键旨在保持可用。
- 未来版本可能会添加新键。
- 用户和下游工具应忽略未知键以实现向前兼容。
- `info` 的文本格式仅供参考,可能会在不同版本之间发生变化。
### 诊断规范
- 时间线事件仅写入 `stdout`。
- 警告、调试日志和解析器诊断信息写入 `stderr`。
- `stderr` 输出是面向操作员的,并不是稳定的机器可解析 API。
## 开发与运维说明
### 解析器边界(面向贡献者)
- `JournalParser.parse_journal()` 是摄取/解析阶段,绝不能发出时间线 JSON。
- `JournalParserCommon.infer_timeline_events()` 构建时间线事件对象。
- `JournalParserCommon.emit_timeline_events()` 是唯一将事件序列化为 `stdout` 上 JSON Lines 的阶段。
- `JournalParserCommon.timeline()` 协调推断/发出;将业务逻辑保留在推断中,将输出逻辑保留在发出中。
### 本地运行测试
- 首先激活虚拟环境。
- 使用 `python -m pytest`(推荐)运行测试,而不是直接使用 `pytest`。
- 这确保测试使用与 FJTA 相同的解释器/环境运行,并避免模块解析差异。
```
source .venv/bin/activate
python -m pytest -q
```
### 性能诊断(面向操作员)
- 使用 `--debug` 启用。
- 性能日志以 `PERF ...` 前缀发送到 `stderr`。
- 当前阶段的日志包括:
- `PERF parse_journal: s`
- `PERF timeline.infer: s; count=`
- `PERF timeline.emit: s; count=`
- `PERF timeline.total: s; count=`
- 这些诊断信息用于观察和调整;它们不是稳定的 API 约定。
## 使用 jq、awk 和 column 进行过滤和格式化以检测数据泄露
在以下输出示例中,您还可以看到被泄露文件的列表。
```
$ python ./fjta.py -i xfs_data_exfiltration.img | jq -r '
select(
(.action == "ACCESS")
or (.names | to_entries | any(.value[] | test("\\.(zip|rar|7z|gz|bz2)$"; "i")))
)
| [
.inode,
(.names | tostring),
.size,
.action,
.mode,
(.mtime | strftime("%Y-%m-%d %H:%M:%S")),
(.atime | strftime("%Y-%m-%d %H:%M:%S")),
(.ctime | strftime("%Y-%m-%d %H:%M:%S")),
(.crtime | strftime("%Y-%m-%d %H:%M:%S"))
]
| @tsv
' |
awk -F'\t' '{printf "%s\t%s\t%s\t%s\t%04o\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $7, $8, $9}' | column -s $'\t' -t -N inode,names,size,action,mode,mtime,atime,ctime,crtime
inode names size action mode mtime atime ctime crtime
132 {"128":["dummy_data"]} 30 ACCESS 0755 2025-12-19 01:54:10 2025-12-19 01:55:20 2025-12-19 01:54:10 2025-12-19 01:54:10
524416 {"132":["dir1"]} 66 ACCESS 0755 2025-12-19 01:54:10 2025-12-19 01:55:20 2025-12-19 01:54:10 2025-12-19 01:54:10
1179776 {"524416":["dir2"]} 137 ACCESS 0755 2025-12-19 01:54:10 2025-12-19 01:55:20 2025-12-19 01:54:10 2025-12-19 01:54:10
1572992 {"524416":["dir3"]} 146 ACCESS 0755 2025-12-19 01:54:10 2025-12-19 01:55:20 2025-12-19 01:54:10 2025-12-19 01:54:10
...
1179777 {"1179776":["file1"]} 1048576 ACCESS 0644 2025-12-19 01:54:10 2025-12-19 01:55:20 2025-12-19 01:54:10 2025-12-19 01:54:10
1179778 {"1179776":["file2"]} 1048576 ACCESS 0644 2025-12-19 01:54:10 2025-12-19 01:55:20 2025-12-19 01:54:10 2025-12-19 01:54:10
1179779 {"1179776":["file3"]} 1048576 ACCESS 0644 2025-12-19 01:54:10 2025-12-19 01:55:20 2025-12-19 01:54:10 2025-12-19 01:54:10
...
164 {"155":["file99"]} 1048576 ACCESS 0644 2025-12-19 01:54:11 2025-12-19 01:55:22 2025-12-19 01:54:11 2025-12-19 01:54:11
165 {"155":["file100"]} 1048576 ACCESS 0644 2025-12-19 01:54:11 2025-12-19 01:55:22 2025-12-19 01:54:11 2025-12-19 01:54:11
167 {"128":["takeout.zip"]} 0 CREATE_INODE|CREATE_HARDLINK 0644 2025-12-19 01:55:22 2025-12-19 01:55:20 2025-12-19 01:55:22 2025-12-19 01:55:20
167 {"128":["takeout.zip"]} 104894079 SIZE_UP 0644 2025-12-19 01:55:22 2025-12-19 01:55:20 2025-12-19 01:55:22 2025-12-19 01:55:20
```
## 如何导出文件系统日志
FJTA 可以分析导出的日志。但是,分析所需的一些参数并不包含在日志本身中。因此,您还必须导出相应的超级块(或文件系统元数据)信息。
导出这两个文件时,请确保它们共享除扩展名以外的相同文件名。
使用以下文件名约定:
- \*.journal(或您选择的任何扩展名)用于导出的日志
- \*.dumpe2fs 用于 dumpe2fs 输出 (ext4)
- \*.xfs_info 用于 xfs_info 输出 (XFS)
### ext4
```
sudo debugfs -R 'dump <8> sda3.journal' /dev/sda3
sudo dumpe2fs /dev/sda3 > sda3.dumpe2fs
python ./fjta.py -i sda3.journal
```
### XFS
```
sudo xfs_logprint -C rl-root.journal /dev/mapper/rl-root
sudo xfs_info /dev/mapper/rl-root > rl-root.xfs_info
python ./fjta.py -i rl-root.journal
```
## 测试环境
- Ubuntu 24.10,内核 6.8.0-88
- Rocky Linux 9.4,内核 5.14.0-570.49.1.el9_6.x86_64
## 支持的格式
- RAW
- EWF
- VMDK
- VHD / VHDX
- 直接访问文件系统(ext4 和 XFS 分区)
## 限制
- FJTA 仍在开发中,因此某些文件系统数据可能无法用于分析。
- FJTA 仅能分析 ext4 和 XFS 版本 5(inode 版本 3)。
- FJTA 不支持 LVM。
- 仅支持以 "data=ordered" 存储的 ext4 日志。data=ordered 是大多数 Linux 发行版中的默认日志模式。
- 不支持 ext4 上的快速提交。
- 不支持外部日志。
## 作者
[Minoru Kobayashi](https://x.com/unkn0wnbit)
## 许可证
FJTA (Forensic Journal Timeline Analyzer) 已依据 [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) 发布。有关更多详细信息,请参阅 LICENSE 文件。
标签:BurpSuite集成, ext4, Filesystem Forensics, inode分析, Journal, Journaling, Python, Timeline, XFS, 事件溯源, 取证工具, 恶意文件检测, 批量测试, 数字取证, 数字鉴证, 数据恢复, 数据篡改检测, 文件删除检测, 文件系统, 无后门, 电子取证, 自动化脚本, 跨平台工具, 身份验证滥用, 逆向工具