This artifact parses $MFT files and returns rows of each in scope MFT record. This artifact can be used as the basis for other artifacts where the MFT needs to be queried or for deleted file recovery.
For deleted file recovery: Take the MFT ID of a file of interest and provide it to the Windows.NTFS.Recover artifact.
To query all attached ntfs drives: check the AllDrives switch.
I have added several filters to uplift search capabilities from the original MFT artifact. Due to the multi-drive features, the MFTPath will output the MFT path of the entry.
Available filters include:
^C:\\folder\\file\.ext$
or partial \\folder\\folder2\\
or string|string2|string3
^filename.ext$
or partial string1|string2
NOTE: Generally more efficient to filter on filename.
Multiple filters are cumulative.
OSPath output now uses expected Windows backslash “\
”.
name: Windows.NTFS.MFT
author: "Matt Green - @mgreen27"
description: |
This artifact parses $MFT files and returns rows of each in scope MFT record.
This artifact can be used as the basis for other artifacts where the MFT needs
to be queried or for deleted file recovery.
For deleted file recovery: Take the MFT ID of a file of interest and provide
it to the Windows.NTFS.Recover artifact.
To query all attached ntfs drives: check the AllDrives switch.
I have added several filters to uplift search capabilities from the original
MFT artifact. Due to the multi-drive features, the MFTPath will output the MFT
path of the entry.
Available filters include:
- PathRegex (OSPath): e.g `^C:\\folder\\file\.ext$` or partial `\\folder\\folder2\\` or `string|string2|string3`
- Fileregex: `^filename.ext$` or partial `string1|string2`
- Time bounds to select files with a timestamp within time ranges
- FileSize bounds
- MFTDrive: drive to target collection and show as source in results during offline pricessing.
- MFTPath: optional filter for offline MFT processing.
NOTE: Generally more efficient to filter on filename.
Multiple filters are cumulative.
OSPath output now uses expected Windows backslash "`\`".
parameters:
- name: MFTDrive
description: |
The path to to the drive that holds the MFT file (can be a pathspec). This
drive is also used for results for offline processing.
default: "C:"
- name: MFTPath
description: Optional path to MFT file for offline processing.
default:
- name: Accessor
default: ntfs
- name: AllNtfs
type: bool
description: "Return all NTFS metadata with results."
- name: PathRegex
description: "Regex search over OSPath."
default: "."
type: regex
- name: FileRegex
description: "Regex search over File Name"
default: "."
type: regex
- name: DateAfter
type: timestamp
description: "search for events after this date. YYYY-MM-DDTmm:hh:ssZ"
- name: DateBefore
type: timestamp
description: "search for events before this date. YYYY-MM-DDTmm:hh:ssZ"
- name: SizeMax
type: int64
description: "Entries in the MFT under this size in bytes."
- name: SizeMin
type: int64
description: "Entries in the MFT over this size in bytes."
- name: AllDrives
type: bool
description: "Select MFT search on all attached ntfs drives."
- name: NTFS_INCLUDE_SHORT_NAMES
description: See all names referencing the file including short names.
type: bool
sources:
- query: |
-- Cater for older clients which do not have the Links column.
LET parse_mft_version(filename, accessor, prefix) = SELECT *
FROM if(condition=version(plugin="parse_mft") > 1,
then={ SELECT *
FROM parse_mft(
filename=filename, accessor=accessor, prefix=prefix)
},
-- Older versions do not have the prefix parameter in
-- the plugin and need the prefix prepended to the
-- OSPath
else={ SELECT *,
prefix + OSPath AS Links,
prefix + OSPath AS OSPath
FROM parse_mft(
filename=filename, accessor=accessor)
})
-- The path to to the drive that holds the MFT file (can be a pathspec)
LET Drive <= pathspec(parse=MFTDrive, path_type="ntfs")
-- time testing
LET time_test(stamp) =
if(condition= DateBefore AND DateAfter,
then= stamp < DateBefore AND stamp > DateAfter,
else=
if(condition=DateBefore,
then= stamp < DateBefore,
else=
if(condition= DateAfter,
then= stamp > DateAfter,
else= True
)))
-- find all ntfs drives
LET ntfs_drives = SELECT
OSPath AS Drive,
OSPath + '$MFT' AS MFTFilename
FROM glob(globs="/*", accessor="ntfs")
WHERE log(message="Processing " + MFTFilename)
-- function returning MFT entries
-- Only check the filename - should be very quick
LET mftsearch_with_filename(Drive, MFTPath) =
SELECT EntryNumber, InUse, ParentEntryNumber,
OSPath,
Links AS _Links,
FileName, FileSize, ReferenceCount, IsDir,
Created0x10, Created0x30,
LastModified0x10, LastModified0x30,
LastRecordChange0x10, LastRecordChange0x30,
LastAccess0x10,LastAccess0x30,
HasADS, SI_Lt_FN, uSecZeros, Copied,
FileNames, FileNameTypes
FROM parse_mft_version(filename=MFTPath,
accessor=Accessor, prefix=Drive)
WHERE FileName =~ FileRegex
AND Links =~ PathRegex
-- Check only one date bound
LET mftsearch_after_date(Drive, MFTPath) =
SELECT
EntryNumber, InUse, ParentEntryNumber,
OSPath,
Links AS _Links,
FileName, FileSize, ReferenceCount, IsDir,
Created0x10, Created0x30,
LastModified0x10, LastModified0x30,
LastRecordChange0x10, LastRecordChange0x30,
LastAccess0x10,LastAccess0x30,
HasADS, SI_Lt_FN, uSecZeros, Copied,
FileNames, FileNameTypes
FROM parse_mft_version(filename=MFTPath,
accessor=Accessor, prefix=Drive)
WHERE
( Created0x10 > DateAfter
OR Created0x30 > DateAfter
OR LastModified0x10 > DateAfter
OR LastModified0x30 > DateAfter
OR LastRecordChange0x10 > DateAfter
OR LastRecordChange0x30 > DateAfter)
AND FileName =~ FileRegex
AND Links =~ PathRegex
LET mftsearch_before_date(Drive, MFTPath) =
SELECT EntryNumber, InUse, ParentEntryNumber,
OSPath,
Links AS _Links,
FileName, FileSize, ReferenceCount, IsDir,
Created0x10, Created0x30,
LastModified0x10, LastModified0x30,
LastRecordChange0x10, LastRecordChange0x30,
LastAccess0x10,LastAccess0x30,
HasADS, SI_Lt_FN, uSecZeros, Copied,
FileNames, FileNameTypes
FROM parse_mft_version(filename=MFTPath,
accessor=Accessor, prefix=Drive)
WHERE
( Created0x10 < DateBefore
OR Created0x30 < DateBefore
OR LastModified0x10 < DateBefore
OR LastModified0x30 < DateBefore
OR LastRecordChange0x10 < DateBefore
OR LastRecordChange0x30 < DateBefore)
AND FileName =~ FileRegex
AND Links =~ PathRegex
-- Check everything can be slow.
LET mftsearch_full(Drive, MFTPath) =
SELECT EntryNumber, InUse, ParentEntryNumber,
OSPath,
Links AS _Links,
FileName, FileSize, ReferenceCount, IsDir,
Created0x10, Created0x30,
LastModified0x10, LastModified0x30,
LastRecordChange0x10, LastRecordChange0x30,
LastAccess0x10,LastAccess0x30,
HasADS, SI_Lt_FN, uSecZeros, Copied,
FileNames, FileNameTypes
FROM parse_mft_version(filename=MFTPath,
accessor=Accessor, prefix=Drive)
WHERE FileName =~ FileRegex
AND Links =~ PathRegex
AND if(condition=SizeMax,
then=FileSize < atoi(string=SizeMax),
else=TRUE)
AND if(condition=SizeMin,
then=FileSize > atoi(string=SizeMin),
else=TRUE)
AND
( time_test(stamp=Created0x10)
OR time_test(stamp=Created0x30)
OR time_test(stamp=LastModified0x10)
OR time_test(stamp=LastModified0x30)
OR time_test(stamp=LastRecordChange0x10)
OR time_test(stamp=LastRecordChange0x30)
OR time_test(stamp=LastAccess0x10)
OR time_test(stamp=LastAccess0x30))
-- Choose a query to run depending on the user's choices.
LET mftsearch(Drive, MFTPath) = SELECT * FROM if(
-- only need to do a filename comparison
condition=NOT DateAfter AND NOT DateBefore AND NOT SizeMin AND NOT SizeMax,
then={ SELECT *
FROM mftsearch_with_filename(Drive=Drive, MFTPath=MFTPath) },
else={ SELECT * FROM if(
-- Only DateAfter is set
condition=NOT DateBefore AND NOT SizeMin AND NOT SizeMax,
then={ SELECT *
FROM mftsearch_after_date(Drive=Drive, MFTPath=MFTPath)},
else={ SELECT * FROM if(
-- Only Date Before is set
condition=NOT DateAfter AND NOT SizeMin AND NOT SizeMax,
then={ SELECT *
FROM mftsearch_before_date(Drive=Drive, MFTPath=MFTPath)},
else={ SELECT *
FROM mftsearch_full(Drive=Drive, MFTPath=MFTPath)})
})
})
-- include all attached drives
LET all_drives = SELECT * FROM foreach(row={
SELECT * FROM ntfs_drives
},
query={
SELECT *, Drive
FROM mftsearch(
Drive=Drive,
MFTPath=MFTFilename)
})
-- return results
LET results = SELECT *
FROM if(condition=AllDrives,
then={
SELECT * FROM all_drives
},
else={
SELECT * FROM mftsearch(Drive=Drive,
MFTPath=if(condition= MFTPath ,
then= MFTPath,
else= Drive + "$MFT"))
})
-- enrich results with NtfsMetadata is requests
LET enriched_results = SELECT *,
parse_ntfs(mft=EntryNumber, device=Drive ) as NtfsMetadata
FROM results
-- return rows
SELECT * FROM if(condition=AllNtfs,
then= enriched_results,
else= results)