Search Results for

# Quick Start Guide - Python

Get from SDK download to working forensic analysis in 10 minutes.

## Prerequisites

- Python 3.14+ (standard library only - no pip install needed)
- FEX Core SDK extracted to a local directory
- Windows x64 or Linux x64 (or Docker)

## Step 1: Set Up Your Project

Create a working directory and copy (or symlink) the Python SDK:

    my-project/
    ├── core/              # Copy from sdk/python/core/
    │   ├── __init__.py
    │   ├── constants.py
    │   ├── fexcore.py
    │   └── loader.py
    ├── my_script.py       # Your code

On Windows, ensure `FEX.Core.dll` and its dependencies are in `bin/Win64/` relative to the SDK root (already included in the SDK package). The loader searches standard locations automatically.

On Linux, place `libfexcore.so` in `/usr/local/lib/` or set `LD_LIBRARY_PATH`.

## Step 2: Read Image Metadata

Create `my_script.py`:

    #!/usr/bin/env python3
    """Example: Read forensic image metadata."""

    import json
    import os
    import sys
    from core import FexCore
    from core.loader import load_library

    def main():
        if len(sys.argv) < 2:
            print("Usage: python my_script.py <image_path>")
            sys.exit(1)

        image_path = sys.argv[1]

        # Load the FEX Core library (auto-detects Windows DLL or Linux .so)
        lib, lib_path = load_library()
        fex = FexCore(lib)
        print(f"Loaded: {lib_path}")

        # FEX2 license key from GetData — set FEX_LICENSE_KEY in your environment
        # (or pull it from your secret store). See ../license-keys.md.
        key = os.environ["FEX_LICENSE_KEY"]

        # Known issue (Windows): InitLibrary uses GetCurrentDirectory rather than
        # GetModuleFileName when locating its license context, so it returns -20
        # (RESULT_INVALIDKEY) when the calling process's CWD is not the same
        # directory as FEX.Core.dll — even with a valid key. Workaround: chdir
        # to the DLL directory across the init_library call. The fix will move
        # to the DLL itself in a future release.
        prev_cwd = os.getcwd()
        os.chdir(os.path.dirname(lib_path))
        try:
            fex.init_library(key)
        finally:
            os.chdir(prev_cwd)
        print("Library initialized")

        # Open the forensic image
        image_id = fex.open_image(image_path)
        if image_id < 0:
            print(f"Error opening image: {image_id}")
            sys.exit(1)
        print(f"Image opened (ID: {image_id})")

        # Get image metadata as JSON
        info_str = fex.image_info_json(image_id)
        info = json.loads(info_str)

        print("\nImage Metadata:")
        print(f"  Description:  {info.get('description', 'N/A')}")
        print(f"  Image Type:   {info.get('imageType', 'N/A')}")
        print(f"  Image Kind:   {info.get('imageKind', 'N/A')}")
        print(f"  Device Size:  {info.get('deviceSize', 'N/A')}")

        # Clean up
        fex.close_image(image_id)
        print("\nDone.")

    if __name__ == "__main__":
        main()

Run it:

    python my_script.py path/to/evidence.E01

Expected output:

    Loaded: C:\FEX-SDK\bin\Win64\FEX.Core.dll
    Library initialized
    Image opened (ID: 0)

    Image Metadata:
      Description:  My Evidence
      Image Type:   E01
      Image Kind:   Physical
      Device Size:  536870912000

    Done.

## Step 3: Create a CSV File Listing

This example reads the filesystem and writes a CSV with full paths, sizes, and timestamps.

    #!/usr/bin/env python3
    """Example: Export file listing to CSV."""

    import csv
    import json
    import os
    import sys
    from datetime import datetime, timedelta
    from core import FexCore
    from core.loader import load_library
    from core.constants import FILESTATUS_FOLDER, FILESTATUS_DELETED

    # TFileRecord date fields are Windows FILETIME: 100-nanosecond intervals
    # since 1601-01-01 UTC. Zero means "not set".
    FILETIME_EPOCH = datetime(1601, 1, 1)

    def filetime_to_datetime(raw_int64):
        """Convert Windows FILETIME int64 to Python datetime (UTC)."""
        if raw_int64 == 0:
            return None
        try:
            return FILETIME_EPOCH + timedelta(microseconds=raw_int64 / 10)
        except (ValueError, OverflowError):
            return None

    def format_datetime(dt):
        """Format datetime as ISO string, or empty string if None."""
        if dt is None:
            return ""
        return dt.strftime("%Y-%m-%d %H:%M:%S")

    def main():
        if len(sys.argv) < 3:
            print("Usage: python csv_listing.py <image_path> <output.csv>")
            sys.exit(1)

        image_path = sys.argv[1]
        csv_path = sys.argv[2]

        # Load and initialize (FEX2 key from environment — see ../license-keys.md).
        # Windows: chdir to DLL directory across init_library — see the first
        # example for the workaround rationale.
        lib, lib_path = load_library()
        fex = FexCore(lib)
        prev_cwd = os.getcwd()
        os.chdir(os.path.dirname(lib_path))
        try:
            fex.init_library(os.environ["FEX_LICENSE_KEY"])
        finally:
            os.chdir(prev_cwd)

        # Open image
        image_id = fex.open_image(image_path)
        if image_id < 0:
            print(f"Error opening image: {image_id}")
            sys.exit(1)

        # Read filesystem (returns (result_code, file_count); records are fetched separately below)
        result, file_count = fex.read_file_system(image_id)
        if result != 0:
            print(f"Error reading filesystem: {result}")
            sys.exit(1)
        print(f"Filesystem read: {file_count} entries")

        # Get file records (includes full paths via V2 API)
        result, records = fex.get_file_system_records(image_id)

        # Write CSV
        with open(csv_path, "w", newline="", encoding="utf-8") as f:
            writer = csv.writer(f)
            writer.writerow([
                "Index", "Path", "Filename", "LogicalSize", "PhysicalSize",
                "Created", "Modified", "Accessed", "IsFolder", "IsDeleted"
            ])

            for rec in records:
                is_folder = bool(rec["status"] & FILESTATUS_FOLDER)
                is_deleted = bool(rec["status"] & FILESTATUS_DELETED)

                writer.writerow([
                    rec["_index"],
                    rec.get("path", ""),
                    rec["filename"],
                    rec["logicalSize"],
                    rec["physicalSize"],
                    format_datetime(filetime_to_datetime(rec["createdDate"])),
                    format_datetime(filetime_to_datetime(rec["modifiedDate"])),
                    format_datetime(filetime_to_datetime(rec["accessedDate"])),
                    is_folder,
                    is_deleted,
                ])

        print(f"Written {len(records)} records to {csv_path}")

        fex.close_image(image_id)

    if __name__ == "__main__":
        main()

Run it:

    python csv_listing.py path/to/evidence.E01 file_listing.csv

Output CSV:

    Index,Path,Filename,LogicalSize,PhysicalSize,Created,Modified,Accessed,IsFolder,IsDeleted
    0,,Root,0,0,,,,True,False
    1,Documents,Documents,0,0,2024-01-15 10:30:00,2024-01-15 10:30:00,,True,False
    2,Documents\report.pdf,report.pdf,1572864,1576960,2024-01-15 11:00:00,2024-01-15 11:05:00,2024-01-15 11:05:00,False,False
    ...

## Step 4: Extract All Files

Use the included `fex_extract.py` tool for bulk extraction:

    # Extract all files to a folder
    python fex_extract.py path/to/evidence.E01 ./extracted_files

    # Skip deleted files
    python fex_extract.py path/to/evidence.E01 ./extracted_files --skip-deleted

    # Dry run (show what would be extracted without writing)
    python fex_extract.py path/to/evidence.E01 ./extracted_files --dry-run

    # Verbose output
    python fex_extract.py path/to/evidence.E01 ./extracted_files --verbose

Expected output:

    FEX Extract - Bulk File Extraction
    ============================================================
    Opening image: evidence.E01
    Reading filesystem... 1,247 files found
    Extracting to: ./extracted_files

      [  1/1247] Documents\report.pdf (1.5 MB)
      [  2/1247] Documents\notes.txt (2.0 KB)
      [  3/1247] Pictures\photo.jpg (100.0 KB)
      ...

    ============================================================
    Extraction complete: 1,200 files extracted, 47 folders created
    Total size: 2.5 GB
    Errors: 0

Files are extracted preserving the original directory structure. The tool automatically:

- Preserves folder hierarchy from the forensic image
- Truncates files to logical size (strips cluster slack)
- Sanitizes Unicode filenames for cross-platform compatibility
- Handles path length limits

## Step 5: Write Your Own Extraction Logic

For custom extraction (e.g., filtering by file type or size):

> ⚠️ Note on bulk extraction. The example below uses `fex.read_file_data()`, which wraps the DLL's `ReadFileData` — fine for the small demo case. For extracting hundreds or thousands of files, switch to the handle-based stream API (`RequestFileStream` / `ReadFileStream` / `ReleaseFileStream`) to avoid unbounded memory growth. See the Python example in Common Workflows — Workflow 3.

    #!/usr/bin/env python3
    """Example: Extract only PDF files larger than 1 KB."""

    import os
    import sys
    from core import FexCore
    from core.loader import load_library
    from core.constants import FILESTATUS_FOLDER, RESULT_OK

    READ_CHUNK = 1024 * 1024  # 1 MB chunks

    def extract_file(fex, image_id, file_index, logical_size, output_path):
        """Extract a single file by reading in chunks."""
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        written = 0
        with open(output_path, "wb") as f:
            offset = 0
            while offset < logical_size:
                chunk_size = min(READ_CHUNK, logical_size - offset)
                result, bytes_read, buf = fex.read_file_data(
                    image_id, file_index, offset, chunk_size
                )
                if result != RESULT_OK or bytes_read == 0:
                    break
                f.write(bytes(buf[:bytes_read]))
                written += bytes_read
                offset += bytes_read
        # Truncate to exact logical size
        if written > logical_size:
            with open(output_path, "r+b") as f:
                f.truncate(logical_size)

    def main():
        image_path = sys.argv[1]
        output_dir = sys.argv[2]

        # Windows: chdir to DLL directory across init_library — see the first
        # example for the workaround rationale.
        lib, lib_path = load_library()
        fex = FexCore(lib)
        prev_cwd = os.getcwd()
        os.chdir(os.path.dirname(lib_path))
        try:
            fex.init_library(os.environ["FEX_LICENSE_KEY"])
        finally:
            os.chdir(prev_cwd)

        image_id = fex.open_image(image_path)
        result, _ = fex.read_file_system(image_id)
        result, records = fex.get_file_system_records(image_id)

        extracted = 0
        for rec in records:
            # Skip folders
            if rec["status"] & FILESTATUS_FOLDER:
                continue
            # Filter: only PDFs larger than 1 KB
            filename = rec["filename"].lower()
            if not filename.endswith(".pdf") or rec["logicalSize"] < 1024:
                continue

            path = rec.get("path", rec["filename"])
            output_path = os.path.join(output_dir, path)
            print(f"Extracting: {path} ({rec['logicalSize']} bytes)")
            extract_file(fex, image_id, rec["_index"], rec["logicalSize"], output_path)
            extracted += 1

        fex.close_image(image_id)
        print(f"\nExtracted {extracted} PDF files")

    if __name__ == "__main__":
        main()

## API Reference

The Python SDK wraps the FEX Core C API. Key methods on the `FexCore` class:

Method | Description
--- | ---
`init_library(key)` | Initialize with license key
`open_image(path)` | Open forensic image, returns `image_id`
`close_image(image_id)` | Close an opened image
`image_info_json(image_id)` | Get image metadata as JSON string
`read_file_system(image_id)` | Read filesystem, returns `(result, file_count)`
`get_file_system_records(image_id)` | Get all file records as list of dicts
`read_file_data(image_id, index, offset, size)` | Read file content bytes
`size_of_file(image_id, index)` | Get logical file size
`get_file_path(image_id, index)` | Get full path for a file
`call_v2_json(func, *args)` | Call any V2 function returning JSON

For the full C API reference, see V2 API Documentation.

## Next Steps

- Docker Guide - Deploy on Linux with Docker
- V2 API Reference - Complete API documentation
- V3 API (Custom Fields) - Flexible property queries
- Error Codes - All error codes with descriptions
- Architecture Overview - API design and memory patterns