private static void Main(string[] args) { ExceptionlessClient.Default.Startup("88KHFwswzxfnYGejAlsVDao47ySGliI6vFbQPt9C"); SetupNLog(); _logger = LogManager.GetLogger("MFTECmd"); _fluentCommandLineParser = new FluentCommandLineParser <ApplicationArguments> { IsCaseSensitive = false }; _fluentCommandLineParser.Setup(arg => arg.File) .As('f') .WithDescription("File to process. Required\r\n"); _fluentCommandLineParser.Setup(arg => arg.CsvDirectory) .As("csv") .WithDescription( "Directory to save CSV formatted results to. Be sure to include the full path in double quotes. Required unless --de or --body is specified\r\n"); _fluentCommandLineParser.Setup(arg => arg.BodyDirectory) .As("body") .WithDescription( "Directory to save bodyfile formatted results to. Be sure to include the full path in double quotes. --bdl is also required when using this option"); _fluentCommandLineParser.Setup(arg => arg.BodyDriveLetter) .As("bdl") .WithDescription( "Drive letter (C, D, etc.) to use with bodyfile formatted results. Only the drive letter itself should be provided\r\n"); _fluentCommandLineParser.Setup(arg => arg.BaseName) .As("bn") .WithDescription( "Base name for file when exporting to CSV. If set, this will be the name of the file created in --csv directory\r\n"); _fluentCommandLineParser.Setup(arg => arg.DumpDir) .As("dd") .WithDescription( "Directory to save exported FILE record. Be sure to include the full path in double quotes. --do is also required when using this option"); _fluentCommandLineParser.Setup(arg => arg.DumpOffset) .As("do") .WithDescription( "Offset of the FILE record to dumpas decimal or hex. Example: 5120 or 0x1400 Use --de or --vl 1 to see offsets\r\n"); _fluentCommandLineParser.Setup(arg => arg.DumpEntry) .As("de") .WithDescription( "Dump full details for the entry/sequence number provided. Format is 'Entry-Seq' as decimal or hex. Example: 624-5 or 0x270-0x5\r\n") .SetDefault(string.Empty); _fluentCommandLineParser.Setup(arg => arg.DateTimeFormat) .As("dt") .WithDescription( "The custom date/time format to use when displaying time stamps. Default is: yyyy-MM-dd HH:mm:ss.fffffff") .SetDefault("yyyy-MM-dd HH:mm:ss.fffffff"); _fluentCommandLineParser.Setup(arg => arg.IncludeShortNames) .As("sn") .WithDescription( "Include DOS file name types. Default is false").SetDefault(false); _fluentCommandLineParser.Setup(arg => arg.Verbose) .As("vl") .WithDescription( "Verbose log messages. 1 == Debug, 2 == Trace").SetDefault(0); _fluentCommandLineParser.Setup(arg => arg.CsvSeparator) .As("cs") .WithDescription( "When true, use comma instead of tab for field separator. Default is true").SetDefault(true); _fluentCommandLineParser.Setup(arg => arg.AllTimeStampsAllTime) .As("at") .WithDescription( "When true, include all timestamps from 0x30 record vs only when they differ from 0x10 record. Default is false").SetDefault(false); var header = $"MFTECmd version {Assembly.GetExecutingAssembly().GetName().Version}" + "\r\n\r\nAuthor: Eric Zimmerman ([email protected])" + "\r\nhttps://github.com/EricZimmerman/MFTECmd"; var footer = @"Examples: MFTECmd.exe -f ""C:\Temp\SomeMFT""" + "\r\n\t " + @" MFTECmd.exe -f ""C:\Temp\SomeMFT"" --csv ""c:\temp\out"" --bn MyOutputFile.csv" + "\r\n\t " + @" MFTECmd.exe -f ""C:\Temp\SomeMFT"" --csv ""c:\temp\out""" + "\r\n\t " + @" MFTECmd.exe -f ""C:\Temp\SomeMFT"" --body ""c:\temp\bout"" --bdl c" + "\r\n\t " + @" MFTECmd.exe -f ""C:\Temp\SomeMFT"" --de 5-5" + "\r\n\t " + "\r\n\t" + " Short options (single letter) are prefixed with a single dash. Long commands are prefixed with two dashes\r\n"; _fluentCommandLineParser.SetupHelp("?", "help") .WithHeader(header) .Callback(text => _logger.Info(text + "\r\n" + footer)); var result = _fluentCommandLineParser.Parse(args); if (result.HelpCalled) { return; } if (result.HasErrors) { _logger.Error(""); _logger.Error(result.ErrorText); _fluentCommandLineParser.HelpOption.ShowHelp(_fluentCommandLineParser.Options); return; } if (_fluentCommandLineParser.Object.File.IsNullOrEmpty()) { _fluentCommandLineParser.HelpOption.ShowHelp(_fluentCommandLineParser.Options); _logger.Warn("-f is required. Exiting"); return; } if (_fluentCommandLineParser.Object.File.IsNullOrEmpty() == false && !File.Exists(_fluentCommandLineParser.Object.File)) { _logger.Warn($"File '{_fluentCommandLineParser.Object.File}' not found. Exiting"); return; } if (_fluentCommandLineParser.Object.CsvDirectory.IsNullOrEmpty() && _fluentCommandLineParser.Object.DumpEntry.IsNullOrEmpty() && _fluentCommandLineParser.Object.BodyDirectory.IsNullOrEmpty() && _fluentCommandLineParser.Object.DumpDir.IsNullOrEmpty()) { _fluentCommandLineParser.HelpOption.ShowHelp(_fluentCommandLineParser.Options); _logger.Warn("--csv, --body, --dd, or --de is required. Exiting"); return; } if (_fluentCommandLineParser.Object.BodyDirectory.IsNullOrEmpty() == false && _fluentCommandLineParser.Object.BodyDriveLetter.IsNullOrEmpty()) { _fluentCommandLineParser.HelpOption.ShowHelp(_fluentCommandLineParser.Options); _logger.Warn("--bdl is required when using --body. Exiting"); return; } if (_fluentCommandLineParser.Object.DumpDir.IsNullOrEmpty() == false && _fluentCommandLineParser.Object.DumpOffset.IsNullOrEmpty()) { _fluentCommandLineParser.HelpOption.ShowHelp(_fluentCommandLineParser.Options); _logger.Warn("--do is required when using --dd. Exiting"); return; } if (_fluentCommandLineParser.Object.CsvSeparator == false) { exportExt = "tsv"; } _logger.Info(header); _logger.Info(""); _logger.Info($"Command line: {string.Join(" ", Environment.GetCommandLineArgs().Skip(1))}\r\n"); if (IsAdministrator() == false) { _logger.Fatal("Warning: Administrator privileges not found!\r\n"); } if (_fluentCommandLineParser.Object.Verbose >= 1) { LogManager.Configuration.LoggingRules.First().EnableLoggingForLevel(LogLevel.Debug); } if (_fluentCommandLineParser.Object.Verbose >= 2) { LogManager.Configuration.LoggingRules.First().EnableLoggingForLevel(LogLevel.Trace); } LogManager.ReconfigExistingLoggers(); var sw = new Stopwatch(); sw.Start(); try { _mft = MftFile.Load(_fluentCommandLineParser.Object.File); } catch (Exception e) { _logger.Error($"There was an error loading the file! Error: {e.Message}"); return; } sw.Stop(); _logger.Info( $"\r\nProcessed '{_fluentCommandLineParser.Object.File}' in {sw.Elapsed.TotalSeconds:N4} seconds"); StreamWriter swBody = null; StreamWriter swCsv = null; if (_fluentCommandLineParser.Object.BodyDirectory.IsNullOrEmpty() == false) { _fluentCommandLineParser.Object.BodyDriveLetter = _fluentCommandLineParser.Object.BodyDriveLetter.Substring(0, 1); if (Directory.Exists(_fluentCommandLineParser.Object.BodyDirectory) == false) { _logger.Warn( $"Path to '{_fluentCommandLineParser.Object.BodyDirectory}' doesn't exist. Creating..."); Directory.CreateDirectory(_fluentCommandLineParser.Object.BodyDirectory); } var outName = $"{DateTimeOffset.Now:yyyyMMddHHmmss}_MFTECmd_Output.body"; var outFile = Path.Combine(_fluentCommandLineParser.Object.BodyDirectory, outName); _logger.Warn($"\r\nBodyfile output will be saved to '{outFile}'"); try { swBody = new StreamWriter(outFile, false, Encoding.UTF8, 4096 * 4); _bodyWriter = new CsvWriter(swBody); _bodyWriter.Configuration.Delimiter = "|"; var foo = _bodyWriter.Configuration.AutoMap <BodyFile>(); foo.Map(t => t.Md5).Index(0); foo.Map(t => t.Name).Index(1); foo.Map(t => t.Inode).Index(2); foo.Map(t => t.Mode).Index(3); foo.Map(t => t.Uid).Index(4); foo.Map(t => t.Gid).Index(5); foo.Map(t => t.Size).Index(6); foo.Map(t => t.AccessTime).Index(7); foo.Map(t => t.ModifiedTime).Index(8); foo.Map(t => t.RecordModifiedTime).Index(9); foo.Map(t => t.CreatedTime).Index(10); _bodyWriter.Configuration.RegisterClassMap(foo); } catch (Exception e) { _logger.Error( $"\r\nError setting up bodyfile export. Please report to [email protected].\r\n\r\nError: {e.Message}"); _bodyWriter = null; } } if (_fluentCommandLineParser.Object.CsvDirectory.IsNullOrEmpty() == false) { if (Directory.Exists(_fluentCommandLineParser.Object.CsvDirectory) == false) { _logger.Warn( $"Path to '{_fluentCommandLineParser.Object.CsvDirectory}' doesn't exist. Creating..."); Directory.CreateDirectory(_fluentCommandLineParser.Object.CsvDirectory); } var outName = $"{DateTimeOffset.Now:yyyyMMddHHmmss}_MFTECmd_Output.{exportExt}"; var outFile = Path.Combine(_fluentCommandLineParser.Object.CsvDirectory, outName); if (_fluentCommandLineParser.Object.BaseName.IsNullOrEmpty() == false) { outFile = Path.Combine(_fluentCommandLineParser.Object.CsvDirectory, _fluentCommandLineParser.Object.BaseName); } _logger.Warn($"\r\nCSV output will be saved to '{outFile}'"); try { swCsv = new StreamWriter(outFile, false, Encoding.UTF8, 4096 * 4); _csvWriter = new CsvWriter(swCsv); if (_fluentCommandLineParser.Object.CsvSeparator == false) { _csvWriter.Configuration.Delimiter = "\t"; } var foo = _csvWriter.Configuration.AutoMap <MFTRecordOut>(); foo.Map(t => t.EntryNumber).Index(0); foo.Map(t => t.SequenceNumber).Index(1); foo.Map(t => t.InUse).Index(2); foo.Map(t => t.ParentEntryNumber).Index(3); foo.Map(t => t.ParentSequenceNumber).Index(4); foo.Map(t => t.ParentPath).Index(5); foo.Map(t => t.FileName).Index(6); foo.Map(t => t.Extension).Index(7); foo.Map(t => t.FileSize).Index(8); foo.Map(t => t.ReferenceCount).Index(9); foo.Map(t => t.ReparseTarget).Index(10); foo.Map(t => t.IsDirectory).Index(11); foo.Map(t => t.HasAds).Index(12); foo.Map(t => t.IsAds).Index(13); foo.Map(t => t.Timestomped).Index(14).Name("SI<FN"); foo.Map(t => t.uSecZeros).Index(15); foo.Map(t => t.Copied).Index(16); foo.Map(t => t.SiFlags).ConvertUsing(t => t.SiFlags.ToString().Replace(", ", "|")).Index(17); foo.Map(t => t.NameType).Index(18); foo.Map(t => t.Created0x10).ConvertUsing(t => $"{t.Created0x10?.ToString(_fluentCommandLineParser.Object.DateTimeFormat)}").Index(19); foo.Map(t => t.Created0x30).ConvertUsing(t => $"{t.Created0x30?.ToString(_fluentCommandLineParser.Object.DateTimeFormat)}").Index(20); foo.Map(t => t.LastModified0x10).ConvertUsing(t => $"{t.LastModified0x10?.ToString(_fluentCommandLineParser.Object.DateTimeFormat)}") .Index(21); foo.Map(t => t.LastModified0x30).ConvertUsing(t => $"{t.LastModified0x30?.ToString(_fluentCommandLineParser.Object.DateTimeFormat)}") .Index(22); foo.Map(t => t.LastRecordChange0x10).ConvertUsing(t => $"{t.LastRecordChange0x10?.ToString(_fluentCommandLineParser.Object.DateTimeFormat)}") .Index(23); foo.Map(t => t.LastRecordChange0x30).ConvertUsing(t => $"{t.LastRecordChange0x30?.ToString(_fluentCommandLineParser.Object.DateTimeFormat)}") .Index(24); foo.Map(t => t.LastAccess0x10).ConvertUsing(t => $"{t.LastAccess0x10?.ToString(_fluentCommandLineParser.Object.DateTimeFormat)}").Index(25); foo.Map(t => t.LastAccess0x30).ConvertUsing(t => $"{t.LastAccess0x30?.ToString(_fluentCommandLineParser.Object.DateTimeFormat)}").Index(26); foo.Map(t => t.UpdateSequenceNumber).Index(27); foo.Map(t => t.LogfileSequenceNumber).Index(28); foo.Map(t => t.SecurityId).Index(29); foo.Map(t => t.ObjectIdFileDroid).Index(30); foo.Map(t => t.LoggedUtilStream).Index(31); foo.Map(t => t.ZoneIdContents).Index(32); foo.Map(t => t.FnAttributeId).Ignore(); foo.Map(t => t.OtherAttributeId).Ignore(); _csvWriter.Configuration.RegisterClassMap(foo); _csvWriter.WriteHeader <MFTRecordOut>(); _csvWriter.NextRecord(); } catch (Exception e) { _logger.Error( $"\r\nError setting up CSV export. Please report to [email protected].\r\n\r\nError: {e.Message}"); _csvWriter = null; } } if (swBody != null || swCsv != null) { try { ProcessRecords(_mft.FileRecords); ProcessRecords(_mft.FreeFileRecords); } catch (Exception ex) { _logger.Error( $"\r\nError exporting data. Please report to [email protected].\r\n\r\nError: {ex.Message}"); } } swCsv?.Flush(); swCsv?.Close(); swBody?.Flush(); swBody?.Close(); #region ExportRecord if (_fluentCommandLineParser.Object.DumpDir.IsNullOrEmpty() == false) { _logger.Info(""); bool offsetOk; long offset; if (_fluentCommandLineParser.Object.DumpOffset.StartsWith("0x")) { offsetOk = long.TryParse(_fluentCommandLineParser.Object.DumpOffset.Replace("0x", ""), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out offset); } else { offsetOk = long.TryParse(_fluentCommandLineParser.Object.DumpOffset, out offset); } if (offsetOk) { using (var b = new BinaryReader(File.OpenRead(_fluentCommandLineParser.Object.File))) { b.BaseStream.Seek(offset, 0); var fileBytes = b.ReadBytes(1024); var outFile = $"MFTECmd_FILE_Offset0x{offset:X}.bin"; var outFull = Path.Combine(_fluentCommandLineParser.Object.DumpDir, outFile); File.WriteAllBytes(outFull, fileBytes); _logger.Warn($"FILE record at offset 0x{offset:X} dumped to '{outFull}'\r\n"); } } else { _logger.Warn( $"Could not parse '{_fluentCommandLineParser.Object.DumpOffset}' to valid value. Exiting"); return; } } #endregion #region DumpEntry if (_fluentCommandLineParser.Object.DumpEntry.IsNullOrEmpty() == false) { _logger.Info(""); FileRecord fr = null; var segs = _fluentCommandLineParser.Object.DumpEntry.Split('-'); if (segs.Length != 2) { _logger.Warn( $"Could not parse '{_fluentCommandLineParser.Object.DumpEntry}' to valid values. Format is Entry#-Sequence# in either decimal or hex format. Exiting"); return; } bool entryOk; bool seqOk; int entry; int seq; if (_fluentCommandLineParser.Object.DumpEntry.StartsWith("0x")) { var seg0 = segs[0].Replace("0x", ""); var seg1 = segs[1].Replace("0x", ""); entryOk = int.TryParse(seg0, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out entry); seqOk = int.TryParse(seg1, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out seq); } else { entryOk = int.TryParse(segs[0], out entry); seqOk = int.TryParse(segs[1], out seq); } if (entryOk == false || seqOk == false) { _logger.Warn( $"Could not parse '{_fluentCommandLineParser.Object.DumpEntry}' to valid values. Exiting"); return; } var key = $"{entry:X8}-{seq:X8}"; if (_mft.FileRecords.ContainsKey(key)) { fr = _mft.FileRecords[key]; } else if (_mft.FreeFileRecords.ContainsKey(key)) { fr = _mft.FreeFileRecords[key]; } if (fr == null) { _logger.Warn( $"Could not find file record with entry/seq '{_fluentCommandLineParser.Object.DumpEntry}'. Exiting"); return; } _logger.Warn($"Dumping details for file record with key '{key}'\r\n"); _logger.Info(fr); } #endregion }
public static Mft ReadMft(BinaryReader r) { var mft = new Mft(); mft.header = r.ReadBytes(40); //check version and header size if (BitConverter.ToUInt32(mft.header, 0) != 441336215U || BitConverter.ToInt32(mft.header, 4) != 40) { throw new IOException("Unknown header"); } r.BaseStream.Position = mft.MftOffset; if (r.ReadUInt32() != 443835981U) { throw new IOException("Unknown MFT header"); } r.BaseStream.Position += 8; //junk var count = r.ReadUInt32() - 1; //this header was counted as an entry r.BaseStream.Position += 8; //0 var entries = mft.entries = new MftEntry[count]; for (var i = 0; i < count; i++) { entries[i] = MftEntry.Read(r); } var e = entries[1]; if (e.compression != 0) { throw new IOException("File is compressed"); } var size = e.size; r.BaseStream.Position = e.offset; while (size > 0) { var id = r.ReadInt32(); var i = r.ReadInt32(); if (i > 0) { var entry = entries[i - 1]; if (entry.baseId == 0) { entry.baseId = id; entry.fileId = id; } else if (id < entry.baseId) { entry.baseId = id; } else if (id > entry.fileId) { entry.fileId = id; } } size -= 8; } return(mft); }