Created
February 13, 2025 11:12
-
-
Save CynicRus/20f38093adc51b14866046960bf84a10 to your computer and use it in GitHub Desktop.
Read Master Fat Table by Powershell > 7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| param( | |
| [Parameter(Mandatory=$true)] | |
| [ValidatePattern('^[A-Za-z]$')] | |
| [string]$DriveLetter | |
| ) | |
| # Структура загрузочного сектора NTFS | |
| $NTFSBootSectorFormat = @{ | |
| JumpInstruction = 0..2 | |
| OemID = 3..10 | |
| BytesPerSector = 11..12 | |
| SectorsPerCluster = 13 | |
| ReservedSectors = 14..15 | |
| MediaDescriptor = 21 | |
| SectorsPerTrack = 24..25 | |
| NumberOfHeads = 26..27 | |
| HiddenSectors = 28..31 | |
| TotalSectors = 40..47 | |
| MFTClusterNumber = 48..55 | |
| MirrorMFTClusterNumber = 56..63 | |
| ClustersPerMFTRecord = 64 | |
| ClustersPerIndexBuffer = 68 | |
| } | |
| # Расширенная структура заголовка MFT | |
| $MFTHeaderFormat = @{ | |
| Signature = 0..3 | |
| UpdateSequenceOffset = 4..5 | |
| UpdateSequenceSize = 6..7 | |
| LogFileSequenceNumber = 8..15 | |
| SequenceNumber = 16..17 | |
| HardLinkCount = 18..19 | |
| FirstAttributeOffset = 20..21 | |
| Flags = 22..23 | |
| UsedSize = 24..27 | |
| AllocatedSize = 28..31 | |
| BaseFileRecord = 32..39 | |
| NextAttributeID = 40..41 | |
| UpdateSequenceNumber = 42..43 | |
| } | |
| # Структура атрибута MFT | |
| $MFTAttributeHeaderFormat = @{ | |
| AttributeType = 0..3 | |
| AttributeLength = 4..7 | |
| NonResidentFlag = 8 | |
| NameLength = 9 | |
| NameOffset = 10..11 | |
| Flags = 12..15 | |
| AttributeID = 16..17 | |
| ContentLength = 16..19 | |
| ContentOffset = 20..21 | |
| } | |
| function Parse-NTFSBootSector { | |
| param([byte[]]$buffer) | |
| if ($buffer[0..7] -notcontains 0xEB -or $buffer[3..10] -notcontains 0x4E) { | |
| throw "Неверная сигнатура загрузочного сектора NTFS" | |
| } | |
| return @{ | |
| BytesPerSector = [BitConverter]::ToUInt16($buffer, 11) | |
| SectorsPerCluster = $buffer[13] | |
| MFTClusterNumber = [BitConverter]::ToUInt64($buffer, 48) | |
| ClustersPerMFTRecord = [int]([char]$buffer[64]) | |
| } | |
| } | |
| function Parse-MFTHeader { | |
| param( | |
| [byte[]]$buffer, | |
| [int]$offset = 0 | |
| ) | |
| if ($buffer.Length -lt ($offset + 42)) { | |
| throw "Недостаточно данных для заголовка MFT" | |
| } | |
| $signature = [System.Text.Encoding]::ASCII.GetString($buffer[$offset..($offset+3)]) | |
| if ($signature -ne 'FILE') { | |
| throw "Неверная сигнатура MFT: $signature" | |
| } | |
| $header = @{ | |
| Signature = $signature | |
| UpdateSequenceOffset = [BitConverter]::ToUInt16($buffer, $offset + 4) | |
| UpdateSequenceSize = [BitConverter]::ToUInt16($buffer, $offset + 6) | |
| LogFileSequenceNumber = [BitConverter]::ToUInt64($buffer, $offset + 8) | |
| SequenceNumber = [BitConverter]::ToUInt16($buffer, $offset + 16) | |
| HardLinkCount = [BitConverter]::ToUInt16($buffer, $offset + 18) | |
| FirstAttributeOffset = [BitConverter]::ToUInt16($buffer, $offset + 20) | |
| Flags = [BitConverter]::ToUInt16($buffer, $offset + 22) | |
| UsedSize = [BitConverter]::ToUInt32($buffer, $offset + 24) | |
| AllocatedSize = [BitConverter]::ToUInt32($buffer, $offset + 28) | |
| BaseFileRecord = [BitConverter]::ToUInt64($buffer, $offset + 32) | |
| NextAttributeID = [BitConverter]::ToUInt16($buffer, $offset + 40) | |
| } | |
| # Применяем исправление последовательности обновления | |
| $usn = [BitConverter]::ToUInt16($buffer, $offset + $header.UpdateSequenceOffset) | |
| $usArray = New-Object byte[] ($header.UpdateSequenceSize * 2 - 2) | |
| [Array]::Copy($buffer, ($offset + $header.UpdateSequenceOffset + 2), $usArray, 0, $usArray.Length) | |
| for ($i = 0; $i -lt ($header.UpdateSequenceSize - 1); $i++) { | |
| $sectorEnd = $offset + (($i + 1) * 512) - 2 | |
| if ([BitConverter]::ToUInt16($buffer, $sectorEnd) -eq $usn) { | |
| $buffer[$sectorEnd] = $usArray[$i * 2] | |
| $buffer[$sectorEnd + 1] = $usArray[$i * 2 + 1] | |
| } | |
| } | |
| return $header | |
| } | |
| function Parse-MFTAttribute { | |
| param( | |
| [byte[]]$buffer, | |
| [int]$offset | |
| ) | |
| # Проверяем, достаточно ли данных для чтения базовых полей | |
| if ($offset + 16 > $buffer.Length) { | |
| return $null | |
| } | |
| $attributeType = [BitConverter]::ToUInt32($buffer, $offset) | |
| if ($attributeType -eq 0xFFFFFFFF) { return $null } | |
| $length = [BitConverter]::ToUInt32($buffer, $offset + 4) | |
| # Проверяем, что длина атрибута корректная | |
| if ($length -le 0 -or ($offset + $length) > $buffer.Length) { | |
| return $null | |
| } | |
| $nonResident = $buffer[$offset + 8] -ne 0 | |
| $nameLength = $buffer[$offset + 9] | |
| $nameOffset = [BitConverter]::ToUInt16($buffer, $offset + 10) | |
| $attribute = @{ | |
| Type = $attributeType | |
| Length = $length | |
| NonResident = $nonResident | |
| Name = "" | |
| } | |
| # Безопасное чтение имени атрибута | |
| if ($nameLength -gt 0 -and $nameOffset -gt 0) { | |
| $nameStartOffset = $offset + $nameOffset | |
| $nameByteLength = $nameLength * 2 # Unicode использует 2 байта на символ | |
| if (($nameStartOffset + $nameByteLength) -le ($offset + $length) -and | |
| ($nameStartOffset + $nameByteLength) -le $buffer.Length) { | |
| try { | |
| $attribute.Name = [System.Text.Encoding]::Unicode.GetString( | |
| $buffer[$nameStartOffset..($nameStartOffset + $nameByteLength - 1)] | |
| ) | |
| } | |
| catch { | |
| $attribute.Name = "<ошибка чтения имени>" | |
| } | |
| } | |
| } | |
| if (-not $nonResident) { | |
| try { | |
| $contentLength = [BitConverter]::ToUInt16($buffer, $offset + 16) | |
| $contentOffset = [BitConverter]::ToUInt16($buffer, $offset + 20) | |
| if ($contentLength -gt 0 -and | |
| ($offset + $contentOffset + $contentLength) -le ($offset + $length) -and | |
| ($offset + $contentOffset + $contentLength) -le $buffer.Length) { | |
| $attribute.Content = New-Object byte[] $contentLength | |
| [Array]::Copy($buffer, ($offset + $contentOffset), $attribute.Content, 0, $contentLength) | |
| } | |
| } | |
| catch { | |
| $attribute.Content = $null | |
| } | |
| } | |
| return $attribute | |
| } | |
| # Добавляем определение Native методов | |
| Add-Type -TypeDefinition @" | |
| using System; | |
| using System.Runtime.InteropServices; | |
| public class NativeFileIO { | |
| public const uint GENERIC_READ = 0x80000000; | |
| public const uint FILE_SHARE_READ = 0x1; | |
| public const uint FILE_SHARE_WRITE = 0x2; | |
| public const uint OPEN_EXISTING = 3; | |
| [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] | |
| public static extern IntPtr CreateFile( | |
| string lpFileName, | |
| uint dwDesiredAccess, | |
| uint dwShareMode, | |
| IntPtr lpSecurityAttributes, | |
| uint dwCreationDisposition, | |
| uint dwFlagsAndAttributes, | |
| IntPtr hTemplateFile | |
| ); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool ReadFile( | |
| IntPtr hFile, | |
| [Out] byte[] lpBuffer, | |
| uint nNumberOfBytesToRead, | |
| out uint lpNumberOfBytesRead, | |
| IntPtr lpOverlapped | |
| ); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool SetFilePointerEx( | |
| IntPtr hFile, | |
| long liDistanceToMove, | |
| out long lpNewFilePointer, | |
| uint dwMoveMethod | |
| ); | |
| [DllImport("kernel32.dll", SetLastError = true)] | |
| public static extern bool CloseHandle( | |
| IntPtr hObject | |
| ); | |
| } | |
| "@ | |
| try { | |
| # Проверка прав администратора | |
| $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| if (-not $isAdmin) { | |
| throw "Требуются права администратора" | |
| } | |
| $DrivePath = "\\.\${DriveLetter}:" | |
| $handle = [NativeFileIO]::CreateFile($DrivePath, [NativeFileIO]::GENERIC_READ, | |
| [NativeFileIO]::FILE_SHARE_READ -bor [NativeFileIO]::FILE_SHARE_WRITE, | |
| [IntPtr]::Zero, [NativeFileIO]::OPEN_EXISTING, 0, [IntPtr]::Zero) | |
| if ($handle -eq [IntPtr]-1) { | |
| throw "Ошибка открытия диска: $([Runtime.InteropServices.Marshal]::GetLastWin32Error())" | |
| } | |
| # Чтение загрузочного сектора | |
| $bootSector = New-Object byte[] 512 | |
| $bytesRead = 0 | |
| $success = [NativeFileIO]::ReadFile($handle, $bootSector, 512, [ref]$bytesRead, [IntPtr]::Zero) | |
| if (-not $success) { | |
| throw "Ошибка чтения загрузочного сектора" | |
| } | |
| $ntfsBootSector = Parse-NTFSBootSector $bootSector | |
| $mftOffset = $ntfsBootSector.MFTClusterNumber * $ntfsBootSector.BytesPerSector * $ntfsBootSector.SectorsPerCluster | |
| # Перемещаемся к началу MFT | |
| $newPosition = 0 | |
| $success = [NativeFileIO]::SetFilePointerEx($handle, $mftOffset, [ref]$newPosition, 0) | |
| if (-not $success) { | |
| throw "Ошибка позиционирования на MFT" | |
| } | |
| # Читаем MFT запись | |
| $mftRecord = New-Object byte[] 1024 | |
| $bytesRead = 0 | |
| $success = [NativeFileIO]::ReadFile($handle, $mftRecord, 1024, [ref]$bytesRead, [IntPtr]::Zero) | |
| if (-not $success) { | |
| throw "Ошибка чтения MFT записи" | |
| } | |
| # Парсим заголовок MFT | |
| $mftHeader = Parse-MFTHeader $mftRecord | |
| Write-Host "`nИнформация о MFT:" | |
| Write-Host "Байт на сектор: $($ntfsBootSector.BytesPerSector)" | |
| Write-Host "Секторов на кластер: $($ntfsBootSector.SectorsPerCluster)" | |
| Write-Host "Смещение MFT: $mftOffset" | |
| Write-Host "`nЗаголовок MFT:" | |
| $mftHeader.Keys | Sort-Object | ForEach-Object { | |
| Write-Host "${_}: $($mftHeader[$_])" | |
| } | |
| # Читаем атрибуты | |
| <#$currentOffset = $mftHeader.FirstAttributeOffset | |
| while ($currentOffset -lt $mftRecord.Length) { | |
| $attribute = Parse-MFTAttribute $mftRecord $currentOffset | |
| if ($null -eq $attribute) { break } | |
| Write-Host "`nАтрибут:" | |
| Write-Host "Тип: 0x$($attribute.Type.ToString('X'))" | |
| Write-Host "Длина: $($attribute.Length)" | |
| Write-Host "Нерезидентный: $($attribute.NonResident)" | |
| if ($attribute.Name) { | |
| Write-Host "Имя: $($attribute.Name)" | |
| } | |
| $currentOffset += $attribute.Length | |
| }#> | |
| } | |
| catch { | |
| Write-Error $_.Exception.Message | |
| } | |
| finally { | |
| if ($handle -ne [IntPtr]::Zero -and $handle -ne [IntPtr]-1) { | |
| [void][NativeFileIO]::CloseHandle($handle) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment