When a file is deleted from an NTFS directory, its parent directory’s $I30 index entry is marked as unused but the bytes — filename, size, timestamps — persist in the page slack until overwritten by a new directory entry. This guide covers the internal structure, acquisition, parsing, anti-forensics resilience, and investigative applications of the $I30 directory index artifact.
| Property | Detail |
|---|---|
| Artifact Name | $I30 (NTFS Directory Index) |
| Location | Within each directory’s MFT entry — $INDEX_ROOT and $INDEX_ALLOCATION attributes |
| Format | NTFS B-tree index; 4,096-byte INDX pages |
| Retention | Persists until the index page is reused by a new directory entry |
| OS Support | All NTFS volumes (Windows NT 3.1 through Windows 11, Server editions, Linux NTFS-3G mounts) |
What Is $I30?
Every directory on an NTFS volume maintains an index of the files and subdirectories it contains. This index is stored in an attribute named $I30 — named after its index type, $I30 for filename. The index is organized as a B-tree data structure, which allows the NTFS driver to perform fast lookups, insertions, and deletions as files are created, renamed, and removed within the directory.
The B-tree is composed of 4,096-byte index pages (matching the default NTFS cluster size). Each page contains multiple index entries, and each index entry stores a copy of the file’s metadata at the time it was added to the directory. This includes the filename, file size, parent directory MFT reference, and four timestamps (Created, Modified, MFT Modified, Accessed). These values are independent of the timestamps stored in the file’s own MFT record — they reflect the state of the file at the moment the directory index entry was created or last updated.
NTFS uses two attributes to store the directory index depending on directory size:
$INDEX_ROOT(attribute type 0x90) — A resident attribute stored directly within the directory’s MFT entry. Used for small directories where all index entries fit within the MFT record (typically fewer than ~20 files depending on filename lengths). This attribute is always present and serves as the root node of the B-tree.$INDEX_ALLOCATION(attribute type 0xA0) — A non-resident attribute that stores additional index pages (called INDX records) when the directory grows beyond what fits in$INDEX_ROOT. Each INDX record is a 4,096-byte page with its own header, update sequence array, and set of index entries. Large directories may have hundreds or thousands of INDX pages.
When a file is deleted, NTFS removes the index entry from the active B-tree. But because the B-tree operates on 4,096-byte pages, the deleted entry’s bytes persist in the page slack — the unused space between the last active entry and the end of the page. The filename, file size, and all four timestamps remain in slack until NTFS allocates a new entry in that exact position. In low-activity directories, this can take months or years. The result: an investigator can recover the names and metadata of deleted files from the parent directory even when the file’s own MFT entry has been reallocated.
Location & Format
Physical Location
The $I30 index is not stored in a single file. It is distributed across the MFT entries of every directory on the volume. Each directory’s MFT record contains an $INDEX_ROOT attribute (resident, always present) and, for larger directories, an $INDEX_ALLOCATION attribute (non-resident, stored in INDX pages scattered across the volume). There is also a companion $BITMAP attribute that tracks which INDX pages are currently in use.
| Component | MFT Attribute | Resident? | Description |
|---|---|---|---|
| $INDEX_ROOT | Type 0x90 | Yes | Root node of the B-tree; always present in every directory’s MFT entry. Contains index entries for small directories. |
| $INDEX_ALLOCATION | Type 0xA0 | No | Non-resident INDX pages (4,096 bytes each) for directories that exceed the $INDEX_ROOT capacity. |
| $BITMAP | Type 0xB0 | Yes/No | Bitmap tracking which INDX pages in $INDEX_ALLOCATION are allocated. |
INDX Page Structure
Each INDX page is a 4,096-byte record with a fixed header followed by a variable number of index entries. The header begins with the magic signature INDX (bytes 49 4E 44 58), followed by an update sequence array (USA) for integrity verification, and offsets indicating where index entries begin and where free space starts.
| Offset | Size | Field | Description |
|---|---|---|---|
0x00 | 4 bytes | Magic | INDX signature (0x49 0x4E 0x44 0x58) |
0x04 | 2 bytes | Update Sequence Offset | Offset to the update sequence array |
0x06 | 2 bytes | Update Sequence Size | Size of the update sequence array in words |
0x08 | 8 bytes | $LogFile Sequence Number | LSN for transaction logging |
0x10 | 8 bytes | VCN of this INDX page | Virtual Cluster Number within $INDEX_ALLOCATION |
0x18 | 4 bytes | Index Entry Offset | Offset (from 0x18) to the first index entry |
0x1C | 4 bytes | Index Entry Size | Total size of the index entry area (used + slack) |
0x20 | 4 bytes | Allocated Size | Allocated size of the index entry area (typically 4,096 minus header) |
0x24 | 1 byte | Flags | 0x00 = leaf node, 0x01 = has sub-nodes |
Index Entry Structure
Each index entry within an INDX page contains a copy of the file’s $FILE_NAME attribute, which includes the filename and a full set of timestamps. This is the data that persists in slack space after deletion.
| Offset | Size | Field | Forensic Relevance |
|---|---|---|---|
0x00 | 8 bytes | MFT Reference | MFT entry number + sequence number of the indexed file |
0x08 | 2 bytes | Index Entry Length | Total length of this index entry |
0x0A | 2 bytes | $FILE_NAME Length | Length of the embedded $FILE_NAME attribute |
0x0C | 4 bytes | Flags | 0x01 = sub-node pointer present, 0x02 = last entry in node |
0x10 | 8 bytes | Parent MFT Reference | MFT reference of the parent directory |
0x18 | 8 bytes | Created Timestamp | FILETIME: when the file was created |
0x20 | 8 bytes | Modified Timestamp | FILETIME: last content modification |
0x28 | 8 bytes | MFT Modified Timestamp | FILETIME: last MFT record change |
0x30 | 8 bytes | Accessed Timestamp | FILETIME: last access (may be disabled on modern Windows) |
0x38 | 8 bytes | Allocated Size | Size allocated on disk (cluster-aligned) |
0x40 | 8 bytes | Real Size | Actual file size in bytes |
0x48 | 4 bytes | Flags | File attribute flags (hidden, system, directory, etc.) |
0x50 | 1 byte | Filename Length | Length of the filename in characters |
0x51 | 1 byte | Filename Namespace | 0x00=POSIX, 0x01=Win32, 0x02=DOS, 0x03=Win32+DOS |
0x52 | Variable | Filename | UTF-16LE encoded filename (2 bytes per character) |
All timestamps in $I30 index entries use Windows FILETIME format: 64-bit values representing 100-nanosecond intervals since January 1, 1601 UTC. These timestamps are copies of the $FILE_NAME attribute timestamps at the time the directory entry was created or updated — they are not the same as the $STANDARD_INFORMATION timestamps in the file’s MFT record. The $FILE_NAME timestamps are rarely updated after initial creation, making them more tamper-resistant than $STANDARD_INFORMATION timestamps.
What It Reveals
The $I30 directory index answers a specific class of investigative questions that other artifacts either cannot answer at all or can only answer after the MFT record has been recovered. The following questions are directly answerable from $I30 slack space analysis:
- What files once existed in a specific directory? — Slack space entries retain the full filename (up to 255 characters, UTF-16LE) even after the file has been deleted and its MFT entry reallocated. This is the primary forensic value of $I30.
- What was the original filename before a rename operation? — When a file is renamed, the old index entry becomes slack and a new entry is created. Both the old and new filenames may coexist in the same INDX page, revealing the rename history.
- When was the file originally created in this directory? — The Created timestamp in the index entry reflects the
$FILE_NAMEcreation time, which is set when the file is first placed in the directory and is not modified by subsequent file content changes. - What was the file size at the time of the directory entry creation? — Both the allocated size (cluster-aligned) and real size (actual bytes) are stored, allowing reconstruction of how large the file was.
- Which parent directory contained the file? — The parent MFT reference field identifies the exact directory, establishing the file’s location in the directory tree at the time of the index entry.
- Did files with suspicious names ever exist in staging directories? — Slack entries in
%TEMP%,ProgramData, or user Desktop directories may reveal tooling filenames (mimikatz.exe,procdump64.exe,rclone.exe) that no longer exist on disk. - Were files copied to a USB-connected drive? — $I30 entries on a mounted USB volume retain filenames of files that were copied to the device, even if the device has since been wiped or reformatted (if the INDX pages survived).
- Did anti-forensics tools miss directory-level evidence? — Because no widely distributed cleanup tool targets INDX page slack, $I30 analysis frequently recovers evidence that survived CCleaner, Eraser, SDelete, and manual deletion.
If an investigator finds 63 slack entries across three directories named C:\Users\CFO\Documents\Meridian Partners, C:\Users\CFO\Documents\Apex Consolidated, and C:\Users\CFO\Documents\Stratos Group — and none of these directories currently contain files, and no MFT entries exist for the referenced files — the $I30 slack is the only artifact proving those files ever existed. The filenames alone (Meridian_Invoice_Q3_2025.pdf, Apex_Wire_Confirmation.pdf) may be sufficient to support a subpoena or search warrant.
Forensic Use Cases
1. Anti-Forensics Bypass — Recovering Deleted File Evidence
The most common forensic use of $I30 is recovering evidence of files that have been deleted and whose MFT entries have been reallocated. A subject deletes files from a directory, empties the Recycle Bin, runs CCleaner, and even uses Eraser on specific files. The MFT entries are gone. Prefetch shows no relevant executables. But the parent directory’s INDX pages still contain slack entries with the original filenames, sizes, and creation timestamps. The investigator extracts these entries and reconstructs a list of files that once existed in the directory.
2. Timeline Reconstruction — File Creation Sequences
By parsing $I30 entries (both active and slack) across multiple directories, an investigator can build a timeline of file creation activity. The Created timestamps in $I30 entries are $FILE_NAME timestamps, which are more resistant to timestomping than $STANDARD_INFORMATION timestamps. If an attacker uses a timestomping tool to backdate files, the $I30 Created timestamp may still reflect the true creation time because most timestomping tools only modify $STANDARD_INFORMATION.
3. Ghost Vendor Fraud Detection
In our CASE #287 investigation, a CFO created fictitious vendor directories, generated fraudulent invoices and wire confirmations, submitted them for payment, then deleted all evidence and ran anti-forensics tools. The MFT was clean. $I30 slack space in the parent directories retained 63 file entries across three ghost vendor directories, including filenames like Meridian_Invoice_Q3_2025.pdf and Apex_Wire_Confirmation_Sept.pdf. The filenames alone were sufficient to subpoena bank records and recover $2.1M in fraudulent payments.
4. Malware Staging Directory Analysis
Threat actors frequently stage tools in temporary directories (%TEMP%, C:\ProgramData, C:\Windows\Temp) before execution. After execution, the tools are deleted. $I30 slack in these staging directories may reveal filenames such as mimikatz.exe, PsExec.exe, nc.exe, procdump64.exe, or renamed variants. Even if the file content is unrecoverable, the filename and size combination (e.g., a 1.2 MB file named m.exe in %TEMP%) provides strong investigative leads.
5. USB File Copy Detection
When an NTFS-formatted USB device is connected and files are copied to it, the destination directories on the USB volume create $I30 entries for each copied file. If the USB device is later recovered (even after user-level deletion of the copied files), $I30 slack analysis on the USB volume reveals which files were placed on the device. This is critical in insider threat cases where the subject claims no files were copied to removable media.
Acquisition Methods
$I30 data is distributed across the MFT and INDX pages scattered throughout the volume. You cannot simply copy a single file. Acquisition requires either a full forensic image (preferred) or extraction of the raw $MFT plus targeted extraction of INDX pages from specific directories. Always collect the full volume image when possible — targeted collection risks missing INDX pages for directories you did not anticipate investigating.
Full Forensic Image (Preferred)
# Create a forensic image using dc3dd (or dd / ewfacquire) dc3dd if=/dev/sdb hof=/evidence/case287/disk.raw hash=sha256 log=/evidence/case287/acquisition.log # Or using FTK Imager CLI ftkimager.exe \\.\PhysicalDrive1 C:\Evidence\disk --e01 # Mount the image read-only for analysis mount -o ro,noexec,nodev,loop /evidence/case287/disk.raw /mnt/evidence
KAPE — Targeted MFT and $I30 Collection
:: Collect $MFT, $I30, $UsnJrnl, and $LogFile kape.exe --tsource C: --tdest C:\Evidence\KAPE_Output --target !SANS_Triage :: The !SANS_Triage target collects: :: $MFT (contains $INDEX_ROOT for every directory) :: $I30 INDX pages (via $INDEX_ALLOCATION extraction) :: $UsnJrnl:$J (change journal for correlation) :: $LogFile (NTFS transaction log) :: For targeted $I30 extraction from a specific directory: icat.exe \\.\C: [MFT_ENTRY_NUMBER] > C:\Evidence\target_dir_i30.bin
FTK Imager — Live System Extraction
# In FTK Imager: # 1. Add Evidence Item > Physical Drive > select the target drive # 2. Navigate to [root] > $MFT # 3. Right-click > Export Files > save to evidence folder # 4. For specific directories, navigate to the directory in the file tree # and export the $I30 attribute via the hex viewer # Using RawCopy for $MFT extraction (bypasses NTFS locks) RawCopy.exe /FileNamePath:C:\$MFT /OutputPath:C:\Evidence\
Always collect the entire raw volume when feasible. The $I30 data for a specific directory may be split across the resident $INDEX_ROOT in the MFT record and non-resident $INDEX_ALLOCATION pages scattered elsewhere on the disk. A full image ensures nothing is missed. If triage time constraints require targeted collection, prioritize the $MFT (which contains $INDEX_ROOT for all directories) plus a full $MFT parse to identify INDX page locations.
Parsing Tools & Analysis
| Tool | Author | License | Output | Notes |
|---|---|---|---|---|
| Indx2Csv | Joakim Schicht | Open source | CSV | Dedicated $I30 parser; extracts both active and slack entries; best for targeted INDX analysis |
| INDXParse | FireEye (Mandiant) | Open source (Python) | CSV / bodyfile | Python-based; parses INDX records and produces timeline-compatible bodyfile output |
| MFTECmd | Eric Zimmerman | Free | CSV | Parses the $MFT and extracts $INDEX_ROOT entries; also handles $I30 via --de flag for directory entries |
| The Sleuth Kit (TSK) | Brian Carrier | Open source (C) | CLI / bodyfile | fls -rd recovers deleted directory entries; icat extracts raw INDX pages; ifind locates index entries |
| Autopsy | Basis Technology | Open source (Java) | GUI | Built on TSK; displays deleted directory entries in the file tree with strikethrough |
| X-Ways Forensics | X-Ways | Commercial | GUI + export | Natively parses $I30 slack; highlights recovered entries in the directory listing |
Parsing with Indx2Csv
:: Extract INDX pages from a forensic image using icat (TSK) :: First, find the MFT entry number for the target directory ifind -n "Users/CFO/Documents/Meridian Partners" /evidence/disk.raw :: Output: 48291 (example MFT entry number) :: Extract the $INDEX_ALLOCATION attribute (type 0xA0) icat -o 2048 /evidence/disk.raw 48291-160 > /analysis/meridian_i30.bin :: Parse with Indx2Csv Indx2Csv.exe /InputFile:C:\Analysis\meridian_i30.bin /OutputFile:C:\Analysis\meridian_i30.csv :: The CSV output includes columns: :: FileName, FileSize, ParentMftRef, CreatedTimestamp, :: ModifiedTimestamp, MftModifiedTimestamp, AccessedTimestamp, :: IsSlackEntry (TRUE/FALSE)
Parsing with The Sleuth Kit (fls)
# List all files (including deleted) in a specific directory # -r = recursive, -d = deleted entries only, -p = full path fls -rdp -o 2048 /evidence/disk.raw 48291 # Output (deleted entries prefixed with *): # r/r * 48305: Users/CFO/Documents/Meridian Partners/Meridian_Invoice_Q3_2025.pdf # r/r * 48306: Users/CFO/Documents/Meridian Partners/Meridian_Wire_Oct.pdf # r/r * 48312: Users/CFO/Documents/Meridian Partners/Meridian_PO_4419.docx # Generate a bodyfile for timeline analysis fls -rd -m "C:" -o 2048 /evidence/disk.raw > /analysis/i30_bodyfile.txt # Convert bodyfile to timeline with mactime mactime -b /analysis/i30_bodyfile.txt -d > /analysis/i30_timeline.csv
Python INDX Record Parser
import struct import datetime def filetime_to_dt(ft): """Convert Windows FILETIME (100-ns since 1601-01-01) to datetime.""" if ft == 0: return None return datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=ft // 10) def parse_indx_page(data): """Parse a 4096-byte INDX page and extract index entries.""" magic = data[0:4] if magic != b'INDX': return [] # Index entry area starts at offset 0x18 + value at 0x18 entry_offset = 0x18 + struct.unpack_from('<I', data, 0x18)[0] entry_size = struct.unpack_from('<I', data, 0x1C)[0] alloc_size = struct.unpack_from('<I', data, 0x20)[0] entries = [] pos = entry_offset while pos < 0x18 + alloc_size: entry_len = struct.unpack_from('<H', data, pos + 8)[0] if entry_len == 0: break flags = struct.unpack_from('<I', data, pos + 0x0C)[0] if flags & 0x02: # Last entry marker break # Extract $FILE_NAME fields mft_ref = struct.unpack_from('<Q', data, pos)[0] & 0xFFFFFFFFFFFF parent_ref = struct.unpack_from('<Q', data, pos + 0x10)[0] & 0xFFFFFFFFFFFF created = filetime_to_dt(struct.unpack_from('<Q', data, pos + 0x18)[0]) modified = filetime_to_dt(struct.unpack_from('<Q', data, pos + 0x20)[0]) real_size = struct.unpack_from('<Q', data, pos + 0x40)[0] name_len = struct.unpack_from('<B', data, pos + 0x50)[0] name_ns = struct.unpack_from('<B', data, pos + 0x51)[0] filename = data[pos + 0x52 : pos + 0x52 + name_len * 2].decode('utf-16-le') is_slack = pos >= (0x18 + entry_size) # Beyond active entries = slack entries.append({ 'mft_ref': mft_ref, 'parent': parent_ref, 'filename': filename, 'size': real_size, 'created': created, 'modified': modified, 'slack': is_slack }) pos += entry_len return entries
Sample Output — Slack Entry Recovery
Source MFT Ref Filename Size Created Modified Slack INDX #12 48305 Meridian_Invoice_Q3_2025.pdf 284,672 2025-07-14 09:31:02 2025-07-14 09:31:02 [SLACK] INDX #12 48306 Meridian_Wire_Oct.pdf 156,288 2025-10-02 14:18:44 2025-10-02 14:18:44 [SLACK] INDX #12 48312 Meridian_PO_4419.docx 89,344 2025-09-11 11:05:17 2025-09-11 11:05:17 [SLACK] INDX #14 48401 Apex_Wire_Confirmation_Sept.pdf 192,000 2025-09-18 16:42:33 2025-09-18 16:42:33 [SLACK] INDX #14 48402 Apex_Invoice_2025-08.pdf 341,504 2025-08-22 10:11:09 2025-08-22 10:11:09 [SLACK] INDX #18 48510 Stratos_Consulting_Agreement.pdf 445,184 2025-06-03 08:55:21 2025-06-03 08:55:21 [SLACK] INDX #18 48511 Stratos_Payment_Schedule_Q2.xlsx 72,448 2025-06-03 09:01:48 2025-06-03 09:01:48 [SLACK]
Every row marked [SLACK] represents a deleted file whose index entry persists in the INDX page slack space. The MFT references (48305, 48306, etc.) point to MFT entries that have since been reallocated to new files — the original file content is gone. But the filenames, sizes, and timestamps survived in the parent directory’s index pages. No MFT-only analysis would have found these entries.
Retention & Persistence
$I30 slack space retention is fundamentally different from time-based artifacts like SRUM (30–60 days) or Prefetch (last 1,024 entries). $I30 entries persist until the specific INDX page position is overwritten by a new directory entry. This is not time-based — it depends entirely on directory activity.
| Factor | Impact on Retention | Example |
|---|---|---|
| Directory activity level | Primary factor. Low-activity directories retain slack for months or years. High-activity directories (e.g., %TEMP%) may overwrite slack within hours. | A user’s Documents\Vendors directory with 5–10 files: slack persists 6–18 months. %TEMP% with hundreds of daily files: slack lasts hours. |
| Number of files in directory | More files = more INDX pages = more total slack space available. Paradoxically, large directories may retain more slack because entries are spread across many pages. | A directory with 500 files may have 20+ INDX pages, each with independent slack areas. |
| B-tree rebalancing | When the B-tree rebalances (after insertions or deletions), entries may be moved between pages. This can create new slack in some pages while filling slack in others. | Deleting a file at a B-tree node boundary may trigger a merge, redistributing entries. |
| Volume defragmentation | Standard Windows defragmentation does not affect INDX page content. The NTFS driver moves file data clusters, not directory index pages (which are metadata). | Running defrag C: does not destroy $I30 slack. |
| NTFS volume repair | chkdsk /r may rebuild directory indexes, destroying slack in the process. | Always image the volume before running chkdsk on evidence drives. |
In the CASE #287 investigation, $I30 slack entries for the ghost vendor directories survived for over 8 months after deletion. The directories were low-activity (the CFO created files, submitted them for payment, then deleted everything). Because no new files were subsequently created in those directories before the directories themselves were deleted, the INDX pages retained their slack content intact. Low-activity directories are a forensic goldmine.
Anti-Forensics Resilience
$I30 directory index slack is one of the most resilient forensic artifacts on an NTFS volume. Every widely distributed tool that claims to “securely delete” a file operates on the file’s data content and MFT record. None of them zero the 4,096-byte index pages of the parent directory.
| Tool | Clears $I30 Slack? | Explanation |
|---|---|---|
| Standard Delete + Recycle Bin Empty | No | Removes the active index entry but leaves bytes in page slack. The most basic deletion leaves $I30 intact. |
| Shift+Delete (bypass Recycle Bin) | No | Same as above — the NTFS driver removes the entry from the B-tree but does not zero the slack. |
| CCleaner | No | Clears browser data, temp files, Recycle Bin, and free space. Does not touch NTFS directory index pages. No module exists for $I30 cleanup. |
| Eraser | No | Overwrites file data clusters and optionally cluster tips. Does not overwrite parent directory INDX pages. The “secure erase” operates on the file’s allocated clusters, not the directory metadata. |
| SDelete (Sysinternals) | No | Zeros file data content, then deletes. Does not touch the parent directory’s $INDEX_ALLOCATION pages. The filename, size, and timestamps persist in directory slack. |
| cipher /w | No | Overwrites free (unallocated) disk space only. INDX pages that are part of an active directory’s $INDEX_ALLOCATION attribute are allocated space — cipher /w does not touch them. |
| BleachBit | No | Same class as CCleaner. Targets user-level files, caches, and logs. No NTFS metadata cleanup capability. |
| Format (quick) | Partially | Quick format recreates the MFT and root directory. INDX pages from the old file system may persist in unallocated space but lose their structural context. |
| Format (full) | Yes | Full format (Windows Vista+) zeros every sector on the volume, destroying all $I30 data. |
Every tool that “securely deletes” a file operates on the file’s data content and MFT record. None of them zero the 4,096-byte index pages of the parent directory. This is because the INDX pages belong to the directory, not to the deleted file. The file deletion API removes the entry from the B-tree and frees the file’s MFT record and data clusters — but the directory’s own allocated index pages are not modified beyond marking the entry as removed from the active tree. The slack bytes remain untouched. An adversary would need to specifically identify every parent directory, extract the INDX pages, zero the slack regions, and write them back — a procedure no publicly available tool automates.
MITRE ATT&CK Detection Mapping
$I30 analysis provides evidentiary support for detecting the following MITRE ATT&CK techniques:
| Technique | Name | $I30 Evidence |
|---|---|---|
T1070.004 T1070.004 | Indicator Removal: File Deletion | Slack entries prove files existed in directories where no active files remain. The presence of slack entries for files with no corresponding MFT record is direct evidence of deletion. |
T1036 T1036 | Masquerading | When a file is renamed, the old filename persists as a slack entry and the new filename appears as an active entry. Both coexisting in the same INDX page reveals the rename operation — e.g., mimikatz.exe renamed to mimi.exe or svchost.exe. |
T1074 T1074 | Data Staged | Slack entries in staging directories (%TEMP%, ProgramData, C:\Perflogs) reveal files that were temporarily placed for collection before exfiltration, even after the staging directory was cleaned. |
T1485 T1485 | Data Destruction | Bulk deletion from a directory leaves a concentration of slack entries with clustered timestamps, indicating a mass cleanup event. The timestamp pattern distinguishes deliberate cleanup from normal file lifecycle. |
T1059 T1059 | Command and Scripting Interpreter | Slack entries for script files (.ps1, .bat, .vbs, .py) in unexpected directories indicate script-based attack activity, even after scripts are deleted. |
T1105 T1105 | Ingress Tool Transfer | Slack entries for known offensive tools (mimikatz, PsExec, Cobalt Strike payloads) in download or staging directories prove tool delivery occurred. |
Related Artifacts & Case Studies
Corroborating Artifacts
| Artifact | Relationship to $I30 | Cross-Correlation Value |
|---|---|---|
| $MFT | Contains $INDEX_ROOT (resident B-tree root) for every directory; also contains $FILE_NAME and $STANDARD_INFORMATION for active files | $MFT provides the structural context for $I30 analysis. Compare $MFT $STANDARD_INFORMATION timestamps against $I30 $FILE_NAME timestamps to detect timestomping. |
| $UsnJrnl | NTFS change journal records file creation, deletion, rename, and attribute changes | $UsnJrnl provides a time-ordered log of the same events reflected in $I30. If $UsnJrnl has rolled over, $I30 slack may retain entries for events no longer in the journal. |
| $LogFile | NTFS transaction log; records metadata operations at the NTFS driver level | $LogFile can contain INDX page modifications, providing a secondary record of directory index changes that may corroborate $I30 slack findings. |
| Prefetch | Records application execution with timestamps and run counts | Prefetch proves an executable ran; $I30 proves the executable existed in a specific directory. Together, they link file placement to execution. |
| SRUM.db | System Resource Usage Monitor; records per-application network and resource usage | SRUM shows what an application did on the network. $I30 shows where the application file was located and when it was placed there. Together, they reconstruct tool deployment and activity. |
| Security.evtx | Event ID 4663 (object access), 4688 (process creation with command line) | Security event logs provide process creation details; $I30 provides the file placement timeline that preceded execution. |
Case Study
The Files That Never Existed — A CFO had wiped his laptop, run CCleaner twice, manually deleted folders, and overwritten specific files with Eraser. The MFT was clean. The $I30 directory index was not. Slack space analysis recovered 63 deleted file entries across three ghost vendor directories — including invoices, wire confirmations, and purchase orders for companies that did not exist. The filenames alone were sufficient to support subpoenas against three banks, recover wire transfer records, and demonstrate $2.1M in fraudulent payments. The suspect’s anti-forensics efforts destroyed every other artifact. $I30 survived.
References
- Joakim Schicht, “Indx2Csv — NTFS $I30 Index Parser” — https://github.com/jschicht/Indx2Csv
- Flatcap, “Linux-NTFS Documentation — Directory Index” — https://flatcap.github.io/linux-ntfs/
- Eric Zimmerman, “MFTECmd — MFT and Directory Entry Parser” — https://ericzimmerman.github.io/
- FireEye (Mandiant), “INDXParse — NTFS Index Record Parser” — https://github.com/williballenthin/INDXParse
- Brian Carrier, “The Sleuth Kit — File System Forensic Analysis” — https://sleuthkit.org/
- SANS Institute, “NTFS $I30 Index Attributes and Slack Space for Digital Forensics” — https://www.sans.org/blog/
- 13Cubed, “NTFS Directory Indexing — $I30 Forensic Analysis” — https://www.13cubed.com/
- ForensicArtifacts.com, “Windows NTFS $I30 Artifact Definition” — https://github.com/ForensicArtifacts/artifacts
- Microsoft, “NTFS Technical Reference — Index Attributes” — https://learn.microsoft.com
Mjolnir Security — Digital Forensics & Incident Response
Mjolnir Security provides 24/7 incident response, digital forensics, and expert witness testimony. Our DFIR team specializes in $I30 slack space analysis, insider threat investigations, and anti-forensics bypass cases where standard artifacts have been destroyed.
mjolnirsecurity.com — 24/7: +1 833 403 5875