From b8997e1ac996788985e69bbd1ad1079cf9454eff Mon Sep 17 00:00:00 2001 From: Grigoryev Ilya Alekseevich Date: Tue, 31 Mar 2026 23:22:50 +0500 Subject: [PATCH] refactor --- app/main.go | 12 +++ internal/cmd/args.go | 178 +++++++++++++++++++++++++++++++++++++ internal/cmd/cmd.go | 195 ++++------------------------------------- internal/cmd/render.go | 47 ++++++++++ 4 files changed, 253 insertions(+), 179 deletions(-) create mode 100644 internal/cmd/args.go create mode 100644 internal/cmd/render.go diff --git a/app/main.go b/app/main.go index 06ab7d0..5144006 100644 --- a/app/main.go +++ b/app/main.go @@ -1 +1,13 @@ package main + +import ( + "fmt" + "mftecmd/internal/cmd" +) + +func main() { + program := cmd.NewCLI() + cmd := program.Run() + + fmt.Printf("%+v\n", cmd) +} diff --git a/internal/cmd/args.go b/internal/cmd/args.go new file mode 100644 index 0000000..3765b01 --- /dev/null +++ b/internal/cmd/args.go @@ -0,0 +1,178 @@ +// Пакет для работы с CLI интерфейсом +package cmd + +// Константы для значений по умолчанию +const ( + DefaultTimeFormat = "yyyy-MM-dd HH:mm:ss.fffffff" // Формат временных меток + DefaultResidentMax = 1024 // Ограничение размера резидентных файлов + MaxResidentDataBytes = 1024000 // Максимальное ограничение размера резидентных файлов +) + +// Корневая структура флагов. +// Используется для выбора типа файлов +type CMD 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"` +} + +// Операции с $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"` // Путь сохранения файла +} + +// Сохранение резульатов в JSON +type JSONOutput struct { + JSONPath string `name:"json" short:"j" help:"Path to save CSV formatted results to" type:"path"` // Путь сохранения файла +} + +// Сохранение резульатов в 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 +} + +// Флаги для работы с 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 +} + +// Флаги для просмотра информации из $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 +} + +// Флаги для извлечения записей из $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` // Директория для экспорта +} + +// Флаги для парсинга $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 +} + +// Флаги для парсинга $Boot +type BootParseCmd struct { + Input string `arg:"" name:"path" help:"Path to the $Boot file." type:"path"` // Путь к $Boot + + CSVOutput + VSSOptions +} + +// Флаги для парсинга $SDS +type SDSParseCmd struct { + Input string `arg:"" name:"path" help:"Path to the $SDS file." type:"path"` // Путь к $SDS + + CSVOutput + VSSOptions +} + +// Флаги для просмотра идентификаторов $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."` // Идентификатор +} + +// Флаги для парсинга $I30 +type I30ParseCmd struct { + Input string `arg:"" name:"path" help:"Path to the $I30 file." type:"path"` // Путь к $I30 + + CSVOutput + TimeFormatOption +} + +// Флаги для парсинга $LogFile +type LogFileParseCmd struct { + Input string `arg:"" name:"path" help:"Path to the $LogFile file." type:"path"` // Путь к $LogFile +} diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 9ed1bf9..bc8ef95 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -5,134 +5,26 @@ import ( "strings" ) -// Константы для значений по умолчанию -const ( - DefaultTimeFormat = "yyyy-MM-dd HH:mm:ss.fffffff" // Формат временных меток - DefaultResidentMax = 1024 // Ограничение размера резидентных файлов - MaxResidentDataBytes = 1024000 // Максимальное ограничение размера резидентных файлов -) - -// Корневая структура флагов. -// Используется для выбора типа файлов -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"` -} - -// Операции с $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"` // Путь сохранения файла -} - // Проверка пути на пустое значение +// Принимает на вход экземпляр bodyOutput и возвращает флаг, означающий непустой путь 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"` // Путь сохранения файла -} - // Проверка пути на пустое значение +// Принимает на вход экземпляр bodyOutput и возвращает флаг, означающий непустой путь 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 -} - -// Проверка пути на пустое значение +// Проверка пути на пустое значение. +// Принимает на вход экземпляр bodyOutput и возвращает флаг, означающий непустой путь 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 -} - // Валидация флагов +// Принимает на вход указатель на экземпляр mftParseCmd и возвращает ошибку 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") // Проверка на наличие флага формата файла вывода @@ -152,46 +44,20 @@ func (c *MFTParseCmd) Validate() error { return nil } -// Флаги для просмотра информации из $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 -} - // Валидация флагов +// Принимает на вход указатель на экземпляр mftShowCmd и возвращает ошибку 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` // Директория для экспорта -} - // Валидация флагов +// Принимает на вход указатель на экземпляр mftExportRecordCmd и возвращает ошибку 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 -} - // Валидация флагов +// Принимает на вход указатель на экземпляр journalParseCmd и возвращает ошибку 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") // Проверка вывода @@ -199,28 +65,14 @@ func (c *JournalParseCmd) Validate() error { return nil } -// Флаги для парсинга $Boot -type BootParseCmd struct { - Input string `arg:"" name:"path" help:"Path to the $Boot file." type:"path"` // Путь к $Boot - - CSVOutput - VSSOptions -} - // Валидация флагов +// Принимает на вход указатель на экземпляр bootParseCmd и возвращает ошибку 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 -} - // Валидация флагов +// Принимает на вход указатель на экземпляр sdsParseCmd и возвращает ошибку func (c *SDSParseCmd) Validate() error { if !c.CSVOutput.Enabled() { return fmt.Errorf("sds parse requires --csv") // Проверка вывода в CSV @@ -228,26 +80,14 @@ func (c *SDSParseCmd) Validate() error { 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."` // Идентификатор -} - // Валидация флагов +// Принимает на вход указатель на экземпляр sdsShowCmd и возвращает ошибку 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 -} - // Валидация флагов +// Принимает на вход указатель на экземпляр i30ParseCmd и возвращает ошибку func (c *I30ParseCmd) Validate() error { if !c.CSVOutput.Enabled() { return fmt.Errorf("i30 parse requires --csv") // Проверка вывода в CSV @@ -255,17 +95,14 @@ func (c *I30ParseCmd) Validate() error { return nil } -// Флаги для парсинга $LogFile -type LogFileParseCmd struct { - Input string `arg:"" name:"path" help:"Path to the $LogFile file." type:"path"` // Путь к $LogFile -} - -// Валидация флагов +// Валидация флагов. +// Принимает на вход указатель на экземпляр logFileParseCmd и возвращает ошибку func (c *LogFileParseCmd) Validate() error { return nil } -// Валидация комманд +// Валидация команд. +// Принимает на вход объект и при наличии метода Validate производит валидацию func ValidateLeaf(cmd any) error { switch v := cmd.(type) { case interface{ Validate() error }: diff --git a/internal/cmd/render.go b/internal/cmd/render.go new file mode 100644 index 0000000..009f8da --- /dev/null +++ b/internal/cmd/render.go @@ -0,0 +1,47 @@ +package cmd + +import ( + "os" + + "github.com/alecthomas/kong" +) + +// Функция создания флагов и рагументов +func getNewCmd() *CMD { + return &CMD{} +} + +// Функция создания парсера +func getNewParser() *kong.Kong { + return kong.Must( + kong.Name("mftecmd"), + kong.Description("Utility for processing $MFT, $J, $LogFile, $Boot, $SDS"), + kong.UsageOnError(), + ) +} + +// Структура CLI +type cli struct { + parser kong.Kong // Парсер + cmd CMD // Аргументы +} + +// Создания экземпляра CLI. +// Возвращает указатель на экземпляр CLI +func NewCLI() *cli { + return &cli{ + parser: *getNewParser(), + cmd: *getNewCmd(), + } +} + +// Метод запуска CLI. +// Возвращает cmd - экземпляр структуры аргументов и ошибку +func (c *cli) Run() *CMD { + var cmd CMD + ctx, err := c.parser.Parse(os.Args[1:]) + c.parser.FatalIfErrorf(err) + _ = ctx + + return &cmd +}