FEX Core SDK
  • Documentation
  • C# Reference
Search Results for

    Common Forensic Workflows

    This guide provides step-by-step tutorials for common forensic operations using FEX Core SDK.

    Prerequisites

    Before starting these tutorials:

    1. FEX Core SDK extracted to a local directory
    2. Python 3.14+ installed (64-bit)
    3. Valid license key (or trial key)
    4. Sample forensic image (sample-data/sample-image.dd)

    Workflow 1: Open and Verify an Image

    Goal: Open a forensic image and verify it loaded correctly.

    Use Case

    Before processing files, you need to open the forensic image and confirm FEX Core can read it. This validates the image format is supported and the file is accessible.

    Python Implementation

    import ctypes
    import json
    import os
    
    # Load the DLL
    dll_path = os.path.join("bin", "Win64", "FEX.Core.dll")
    dll = ctypes.CDLL(dll_path)
    
    # Define function signatures
    dll.ImageOpen.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
    dll.ImageOpen.restype = ctypes.c_int
    
    dll.GetImageInfoByIdAsJSON.argtypes = [
        ctypes.c_int,
        ctypes.POINTER(ctypes.c_void_p),
        ctypes.POINTER(ctypes.c_uint32)
    ]
    dll.GetImageInfoByIdAsJSON.restype = ctypes.c_int
    
    dll.FreeAllocatedBuffer.argtypes = [ctypes.c_void_p]
    dll.FreeAllocatedBuffer.restype = None
    
    dll.ImageClose.argtypes = [ctypes.c_int]
    dll.ImageClose.restype = ctypes.c_int
    
    # Open the image
    image_path = b"sample-data/sample-image.dd"
    license_key = b"YOUR_LICENSE_KEY"
    
    image_id = dll.ImageOpen(image_path, license_key)
    if image_id < 0:
        print(f"Failed to open image: error code {image_id}")
        exit(1)
    
    print(f"Image opened successfully! ID: {image_id}")
    
    # Get image info
    buffer = ctypes.c_void_p()
    size = ctypes.c_uint32()
    
    result = dll.GetImageInfoByIdAsJSON(image_id, ctypes.byref(buffer), ctypes.byref(size))
    if result == 0:
        try:
            json_data = ctypes.string_at(buffer, size.value).decode('utf-8')
            info = json.loads(json_data)
            print(f"Image Type: {info.get('ImageType', 'Unknown')}")
            print(f"Total Size: {info.get('TotalSize', 0):,} bytes")
        finally:
            dll.FreeAllocatedBuffer(buffer)
    
    # Close image when done
    dll.ImageClose(image_id)
    print("Image closed.")
    

    Expected Output

    Image opened successfully! ID: 1
    Image Type: DD
    Total Size: 52,428,800 bytes
    Image closed.
    

    Error Handling

    Error Code Meaning Solution
    -4 Image not supported Check file format, use supported type
    -6 Could not open Verify path, check permissions
    -20 Invalid license Check license key

    Workflow 2: List All Files

    Goal: Enumerate all files and folders in a forensic image.

    Use Case

    File listing is the foundation for most forensic operations - finding specific files, building timelines, or exporting file inventories.

    Python Implementation

    import ctypes
    import json
    import os
    
    # Assume DLL is loaded and image is open (image_id from Workflow 1)
    
    # Define GetFileSystemRecords_V2
    dll.GetFileSystemRecords_V2.argtypes = [
        ctypes.c_int,  # image_id
        ctypes.c_int,  # partition_index (0 for first)
        ctypes.POINTER(ctypes.c_void_p),  # buffer
        ctypes.POINTER(ctypes.c_uint32)   # size
    ]
    dll.GetFileSystemRecords_V2.restype = ctypes.c_int
    
    def list_files(image_id, partition_index=0):
        """List all files in a partition."""
        buffer = ctypes.c_void_p()
        size = ctypes.c_uint32()
    
        result = dll.GetFileSystemRecords_V2(
            image_id,
            partition_index,
            ctypes.byref(buffer),
            ctypes.byref(size)
        )
    
        if result != 0:
            print(f"Error listing files: {result}")
            return []
    
        try:
            json_data = ctypes.string_at(buffer, size.value).decode('utf-8')
            records = json.loads(json_data)
            return records
        finally:
            dll.FreeAllocatedBuffer(buffer)
    
    # Get and display files
    files = list_files(image_id)
    print(f"Found {len(files)} files/folders\n")
    
    # Display first 10 files
    print(f"{'Name':<30} {'Size':>12} {'Type':<10}")
    print("-" * 55)
    
    for record in files[:10]:
        name = record.get('Name', 'Unknown')
        size = record.get('Size', 0)
        is_folder = record.get('Status', 0) & 0x04  # FILESTATUS_FOLDER
        file_type = "Folder" if is_folder else "File"
        print(f"{name:<30} {size:>12,} {file_type:<10}")
    

    Expected Output

    Found 47 files/folders
    
    Name                                  Size Type
    -------------------------------------------------------
    .                                        0 Folder
    ..                                       0 Folder
    Documents                                0 Folder
    Pictures                                 0 Folder
    report.txt                           1,234 File
    invoice.pdf                         45,678 File
    photo.jpg                          234,567 File
    ...
    

    Filtering Files

    To filter results, process the returned list:

    # Find only deleted files
    deleted_files = [f for f in files if f.get('Status', 0) & 0x02]
    
    # Find files larger than 1MB
    large_files = [f for f in files if f.get('Size', 0) > 1_000_000]
    
    # Find files by extension
    pdf_files = [f for f in files if f.get('Name', '').lower().endswith('.pdf')]
    
    # Find files modified in date range
    from datetime import datetime
    def parse_date(timestamp):
        # FEX Core returns Windows FILETIME or Unix timestamp
        return datetime.fromtimestamp(timestamp) if timestamp else None
    
    recent_files = [
        f for f in files
        if parse_date(f.get('Modified', 0)) and
           parse_date(f.get('Modified', 0)).year >= 2024
    ]
    

    Workflow 3: Extract File Content

    Goal: Read the contents of a specific file from the forensic image.

    Use Case

    Extract files for analysis, export evidence, or preview file contents during investigation.

    API choice

    FEX Core exposes two ways to read file data:

    Function When to use
    ReadFileData One-off reads of a few files. Simple, but each first-use of a FileIndex allocates an internal reader that is not freed until CloseImage() — do not use in a loop over many files.
    RequestFileStream → ReadFileStream → ReleaseFileStream Bulk extraction or anything multi-threaded. Backed by an LRU pool (256 slots per image); ReleaseFileStream returns the slot immediately.

    The extraction example below uses the handle-based stream API. See V2 API Reference for full signatures.

    Python implementation (handle-based stream)

    import ctypes
    
    # Wire up the handle-based stream API
    dll.RequestFileStream.argtypes = [
        ctypes.c_int32,                     # image_id
        ctypes.c_int32,                     # file_index
        ctypes.POINTER(ctypes.c_int32),     # out: stream_handle
    ]
    dll.RequestFileStream.restype = ctypes.c_int32
    
    dll.ReadFileStream.argtypes = [
        ctypes.c_int32,                     # image_id
        ctypes.c_int32,                     # stream_handle
        ctypes.c_int64,                     # offset
        ctypes.POINTER(ctypes.c_ubyte),     # buffer (caller-allocated)
        ctypes.c_int32,                     # buffer_size
        ctypes.POINTER(ctypes.c_int32),     # out: bytes_read
    ]
    dll.ReadFileStream.restype = ctypes.c_int32
    
    dll.ReleaseFileStream.argtypes = [ctypes.c_int32, ctypes.c_int32]
    dll.ReleaseFileStream.restype = ctypes.c_int32
    
    dll.GetFileSize.argtypes = [ctypes.c_int32, ctypes.c_int32, ctypes.POINTER(ctypes.c_uint64)]
    dll.GetFileSize.restype = ctypes.c_int32
    
    CHUNK = 1024 * 1024  # 1 MB
    
    def extract_file(image_id, file_index, output_path):
        """Extract a file from the forensic image to disk by its file index."""
    
        # Logical size — how many bytes of content to write. GetFileSize returns
        # logicalSize, not the allocated-on-disk physicalSize.
        logical_size = ctypes.c_uint64(0)
        rc = dll.GetFileSize(image_id, file_index, ctypes.byref(logical_size))
        if rc != 0 or logical_size.value == 0:
            return False
    
        handle = ctypes.c_int32(0)
        rc = dll.RequestFileStream(image_id, file_index, ctypes.byref(handle))
        if rc != 0:
            print(f"RequestFileStream failed: {rc}")
            return False
    
        try:
            buf = (ctypes.c_ubyte * CHUNK)()
            bytes_read = ctypes.c_int32(0)
            offset = 0
            remaining = logical_size.value
    
            with open(output_path, "wb") as out_file:
                while remaining > 0:
                    to_read = min(CHUNK, remaining)
                    rc = dll.ReadFileStream(
                        image_id, handle, offset, buf, to_read, ctypes.byref(bytes_read)
                    )
                    if rc != 0 or bytes_read.value <= 0:
                        break
                    out_file.write(bytes(buf[: bytes_read.value]))
                    offset += bytes_read.value
                    remaining -= bytes_read.value
                    print(f"\rExtracted {offset:,} bytes...", end="")
    
            print(f"\nFile extracted to: {output_path}")
            return True
        finally:
            # ALWAYS release — the pool slot is held until you do.
            dll.ReleaseFileStream(image_id, handle)
    
    # file_index comes from GetFileSystemRecords_V2() (see Workflow 2).
    extract_file(image_id, 42, "extracted_report.txt")
    

    Expected output

    Extracted 1,234 bytes...
    File extracted to: extracted_report.txt
    

    Hex-dump preview

    For a quick preview without writing to disk — same handle pattern, single read:

    def hex_dump(image_id, file_index, num_bytes=256):
        handle = ctypes.c_int32(0)
        if dll.RequestFileStream(image_id, file_index, ctypes.byref(handle)) != 0:
            return
        try:
            buf = (ctypes.c_ubyte * num_bytes)()
            bytes_read = ctypes.c_int32(0)
            rc = dll.ReadFileStream(image_id, handle, 0, buf, num_bytes, ctypes.byref(bytes_read))
            if rc == 0 and bytes_read.value > 0:
                data = bytes(buf[: bytes_read.value])
                for i in range(0, len(data), 16):
                    hex_part = " ".join(f"{b:02x}" for b in data[i:i+16])
                    ascii_part = "".join(chr(b) if 32 <= b < 127 else "." for b in data[i:i+16])
                    print(f"{i:08x}  {hex_part:<48}  {ascii_part}")
        finally:
            dll.ReleaseFileStream(image_id, handle)
    
    hex_dump(image_id, 42)
    

    Workflow 4: Batch Processing Multiple Images

    Goal: Process multiple forensic images efficiently.

    Use Case

    Forensic labs often need to process dozens of images. This workflow shows how to handle multiple images with proper error handling and progress tracking.

    Python Implementation

    import ctypes
    import os
    from concurrent.futures import ThreadPoolExecutor, as_completed
    
    def process_image(image_path, license_key):
        """Process a single image and return summary."""
    
        result = {
            'path': image_path,
            'status': 'success',
            'file_count': 0,
            'total_size': 0,
            'error': None
        }
    
        # Open image
        image_id = dll.ImageOpen(
            image_path.encode('utf-8'),
            license_key.encode('utf-8')
        )
    
        if image_id < 0:
            result['status'] = 'error'
            result['error'] = f"Failed to open: error code {image_id}"
            return result
    
        try:
            # Get file list
            files = list_files(image_id)  # From Workflow 2
    
            result['file_count'] = len(files)
            result['total_size'] = sum(f.get('Size', 0) for f in files)
    
        except Exception as e:
            result['status'] = 'error'
            result['error'] = str(e)
    
        finally:
            dll.ImageClose(image_id)
    
        return result
    
    def batch_process(image_paths, license_key, max_workers=4):
        """Process multiple images in parallel."""
    
        results = []
    
        print(f"Processing {len(image_paths)} images...")
        print("-" * 60)
    
        # Sequential processing (safer for DLL)
        for i, path in enumerate(image_paths, 1):
            print(f"[{i}/{len(image_paths)}] Processing: {os.path.basename(path)}")
    
            result = process_image(path, license_key)
            results.append(result)
    
            if result['status'] == 'success':
                print(f"    Files: {result['file_count']:,}, "
                      f"Size: {result['total_size']:,} bytes")
            else:
                print(f"    ERROR: {result['error']}")
    
        # Summary
        print("-" * 60)
        successful = sum(1 for r in results if r['status'] == 'success')
        print(f"Completed: {successful}/{len(image_paths)} images processed successfully")
    
        return results
    
    # Process multiple images
    image_list = [
        "evidence/case001.E01",
        "evidence/case002.dd",
        "evidence/case003.L01"
    ]
    
    results = batch_process(image_list, "YOUR_LICENSE_KEY")
    

    Expected Output

    Processing 3 images...
    ------------------------------------------------------------
    [1/3] Processing: case001.E01
        Files: 1,234, Size: 45,678,901 bytes
    [2/3] Processing: case002.dd
        Files: 567, Size: 12,345,678 bytes
    [3/3] Processing: case003.L01
        ERROR: Failed to open: error code -4
    ------------------------------------------------------------
    Completed: 2/3 images processed successfully
    

    Error Handling Patterns

    def robust_process(image_path, license_key, retries=3):
        """Process with retry logic for transient failures."""
    
        for attempt in range(retries):
            try:
                result = process_image(image_path, license_key)
                if result['status'] == 'success':
                    return result
    
                # Don't retry certain errors
                if result['error'] and 'not supported' in result['error']:
                    return result  # Format issue, won't help to retry
    
            except Exception as e:
                if attempt < retries - 1:
                    print(f"Attempt {attempt + 1} failed, retrying...")
                    continue
                result = {
                    'path': image_path,
                    'status': 'error',
                    'error': str(e)
                }
    
        return result
    

    Cross-Platform Notes

    Windows vs Linux Path Handling

    import os
    
    # Use os.path for cross-platform compatibility
    dll_name = "FEX.Core.dll" if os.name == 'nt' else "libfexcore.so"
    dll_path = os.path.join("bin", "Win64" if os.name == 'nt' else "Linux64", dll_name)
    
    # Normalize paths
    image_path = os.path.normpath(image_path)
    

    DLL Loading Differences

    import ctypes
    import os
    
    if os.name == 'nt':
        # Windows: CDLL or WinDLL
        dll = ctypes.CDLL(dll_path)
    else:
        # Linux: Use CDLL with full path
        dll = ctypes.CDLL(os.path.abspath(dll_path))
    

    Next Steps

    • Best Practices - Optimization and patterns
    • V2 API Reference - Complete function documentation
    • Troubleshooting - Common problems and solutions

    Back to Documentation Index

    In this article
    Back to top © GetData Forensics