The following is a lightly anonymised account of a real digital forensics engagement conducted under legal privilege. All individual identifiers have been altered. Technical findings and artifact details are presented as they occurred. We are publishing this case because the NTFS $I30 directory index — specifically the slack space within its 4,096-byte index allocation pages — remains one of the most overlooked and powerful forensic artifacts in Windows investigations. When every other artifact has been destroyed, the directory that once held the files may still remember their names.
| Parameter | Detail |
|---|---|
| Case Type | Digital Forensics — Corporate Fraud Investigation, Ghost Vendor Scheme |
| Industry | Construction (Mid-size general contractor, ~$180M annual revenue) |
| Artifact | $I30 — NTFS Directory Index Attribute (slack space within index allocation pages) |
| Duration | 11 days forensic analysis, criminal referral and civil proceedings |
| Year | Year 7 of operations |
| Notoriety | The one where deleted file evidence was recovered from a structure nobody on the opposing side had ever heard of |
Background — What $I30 Is and Why It Matters
Every directory on an NTFS volume has an attribute called $I30. It is the directory index — a B-tree structure that NTFS uses to look up files by name within that directory. When you open a folder in Windows Explorer and see a list of files, you are looking at the rendered contents of the $I30 index. Every file entry in the index contains the filename (Unicode), the file’s MFT reference number, the file size, and four timestamps — Created, Modified, Accessed, and Entry Modified — duplicated from the file’s $STANDARD_INFORMATION attribute at the time the entry was last updated.
The index is stored in fixed-size allocation blocks of 4,096 bytes. Each block is called an index record (sometimes referred to as an INDX record). When files are added to a directory, NTFS inserts entries into these index records. When files are deleted, NTFS removes the entry from the active portion of the record and may shift entries forward — but it does not zero the bytes at the end of the record. The space between the last valid entry and the end of the 4,096-byte allocation is called $I30 slack space. Those bytes may contain remnants of previously deleted directory entries — filenames, sizes, and timestamps of files that no longer exist anywhere else in the filesystem.
This is not an obscure edge case. It is how NTFS has worked since Windows NT 3.1. Every directory on every NTFS volume has an $I30 attribute. Every deletion leaves potential residue. The forensic community has known about this since the early 2000s. Anti-forensics tools, by and large, have not caught up.
He had deleted the files. He had overwritten the content. He had cleared the MFT. What he had not done was zero the 4,096-byte index pages of the directories those files had lived in.
The Engagement
How We Were Called In
The company’s board retained external auditors after an anonymous tip submitted through the company’s ethics hotline. The tip alleged that the CFO, Graham, had been running a ghost vendor scheme — creating fictitious vendor entities, submitting invoices on their behalf, approving payments to them, and routing the funds to accounts he controlled. The auditors confirmed the pattern: $2.1 million had been paid to three vendor entities over a period of approximately three years. None of the three vendors had a physical office, none had employees other than on paper, and all three had been onboarded into the company’s ERP system by Graham personally, bypassing the standard vendor approval workflow.
When the audit committee confronted Graham, he denied everything. He stated that all three vendors were legitimate subcontractors he had sourced for specialty work, that invoices corresponded to real services rendered, and that payment approvals followed standard procedure. The board suspended Graham pending investigation and instructed him to surrender his company laptop. Graham had approximately two hours with the laptop between being informed of the suspension and physically handing the device to IT.
Those two hours were busy ones.
The board’s legal counsel engaged us the following morning. The laptop — a Dell Latitude running Windows 11, 512GB NVMe SSD, BitLocker encrypted with TPM — was delivered to our lab by courier with chain of custody documentation. BitLocker recovery key was retrieved from the company’s Azure AD. We had a forensic image mounted and indexed by end of day.
Finding 1: Anti-Forensics — The Two-Hour Window
Path: NTUSER.DAT (CCleaner keys) | C:\Windows\Prefetch\ | $Extend\$UsnJrnl:$J
- CCleaner stores configuration and last-run timestamps in
HKCU\Software\Piriform\CCleaner - Prefetch files record last 8 execution timestamps and loaded DLLs per binary
- $UsnJrnl (Update Sequence Number Journal) records every file creation, deletion, rename, and modification on the volume
- $UsnJrnl is circular — old entries are overwritten by new ones, but 2,847 deletions in 46 minutes is anomalous regardless of retention
The first indicator was CCleaner. Graham had installed it months earlier — the Prefetch file for CCLEANER64.EXE showed multiple historical executions — but the registry timestamps told us it had been run at 14:44 on the day of confrontation, approximately 2 hours and 44 minutes after the audit committee meeting that began at noon. The CCleaner configuration in the registry showed the ‘Wipe Free Space’ option was not enabled — a detail that would prove critical later.
# CCleaner registry analysis — last run timestamp and configuration # Registry path: HKCU\Software\Piriform\CCleaner regripper -r /analysis/registry/NTUSER.DAT -p ccleaner # OUTPUT: # CCleaner # LastWrite: 2024-01-██ 14:44:17 UTC # (App)Delete Index.dat files = True # (App)Delete Cookies = True # (App)Delete History = True # (App)Recent Documents = True # (App)Empty Recycle Bin = True # (App)Temporary Files = True # WipeFreeSpace = False ← CRITICAL: free space NOT wiped # UpdateCheck = 0
The second indicator was Eraser — a dedicated secure file deletion tool. Prefetch analysis revealed two executions: 15:01 and 15:22 on the same afternoon. Eraser is not a casual utility. It is specifically designed to overwrite file content with multiple passes, rendering recovery impossible. Graham had used it twice in 21 minutes, targeting specific files. Between CCleaner at 14:44 and laptop surrender at approximately 16:00, he had a 76-minute window of active anti-forensics.
# Prefetch analysis — CCleaner and Eraser execution timestamps on confrontation day PECmd.exe -d /analysis/prefetch/ --csv /analysis/output/ -q # Relevant Prefetch entries: # Filename Last Run (UTC) Run Count # CCLEANER64.EXE-████████.pf 2024-01-██ 14:44:17 14 # ERASER.EXE-████████.pf 2024-01-██ 15:22:03 2 # Previous run: 2024-01-██ 15:01:41 # ERASERCMD.EXE-████████.pf (not found — GUI only, no CLI usage) # Timeline: Confrontation 12:00 → CCleaner 14:44 → Eraser 15:01 → Eraser 15:22 → Surrender ~16:00 # 76 minutes of active anti-forensics before laptop handover
The $UsnJrnl — the NTFS change journal that records every file operation on the volume — painted the picture of those 76 minutes in stark detail. We extracted the journal and filtered for deletion operations within the confrontation window.
# $UsnJrnl deletion volume analysis — 2,847 file deletions in 46 minutes import pandas as pd from datetime import datetime df = pd.read_csv('/analysis/usnjrnl/usnjrnl_parsed.csv') df['Timestamp'] = pd.to_datetime(df['Timestamp']) # Filter: deletion operations on confrontation day, 14:30-16:00 UTC window_start = pd.Timestamp('2024-01-██ 14:30:00') window_end = pd.Timestamp('2024-01-██ 16:00:00') deletions = df[ (df['Timestamp'] >= window_start) & (df['Timestamp'] <= window_end) & (df['Reason'].str.contains('FILE_DELETE|CLOSE')) ] print(f'Total deletions in window: {len(deletions)}') print(f'Time span: {deletions.Timestamp.min()} to {deletions.Timestamp.max()}') print(f'Duration: {(deletions.Timestamp.max() - deletions.Timestamp.min()).total_seconds() / 60:.0f} minutes') # OUTPUT: # Total deletions in window: 2,847 # Time span: 2024-01-██ 14:44:22 to 2024-01-██ 15:30:41 # Duration: 46 minutes # Top parent directories by deletion count: parent_counts = deletions.groupby('ParentPath').size().sort_values(ascending=False) print(parent_counts.head(10)) # OUTPUT: # C:\Users\graham\Documents\████ Consulting\ 187 # C:\Users\graham\Documents\████ Services LLC\ 143 # C:\Users\graham\Documents\████ Partners\ 112 # C:\Users\graham\AppData\Local\Temp\ 891 ← CCleaner temp cleanup # C:\Users\graham\AppData\Local\Microsoft\Windows\... 1,204 ← CCleaner cache cleanup # ... (remaining entries are browser cache, temp files)
Three directory paths stood out immediately. They were not cache directories. They were not temporary files. They were directories named after three entities — entities whose names matched the three fictitious vendors identified by the auditors. Graham had maintained dedicated folders for each ghost vendor on his company laptop. And in the 46 minutes between CCleaner execution and laptop surrender, he had deleted everything in them.
The content was gone. The MFT entries had been overwritten by Eraser for the targeted files and reallocated by CCleaner’s cleanup activity for the rest. There was nothing to recover from unallocated space — the NVMe SSD’s TRIM command had already zeroed the underlying flash blocks. By every standard forensic measure, those files no longer existed.
But the directories had existed. And directories have $I30 indexes.
Anti-Forensics Confirmed — CCleaner (14:44), Eraser (15:01, 15:22), 2,847 Deletions in 46 Minutes
Forensic triage identified coordinated anti-forensics activity within a 76-minute window on the day of confrontation. CCleaner was executed at 14:44 with history, temp files, and recycle bin enabled but ‘Wipe Free Space’ disabled. Eraser was run twice (15:01, 15:22) targeting specific files. $UsnJrnl recorded 2,847 file deletion operations between 14:44 and 15:30, including 442 deletions across three directories named after the suspected ghost vendor entities. MFT entries for targeted files were overwritten. SSD TRIM had zeroed unallocated blocks. File content recovery was not possible through standard methods. The $I30 directory index had not been cleared by either tool.
Finding 2: $I30 Slack Recovery — 63 Deleted File Entries
Path: MFT attribute $INDEX_ALLOCATION ($I30) for each target directory
- $I30 index records are 4,096 bytes; NTFS does not zero slack space after entry removal
- Each INDX_ENTRY contains: filename (Unicode), MFT reference, file size, 4 SI timestamps
- Slack entries retain full structure — filename, size, Created/Modified/Accessed/Entry timestamps
- Extract with
ffind/icat(The Sleuth Kit) or direct MFT attribute parsing - 63 entries recovered across 3 directories — file content unrecoverable, but filenames and metadata intact
We identified the MFT entry numbers for the three vendor directories from the $UsnJrnl parent references. The directories themselves had been deleted, but the MFT entries had not yet been fully overwritten — NTFS marks directory MFT entries as unallocated but does not immediately zero them, and the $INDEX_ALLOCATION attribute within each entry still contained the original index records. We extracted the raw $I30 data using The Sleuth Kit.
# Extract $I30 index allocation from deleted vendor directories # Step 1: Identify MFT entry numbers for deleted directories from $UsnJrnl parent refs # MFT entries identified: # ████ Consulting → MFT #148203 # ████ Services LLC → MFT #148891 # ████ Partners → MFT #149102 # Step 2: Verify directory entries exist (even if deleted/unallocated) ffind -a /analysis/image.raw 148203 # /Users/graham/Documents/████ Consulting (deleted) # Step 3: Extract $INDEX_ALLOCATION ($I30) attribute using icat # Attribute type 0xA0 = $INDEX_ALLOCATION, attribute name $I30 icat -o 2048 /analysis/image.raw 148203-160-1 > /analysis/i30/vendor1_i30.raw icat -o 2048 /analysis/image.raw 148891-160-1 > /analysis/i30/vendor2_i30.raw icat -o 2048 /analysis/image.raw 149102-160-1 > /analysis/i30/vendor3_i30.raw # Each file contains the raw INDX records (4096 bytes each) ls -la /analysis/i30/ # -rw-r--r-- vendor1_i30.raw 32768 (8 index records × 4096 bytes) # -rw-r--r-- vendor2_i30.raw 24576 (6 index records) # -rw-r--r-- vendor3_i30.raw 16384 (4 index records)
With the raw $I30 data extracted, we wrote a parser to walk each index record and identify entries in the slack space — the region between the last valid (active) entry and the end of each 4,096-byte block. Active entries have their flags set; slack entries have the UNUSED flag (0x00) but retain the original INDX_ENTRY byte structure.
# Parse $I30 index records — extract active and slack entries with filenames and timestamps import struct from datetime import datetime, timedelta def parse_filetime(raw_bytes): """Convert Windows FILETIME (100ns since 1601-01-01) to datetime.""" ts = struct.unpack('<Q', raw_bytes)[0] if ts == 0: return None return datetime(1601, 1, 1) + timedelta(microseconds=ts // 10) def parse_i30_records(raw_data, label): """Walk INDX records, extract entries from both active and slack regions.""" record_size = 4096 entries = [] for offset in range(0, len(raw_data), record_size): record = raw_data[offset:offset + record_size] if record[:4] != b'INDX': continue # INDX header: offset to first entry at 0x18, offset to end of used at 0x1C entry_offset = struct.unpack('<I', record[0x18:0x1C])[0] + 0x18 used_end = struct.unpack('<I', record[0x1C:0x20])[0] + 0x18 alloc_end = record_size pos = entry_offset while pos < alloc_end - 0x52: # minimum INDX_ENTRY size entry_len = struct.unpack('<H', record[pos+8:pos+10])[0] if entry_len == 0 or entry_len > 0x400: break name_len = record[pos + 0x50] name_type = record[pos + 0x51] if name_len > 0 and name_len < 256: name = record[pos+0x52 : pos+0x52+name_len*2].decode('utf-16-le', errors='replace') fsize = struct.unpack('<Q', record[pos+0x40:pos+0x48])[0] created = parse_filetime(record[pos+0x18:pos+0x20]) modified = parse_filetime(record[pos+0x20:pos+0x28]) in_slack = pos >= used_end entries.append({ 'source': label, 'status': '[SLACK]' if in_slack else '[ACTIVE]', 'filename': name, 'size': fsize, 'created': created, 'modified': modified }) pos += entry_len return entries # Parse all three vendor directories all_entries = [] for vendor, path in [ ('████ Consulting', '/analysis/i30/vendor1_i30.raw'), ('████ Services LLC', '/analysis/i30/vendor2_i30.raw'), ('████ Partners', '/analysis/i30/vendor3_i30.raw') ]: with open(path, 'rb') as f: all_entries.extend(parse_i30_records(f.read(), vendor)) # Filter slack entries only slack = [e for e in all_entries if e['status'] == '[SLACK]'] print(f'Total entries recovered: {len(all_entries)}') print(f'Slack entries (deleted): {len(slack)}') print() for e in sorted(slack, key=lambda x: x['created'] or datetime.min): print(f"{e['status']} {e['source']:<24} {e['filename']:<55} {e['size']:>12,} {e['created']}")
# Total entries recovered: 71 # Slack entries (deleted): 63 # Status Source Filename Size Created [SLACK] ████ Consulting Invoice_████Consulting_001_Jan2021.xlsx 48,291 2021-01-14 09:22:41 [SLACK] ████ Consulting Invoice_████Consulting_002_Mar2021.xlsx 51,408 2021-03-22 11:04:18 [SLACK] ████ Consulting Invoice_████Consulting_003_Jun2021.xlsx 49,712 2021-06-08 14:33:02 [SLACK] ████ Consulting Invoice_████Consulting_004_Sep2021.xlsx 52,184 2021-09-17 10:11:47 # ... (24 total invoice files for ████ Consulting, quarterly pattern, Jan 2021 – Oct 2023) [SLACK] ████ Services LLC Invoice_████Services_001_Feb2021.xlsx 44,891 2021-02-03 08:47:22 [SLACK] ████ Services LLC Invoice_████Services_002_May2021.xlsx 47,203 2021-05-11 13:28:55 # ... (18 total invoice files for ████ Services LLC) [SLACK] ████ Partners Invoice_████Partners_001_Apr2021.xlsx 41,677 2021-04-19 15:02:33 # ... (12 total invoice files for ████ Partners) [SLACK] ████ Consulting Bank_Account_Details_████Consulting.txt 1,247 2021-01-11 08:14:03 [SLACK] ████ Services LLC Bank_Account_Details_████Services.txt 1,184 2021-02-01 09:33:17 [SLACK] ████ Partners Bank_Account_Details_████Partners.txt 1,312 2021-04-15 11:44:28 # Additional files: vendor_agreement_*.pdf, W9_*.pdf, scope_of_work_*.docx # 63 total slack entries with intact filenames, sizes, and Created timestamps
Sixty-three entries. Every one of them from the slack space of deleted directory index records. The files themselves were gone — content overwritten, MFT entries reallocated. But the $I30 index pages of the three vendor directories still contained the byte-level remnants of every filename, file size, and creation timestamp for files that had once lived there. Invoice files with sequential numbering, quarterly dates spanning three years, and three files whose names would prove decisive: Bank_Account_Details_████Consulting.txt, Bank_Account_Details_████Services.txt, and Bank_Account_Details_████Partners.txt.
63 Deleted File Entries Recovered from $I30 Slack Space Across 3 Ghost Vendor Directories
$I30 directory index slack analysis of the three deleted vendor directories recovered 63 INDX_ENTRY structures with intact filenames (Unicode), file sizes, and $STANDARD_INFORMATION timestamps. Entries included 54 invoice files with sequential numbering and quarterly creation dates spanning January 2021 to October 2023, 3 bank account detail files, and 6 supporting documents (vendor agreements, W-9 forms, scope-of-work documents). All entries were in slack space — beyond the valid data boundary of each index record — confirming they represent deleted files. MFT entries for these files had been overwritten; $I30 slack was the sole surviving record of their existence.
Finding 3: Timestamp Correlation — $I30 Created Dates vs. ERP Payments
Path: $I30 slack entries (Created timestamps) | Company ERP system (accounts payable ledger)
- $I30 Created timestamp = when the file was first written to the directory (not when the directory entry was last updated)
- ERP payment records provided by auditors: vendor name, invoice number, payment date, amount
- Correlation: if invoice files were created before payment, it establishes the fraud workflow (create invoice file → submit to ERP → approve payment)
- Critical anomaly: one invoice file’s Created timestamp predates the corresponding vendor’s ABN registration by 3 days
The auditors had provided us with the complete ERP payment ledger for the three vendor entities. We now had two independent data sets: the $I30 Created timestamps showing when each invoice file was first written to disk, and the ERP records showing when each corresponding payment was approved and disbursed. If the ghost vendor scheme was real, we would expect to see a consistent pattern: file creation preceding payment by a short, regular interval — the time between Graham creating the invoice document and submitting it for payment approval.
# Correlate $I30 Created timestamps with ERP payment dates import pandas as pd import re # Load $I30 slack entries (invoice files only) i30 = pd.read_csv('/analysis/i30/slack_entries.csv') invoices = i30[i30['filename'].str.startswith('Invoice_')].copy() invoices['created'] = pd.to_datetime(invoices['created']) # Extract invoice number from filename for matching invoices['inv_num'] = invoices['filename'].apply( lambda x: re.search(r'_(\d{3})_', x).group(1) if re.search(r'_(\d{3})_', x) else None ) # Load ERP payment records (provided by auditors) erp = pd.read_csv('/analysis/erp/vendor_payments.csv') erp['payment_date'] = pd.to_datetime(erp['payment_date']) # Merge on vendor + invoice number merged = invoices.merge(erp, on=['vendor', 'inv_num'], how='inner') merged['days_before_payment'] = (merged['payment_date'] - merged['created']).dt.days print('Invoice File Creation to ERP Payment Correlation:') print(f'Matched invoices: {len(merged)} of {len(invoices)}') print(f'Average days file created before payment: {merged["days_before_payment"].mean():.1f}') print(f'Std dev: {merged["days_before_payment"].std():.1f} days') print(f'Range: {merged["days_before_payment"].min()} to {merged["days_before_payment"].max()} days') print() # OUTPUT: # Matched invoices: 51 of 54 # Average days file created before payment: 4.2 # Std dev: 1.8 days # Range: 1 to 9 days # CRITICAL ANOMALY: anomaly = merged[merged['vendor'] == '████ Partners'].iloc[0] print(f'████ Partners first invoice:') print(f' $I30 Created: {anomaly["created"]}') print(f' ABN Registration: 2021-04-22') print(f' File created BEFORE vendor legally existed: 3 days') # OUTPUT: # ████ Partners first invoice: # $I30 Created: 2021-04-19 15:02:33 # ABN Registration: 2021-04-22 # File created BEFORE vendor legally existed: 3 days
The correlation was damning. Fifty-one of the fifty-four invoice files had matching ERP payment records. The average gap between file creation (as recorded in $I30) and payment disbursement was 4.2 days — consistent with a workflow where Graham created the invoice file, submitted it to the ERP system for payment, and approved it within a few business days. The standard deviation of 1.8 days suggested a remarkably consistent process.
But it was the anomaly that proved decisive. The first invoice file for ████ Partners had a $I30 Created timestamp of 2021-04-19. The ABN (Australian Business Number) registration for ████ Partners was dated 2021-04-22. The invoice file had been created on Graham’s laptop three days before the vendor entity legally existed. This was not a discrepancy that could be explained by timezone conversion or clock drift. It was a sequencing impossibility — proof that the invoice had been fabricated before the corresponding vendor had even been registered.
$I30 Timestamps Correlate with ERP Payments (4.2 Days Average) — One Invoice Predates Vendor Registration by 3 Days
Correlation of 51 $I30 Created timestamps with ERP payment records showed invoice files were created an average of 4.2 days before corresponding payments were disbursed (std dev: 1.8 days), consistent with a fabrication-then-submission workflow. Critical anomaly: the first invoice for ████ Partners (Created: 2021-04-19) predates the entity’s ABN registration (2021-04-22) by 3 days. The invoice file was created before the vendor legally existed. This timestamp came from $I30 slack — a filesystem metadata structure that Graham had no knowledge of and no means to manipulate retroactively.
Finding 4: Bank Account Filenames — The Subpoena That Followed
Path: $I30 slack entries: Bank_Account_Details_████Consulting.txt, Bank_Account_Details_████Services.txt, Bank_Account_Details_████Partners.txt
- File content was overwritten by Eraser (multi-pass wipe) — no content recovery possible
- Filenames survived in $I30 slack because Eraser targets file data and MFT, not directory index pages
- Filename alone established that Graham maintained bank account details for each ghost vendor on his laptop
- Filenames provided sufficient basis for subpoena to financial institutions
Three of the sixty-three recovered $I30 slack entries were not invoices. They were text files named Bank_Account_Details_████Consulting.txt, Bank_Account_Details_████Services.txt, and Bank_Account_Details_████Partners.txt. The file content had been overwritten by Eraser — this was clearly what the two Eraser runs had been targeting. Graham had known these files were the most incriminating items on his laptop, and he had used a secure deletion tool specifically to destroy them.
The content was irrecoverable. Multi-pass overwriting is effective. But the filenames survived in $I30 slack, and filenames were enough. The existence of files named ‘Bank Account Details’ for each of the three fictitious vendors, stored on the CFO’s personal laptop, created in the first days of each vendor’s existence, established that Graham had maintained the banking credentials for all three entities. This was the link the auditors had been missing — proof that Graham was not merely approving payments to vendors he had onboarded, but that he personally held and managed their bank account information.
Legal counsel issued subpoenas to the financial institutions where the three vendor entities held accounts. The bank records showed that all three accounts were held in the name of a family member of Graham. The funds had been transferred onward to personal accounts within days of each payment clearing.
Bank Account Filenames in $I30 Slack Enabled Subpoena — Accounts Held by Family Member
Three files named Bank_Account_Details_[vendor].txt were identified in $I30 slack space. File content had been securely overwritten by Eraser (targeted deletion at 15:01 and 15:22). Filenames alone established that Graham maintained bank account credentials for all three ghost vendors on his company laptop. This finding enabled subpoena to financial institutions. Bank records confirmed all three vendor accounts were held in the name of a family member of Graham, with funds transferred to personal accounts within days of receipt. The filenames recovered from $I30 slack — a structure Graham did not know existed — were the sole evidence linking him to the bank accounts.
Finding 5: Why Eraser Failed — The $I30 Gap
Explanation: Why secure deletion tools do not reach $I30 directory index slack space
- Eraser operates on three targets: file content (data runs), MFT entry (file record), and filename in MFT
- Eraser does NOT operate on the parent directory’s $I30 index allocation pages
- When a file is deleted, NTFS removes its entry from the active $I30 index but does not zero the slack
- CCleaner’s ‘Wipe Free Space’ option (which Graham had disabled) targets unallocated clusters, not NTFS metadata attributes
- $I30 index pages are NTFS metadata managed by the filesystem driver — they are not ‘free space’ and not ‘file content’
Understanding why Graham’s anti-forensics effort failed requires understanding how Eraser and CCleaner interact with the NTFS filesystem — and where they stop.
Eraser is a capable tool for its intended purpose. When you point Eraser at a file, it overwrites the file’s data content with multiple passes of random or patterned bytes, then deletes the file. Some versions also overwrite the MFT file record and the filename attribute within it. This renders the file content and its MFT entry unrecoverable. What Eraser does not do — because it operates through the Windows file API, not through raw disk writes to NTFS metadata structures — is modify the $I30 index allocation pages of the parent directory. The NTFS driver handles directory index maintenance internally. When a file is deleted (whether by Eraser or by normal deletion), the NTFS driver removes the entry from the active portion of the $I30 index and may compact the remaining entries. But it does not zero the bytes that the removed entry occupied. Those bytes become slack.
# What Eraser wipes vs. what survives in $I30 BEFORE Eraser: +------------------+ +------------------+ +---------------------------+ | FILE DATA | | MFT ENTRY #12847 | | PARENT DIR $I30 INDEX | | (disk clusters) | | $FILE_NAME | | INDX Record (4096 bytes) | | | | $DATA | | | | Invoice_001.xlsx | | Invoice_001.xlsx | | [ACTIVE] Invoice_001.xlsx | | [content bytes] | | [timestamps] | | Size: 48,291 | | | | [size, attrs] | | Created: 2021-01-14 | +------------------+ +------------------+ +---------------------------+ AFTER Eraser: +------------------+ +------------------+ +---------------------------+ | FILE DATA | | MFT ENTRY #12847 | | PARENT DIR $I30 INDEX | | (disk clusters) | | | | INDX Record (4096 bytes) | | | | [OVERWRITTEN] | | | | [OVERWRITTEN] | | [OVERWRITTEN] | | [UNUSED FLAG] | | [0x00 or random] | | [0x00 or random] | | Invoice_001.xlsx ← INTACT | | | | | Size: 48,291 ← INTACT +------------------+ +------------------+ | Created: 2021-01-14← INTACT +---------------------------+ WIPED WIPED SURVIVED IN SLACK # Eraser operates through Windows file API → targets file data + MFT entry # NTFS driver removes $I30 entry from active list but does NOT zero slack bytes # $I30 slack is filesystem metadata, not ‘free space’ — no tool in Graham’s arsenal reached it
CCleaner compounds the issue. Graham had used CCleaner to clear browser history, temporary files, and the recycle bin. CCleaner offers a ‘Wipe Free Space’ option that writes zeros to unallocated disk clusters — the clusters that once held deleted file content. Graham had this option disabled. But even if he had enabled it, ‘free space’ in CCleaner’s definition means unallocated clusters on the volume. $I30 index allocation pages are not unallocated clusters. They are NTFS metadata structures that belong to the directory’s MFT entry. They are allocated space, managed by the NTFS driver, and not addressable through the free-space wiping mechanism that CCleaner uses.
In short: Eraser killed the files. CCleaner cleaned the traces. Neither tool touched the directory index. The $I30 slack space existed in a gap between what anti-forensics tools target and what the filesystem actually retains. Graham fell into that gap.
Eraser + CCleaner Anti-Forensics Failed to Reach $I30 Index Slack — Architectural Limitation, Not Operator Error
Eraser operates on file data content and MFT entries through the Windows file API. It does not write to the parent directory’s $I30 index allocation pages, which are managed internally by the NTFS driver. When NTFS removes a directory entry during file deletion, it sets the entry’s flag to unused and may compact active entries, but does not zero the slack bytes at the end of the 4,096-byte index record. CCleaner’s ‘Wipe Free Space’ option (disabled in Graham’s configuration) targets unallocated disk clusters, not NTFS metadata structures. $I30 index pages are allocated metadata belonging to the directory MFT entry — they are neither ‘free space’ nor ‘file content’ as defined by either tool. This is an architectural gap in consumer anti-forensics tooling, not an operator error.
Reconstructed Timeline
| Timestamp | Artifact Source | Event |
|---|---|---|
Jan 2021 | $I30 slack (Created) | First invoice file created: Invoice_████Consulting_001_Jan2021.xlsx. First bank account detail file: Bank_Account_Details_████Consulting.txt created 3 days earlier. Ghost vendor scheme begins. |
Jan 2021 – Oct 2023 | $I30 slack + ERP records | 54 invoice files created across 3 vendor directories over approximately 3 years. $2.1M paid to the three entities. Average 4.2 days between file creation and ERP payment. Quarterly invoice cadence. |
Apr 2021 | $I30 slack + ABN registry | ████ Partners invoice file Created timestamp (Apr 19) predates ABN registration (Apr 22) by 3 days. Invoice fabricated before vendor entity legally existed. |
Late 2023 | Ethics hotline | Anonymous tip alleges CFO running ghost vendor scheme. Board retains external auditors. |
Confrontation Day, 12:00 | Company records | Audit committee confronts Graham. Denies all allegations. Told to surrender laptop. Approximately 2 hours between notification and expected handover. |
Day 0, 14:44 | CCleaner registry | CCleaner executed. Browser history, temp files, recycle bin cleared. ‘Wipe Free Space’ not enabled. |
Day 0, 15:01 | Eraser Prefetch | Eraser first run. Targets specific files — likely the 3 bank account detail files and other high-sensitivity documents. |
Day 0, 15:22 | Eraser Prefetch | Eraser second run. Additional targeted file destruction. Combined with first run, Eraser overwrites content and MFT entries for targeted files. |
Day 0, 14:44 – 15:30 | $UsnJrnl | 2,847 file deletion operations recorded in 46 minutes. 442 deletions in the three vendor directories. Manual folder deletion after Eraser passes. |
Day 0, ~16:00 | Chain of custody | Graham surrenders laptop to IT. Device secured for forensic acquisition. |
Day +1 | Engagement | Mjolnir Security retained by legal counsel. Laptop delivered to lab. Forensic image created, BitLocker recovered via Azure AD. |
Day +4 | Forensic analysis | $I30 extraction and slack parsing complete. 63 deleted file entries recovered across 3 vendor directories. Bank account filenames identified. |
Day +5 | Forensic analysis | Timestamp correlation with ERP records complete. 4.2-day average. ABN predating anomaly identified for ████ Partners. |
Day +11 | Report delivery | Forensic report delivered to legal counsel: $I30 slack evidence, timestamp correlation, anti-forensics documentation, bank account filename evidence. |
Week +3 | Legal proceedings | Preliminary hearing. $I30 evidence presented. Opposing counsel challenges admissibility of ‘slack space’ evidence — challenge fails on Daubert standard (published methodology, peer review, standard NTFS structure). |
Week +5 | Settlement | Graham agrees to settlement. Criminal referral made to law enforcement. Bank account subpoena confirms accounts held by family member. |
What This Engagement Teaches Us
For Digital Forensic Examiners
- $I30 directory index slack should be part of your standard NTFS examination workflow. It is not an exotic artifact. It is a standard NTFS metadata structure present on every directory on every NTFS volume since Windows NT 3.1. When MFT entries have been overwritten and file content is unrecoverable, $I30 slack may be the last remaining record that files existed. The Sleuth Kit’s
icatcan extract the raw $INDEX_ALLOCATION attribute; custom parsing is straightforward once you understand the INDX_ENTRY structure. - The Eraser + CCleaner anti-forensics pattern is common and has a known gap. This combination appears regularly in insider threat and fraud cases. Eraser targets file content and MFT entries. CCleaner targets browser history, temp files, and optionally free space. Neither tool zeroes $I30 index pages. When you see this pattern in $UsnJrnl or Prefetch, immediately pivot to $I30 analysis of the directories those tools targeted.
- Filenames alone can be investigative leads even when file content is irrecoverable. In this case, the filenames
Bank_Account_Details_[vendor].txtwere sufficient to establish that the CFO maintained bank credentials for each ghost vendor and to support subpoena to financial institutions. Do not dismiss $I30 slack as ‘just filenames’ — filenames with timestamps in the context of a fraud investigation are evidence. - $I30 Created timestamps should be interpreted carefully but are forensically sound. The Created timestamp in an $I30 entry is copied from the file’s $STANDARD_INFORMATION attribute when the entry is created or last updated. It represents the time the file was first written to that directory. In the absence of deliberate timestamp manipulation (which requires raw NTFS access, not available through Eraser or CCleaner), $I30 timestamps are reliable indicators of file creation timing. The 3-day predating anomaly in this case would not have been identified without $I30 timestamps.
For Legal Professionals
- $I30 evidence is admissible under standard forensic methodology. The NTFS $I30 directory index is a documented, published filesystem structure. Its forensic analysis has been peer-reviewed and presented at conferences (SANS DFIR Summit, DFRWS, Enfuse) since the mid-2000s. In this case, opposing counsel challenged admissibility on the basis that $I30 slack was an ‘obscure’ and ‘unreliable’ data source. The challenge failed on Daubert grounds: the methodology is published, testable, and has a known error rate. $I30 is not experimental — it is standard NTFS architecture.
- Litigation hold must encompass entire devices, not just user files. If Graham had been permitted to keep his laptop for even 24 additional hours, or if IT had wiped and re-imaged it before forensic acquisition, the $I30 evidence would have been lost. NTFS metadata structures like $I30 are fragile in the sense that any new file activity in the same directories can overwrite slack space. Immediate device seizure and forensic imaging is essential. ‘Preserve the documents’ is not sufficient — the relevant evidence may exist only in filesystem metadata, not in user-accessible files.
- Anti-forensics activity is itself evidence of consciousness of guilt. The 76-minute window of CCleaner and Eraser activity, documented by Prefetch, registry timestamps, and $UsnJrnl, established that Graham undertook deliberate, sustained efforts to destroy evidence after being confronted. Even if the $I30 evidence had not been recoverable, the documented anti-forensics activity would have been presented as evidence of intent.
- Filenames recovered from filesystem metadata can support subpoena applications. The $I30-recovered filenames
Bank_Account_Details_[vendor].txtprovided the factual basis for subpoena to financial institutions. File content was not required — the existence of files with those names, on the CFO’s laptop, with creation timestamps contemporaneous to the vendor onboarding dates, was sufficient to establish reasonable basis for further discovery.
Mjolnir Security — Digital Forensics & Litigation Support
Mjolnir Security provides digital forensic investigation, insider threat analysis, and expert witness testimony for civil and criminal proceedings. Our DFIR team specialises in NTFS artifact analysis, anti-forensics detection, and forensic evidence presentation for fraud, IP theft, and corporate misconduct matters.
mjolnirsecurity.com — 24/7 Incident Response Hotline: +1 833 403 5875
Written by Mjolnir Security DFIR team
Published January 2024 · DFIR Engagement Series · TLP:WHITE
Case #287 · Skuggaheimar · Mjolnir Security · All client details anonymized · TLP:WHITE
