diff --git a/go.mod b/go.mod index ce91eb3..bb764f1 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module mftecmd go 1.25.3 -require github.com/fred1268/go-clap v1.2.1 // indirect +require ( + github.com/alecthomas/kong v1.14.0 // indirect + github.com/fred1268/go-clap v1.2.1 // indirect +) diff --git a/go.sum b/go.sum index 68054e4..b938618 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s= +github.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I= github.com/fred1268/go-clap v1.2.1 h1:wi8Tokb2zmOEuwwTTfKX5Sj1h6ZpT2BxRtx1/ZJsol4= github.com/fred1268/go-clap v1.2.1/go.mod h1:A5/yYBapOy6UyujlbxL7p/bX9J7bzyoMRzQKFwveXF0= diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 2b4eb5f..9ed1bf9 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -1,69 +1,276 @@ package cmd import ( - "os" - - "github.com/fred1268/go-clap/clap" + "fmt" + "strings" ) -// Перечень флагов -type Config struct { - InputFile string `clap:"trailing,mandatory"` // Входящий файл $MFT, $J, $LogFile, $Boot, $SDS - FileFormat string `clap:"--format,-F,mandatory"` // Формат выходного файла JSON, CSV, bodyfile. По умолчанию CSV - OutputDirectory string `clap:"--output,-o,mandatory"` // Путь выходного файла - DiskLetter string `clap:"--disk-later,-bdl"` // Буква диска для bodyfile - LineFeed bool `clap:"--line-feed,-blf"` // Используемый конец строки bodyfile. true - LF, false - CRLF. По умолчанию false - // TODO: Найти описание аргументов - DD string `clap:"-dd"` // -dd - DO string `clap:"-do"` // -do - DE string `clap:"-de"` // -de - FLS bool `clap:"-fls"` // -fls - SecurityIdentifier bool `clap:"--security-identifier,-ds"` // Выводит полные детали для идентификатора безопасности в десятичном или шестнадцатеричном виде - DatetimeFormat string `clap:"--date-format,-dt"` // Формат даты и времени. По умолчанию yyyy-MM-dd HH:mm:ss.fffffff - FilenameType bool `clap:"--filename-type,-sn"` // Включает типы имён файлов DOS. По умолчанию false - FileListingBrief bool `clap:"--brief-filelisting,-fl"` // Включает сокращённый список файлов. Используется с CSV - AttributeLabel bool `clap:"--atribute-label,-at"` // Включает все временные метки из атрибута 0x30, а не только когда они отличаются от 0x10. По умолчанию false - VolumeShadowCopy bool `clap:"--volume-shadow-copy,-vss"` // Включает все теневые копии томов, которые существуют на диске. По умолчанию false - Dedup bool `clap:"--dedup"` // Убирает все дубликаты в теневых копиях на основе SHA1. Первый кандидат выигрывает. По умолчанию false +// Константы для значений по умолчанию +const ( + DefaultTimeFormat = "yyyy-MM-dd HH:mm:ss.fffffff" // Формат временных меток + DefaultResidentMax = 1024 // Ограничение размера резидентных файлов + MaxResidentDataBytes = 1024000 // Максимальное ограничение размера резидентных файлов +) - Debug bool `clap:"--debug"` // Вывод дебаг-информации. По умолчанию false - Trace bool `clap:"--trace"` // Вывод информации трассировки. По умолчанию false +// Корневая структура флагов. +// Используется для выбора типа файлов +type CLI struct { + Debug bool `help:"Show debug information during processing"` + Trace bool `help:"Show trace information during processing"` + + MFT MFTGroup `name:"mft" cmd:"" help:"Processing with $MFT"` + J JournalGroup `name:"j" cmd:"" help:"Processing with $J (USN Journal)"` + Boot BootGroup `name:"boot" cmd:"" help:"Processing with $Boot"` + SDS SDSGroup `name:"sds" cmd:"" help:"Processing with $SDS"` + I30 I30Group `name:"i30" cmd:"" help:"Processing with $I30"` + LogFile LogFileGroup `name:"logfile" cmd:"" help:"Processing with $LogFile"` } -// Установка значений по умолчанию для параметров -func setDefault() *Config { - cfg := &Config{ - FileFormat: "csv", - LineFeed: false, - DatetimeFormat: "yyyy-MM-dd HH:mm:ss.fffffff", - FilenameType: false, - AttributeLabel: false, - VolumeShadowCopy: false, - Dedup: false, - Debug: false, - Trace: false, +// Операции с $MFT +type MFTGroup struct { + Parse MFTParseCmd `name:"parse" cmd:"" help:"Parse $MFT and write CSV, JSON, or bodyfile output"` // Парсинг $MFT записей + Show MFTShowCmd `name:"show" cmd:"" help:"Show full details for a specific $MFT entry or entry-sequence"` // Вывод информации о сущностях или последовательностях сущностей + ExportRecord MFTExportRecordCmd `name:"export-record" cmd:"" help:"Export one raw $MFT FILE record by offset"` // Извлечение конкретной $MFT записи +} + +// Операции с $J (USN Journal) +type JournalGroup struct { + Parse JournalParseCmd `name:"parse" cmd:"" help:"Parse $J and write CSV or JSON output"` // Парсинг $J записей +} + +// Операции с $Boot +type BootGroup struct { + Parse BootParseCmd `name:"parse" cmd:"" help:"Parse $Boot. CSV output is optional, matching MFTECmd behavior"` // Парсинг $Boot записей +} + +// Операции с $SDS +type SDSGroup struct { + Parse SDSParseCmd `name:"parse" cmd:"" help:"Parse $SDS and write CSV output"` // Парсинг $SDS записей + Show SDSShowCmd `name:"show" cmd:"" help:"Show full details for a specific Security ID from $SDS"` // Вывод информации по конкретному идентификатору из $SDS +} + +// Операции с $I30 +type I30Group struct { + Parse I30ParseCmd `name:"parse" cmd:"" help:"Parse $I30 and write CSV output"` // Парсинг $I30 записей +} + +// Операции с $LogFile +type LogFileGroup struct { + Parse LogFileParseCmd `name:"parse" cmd:"" help:"Recognize $LogFile input, but report that parsing is not supported yet"` // Парсинг $LogFile записей +} + +// Переиспользуемые блоки флагов. +// Сохранение резульатов в CSV +type CSVOutput struct { + CSVPath string `name:"csv" short:"c" help:"Path to save CSV formatted results to" type:"path"` // Путь сохранения файла +} + +// Проверка пути на пустое значение +func (o CSVOutput) Enabled() bool { + return strings.TrimSpace(o.CSVPath) != "" +} + +// Сохранение резульатов в JSON +type JSONOutput struct { + JSONPath string `name:"json" short:"j" help:"Path to save CSV formatted results to" type:"path"` // Путь сохранения файла +} + +// Проверка пути на пустое значение +func (o JSONOutput) Enabled() bool { + return strings.TrimSpace(o.JSONPath) != "" +} + +// Сохранение резульатов в Bodyfile +type BodyOutput struct { + BodyPath string `name:"body" short:"b" help:"Path to save bodyfile formatted results to" type:"path"` // Путь сохранения файла + DriveLetter string `name:"drive-letter" short:"l" help:"Drive letter (C, D, etc.) to use with bodyfile. Only the drive letter itself should be provided"` //Буква диска + UseLF bool `name:"line-feed" default:"false" help:"When true, use LF vs CRLF for newlines. Default is FALSE"` // Окончание строки. false - CRLF, true - LF +} + +// Проверка пути на пустое значение +func (o BodyOutput) Enabled() bool { + return strings.TrimSpace(o.BodyPath) != "" +} + +// Флаги для работы с VSS +type VSSOptions struct { + VSS bool `name:"vss" default:"false" help:"Process all Volume Shadow Copies that exist on drive. Default is FALSE"` // Включает вывод VSC для диска + Dedupe bool `name:"dedupe" default:"false" help:"Deduplicate VSCs based on SHA-1. First file found wins. Default is FALSE"` // Включает удаление дубликатов VSS +} + +// Флаги для работы с временными метками +type TimeFormatOption struct { + TimeFormat string `name:"time-format" short:"t" default:"yyyy-MM-dd HH:mm:ss.fffffff" help:"The custom date/time format to use when displaying time stamps. Default is: yyyy-MM-dd HH:mm:ss.fffffff"` // Задаёт формат временных меток +} + +// Флаши для работы с резидентными файлами +type ResidentDataOptions struct { + DumpResidentFiles bool `name:"dump-resident-files" default:"false" help:"Dump resident files from $MFT into a Resident subdirectory under --csv or --json"` // Включает сбор резидентных файлов + IncludeResidentData bool `name:"include-resident-data" default:"false" help:"Include resident data directly in CSV or JSON output"` // Включает вывод резидентных файлов в файл вывода + ResidentExt string `name:"resident-ext" help:"Comma-separated extensions to include for resident data, for example .txt,.ps1,.bat. Empty means all extensions"` // Расширения резидентных файлов + ResidentMaxBytes int `name:"resident-max-bytes" default:"1024" help:"Maximum resident data size to include in bytes. Max 1024000"` // Максимальный размер резидентных файлов +} + +// Флаги настройки отображения $MFT +type MFTDisplayOptions struct { + ShortNames bool `name:"short-names" help:"Include DOS 8.3 short names in $MFT output"` // Сокращённые имена файлов DOS + FileList bool `name:"file-list" help:"Generate condensed file listing output. Requires --csv"` // Сжатый листинг файлов + AllFNTimes bool `name:"all-fn-times" help:"Include all timestamps from the 0x30 FILE_NAME attribute, not only those that differ from 0x10"` // Включает все временные метки для 0x30 атрибутов + RecoverSlack bool `name:"recover-slack" help:"Recover slack space from FILE records when processing $MFT"` // Восстанавливает slack space из FILE +} + +// Флаги для парсинга с $MFT +type MFTParseCmd struct { + Input string `arg name:"path" help:"Path to the $MFT file." type:"path"` // Путь к $MFT + + CSVOutput + JSONOutput + BodyOutput + VSSOptions + TimeFormatOption + MFTDisplayOptions + ResidentDataOptions +} + +// Валидация флагов +func (c *MFTParseCmd) Validate() error { + if !c.CSVOutput.Enabled() && !c.JSONOutput.Enabled() && !c.BodyOutput.Enabled() { + return fmt.Errorf("mft parse requires at least one output: --csv, --json, or --body") // Проверка на наличие флага формата файла вывода } - - return cfg -} - -// Запуск утилиты -// TODO: Поправить парсинг аргументов из консоли -func Init(args []string) (*clap.Results, error) { - cfg := setDefault() // Импорт параметров по умолчанию - - var ( - results *clap.Results // Массив результатов - err error - ) - - // Получение аргументов - if results, err = clap.Parse(args, cfg); err != nil { - return nil, err + if c.BodyOutput.Enabled() && strings.TrimSpace(c.DriveLetter) == "" { + return fmt.Errorf("--drive-letter is required when using --body") // Проверка наличия буквы диска в Bodyfile } - - return results, nil + if c.FileList && !c.CSVOutput.Enabled() { + return fmt.Errorf("--file-list requires --csv") // Проверка CSV вывода для листинга файлов + } + if (c.DumpResidentFiles || c.IncludeResidentData) && !c.CSVOutput.Enabled() && !c.JSONOutput.Enabled() { + return fmt.Errorf("--dump-resident-files and --include-resident-data require --csv or --json") // Проверка CSV или JSON вывода для резидентных файлов + } + if c.ResidentMaxBytes <= 0 || c.ResidentMaxBytes > MaxResidentDataBytes { + return fmt.Errorf("--resident-max-bytes must be between 1 and %d", MaxResidentDataBytes) // Проверка максимального значения размера резидентных файлов + } + return nil } -func Run(results *clap.Results) { +// Флаги для просмотра информации из $MFT +type MFTShowCmd struct { + Input string `arg name:"path" help:"Path to the $MFT file" type:"path"` // Путь к $MFT + Entry string `arg name:"entry" help:"Entry or entry-sequence. Examples: 5, 624-5, 0x270-0x5"` // Идентификатор сущности + + ListDir bool `name:"list-dir" help:"If the selected entry is a directory, list its contents"` // Листинг директори + + TimeFormatOption +} + +// Валидация флагов +func (c *MFTShowCmd) Validate() error { + return nil +} + +// Флаги для извлечения записей из $MFT +type MFTExportRecordCmd struct { + Input string `arg name:"path" help:"Path to the $MFT file" type:"path"` // Путь к $MFT + Offset string `name:"offset" help:"FILE record offset in decimal or hex, for example 5120 or 0x1400" required` // Оффсет + OutDir string `name:"out" short:"o" help:"Directory to write the exported FILE record into" type:"path" required` // Директория для экспорта +} + +// Валидация флагов +func (c *MFTExportRecordCmd) Validate() error { + return nil +} + +// Флаги для парсинга $J +type JournalParseCmd struct { + Input string `arg:"" name:"path" help:"Path to the $J file." type:"path"` // Путь к $J + + CSVOutput + JSONOutput + VSSOptions + TimeFormatOption + + MFTPath string `name:"mft" short:"m" help:"Path to $MFT used to resolve parent paths in $J output." type:"path"` // Путь к $MFT +} + +// Валидация флагов +func (c *JournalParseCmd) Validate() error { + if !c.CSVOutput.Enabled() && !c.JSONOutput.Enabled() { + return fmt.Errorf("j parse requires at least one output: --csv or --json") // Проверка вывода + } + return nil +} + +// Флаги для парсинга $Boot +type BootParseCmd struct { + Input string `arg:"" name:"path" help:"Path to the $Boot file." type:"path"` // Путь к $Boot + + CSVOutput + VSSOptions +} + +// Валидация флагов +func (c *BootParseCmd) Validate() error { + return nil +} + +// Флаги для парсинга $SDS +type SDSParseCmd struct { + Input string `arg:"" name:"path" help:"Path to the $SDS file." type:"path"` // Путь к $SDS + + CSVOutput + VSSOptions +} + +// Валидация флагов +func (c *SDSParseCmd) Validate() error { + if !c.CSVOutput.Enabled() { + return fmt.Errorf("sds parse requires --csv") // Проверка вывода в CSV + } + return nil +} + +// Флаги для просмотра идентификаторов $SDS +type SDSShowCmd struct { + Input string `arg:"" name:"path" help:"Path to the $SDS file." type:"path"` // Путь к $SDS + SecurityID string `arg:"" name:"security-id" help:"Security ID in decimal or hex, for example 624 or 0x270."` // Идентификатор +} + +// Валидация флагов +func (c *SDSShowCmd) Validate() error { + return nil +} + +// Флаги для парсинга $I30 +type I30ParseCmd struct { + Input string `arg:"" name:"path" help:"Path to the $I30 file." type:"path"` // Путь к $I30 + + CSVOutput + TimeFormatOption +} + +// Валидация флагов +func (c *I30ParseCmd) Validate() error { + if !c.CSVOutput.Enabled() { + return fmt.Errorf("i30 parse requires --csv") // Проверка вывода в CSV + } + return nil +} + +// Флаги для парсинга $LogFile +type LogFileParseCmd struct { + Input string `arg:"" name:"path" help:"Path to the $LogFile file." type:"path"` // Путь к $LogFile +} + +// Валидация флагов +func (c *LogFileParseCmd) Validate() error { + return nil +} + +// Валидация комманд +func ValidateLeaf(cmd any) error { + switch v := cmd.(type) { + case interface{ Validate() error }: + return v.Validate() + default: + return nil + } }