Lucene search
+L

Forensic File Hash Inventory

🗓️ 26 Jun 2026 08:41:24Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 4 Views

The Forensic File Hash Inventory module builds an offline file inventory with hashes.

Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from kittysploit import *

import hashlib
import json
import os
from datetime import datetime, timezone


class Module(Analysis):
    __info__ = {
        "name": "Forensic File Hash Inventory",
        "author": ["KittySploit Team"],
        "description": "Builds an offline file inventory with hashes, sizes and timestamps for evidence triage.",
        "tags": ["forensic", "dfir", "hash", "inventory", "evidence"],
    }

    path = OptString("", "File or directory to inventory", required=True)
    algorithms = OptString("sha256,md5", "Comma-separated hash algorithms", required=False)
    recursive = OptBool(True, "Recurse into subdirectories", required=False)
    max_files = OptInteger(5000, "Maximum files to process", required=False, advanced=True)
    output_file = OptString("", "Optional JSON output file", required=False)

    def _parse_algorithms(self):
        available = set(hashlib.algorithms_available)
        algos = []
        for name in str(self.algorithms or "").split(","):
            algo = name.strip().lower()
            if algo and algo in available:
                algos.append(algo)
            elif algo:
                print_warning(f"Unsupported hash algorithm ignored: {algo}")
        return algos or ["sha256"]

    def _iter_files(self, root):
        if os.path.isfile(root):
            yield root
            return
        if not os.path.isdir(root):
            return
        if self.recursive:
            for base, _, files in os.walk(root):
                for name in sorted(files):
                    yield os.path.join(base, name)
        else:
            for name in sorted(os.listdir(root)):
                candidate = os.path.join(root, name)
                if os.path.isfile(candidate):
                    yield candidate

    def _hash_file(self, path, algos):
        hashers = {algo: hashlib.new(algo) for algo in algos}
        with open(path, "rb") as fp:
            for chunk in iter(lambda: fp.read(1024 * 1024), b""):
                for h in hashers.values():
                    h.update(chunk)
        return {algo: h.hexdigest() for algo, h in hashers.items()}

    def _iso_time(self, timestamp):
        return datetime.fromtimestamp(timestamp, timezone.utc).isoformat()

    def check(self):
        evidence_path = os.path.abspath(str(self.path or "").strip())
        if not evidence_path:
            print_error("path option is required")
            return False
        if not os.path.exists(evidence_path):
            print_error(f"Path not found: {evidence_path}")
            return False
        return True

    def run(self):
        root = os.path.abspath(str(self.path or "").strip())
        algos = self._parse_algorithms()
        try:
            limit = max(1, int(self.max_files))
        except Exception:
            limit = 5000

        print_info(f"Inventorying evidence path: {root}")
        print_info(f"Hash algorithms: {', '.join(algos)}")

        files = []
        errors = []
        for idx, file_path in enumerate(self._iter_files(root)):
            if idx >= limit:
                print_warning(f"Reached max_files limit ({limit}); stopping inventory")
                break
            try:
                stat = os.stat(file_path)
                files.append({
                    "path": file_path,
                    "relative_path": os.path.relpath(file_path, root if os.path.isdir(root) else os.path.dirname(root)),
                    "size": stat.st_size,
                    "mtime": self._iso_time(stat.st_mtime),
                    "ctime": self._iso_time(stat.st_ctime),
                    "atime": self._iso_time(stat.st_atime),
                    "hashes": self._hash_file(file_path, algos),
                })
            except Exception as exc:
                errors.append({"path": file_path, "error": str(exc)})

        data = {
            "root": root,
            "algorithms": algos,
            "file_count": len(files),
            "error_count": len(errors),
            "files": files,
            "errors": errors,
        }

        print_success(f"Inventory complete: files={len(files)} errors={len(errors)}")
        if self.output_file:
            try:
                with open(str(self.output_file), "w") as fp:
                    json.dump(data, fp, indent=2)
                print_success(f"Results saved to {self.output_file}")
            except Exception as exc:
                print_error(f"Failed to save output: {exc}")
        return data

    def get_graph_nodes(self, data):
        if not isinstance(data, dict):
            return [], []
        root = "forensic_inventory"
        nodes = [{"id": root, "label": "Forensic Inventory", "group": "event", "icon": "file"}]
        edges = []
        for i, row in enumerate(data.get("files", [])[:50]):
            node_id = f"file_{i}"
            nodes.append({"id": node_id, "label": os.path.basename(row.get("path", "file")), "group": "file", "icon": "file"})
            edges.append({"from": root, "to": node_id, "label": row.get("hashes", {}).get("sha256", "")[:12]})
        return nodes, edges

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

29 Jun 2026 20:47Current
5.8Medium risk
Vulners AI Score5.8
4