Skip to content

Instantly share code, notes, and snippets.

@CynicRus
Created February 13, 2025 11:12
Show Gist options
  • Select an option

  • Save CynicRus/20f38093adc51b14866046960bf84a10 to your computer and use it in GitHub Desktop.

Select an option

Save CynicRus/20f38093adc51b14866046960bf84a10 to your computer and use it in GitHub Desktop.
Read Master Fat Table by Powershell > 7
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