├── .gitattributes ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── Boot ├── Boot.cs ├── Boot.csproj └── BootFile.cs ├── I30 ├── I30.cs ├── I30.csproj └── I30File.cs ├── LICENSE ├── LogFile ├── FixupData.cs ├── LogFile.cs ├── LogFile.csproj ├── LogPageRcrd.cs ├── LogPageRstr.cs └── Log_File.cs ├── MFT.Test ├── MFT.Test.csproj ├── TestFiles │ ├── $I30 │ │ ├── FirstDelete │ │ │ └── $I30 │ │ ├── SecondDelete │ │ │ └── $I30 │ │ └── Start │ │ │ └── $I30 │ ├── Boot │ │ └── $Boot │ ├── NIST │ │ └── DFR-16 │ │ │ └── $MFT │ ├── Usn │ │ └── record.usn │ ├── tdungan │ │ └── $MFT │ └── xw │ │ └── $MFT └── TestMain.cs ├── MFT.sln ├── MFT ├── Attributes │ ├── ACERecord.cs │ ├── Attribute.cs │ ├── AttributeList.cs │ ├── Bitmap.cs │ ├── Data.cs │ ├── ExtendedAttribute.cs │ ├── ExtendedAttributeInformation.cs │ ├── FileInfo.cs │ ├── FileName.cs │ ├── Helpers.cs │ ├── IndexAllocation.cs │ ├── IndexNodeHeader.cs │ ├── IndexRoot.cs │ ├── LoggedUtilityStream.cs │ ├── NonResidentData.cs │ ├── ObjectId_.cs │ ├── ReparsePoint.cs │ ├── ResidentData.cs │ ├── SKSecurityDescriptor.cs │ ├── SecurityDescriptor.cs │ ├── StandardInfo.cs │ ├── VolumeInformation.cs │ ├── VolumeName.cs │ └── xACLRecord.cs ├── FileRecord.cs ├── MFT.csproj ├── Mft.cs ├── MftFile.cs └── Other │ ├── AdsInfo.cs │ ├── AttributeInfo.cs │ ├── DataRun.cs │ ├── DirectoryNameMapValue.cs │ ├── ExtensionMethods.cs │ ├── FixupData.cs │ ├── IndexEntry.cs │ ├── IndexEntryI30.cs │ ├── MftEntryInfo.cs │ └── ParentMapEntry.cs ├── O ├── O.cs ├── O.csproj └── OFile.cs ├── README.md ├── SDS ├── FixupData.cs ├── Sdh.cs ├── SdhFile.cs ├── Sds.cs ├── SdsEntry.cs ├── SdsFile.cs ├── Secure.csproj ├── Sii.cs └── SiiFile.cs ├── Usn ├── MFTInformation.cs ├── Usn.cs ├── Usn.csproj ├── UsnEntry.cs └── UsnFile.cs └── icon.png /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 2 | 3 | name: publish 4 | on: 5 | workflow_dispatch: # Allow running the workflow manually from the GitHub UI 6 | push: 7 | branches: 8 | - 'main' # Run the workflow when pushing to the main branch 9 | pull_request: 10 | branches: 11 | - '*' # Run the workflow for all pull requests 12 | release: 13 | types: 14 | - published # Run the workflow when a new GitHub release is published 15 | 16 | env: 17 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 18 | DOTNET_NOLOGO: true 19 | NuGetDirectory: ${{ github.workspace}}/nuget 20 | 21 | defaults: 22 | run: 23 | shell: pwsh 24 | 25 | jobs: 26 | create_nuget: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 0 # Get all history to allow automatic versioning using MinVer 32 | 33 | # Install the .NET SDK indicated in the global.json file 34 | - name: Setup .NET 35 | uses: actions/setup-dotnet@v4 36 | 37 | # Create the NuGet package in the folder from the environment variable NuGetDirectory 38 | - run: dotnet pack --configuration Release --output ${{ env.NuGetDirectory }} 39 | 40 | # Publish the NuGet package as an artifact, so they can be used in the following jobs 41 | - uses: actions/upload-artifact@v4 42 | with: 43 | name: nuget 44 | if-no-files-found: error 45 | retention-days: 7 46 | path: ${{ env.NuGetDirectory }}/*.nupkg 47 | 48 | validate_nuget: 49 | runs-on: ubuntu-latest 50 | needs: [ create_nuget ] 51 | steps: 52 | # Install the .NET SDK indicated in the global.json file 53 | - name: Setup .NET 54 | uses: actions/setup-dotnet@v4 55 | 56 | # Download the NuGet package created in the previous job 57 | - uses: actions/download-artifact@v4 58 | with: 59 | name: nuget 60 | path: ${{ env.NuGetDirectory }} 61 | 62 | - name: Install nuget validator 63 | run: dotnet tool update Meziantou.Framework.NuGetPackageValidation.Tool --global 64 | 65 | # Validate metadata and content of the NuGet package 66 | # https://www.nuget.org/packages/Meziantou.Framework.NuGetPackageValidation.Tool#readme-body-tab 67 | # If some rules are not applicable, you can disable them 68 | # using the --excluded-rules or --excluded-rule-ids option 69 | - name: Validate package 70 | run: meziantou.validate-nuget-package (Get-ChildItem "${{ env.NuGetDirectory }}/*.nupkg") 71 | 72 | run_test: 73 | runs-on: ubuntu-latest 74 | steps: 75 | - uses: actions/checkout@v4 76 | - name: Setup .NET 77 | uses: actions/setup-dotnet@v4 78 | - name: Run tests 79 | run: dotnet test --configuration Release 80 | 81 | deploy: 82 | # Publish only when creating a GitHub Release 83 | # https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository 84 | # You can update this logic if you want to manage releases differently 85 | if: github.event_name == 'release' 86 | runs-on: ubuntu-latest 87 | needs: [ validate_nuget ] 88 | steps: 89 | # Download the NuGet package created in the previous job 90 | - uses: actions/download-artifact@v4 91 | with: 92 | name: nuget 93 | path: ${{ env.NuGetDirectory }} 94 | 95 | # Install the .NET SDK indicated in the global.json file 96 | - name: Setup .NET Core 97 | uses: actions/setup-dotnet@v4 98 | 99 | # Publish all NuGet packages to NuGet.org 100 | # Use --skip-duplicate to prevent errors if a package with the same version already exists. 101 | # If you retry a failed workflow, already published packages will be skipped without error. 102 | - name: Publish NuGet package 103 | run: | 104 | foreach($file in (Get-ChildItem "${{ env.NuGetDirectory }}" -Recurse -Include *.nupkg)) { 105 | dotnet nuget push $file --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate 106 | } 107 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /Boot/Boot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using Serilog; 5 | 6 | namespace Boot; 7 | 8 | public class Boot 9 | { 10 | public Boot(Stream fileStream) 11 | { 12 | const int expectedSectorSig = 0xaa55; 13 | 14 | var rawBytes = new byte[512]; 15 | fileStream.Read(rawBytes, 0, 512); 16 | 17 | SectorSignature = BitConverter.ToUInt16(rawBytes, 510); 18 | 19 | if (SectorSignature != expectedSectorSig) 20 | { 21 | Log.Warning( 22 | "Expected signature (0x55 0xAA) not found at offset 0x1FE. Value found: {SectorSignature}", 23 | GetSectorSignature()); 24 | } 25 | 26 | BootEntryPoint = $"0x{rawBytes[0]:X2} 0x{rawBytes[1]:X2} 0x{rawBytes[2]:X2}"; 27 | 28 | FileSystemSignature = Encoding.ASCII.GetString(rawBytes, 3, 8); 29 | 30 | BytesPerSector = BitConverter.ToInt16(rawBytes, 11); 31 | SectorsPerCluster = rawBytes[13]; 32 | 33 | ReservedSectors = BitConverter.ToInt16(rawBytes, 14); 34 | NumberOfFaTs = rawBytes[16]; 35 | 36 | RootDirectoryEntries = BitConverter.ToInt16(rawBytes, 17); 37 | TotalNumberOfSectors16 = BitConverter.ToInt16(rawBytes, 19); 38 | 39 | MediaDescriptor = rawBytes[21]; 40 | 41 | SectorsPerFat = BitConverter.ToInt16(rawBytes, 22); 42 | 43 | SectorsPerTrack = BitConverter.ToInt16(rawBytes, 24); 44 | NumberOfHeads = BitConverter.ToInt16(rawBytes, 26); 45 | NumberOfHiddenSectors = BitConverter.ToInt32(rawBytes, 28); 46 | TotalNumberOfSectors = BitConverter.ToInt32(rawBytes, 32); 47 | 48 | DiskUnitNumber = rawBytes[36]; 49 | UnknownFlags = rawBytes[37]; 50 | BpbVersionSignature = rawBytes[38]; 51 | UnknownReserved = rawBytes[39]; 52 | 53 | TotalSectors = BitConverter.ToInt64(rawBytes, 40); 54 | MftClusterBlockNumber = BitConverter.ToInt64(rawBytes, 48); 55 | MirrorMftClusterBlockNumber = BitConverter.ToInt64(rawBytes, 56); 56 | 57 | var clusterSize = BytesPerSector * SectorsPerCluster; 58 | 59 | var mftEntrySize = rawBytes[64]; 60 | 61 | MftEntrySize = GetSizeAsBytes(mftEntrySize, clusterSize); 62 | 63 | var indexEntrySize = rawBytes[68]; 64 | 65 | IndexEntrySize = GetSizeAsBytes(indexEntrySize, clusterSize); 66 | 67 | VolumeSerialNumberRaw = BitConverter.ToInt64(rawBytes, 72); 68 | 69 | Checksum = BitConverter.ToInt32(rawBytes, 80); 70 | } 71 | 72 | public string BootEntryPoint { get; } 73 | public string FileSystemSignature { get; } 74 | 75 | public int BytesPerSector { get; } 76 | public int SectorSignature { get; } 77 | public int SectorsPerCluster { get; } 78 | 79 | /// 80 | /// Not used by NTFS 81 | /// 82 | public int ReservedSectors { get; } 83 | 84 | /// 85 | /// Not used by NTFS 86 | /// 87 | public int NumberOfFaTs { get; } 88 | 89 | /// 90 | /// Not used by NTFS 91 | /// 92 | public int RootDirectoryEntries { get; } 93 | 94 | public int TotalNumberOfSectors16 { get; } 95 | 96 | public byte MediaDescriptor { get; } 97 | 98 | /// 99 | /// Not used by NTFS 100 | /// 101 | public int SectorsPerFat { get; } 102 | 103 | /// 104 | /// Not used by NTFS 105 | /// 106 | public int SectorsPerTrack { get; } 107 | 108 | /// 109 | /// Not used by NTFS 110 | /// 111 | public int NumberOfHeads { get; } 112 | 113 | /// 114 | /// Not used by NTFS 115 | /// 116 | public int NumberOfHiddenSectors { get; } 117 | 118 | /// 119 | /// Not used by NTFS 120 | /// 121 | public int TotalNumberOfSectors { get; } 122 | 123 | /// 124 | /// Not used by NTFS 125 | /// 126 | public byte DiskUnitNumber { get; } 127 | 128 | /// 129 | /// Not used by NTFS 130 | /// 131 | public byte UnknownFlags { get; } 132 | 133 | /// 134 | /// Not used by NTFS 135 | /// 136 | public byte BpbVersionSignature { get; } 137 | 138 | /// 139 | /// Not used by NTFS 140 | /// 141 | public byte UnknownReserved { get; } 142 | 143 | public long TotalSectors { get; } 144 | public long MftClusterBlockNumber { get; } 145 | public long MirrorMftClusterBlockNumber { get; } 146 | 147 | /// 148 | /// As bytes 149 | /// 150 | public int MftEntrySize { get; } 151 | 152 | /// 153 | /// As bytes 154 | /// 155 | public int IndexEntrySize { get; } 156 | 157 | /// 158 | /// Use GetVolumeSerialNumber() to convert to different forms 159 | /// 160 | public long VolumeSerialNumberRaw { get; } 161 | 162 | /// 163 | /// Not used by NTFS 164 | /// 165 | public int Checksum { get; } 166 | 167 | public string DecodeMediaDescriptor() 168 | { 169 | var desc = new StringBuilder(); 170 | 171 | var mdBits = Convert.ToString(MediaDescriptor, 2); 172 | 173 | switch (mdBits[0]) 174 | { 175 | case '0': 176 | desc.Append("Single-sided"); 177 | break; 178 | default: 179 | desc.Append("Double-sided"); 180 | break; 181 | } 182 | 183 | switch (mdBits[1]) 184 | { 185 | case '0': 186 | desc.Append(", 9 sectors per track"); 187 | break; 188 | default: 189 | desc.Append(", 8 sectors per track"); 190 | break; 191 | } 192 | 193 | switch (mdBits[2]) 194 | { 195 | case '0': 196 | desc.Append(", 80 tracks"); 197 | break; 198 | default: 199 | desc.Append(", 40 tracks"); 200 | break; 201 | } 202 | 203 | switch (mdBits[3]) 204 | { 205 | case '0': 206 | desc.Append(", Fixed disc"); 207 | break; 208 | default: 209 | desc.Append(", Removable disc"); 210 | break; 211 | } 212 | 213 | return desc.ToString(); 214 | } 215 | 216 | public string GetSectorSignature() 217 | { 218 | var b = BitConverter.GetBytes(SectorSignature); 219 | return $"{b[0]:X2} {b[1]:X2}"; 220 | } 221 | 222 | public string GetVolumeSerialNumber(bool as32Bit = false, bool reverse = false) 223 | { 224 | var b = BitConverter.GetBytes(VolumeSerialNumberRaw); 225 | 226 | var sn = string.Empty; 227 | 228 | if (as32Bit) 229 | { 230 | if (reverse) 231 | { 232 | for (var i = 3; i > -1; i--) 233 | { 234 | sn = $"{sn} {b[i]:X2}"; 235 | } 236 | } 237 | else 238 | { 239 | for (var i = 0; i < 4; i++) 240 | { 241 | sn = $"{sn} {b[i]:X2}"; 242 | } 243 | } 244 | 245 | return sn.Trim(); 246 | } 247 | 248 | for (var i = 0; i < 8; i++) 249 | { 250 | sn = $"{sn} {b[i]:X2}"; 251 | } 252 | 253 | return sn.Trim(); 254 | } 255 | 256 | private static int GetSizeAsBytes(byte size, int clusterSize) 257 | { 258 | if (size <= 127) 259 | { 260 | return size * clusterSize; 261 | } 262 | 263 | return (int)Math.Pow(2, 256 - size); 264 | } 265 | } -------------------------------------------------------------------------------- /Boot/Boot.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | MIT 5 | 10 6 | Boot parser 7 | Eric R. Zimmerman 8 | Eric R. Zimmerman 9 | https://github.com/EricZimmerman/MFT 10 | https://github.com/EricZimmerman/MFT 11 | 1.5.1 12 | 13 | $MFT, $Boot, usn, $J, $I30, NTFS 14 | README.md 15 | icon.png 16 | True 17 | 18 | $(NoWarn);CS1591 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | all 31 | runtime; build; native; contentfiles; analyzers; buildtransitive 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Boot/BootFile.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Boot; 4 | 5 | public static class BootFile 6 | { 7 | public static Boot Load(string bootFilePath) 8 | { 9 | if (File.Exists(bootFilePath) == false) 10 | { 11 | throw new FileNotFoundException($"'{bootFilePath}' not found"); 12 | } 13 | 14 | using var fs = new FileStream(bootFilePath, FileMode.Open, FileAccess.Read); 15 | return new Boot(fs); 16 | } 17 | } -------------------------------------------------------------------------------- /I30/I30.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using MFT; 5 | using MFT.Other; 6 | using Serilog; 7 | 8 | namespace I30; 9 | 10 | public class I30 11 | { 12 | public I30(Stream fileStream) 13 | { 14 | var pageSize = 0x1000; 15 | 16 | var sig = 0x58444E49; 17 | 18 | Entries = new List(); 19 | 20 | var pages = new List(); 21 | 22 | using (var br = new BinaryReader(fileStream)) 23 | { 24 | while (br.BaseStream.Position < br.BaseStream.Length) 25 | { 26 | pages.Add(br.ReadBytes(pageSize)); 27 | } 28 | } 29 | 30 | var uniqueSlackEntryMd5s = new HashSet(); 31 | 32 | var pageNumber = 0; 33 | foreach (var page in pages) 34 | { 35 | //INDX pages are 4096 bytes each, so process them accordingly 36 | 37 | Log.Debug("Processing page 0x{PageNumber:X}", pageNumber); 38 | 39 | using (var br = new BinaryReader(new MemoryStream(page))) 40 | { 41 | var sigActual = br.ReadInt32(); 42 | 43 | if (sigActual == 0x00) 44 | { 45 | //empty page 46 | Log.Warning("Empty page found at offset {Offset}. Skipping", $"0x{pageNumber * 0x1000:X}"); 47 | pageNumber++; 48 | continue; 49 | } 50 | 51 | if (sig != sigActual) 52 | { 53 | throw new Exception("Invalid header! Expected 'INDX' Signature"); 54 | } 55 | 56 | var fixupOffset = br.ReadInt16(); 57 | 58 | var numFixupPairs = br.ReadInt16(); 59 | 60 | var logFileSequenceNumber = br.ReadInt64(); 61 | 62 | var virtualClusterNumber = br.ReadInt64(); 63 | 64 | var dataStartOffset = br.ReadInt32(); 65 | var dataSize = br.ReadInt32(); 66 | var dataSizeAllocated = br.ReadInt32(); 67 | 68 | var isLeafNode = br.ReadInt32() == 0; //this gets us by padding too 69 | 70 | var fixupTotalLength = numFixupPairs * 2; 71 | 72 | var fixupBuffer = new byte[fixupTotalLength]; 73 | 74 | fixupBuffer = br.ReadBytes(fixupTotalLength); 75 | 76 | while (br.BaseStream.Position % 8 != 0) 77 | { 78 | br.ReadByte(); //gets us past padding 79 | } 80 | 81 | //since we need to change bytes for the index entries based on fixup, get an array of those bytes 82 | 83 | var rawBytes = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position)); 84 | 85 | var fixupData = new FixupData(fixupBuffer); 86 | 87 | //fixup verification 88 | var counter = 89 | 512 - dataStartOffset - 90 | 0x18; //datastartOffset is relative, so we need to account for where it begins, at 0x18 91 | foreach (var bytese in fixupData.FixupActual) 92 | { 93 | //adjust the offset to where we need to check 94 | var fixupOffset1 = counter - 2; 95 | 96 | var expected = BitConverter.ToInt16(rawBytes, fixupOffset1); 97 | if (expected != fixupData.FixupExpected) 98 | { 99 | Log.Warning( 100 | "Fixup values do not match at 0x{FixupOffset1:X}. Expected: 0x{FixupExpected:X2}, actual: 0x{Expected:X2}", 101 | fixupOffset1, fixupData.FixupExpected, expected); 102 | } 103 | 104 | //replace fixup expected with actual bytes. bytese has actual replacement values in it. 105 | Buffer.BlockCopy(bytese, 0, rawBytes, fixupOffset1, 2); 106 | 107 | counter += 512; 108 | } 109 | 110 | //rawbytes contains the data from the current page we need to parse to get to indexes 111 | //datasize includes startoffset plus fixup, etc, so subtract data offset from size for the active index allocations 112 | //valid data is allocated - dataoffset 113 | //after that is slack 114 | 115 | var activeSpace = new byte[dataSize - dataStartOffset]; 116 | Buffer.BlockCopy(rawBytes, 0, activeSpace, 0, activeSpace.Length); 117 | 118 | var slackSpace = new byte[rawBytes.Length - activeSpace.Length]; 119 | Buffer.BlockCopy(rawBytes, dataSize - dataStartOffset, slackSpace, 0, slackSpace.Length); 120 | 121 | // File.WriteAllBytes($@"C:\temp\{pageNumber}_slack.bin",slackSpace); 122 | 123 | //absolute offset is page # * 0x1000 + 0x18 + datastartoffset 124 | //for slack, add activespace.len 125 | 126 | using (var binaryReader = new BinaryReader(new MemoryStream(activeSpace))) 127 | { 128 | while (binaryReader.BaseStream.Position < binaryReader.BaseStream.Length) 129 | { 130 | var absoluteOffset = pageNumber * 0x1000 + 0x18 + dataStartOffset + 131 | binaryReader.BaseStream.Position; 132 | 133 | Log.Verbose( 134 | "IN ACTIVE LOOP: Absolute offset: 0x{AbsoluteOffset:X} brActive.BaseStream.Position: 0x{Position:X}", 135 | absoluteOffset, binaryReader.BaseStream.Position); 136 | 137 | binaryReader.ReadInt64(); //mft info 138 | var indexSize = binaryReader.ReadInt16(); 139 | binaryReader.BaseStream.Seek(-10, SeekOrigin.Current); //go back to start of the index data 140 | 141 | var indxBuffer = binaryReader.ReadBytes(indexSize); 142 | 143 | var ie = new IndexEntryI30(indxBuffer, absoluteOffset, pageNumber, false); 144 | 145 | if (ie.MftReferenceSelf.MftEntryNumber == 0) 146 | { 147 | continue; 148 | } 149 | 150 | //its ok 151 | Log.Debug("{Ie}", ie); 152 | Entries.Add(ie); 153 | } 154 | } 155 | 156 | Log.Verbose("IN SLACK LOOP for {Page", pageNumber); 157 | var slackAbsOffset = pageNumber * 0x1000 + 0x18 + dataStartOffset + 158 | activeSpace.Length; 159 | 160 | var slackIe = FileRecord.GetSlackFileEntries(slackSpace, pageNumber, slackAbsOffset,0); 161 | 162 | //var h = GetUnicodeHits(slackSpace); 163 | 164 | foreach (var indexEntry in slackIe) 165 | { 166 | if (uniqueSlackEntryMd5s.Contains(indexEntry.Md5)) 167 | { 168 | Log.Debug("Discarding duplicate slack buffer with MD5 {Md5}", indexEntry.Md5); 169 | continue; 170 | } 171 | 172 | Entries.Add(indexEntry); 173 | 174 | uniqueSlackEntryMd5s.Add(indexEntry.Md5); 175 | 176 | } 177 | } 178 | 179 | pageNumber += 1; 180 | } 181 | } 182 | 183 | 184 | public List Entries { get; } 185 | 186 | 187 | 188 | } 189 | 190 | -------------------------------------------------------------------------------- /I30/I30.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | https://github.com/EricZimmerman/MFT 5 | https://github.com/EricZimmerman/MFT 6 | $I30 parser 7 | 10 8 | 9 | Eric R. Zimmerman 10 | Eric R. Zimmerman 11 | MIT 12 | 1.5.1 13 | 14 | $MFT, $Boot, usn, $J, $I30, NTFS 15 | README.md 16 | icon.png 17 | True 18 | 19 | $(NoWarn);CS1591 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | all 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /I30/I30File.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace I30; 4 | 5 | public static class I30File 6 | { 7 | public static I30 Load(string indexFile) 8 | { 9 | if (File.Exists(indexFile) == false) 10 | { 11 | throw new FileNotFoundException($"'{indexFile}' not found"); 12 | } 13 | 14 | using var fs = new FileStream(indexFile, FileMode.Open, FileAccess.Read); 15 | return new I30(fs); 16 | } 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Eric Zimmerman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LogFile/FixupData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace LogFile; 6 | 7 | public class FixupData 8 | { 9 | public FixupData(byte[] fixupDataRaw) 10 | { 11 | FixupExpected = BitConverter.ToInt16(fixupDataRaw, 0); 12 | FixupActual = new List(); 13 | 14 | var index = 2; 15 | 16 | while (index < fixupDataRaw.Length) 17 | { 18 | var b = new byte[2]; 19 | Buffer.BlockCopy(fixupDataRaw, index, b, 0, 2); 20 | FixupActual.Add(b); 21 | index += 2; 22 | } 23 | } 24 | 25 | /// 26 | /// the data expected at the end of each 512 byte chunk 27 | /// 28 | public short FixupExpected { get; } 29 | 30 | /// 31 | /// The actual bytes to be overlayed before processing a record, in order 32 | /// 33 | public List FixupActual { get; } 34 | 35 | public override string ToString() 36 | { 37 | var sb = new StringBuilder(); 38 | 39 | foreach (var bytese in FixupActual) 40 | { 41 | var bb = BitConverter.ToString(bytese); 42 | sb.Append($"{bb}|"); 43 | } 44 | 45 | var fua = sb.ToString().TrimEnd('|'); 46 | 47 | return $"Expected: {BitConverter.ToString(BitConverter.GetBytes(FixupExpected))} Fixup Actual: {fua}"; 48 | } 49 | } -------------------------------------------------------------------------------- /LogFile/LogFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Serilog; 5 | 6 | namespace LogFile; 7 | 8 | public class LogFile 9 | { 10 | private const int PageSize = 0x1000; 11 | 12 | private const int RstrSig = 0x52545352; 13 | private const int RcrdSig = 0x44524352; 14 | private const int ChkdSig = 0x52545351; 15 | 16 | public static uint LastOffset; 17 | 18 | public LogFile(Stream fileStream) 19 | { 20 | //preliminary sig check to get us started 21 | const int sig = 0x52545352; 22 | 23 | var br = new BinaryReader(fileStream); 24 | 25 | var index = 0x0; 26 | var sigCheck = br.ReadInt32(); // BitConverter.ToInt32(rawBytes, index); 27 | 28 | if (sig != sigCheck) 29 | { 30 | throw new Exception("Invalid header! Expected 'RSTR' Signature."); 31 | } 32 | 33 | br.BaseStream.Seek(0, SeekOrigin.Begin); //reset 34 | 35 | NormalPageArea = new List(); 36 | 37 | while (fileStream.Position < fileStream.Length) 38 | { 39 | LastOffset = (uint)index; 40 | 41 | var buff = br.ReadBytes(PageSize); 42 | 43 | Log.Debug("Processing log page at offset 0x{Index:X}", index); 44 | 45 | var sigActual = BitConverter.ToInt32(buff, 0); 46 | 47 | switch (sigActual) 48 | { 49 | case RstrSig: 50 | var lprstr = new LogPageRstr(buff, index); 51 | 52 | Log.Information("{Lprstr}", lprstr); 53 | 54 | if (index == 0) 55 | { 56 | PrimaryRstrPage = lprstr; 57 | } 58 | else 59 | { 60 | SecondaryRstrPage = lprstr; 61 | } 62 | 63 | break; 64 | case RcrdSig: 65 | // 66 | LogPageRcrd lprcrd = null; 67 | 68 | //loop thru all pages, then walk thru again, grouping into chunks based on PageCount 69 | //then process each chunk with each page inside 70 | 71 | try 72 | { 73 | lprcrd = new LogPageRcrd(buff, index); 74 | } 75 | catch (Exception e) 76 | { 77 | Console.WriteLine(e); 78 | } 79 | 80 | 81 | if (index == 0x2000) 82 | { 83 | BufferPrimary = lprcrd; 84 | } 85 | else if (index == 0x3000) 86 | { 87 | BufferSecondary = lprcrd; 88 | } 89 | else 90 | { 91 | NormalPageArea.Add(lprcrd); 92 | } 93 | 94 | 95 | break; 96 | // case chkd_sig: //havent seen one of these to test 97 | // PageType = PageTypes.Chkd; 98 | // break; 99 | default: 100 | throw new Exception( 101 | $"Invalid signature at offset 0x{index:X}! Expected 'RCRD|RSTR|CHKD' signature."); 102 | } 103 | 104 | 105 | index += PageSize; 106 | } 107 | } 108 | 109 | public List NormalPageArea { get; } 110 | public LogPageRstr PrimaryRstrPage { get; } 111 | public LogPageRstr SecondaryRstrPage { get; } 112 | 113 | public LogPageRcrd BufferPrimary { get; } 114 | public LogPageRcrd BufferSecondary { get; } 115 | } -------------------------------------------------------------------------------- /LogFile/LogFile.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | https://github.com/EricZimmerman/MFT 5 | https://github.com/EricZimmerman/MFT 6 | Eric R. Zimmerman 7 | 10 8 | Eric R. Zimmerman 9 | $LogFile parser 10 | MIT 11 | 1.5.1 12 | 13 | $MFT, $Boot, usn, $J, $I30, NTFS 14 | README.md 15 | icon.png 16 | True 17 | 18 | $(NoWarn);CS1591 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | all 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /LogFile/LogPageRcrd.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Serilog; 5 | 6 | namespace LogFile; 7 | 8 | public enum PageRecordFlag 9 | { 10 | MultiplePages = 0x1, 11 | NoRedo = 0x2, 12 | NoUndo = 0x4 13 | } 14 | 15 | public class LogPageRcrd 16 | { 17 | private const int RcrdSig = 0x44524352; 18 | 19 | public LogPageRcrd(byte[] rawBytes, int offset) 20 | { 21 | var index = 0x0; 22 | var sigCheck = BitConverter.ToInt32(rawBytes, index); 23 | 24 | if (sigCheck != RcrdSig) 25 | { 26 | throw new Exception("Invalid signature! Expected 'RCRD' signature."); 27 | } 28 | 29 | Offset = offset; 30 | 31 | index += 4; 32 | 33 | var fixupOffset = BitConverter.ToInt16(rawBytes, index); 34 | index += 2; 35 | var numFixupPairs = BitConverter.ToInt16(rawBytes, index); 36 | index += 2; 37 | 38 | LastLogFileSequenceNumber = BitConverter.ToInt64(rawBytes, index); 39 | index += 8; 40 | Flags = BitConverter.ToInt32(rawBytes, index); 41 | index += 4; 42 | 43 | PageCount = BitConverter.ToInt16(rawBytes, index); 44 | index += 2; 45 | 46 | PagePosition = BitConverter.ToInt16(rawBytes, index); 47 | index += 2; 48 | 49 | FreeSpaceOffset = BitConverter.ToInt16(rawBytes, index); 50 | index += 2; 51 | 52 | var wordAlign = BitConverter.ToInt16(rawBytes, index); 53 | index += 2; 54 | 55 | var dwordAlign = BitConverter.ToInt32(rawBytes, index); 56 | index += 4; 57 | 58 | LastEndLogFileSequenceNumber = BitConverter.ToInt64(rawBytes, index); 59 | index += 8; 60 | 61 | var fixupTotalLength = numFixupPairs * 2; 62 | 63 | var fixupBuffer = new byte[fixupTotalLength]; 64 | Buffer.BlockCopy(rawBytes, fixupOffset, fixupBuffer, 0, fixupTotalLength); 65 | 66 | var fixupData = new FixupData(fixupBuffer); 67 | 68 | var fixupOk = true; 69 | 70 | //fixup verification 71 | var counter = 512; 72 | foreach (var bytese in fixupData.FixupActual) 73 | { 74 | //adjust the offset to where we need to check 75 | var fixupOffset1 = counter - 2; 76 | 77 | var expected = BitConverter.ToInt16(rawBytes, fixupOffset1); 78 | if (expected != fixupData.FixupExpected) 79 | { 80 | fixupOk = false; 81 | Log.Warning( 82 | "Fixup values do not match at 0x{FixupOffset:X}. Expected: 0x{FixupExpected:X2}, actual: 0x{Expected:X2}", 83 | fixupOffset1, fixupData.FixupExpected, expected); 84 | } 85 | 86 | //replace fixup expected with actual bytes. bytese has actual replacement values in it. 87 | Buffer.BlockCopy(bytese, 0, rawBytes, fixupOffset1, 2); 88 | 89 | counter += 512; 90 | } 91 | 92 | index += fixupTotalLength; 93 | 94 | while (index % 8 != 0) 95 | { 96 | index += 1; 97 | } 98 | 99 | //header is 0x58 bytes, so go past it 100 | 101 | // index = 0x58; 102 | 103 | Log.Information( 104 | " LastLogFileSequenceNumber: 0x{LastLogFileSequenceNumber:X} Flags: {Flags} PageCount: 0x{PageCount:X} PagePosition: 0x{PagePosition:X} Free space offset: 0x{FreeSpaceOffset:X} " + 105 | "LastEndLogFileSequenceNumber: 0x{LastEndLogFileSequenceNumber:X} " + 106 | "LastLogFileSequenceNumber==LastEndLogFileSequenceNumber: {LastEndLogFileSequenceNumber==LastLogFileSequenceNumber}", 107 | LastLogFileSequenceNumber, Flags, PageCount, PagePosition, FreeSpaceOffset, LastEndLogFileSequenceNumber, 108 | LastEndLogFileSequenceNumber == LastLogFileSequenceNumber); 109 | 110 | //record is 0x30 + clientDatalen long 111 | 112 | 113 | Records = new List(); 114 | 115 | while (index < rawBytes.Length) 116 | { 117 | var so = index; 118 | var thisLsn = BitConverter.ToInt64(rawBytes, index); 119 | var prevLsn = BitConverter.ToInt64(rawBytes, index + 8); 120 | var clientUndoLsn = BitConverter.ToInt64(rawBytes, index + 16); 121 | 122 | // _logger.Info($" this: {thisLsn:X} prev: {prevLsn:X} undo: {clientUndoLsn:X}"); 123 | var clientDataLen = BitConverter.ToInt32(rawBytes, index + 24); 124 | var buff = new byte[clientDataLen + 0x30]; 125 | Buffer.BlockCopy(rawBytes, index, buff, 0, buff.Length); 126 | 127 | var rec = new Record(buff); 128 | 129 | Records.Add(rec); 130 | 131 | index += buff.Length; 132 | 133 | Log.Information(" Record: {Record}", rec); 134 | 135 | if (thisLsn == LastEndLogFileSequenceNumber) 136 | { 137 | Log.Warning("At last LSN in this page (0x{ThisLsn:X}). Found it at offset 0x{So:X}\r\n", thisLsn, so); 138 | break; 139 | } 140 | } 141 | 142 | //Debug.WriteLine($"at abs offset: 0x{(offset+index):X}, RCRD Offset: 0x{Offset:X}"); 143 | } 144 | 145 | public long LastLogFileSequenceNumber { get; } 146 | public int Flags { get; } 147 | public short PageCount { get; } 148 | public short PagePosition { get; } 149 | public short FreeSpaceOffset { get; } 150 | public long LastEndLogFileSequenceNumber { get; } 151 | 152 | public int Offset { get; } 153 | 154 | public List Records { get; } 155 | } 156 | 157 | public enum RecTypeFlag 158 | { 159 | RestRecord = 0x1, 160 | CheckPointRecord = 0x02 161 | } 162 | 163 | public enum RecordHeaderFlag 164 | { 165 | ClientRecord = 0x1, 166 | ClientRestartArea = 0x2 167 | } 168 | 169 | public enum OpCode 170 | { 171 | Noop = 0x00, 172 | CompensationlogRecord = 0x01, 173 | InitializeFileRecordSegment = 0x02, 174 | DeallocateFileRecordSegment = 0x03, 175 | WriteEndofFileRecordSegement = 0x04, 176 | CreateAttribute = 0x05, 177 | DeleteAttribute = 0x06, 178 | UpdateResidentValue = 0x07, 179 | UpdataeNonResidentValue = 0x08, 180 | UpdateMappingPairs = 0x09, 181 | DeleteDirtyClusters = 0x0A, 182 | SetNewAttributeSizes = 0x0B, 183 | AddIndexEntryRoot = 0x0C, 184 | DeleteIndexEntryRoot = 0x0D, 185 | AddIndexEntryAllocation = 0x0E, 186 | DeleteIndexEntryAllocation = 0x0F, 187 | WriteEndOfIndexBuffer = 0x10, 188 | SetIndexEntryVcnRoot = 0x11, 189 | SetIndexEntryVcnAllocation = 0x12, 190 | UpdateFileNameRoot = 0x13, 191 | UpdateFileNameAllocation = 0x14, 192 | SetBitsInNonresidentBitMap = 0x15, 193 | ClearBitsInNonresidentBitMap = 0x16, 194 | HotFix = 0x17, 195 | EndTopLevelAction = 0x18, 196 | PrepareTransaction = 0x19, 197 | CommitTransaction = 0x1A, 198 | ForgetTransaction = 0x1B, 199 | OpenNonresidentAttribute = 0x1C, 200 | OpenAttributeTableDump = 0x1D, 201 | AttributeNamesDump = 0x1E, 202 | DirtyPageTableDump = 0x1F, 203 | TransactionTableDump = 0x20, 204 | UpdateRecordDataRoot = 0x21, 205 | UpdateRecordDataAllocation = 0x22, 206 | UpdateRelativeDataIndex = 0x23, 207 | UpdateRelativeDataAllocation = 0x24, 208 | ZeroEndOfFileRecord = 0x25 209 | } 210 | 211 | public class Record 212 | { 213 | public Record(byte[] rawBytes) 214 | { 215 | var br = new BinaryReader(new MemoryStream(rawBytes)); 216 | 217 | ThisLsn = br.ReadInt64(); 218 | PreviousLsn = br.ReadInt64(); 219 | UndoLsn = br.ReadInt64(); 220 | 221 | DataLength = br.ReadInt32(); 222 | ClientId = br.ReadInt32(); 223 | RecordType = (RecTypeFlag)br.ReadInt32(); 224 | TransactionId = br.ReadInt32(); 225 | 226 | Flags = (RecordHeaderFlag)br.ReadInt16(); 227 | RedoOpCode = (OpCode)br.ReadInt16(); 228 | UndoOpCode = (OpCode)br.ReadInt16(); 229 | RedoOffset = br.ReadInt16(); 230 | RedoLength = br.ReadInt16(); 231 | UndoOffset = br.ReadInt16(); 232 | UndoLength = br.ReadInt16(); 233 | TargetAtrribute = br.ReadInt16(); 234 | LcnToFollow = br.ReadInt16(); 235 | RecordOffset = br.ReadInt16(); 236 | AttributeOffset = br.ReadInt16(); 237 | ClusterBlockOffset = br.ReadInt16(); 238 | TargetblockSize = br.ReadInt16(); 239 | 240 | TargetVcn = br.ReadInt64(); 241 | 242 | // var l = LogManager.GetCurrentClassLogger(); 243 | // l.Info($"This LSN: 0x{br.ReadInt64():X}"); //this 244 | // l.Info($"prev LSN: 0x{br.ReadInt64():X}"); //this 245 | // l.Info($"undo LSN: 0x{br.ReadInt64():X}"); //this 246 | // 247 | // l.Info($"data len: 0x{br.ReadInt32():X}"); //this 248 | // l.Info($"clientid: 0x{br.ReadInt32():X}"); //this 249 | // l.Info($"rec type: 0x{br.ReadInt32():X}"); //this 250 | // l.Info($"trans id: 0x{br.ReadInt32():X}"); //this 251 | // 252 | // l.Info($"flags: 0x{br.ReadInt16():X}"); //this 253 | // 254 | // br.ReadBytes(6); //reserved 255 | // 256 | // l.Info($"redo op: 0x{br.ReadInt16():X}"); //this 257 | // l.Info($"undo op: 0x{br.ReadInt16():X}"); //this 258 | // l.Info($"redo offset: 0x{br.ReadInt16():X}"); //this 259 | // l.Info($"redo len:0x {br.ReadInt16():X}"); //this 260 | // l.Info($"undo offset: 0x{br.ReadInt16():X}"); //this 261 | // l.Info($"undo len: 0x{br.ReadInt16():X}"); //this 262 | // l.Info($"target attr: 0x{br.ReadInt16():X}"); //this 263 | // l.Info($"LCN to follow: 0x{br.ReadInt16():X}"); //this 264 | // l.Info($"record offset: 0x{br.ReadInt16():X}"); //this 265 | // l.Info($"attr offset: 0x{br.ReadInt16():X}"); //this 266 | // l.Info($"clusterblockOffset 0x{br.ReadInt16():X}"); //this 267 | // l.Info($"TargetblockSize 0x{br.ReadInt16():X}"); //this 268 | // 269 | // 270 | // l.Info($"target vcn: 0x{br.ReadInt64():X}"); //this 271 | 272 | //lcns are here 273 | 274 | // var ClusterNums = new List(); 275 | // 276 | // for (int i = 0; i < LcnToFollow; i++) 277 | // { 278 | // var lc = br.ReadInt64(); 279 | // ClusterNums.Add(lc); 280 | // } 281 | } 282 | 283 | public long ThisLsn { get; } 284 | public long PreviousLsn { get; } 285 | public long UndoLsn { get; } 286 | 287 | public int DataLength { get; } 288 | public int ClientId { get; } 289 | public RecTypeFlag RecordType { get; } 290 | public int TransactionId { get; } 291 | public RecordHeaderFlag Flags { get; } 292 | public OpCode RedoOpCode { get; } 293 | public OpCode UndoOpCode { get; } 294 | public short RedoOffset { get; } 295 | public short RedoLength { get; } 296 | public short UndoOffset { get; } 297 | public short UndoLength { get; } 298 | public short TargetAtrribute { get; } 299 | public short LcnToFollow { get; } 300 | public short RecordOffset { get; } 301 | public short AttributeOffset { get; } 302 | public short ClusterBlockOffset { get; } 303 | public short TargetblockSize { get; } 304 | public long TargetVcn { get; } 305 | 306 | public override string ToString() 307 | { 308 | return 309 | $"ThisLsn: 0x{ThisLsn:X} PrevLsn: 0x{PreviousLsn:X} UndoLsn: 0x{UndoLsn:X} size: 0x{DataLength:X} Client id: 0x{ClientId:X} Rec type: {RecordType} TransId: 0x{TransactionId:X} Flags: {Flags} Redo code: {RedoOpCode} Undo code: {UndoOpCode} redo offset: 0x{RedoOffset:X} redo len: 0x{RedoLength:X} undo offset: 0x{UndoOffset:X} undo len: 0x{UndoLength:X} target attr: 0x{TargetAtrribute:X} LsnToFollow: 0x{LcnToFollow:X} RecordOffset: 0x{RecordOffset:X} attr offset: 0x{AttributeOffset:X} cluster block offset: 0x{ClusterBlockOffset:X} target block size: 0x{TargetblockSize:X} target vcn: 0x{TargetVcn:X}"; 310 | } 311 | } -------------------------------------------------------------------------------- /LogFile/LogPageRstr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using Serilog; 6 | 7 | namespace LogFile; 8 | 9 | public enum RestartFlag 10 | { 11 | None = 0x0, 12 | OneByOne = 0x1, 13 | NewArea = 0x2 14 | } 15 | 16 | public class LogPageRstr 17 | { 18 | private const int RstrSig = 0x52545352; 19 | private const int ChkdSig = 0x52545351; 20 | public long CheckDiskLsn; 21 | public short ClientArrayOffset; 22 | public short ClientFreeList; 23 | public short ClientInUseList; 24 | public long CurrentLsn; 25 | public RestartFlag Flags; 26 | public int LastLsnDataLen; 27 | public short LogClientCount; 28 | public long LogFileSize; 29 | public short LogPageDataOffset; 30 | public int LogPageSize; 31 | public short MajorFormatVersion; 32 | public short MinorFormatVersion; 33 | public short RecordHeaderLen; 34 | public short RestartAreaLen; 35 | public short RestartOffset; 36 | public int RevisionNumber; 37 | public int SeqNumBits; 38 | public int SystemPageSize; 39 | 40 | public LogPageRstr(byte[] rawBytes, int offset) 41 | { 42 | var index = 0x0; 43 | 44 | var sigCheck = BitConverter.ToInt32(rawBytes, index); 45 | 46 | if (sigCheck != RstrSig && sigCheck != ChkdSig) 47 | { 48 | throw new Exception("Invalid signature! Expected 'RSTR|CHKD' signature."); 49 | } 50 | 51 | Offset = offset; 52 | 53 | index += 4; 54 | 55 | var fixupOffset = BitConverter.ToInt16(rawBytes, index); 56 | index += 2; 57 | var numFixupPairs = BitConverter.ToInt16(rawBytes, index); 58 | index += 2; 59 | 60 | CheckDiskLsn = BitConverter.ToInt64(rawBytes, index); 61 | index += 8; 62 | SystemPageSize = BitConverter.ToInt32(rawBytes, index); 63 | index += 4; 64 | 65 | LogPageSize = BitConverter.ToInt32(rawBytes, index); 66 | index += 4; 67 | 68 | RestartOffset = BitConverter.ToInt16(rawBytes, index); 69 | index += 2; 70 | 71 | MinorFormatVersion = BitConverter.ToInt16(rawBytes, index); 72 | index += 2; 73 | 74 | MajorFormatVersion = BitConverter.ToInt16(rawBytes, index); 75 | index += 2; 76 | 77 | 78 | var fixupTotalLength = numFixupPairs * 2; 79 | 80 | var fixupBuffer = new byte[fixupTotalLength]; 81 | Buffer.BlockCopy(rawBytes, fixupOffset, fixupBuffer, 0, fixupTotalLength); 82 | 83 | var fixupData = new FixupData(fixupBuffer); 84 | 85 | var fixupOk = true; 86 | 87 | //fixup verification 88 | var counter = 512; 89 | foreach (var bytese in fixupData.FixupActual) 90 | { 91 | //adjust the offset to where we need to check 92 | var fixupOffset1 = counter - 2; 93 | 94 | var expected = BitConverter.ToInt16(rawBytes, fixupOffset1); 95 | if (expected != fixupData.FixupExpected) 96 | { 97 | fixupOk = false; 98 | Log.Warning( 99 | "Fixup values do not match at 0x{FixupOffset1:X}. Expected: 0x{FixupExpected:X2}, actual: 0x{Expected:X2}", 100 | fixupOffset1, fixupData.FixupExpected, expected); 101 | } 102 | 103 | //replace fixup expected with actual bytes. bytese has actual replacement values in it. 104 | Buffer.BlockCopy(bytese, 0, rawBytes, fixupOffset1, 2); 105 | 106 | counter += 512; 107 | } 108 | 109 | index += fixupTotalLength; 110 | 111 | while (index % 8 != 0) 112 | { 113 | index += 1; 114 | } 115 | 116 | CurrentLsn = BitConverter.ToInt64(rawBytes, index); 117 | index += 8; 118 | LogClientCount = BitConverter.ToInt16(rawBytes, index); 119 | index += 2; 120 | ClientFreeList = BitConverter.ToInt16(rawBytes, index); 121 | index += 2; 122 | ClientInUseList = BitConverter.ToInt16(rawBytes, index); 123 | index += 2; 124 | Flags = (RestartFlag)BitConverter.ToInt16(rawBytes, index); 125 | index += 2; 126 | SeqNumBits = BitConverter.ToInt32(rawBytes, index); 127 | index += 4; 128 | RestartAreaLen = BitConverter.ToInt16(rawBytes, index); 129 | index += 2; 130 | ClientArrayOffset = BitConverter.ToInt16(rawBytes, index); 131 | index += 2; 132 | LogFileSize = BitConverter.ToInt64(rawBytes, index); 133 | index += 8; 134 | LastLsnDataLen = BitConverter.ToInt32(rawBytes, index); 135 | index += 4; 136 | RecordHeaderLen = BitConverter.ToInt16(rawBytes, index); 137 | index += 2; 138 | LogPageDataOffset = BitConverter.ToInt16(rawBytes, index); 139 | index += 2; 140 | RevisionNumber = BitConverter.ToInt32(rawBytes, index); 141 | 142 | index = 0x30 + ClientArrayOffset; 143 | ClientRecords = new List(); 144 | 145 | for (var i = 0; i < LogClientCount; i++) 146 | { 147 | var buff = new byte[160]; //len of clientRecord 148 | 149 | Buffer.BlockCopy(rawBytes, index, buff, 0, 160); 150 | 151 | var cr = new ClientRecord(buff); 152 | ClientRecords.Add(cr); 153 | index += 160; 154 | } 155 | } 156 | 157 | public int Offset { get; } 158 | 159 | public List ClientRecords { get; } 160 | 161 | public override string ToString() 162 | { 163 | var sb = new StringBuilder(); 164 | 165 | sb.Append($"checkDiskLsn: 0x{CheckDiskLsn:X} "); 166 | sb.Append($"systemPageSize: 0x{SystemPageSize:X} "); 167 | sb.Append($"logPageSize: 0x{LogPageSize:X} "); 168 | sb.Append($"restartOffset: 0x{RestartOffset:X} "); 169 | sb.Append($"majorFormatVersion: 0x{MajorFormatVersion:X} "); 170 | sb.Append($"minorFormatVersion: 0x{MinorFormatVersion:X} "); 171 | sb.Append($"currentLsn: 0x{CurrentLsn:X} "); 172 | sb.Append($"logClient: 0x{LogClientCount:X} "); 173 | sb.Append($"ClientFreeList: 0x{ClientFreeList:X} "); 174 | sb.Append($"ClientInUseList: 0x{ClientInUseList:X} "); 175 | sb.Append($"flags: {Flags} "); 176 | sb.Append($"SeqNumBits: 0x{SeqNumBits:X} "); 177 | sb.Append($"RestartAreaLen: 0x{RestartAreaLen:X} "); 178 | sb.Append($"ClientArrayOffset: 0x{ClientArrayOffset:X} "); 179 | sb.Append($"LogFileSize: 0x{LogFileSize:X} "); 180 | sb.Append($"lastLsnDataLen: 0x{LastLsnDataLen:X} "); 181 | sb.Append($"recordHeaderLen: 0x{RecordHeaderLen:X} "); 182 | sb.Append($"LogPageDataOffset: 0x{LogPageDataOffset:X} "); 183 | sb.Append($"revisionNumber: 0x{RevisionNumber:X} "); 184 | sb.AppendLine(); 185 | 186 | sb.AppendLine($"Client Records ({ClientRecords.Count:N0})"); 187 | foreach (var clientRecord in ClientRecords) 188 | { 189 | sb.AppendLine(clientRecord.ToString()); 190 | } 191 | 192 | sb.AppendLine(); 193 | 194 | return sb.ToString(); 195 | } 196 | } 197 | 198 | public class ClientRecord 199 | { 200 | public ClientRecord(byte[] rawBytes) 201 | { 202 | var br = new BinaryReader(new MemoryStream(rawBytes)); 203 | 204 | OldestLsn = br.ReadInt64(); 205 | ClientRestartLsn = br.ReadInt64(); 206 | PrevClient = br.ReadInt16(); 207 | NextClient = br.ReadInt16(); 208 | SeqNumber = br.ReadInt16(); 209 | br.ReadBytes(6); //skip 210 | var clientNameLen = br.ReadInt32(); 211 | 212 | ClientName = Encoding.Unicode.GetString(br.ReadBytes(clientNameLen)); 213 | } 214 | 215 | public long OldestLsn { get; } 216 | public long ClientRestartLsn { get; } 217 | public short PrevClient { get; } 218 | public short NextClient { get; } 219 | public short SeqNumber { get; } 220 | public string ClientName { get; } 221 | 222 | public override string ToString() 223 | { 224 | return 225 | $" oldestLsn: 0x{OldestLsn:X} clientRestartLsn: 0x{ClientRestartLsn:X} prevClient: 0x{PrevClient:X} nextClient: 0x{NextClient:X} seqNumber: 0x{SeqNumber:X} ClientName: {ClientName}"; 226 | } 227 | } -------------------------------------------------------------------------------- /LogFile/Log_File.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace LogFile; 4 | 5 | public static class Log_File 6 | { 7 | public static LogFile Load(string logFile) 8 | { 9 | if (File.Exists(logFile) == false) 10 | { 11 | throw new FileNotFoundException($"'{logFile}' not found"); 12 | } 13 | 14 | using var fs = new FileStream(logFile, FileMode.Open, FileAccess.Read); 15 | return new LogFile(fs); 16 | } 17 | 18 | public static byte[] ReadAllBytes(this BinaryReader reader) 19 | { 20 | const int bufferSize = 4096; 21 | using var ms = new MemoryStream(); 22 | var buffer = new byte[bufferSize]; 23 | int count; 24 | while ((count = reader.Read(buffer, 0, buffer.Length)) != 0) 25 | { 26 | ms.Write(buffer, 0, count); 27 | } 28 | 29 | return ms.ToArray(); 30 | } 31 | } -------------------------------------------------------------------------------- /MFT.Test/MFT.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net462;net6.0 4 | 10 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 17.12.0 22 | 23 | 24 | 25 | 4.3.2 26 | 27 | 28 | 6.0.0 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /MFT.Test/TestFiles/$I30/FirstDelete/$I30: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricZimmerman/MFT/50e4ea636a96359f1d5f0ee0f993802564ff6b86/MFT.Test/TestFiles/$I30/FirstDelete/$I30 -------------------------------------------------------------------------------- /MFT.Test/TestFiles/$I30/SecondDelete/$I30: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricZimmerman/MFT/50e4ea636a96359f1d5f0ee0f993802564ff6b86/MFT.Test/TestFiles/$I30/SecondDelete/$I30 -------------------------------------------------------------------------------- /MFT.Test/TestFiles/$I30/Start/$I30: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricZimmerman/MFT/50e4ea636a96359f1d5f0ee0f993802564ff6b86/MFT.Test/TestFiles/$I30/Start/$I30 -------------------------------------------------------------------------------- /MFT.Test/TestFiles/Boot/$Boot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricZimmerman/MFT/50e4ea636a96359f1d5f0ee0f993802564ff6b86/MFT.Test/TestFiles/Boot/$Boot -------------------------------------------------------------------------------- /MFT.Test/TestFiles/NIST/DFR-16/$MFT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricZimmerman/MFT/50e4ea636a96359f1d5f0ee0f993802564ff6b86/MFT.Test/TestFiles/NIST/DFR-16/$MFT -------------------------------------------------------------------------------- /MFT.Test/TestFiles/Usn/record.usn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricZimmerman/MFT/50e4ea636a96359f1d5f0ee0f993802564ff6b86/MFT.Test/TestFiles/Usn/record.usn -------------------------------------------------------------------------------- /MFT.Test/TestFiles/tdungan/$MFT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricZimmerman/MFT/50e4ea636a96359f1d5f0ee0f993802564ff6b86/MFT.Test/TestFiles/tdungan/$MFT -------------------------------------------------------------------------------- /MFT.Test/TestFiles/xw/$MFT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricZimmerman/MFT/50e4ea636a96359f1d5f0ee0f993802564ff6b86/MFT.Test/TestFiles/xw/$MFT -------------------------------------------------------------------------------- /MFT.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.452 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MFT", "MFT\MFT.csproj", "{DDA9E417-B536-4730-B578-AA45F8B130C7}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MFT.Test", "MFT.Test\MFT.Test.csproj", "{6DDDB050-A947-4B94-8E1C-70326008C65B}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Boot", "Boot\Boot.csproj", "{BA9C6030-EC7D-4C1D-A6BC-0D86824E1111}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Usn", "Usn\Usn.csproj", "{23D6FA96-D020-4CB6-8ED8-82D3F49317A2}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Secure", "SDS\Secure.csproj", "{21E6B751-CA17-4612-9806-9B52D3031FCF}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LogFile", "LogFile\LogFile.csproj", "{CCC2144C-33A7-41B9-8355-C240307A9611}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "O", "O\O.csproj", "{9B30002D-B041-41E6-9EDD-34CF16EFCFCF}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "I30", "I30\I30.csproj", "{7D1BAE41-7E23-42EC-AA25-7F54716655AD}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {DDA9E417-B536-4730-B578-AA45F8B130C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {DDA9E417-B536-4730-B578-AA45F8B130C7}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {DDA9E417-B536-4730-B578-AA45F8B130C7}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {DDA9E417-B536-4730-B578-AA45F8B130C7}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {6DDDB050-A947-4B94-8E1C-70326008C65B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {6DDDB050-A947-4B94-8E1C-70326008C65B}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {6DDDB050-A947-4B94-8E1C-70326008C65B}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {6DDDB050-A947-4B94-8E1C-70326008C65B}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {BA9C6030-EC7D-4C1D-A6BC-0D86824E1111}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {BA9C6030-EC7D-4C1D-A6BC-0D86824E1111}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {BA9C6030-EC7D-4C1D-A6BC-0D86824E1111}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {BA9C6030-EC7D-4C1D-A6BC-0D86824E1111}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {23D6FA96-D020-4CB6-8ED8-82D3F49317A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {23D6FA96-D020-4CB6-8ED8-82D3F49317A2}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {23D6FA96-D020-4CB6-8ED8-82D3F49317A2}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {23D6FA96-D020-4CB6-8ED8-82D3F49317A2}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {21E6B751-CA17-4612-9806-9B52D3031FCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {21E6B751-CA17-4612-9806-9B52D3031FCF}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {21E6B751-CA17-4612-9806-9B52D3031FCF}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {21E6B751-CA17-4612-9806-9B52D3031FCF}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {CCC2144C-33A7-41B9-8355-C240307A9611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {CCC2144C-33A7-41B9-8355-C240307A9611}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {CCC2144C-33A7-41B9-8355-C240307A9611}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {CCC2144C-33A7-41B9-8355-C240307A9611}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {9B30002D-B041-41E6-9EDD-34CF16EFCFCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {9B30002D-B041-41E6-9EDD-34CF16EFCFCF}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {9B30002D-B041-41E6-9EDD-34CF16EFCFCF}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {9B30002D-B041-41E6-9EDD-34CF16EFCFCF}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {7D1BAE41-7E23-42EC-AA25-7F54716655AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {7D1BAE41-7E23-42EC-AA25-7F54716655AD}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {7D1BAE41-7E23-42EC-AA25-7F54716655AD}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {7D1BAE41-7E23-42EC-AA25-7F54716655AD}.Release|Any CPU.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(ExtensibilityGlobals) = postSolution 65 | SolutionGuid = {84153A83-ACF2-4023-B3AC-6E55C42DB388} 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /MFT/Attributes/ACERecord.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | // namespaces... 5 | 6 | namespace MFT.Attributes; 7 | 8 | // public classes... 9 | public class AceRecord 10 | { 11 | // public enums... 12 | [Flags] 13 | public enum AceFlagsEnum 14 | { 15 | ContainerInheritAce = 0x02, 16 | FailedAccessAceFlag = 0x80, 17 | InheritedAce = 0x10, 18 | InheritOnlyAce = 0x08, 19 | None = 0x0, 20 | NoPropagateInheritAce = 0x04, 21 | ObjectInheritAce = 0x01, 22 | SuccessfulAccessAceFlag = 0x40 23 | } 24 | 25 | public enum AceTypeEnum 26 | { 27 | AccessAllowed = 0x0, 28 | AccessAllowedCompound = 0x4, 29 | AccessAllowedObject = 0x5, 30 | AccessDenied = 0x1, 31 | AccessDeniedObject = 0x6, 32 | SystemAlarm = 0x3, 33 | SystemAlarmObject = 0x8, 34 | SystemAudit = 0x2, 35 | SystemAuditObject = 0x7, 36 | AccessAllowedCallback = 0x9, 37 | AccessDeniedCallback = 0xa, 38 | AccessAllowedCallbackObject = 0xb, 39 | AccessDeniedCallbackObject = 0xc, 40 | SystemAuditCallback = 0xd, 41 | SystemAlarmCallback = 0xe, 42 | SystemAuditCallbackObject = 0xf, 43 | SystemAlarmCallbackObject = 0x10, 44 | SystemMandatoryLabel = 0x11, 45 | SystemResourceAttribute = 0x12, 46 | SystemScopedPolicyId = 0x13, 47 | SystemProcessTrustLabel = 0x14, 48 | Unknown = 0x99 49 | } 50 | 51 | [Flags] 52 | public enum MasksEnum 53 | { 54 | FilEExecute = 0x00000020, 55 | CreateSubDir = 0x00000004, 56 | ReadAttrs = 0x00000080, 57 | WriteAttrs = 0x00000100, 58 | WriteOwnProp = 0x00000200, 59 | DeleteOwnProp = 0x00000400, 60 | ViewOwnProp = 0x00000800, 61 | Delete = 0x00010000, 62 | ReadEa = 0x00000008, 63 | FullControl = 0x000F003F, 64 | WriteEa = 0x00000010, 65 | FileReadDirList = 0x00000001, 66 | ReadControl = 0x00020000, 67 | FileWriteFileAdd = 0x00000002, 68 | WriteDac = 0x00040000, 69 | WriteOwner = 0x00080000, 70 | Synchronize = 0x000100000, 71 | TrusteeOwn = 0x00004000, 72 | UserAsContact = 0x00008000 73 | } 74 | 75 | // public constructors... 76 | /// 77 | /// Initializes a new instance of the class. 78 | /// 79 | public AceRecord(byte[] rawBytes) 80 | { 81 | RawBytes = rawBytes; 82 | } 83 | 84 | // public properties... 85 | public AceFlagsEnum AceFlags => (AceFlagsEnum)RawBytes[1]; 86 | 87 | public ushort AceSize => BitConverter.ToUInt16(RawBytes, 2); 88 | 89 | public AceTypeEnum AceType 90 | { 91 | get 92 | { 93 | switch (RawBytes[0]) 94 | { 95 | case 0x0: 96 | return AceTypeEnum.AccessAllowed; 97 | //ncrunch: no coverage start 98 | case 0x1: 99 | return AceTypeEnum.AccessDenied; 100 | 101 | case 0x2: 102 | return AceTypeEnum.SystemAudit; 103 | 104 | case 0x3: 105 | return AceTypeEnum.SystemAlarm; 106 | 107 | case 0x4: 108 | return AceTypeEnum.AccessAllowedCompound; 109 | 110 | case 0x5: 111 | return AceTypeEnum.AccessAllowedObject; 112 | 113 | case 0x6: 114 | return AceTypeEnum.AccessDeniedObject; 115 | 116 | case 0x7: 117 | return AceTypeEnum.SystemAuditObject; 118 | 119 | case 0x8: 120 | return AceTypeEnum.SystemAlarmObject; 121 | case 0x9: 122 | return AceTypeEnum.AccessAllowedCallback; 123 | case 0xa: 124 | return AceTypeEnum.AccessDeniedCallback; 125 | case 0xb: 126 | return AceTypeEnum.AccessAllowedCallbackObject; 127 | case 0xc: 128 | return AceTypeEnum.AccessDeniedCallbackObject; 129 | case 0xd: 130 | return AceTypeEnum.SystemAuditCallback; 131 | case 0xe: 132 | return AceTypeEnum.SystemAlarmCallback; 133 | case 0xf: 134 | return AceTypeEnum.SystemAuditCallbackObject; 135 | case 0x10: 136 | return AceTypeEnum.SystemAlarmCallbackObject; 137 | case 0x11: 138 | return AceTypeEnum.SystemMandatoryLabel; 139 | case 0x12: 140 | return AceTypeEnum.SystemResourceAttribute; 141 | case 0x13: 142 | return AceTypeEnum.SystemScopedPolicyId; 143 | case 0x14: 144 | return AceTypeEnum.SystemProcessTrustLabel; 145 | default: 146 | return AceTypeEnum.Unknown; 147 | //ncrunch: no coverage end 148 | } 149 | } 150 | } 151 | 152 | public MasksEnum Mask => (MasksEnum)BitConverter.ToUInt32(RawBytes, 4); 153 | 154 | public byte[] RawBytes { get; } 155 | 156 | public string Sid 157 | { 158 | get 159 | { 160 | var rawSidBytes = new byte[AceSize - 0x8]; 161 | Buffer.BlockCopy(RawBytes, 0x8, rawSidBytes, 0, rawSidBytes.Length); 162 | 163 | return Helpers.ConvertHexStringToSidString(rawSidBytes); 164 | } 165 | } 166 | 167 | public Helpers.SidTypeEnum SidType => Helpers.GetSidTypeFromSidString(Sid); 168 | 169 | // public methods... 170 | public override string ToString() 171 | { 172 | var sb = new StringBuilder(); 173 | 174 | sb.AppendLine($"ACE Size: 0x{AceSize:X}"); 175 | 176 | sb.AppendLine($"ACE Type: {AceType}"); 177 | 178 | sb.AppendLine($"ACE Flags: {AceFlags.ToString().Replace(", ", "|")}"); 179 | 180 | sb.AppendLine($"Mask: {Mask}"); 181 | 182 | sb.AppendLine($"SID: {Sid}"); 183 | sb.AppendLine($"SID Type: {SidType}"); 184 | 185 | sb.AppendLine($"SID Type Description: {Helpers.GetDescriptionFromEnumValue(SidType)}"); 186 | 187 | return sb.ToString(); 188 | } 189 | } -------------------------------------------------------------------------------- /MFT/Attributes/Attribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MFT.Attributes; 5 | 6 | public enum AttributeType 7 | { 8 | EndOfAttributes = -0x1, 9 | Unused = 0x0, 10 | StandardInformation = 0x10, 11 | AttributeList = 0x20, 12 | FileName = 0x30, 13 | VolumeVersionObjectId = 0x40, 14 | SecurityDescriptor = 0x50, 15 | VolumeName = 0x60, 16 | VolumeInformation = 0x70, 17 | Data = 0x80, 18 | IndexRoot = 0x90, 19 | IndexAllocation = 0xa0, 20 | Bitmap = 0xb0, 21 | ReparsePoint = 0xc0, 22 | EaInformation = 0xd0, 23 | Ea = 0xe0, 24 | PropertySet = 0xf0, 25 | LoggedUtilityStream = 0x100, 26 | UserDefinedAttribute = 0x1000 27 | } 28 | 29 | [Flags] 30 | public enum AttributeDataFlag 31 | { 32 | Compressed = 0x0001, 33 | Encrypted = 0x4000, 34 | Sparse = 0x8000 35 | } 36 | 37 | public abstract class Attribute 38 | { 39 | protected Attribute(byte[] rawBytes) 40 | { 41 | AttributeNumber = BitConverter.ToInt16(rawBytes, 0xE); 42 | 43 | AttributeType = (AttributeType)BitConverter.ToInt32(rawBytes, 0); 44 | AttributeSize = BitConverter.ToInt32(rawBytes, 4); 45 | 46 | IsResident = rawBytes[0x8] == 0; 47 | 48 | NameSize = rawBytes[0x09]; 49 | NameOffset = BitConverter.ToInt16(rawBytes, 0xA); 50 | 51 | AttributeDataFlag = (AttributeDataFlag)BitConverter.ToInt16(rawBytes, 0xC); 52 | 53 | AttributeContentLength = BitConverter.ToInt32(rawBytes, 0x10); 54 | ContentOffset = BitConverter.ToInt16(rawBytes, 0x14); 55 | 56 | Name = string.Empty; 57 | if (NameSize > 0) 58 | { 59 | Name = Encoding.Unicode.GetString(rawBytes, NameOffset, NameSize * 2); 60 | } 61 | } 62 | 63 | public AttributeType AttributeType { get; } 64 | public int AttributeSize { get; } 65 | public int AttributeContentLength { get; } 66 | public int NameSize { get; } 67 | public int NameOffset { get; } 68 | 69 | public AttributeDataFlag AttributeDataFlag { get; } 70 | 71 | public string Name { get; } 72 | public int AttributeNumber { get; } 73 | 74 | public bool IsResident { get; } 75 | 76 | public short ContentOffset { get; } 77 | 78 | public override string ToString() 79 | { 80 | var name = string.Empty; 81 | 82 | if (NameSize > 0) 83 | { 84 | name = $", Name: {Name}"; 85 | } 86 | 87 | var flags = string.Empty; 88 | 89 | if (AttributeDataFlag > 0) 90 | { 91 | flags = $" Attribute flags: {AttributeDataFlag.ToString().Replace(", ", "|")},"; 92 | } 93 | 94 | return 95 | $"Type: {AttributeType}, Attribute #: 0x{AttributeNumber:X},{flags} Size: 0x{AttributeSize:X}, Content size: 0x{AttributeContentLength:X}, Name size: 0x{NameSize:X}{name}, Content offset: 0x{ContentOffset:X}, Resident: {IsResident}"; 96 | } 97 | } -------------------------------------------------------------------------------- /MFT/Attributes/AttributeList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using MFT.Other; 5 | using Serilog; 6 | 7 | namespace MFT.Attributes; 8 | 9 | public class AttributeList : Attribute 10 | { 11 | public AttributeList(byte[] rawBytes) : base(rawBytes) 12 | { 13 | DataRuns = new List(); 14 | AttributeInformations = new List(); 15 | 16 | //TODO Refactor using the NonResident and ResidentData classes? 17 | 18 | if (IsResident) 19 | { 20 | var index = ContentOffset; 21 | 22 | while (index < rawBytes.Length) 23 | { 24 | var size = BitConverter.ToInt16(rawBytes, index + 4); 25 | 26 | if (size < rawBytes.Length - index) 27 | { 28 | Log.Debug("Not enough data to process attribute list. {Msg}","Skipping remaining bytes in attribute list"); 29 | break; 30 | } 31 | 32 | var buffer = new byte[size]; 33 | Buffer.BlockCopy(rawBytes, index, buffer, 0, size); 34 | 35 | var er = new AttributeInfo(buffer); 36 | 37 | AttributeInformations.Add(er); 38 | 39 | index += size; 40 | } 41 | } 42 | else 43 | { 44 | StartingVirtualCluster = BitConverter.ToUInt64(rawBytes, 0x10); 45 | EndingVirtualCluster = BitConverter.ToUInt64(rawBytes, 0x18); 46 | OffsetToDataRun = BitConverter.ToUInt16(rawBytes, 0x20); 47 | AllocatedSize = BitConverter.ToUInt64(rawBytes, 0x28); 48 | ActualSize = BitConverter.ToUInt64(rawBytes, 0x30); 49 | InitializedSize = BitConverter.ToUInt64(rawBytes, 0x38); 50 | 51 | var index = OffsetToDataRun; 52 | 53 | var hasAnother = rawBytes[index] > 0; 54 | 55 | //when data is split across several entries, must find them all and process in order of 56 | //StartingVCN: 0x0 EndingVCN: 0x57C 57 | //StartingVCN: 0x57D EndingVCN: 0x138F 58 | //so things go back in right order 59 | 60 | //TODO this should be a function vs here and in Data class. 61 | 62 | while (hasAnother) 63 | { 64 | var drStart = rawBytes[index]; 65 | index += 1; 66 | 67 | var clustersToReadAtOffset = (byte)(drStart & 0x0F); 68 | var offsetToRun = (byte)((drStart & 0xF0) >> 4); 69 | 70 | var clusterCountRaw = new byte[8]; 71 | var offsetToRunRaw = new byte[8]; 72 | 73 | Buffer.BlockCopy(rawBytes, index, clusterCountRaw, 0, clustersToReadAtOffset); 74 | 75 | index += clustersToReadAtOffset; 76 | Buffer.BlockCopy(rawBytes, index, offsetToRunRaw, 0, offsetToRun); 77 | index += offsetToRun; 78 | 79 | var clusterCount = BitConverter.ToUInt64(clusterCountRaw, 0); 80 | var offset = BitConverter.ToInt64(offsetToRunRaw, 0); 81 | 82 | var dr = new DataRun(clusterCount, offset); 83 | DataRuns.Add(dr); 84 | 85 | hasAnother = rawBytes[index] > 0; 86 | } 87 | } 88 | } 89 | 90 | public ushort OffsetToDataRun { get; } 91 | public ulong StartingVirtualCluster { get; } 92 | public ulong EndingVirtualCluster { get; } 93 | public ulong AllocatedSize { get; } 94 | public ulong ActualSize { get; } 95 | public ulong InitializedSize { get; } 96 | 97 | /// 98 | /// Contains cluster where the actual data lives when it is non-resident 99 | /// 100 | public List DataRuns { get; } 101 | 102 | public List AttributeInformations { get; } 103 | 104 | public override string ToString() 105 | { 106 | var sb = new StringBuilder(); 107 | 108 | sb.AppendLine("**** ATTRIBUTE LIST ****"); 109 | 110 | sb.AppendLine(base.ToString()); 111 | 112 | sb.AppendLine(); 113 | 114 | sb.AppendLine( 115 | $"DataRuns: {string.Join("\r\n", DataRuns)}\r\nAttribute Infos: {string.Join("\r\n", AttributeInformations)}"); 116 | 117 | return sb.ToString(); 118 | } 119 | } -------------------------------------------------------------------------------- /MFT/Attributes/Bitmap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MFT.Attributes; 5 | 6 | public class Bitmap : Attribute 7 | { 8 | public Bitmap(byte[] rawBytes) : base(rawBytes) 9 | { 10 | if (IsResident) 11 | { 12 | var content = new byte[AttributeContentLength]; 13 | 14 | Buffer.BlockCopy(rawBytes, ContentOffset, content, 0, AttributeContentLength); 15 | 16 | ResidentData = new ResidentData(content); 17 | } 18 | else 19 | { 20 | NonResidentData = new NonResidentData(rawBytes); 21 | } 22 | } 23 | 24 | public ResidentData ResidentData { get; } 25 | 26 | public NonResidentData NonResidentData { get; } 27 | 28 | public override string ToString() 29 | { 30 | var sb = new StringBuilder(); 31 | 32 | sb.AppendLine("**** BITMAP ****"); 33 | 34 | sb.AppendLine(base.ToString()); 35 | 36 | sb.AppendLine(); 37 | 38 | if (ResidentData == null) 39 | { 40 | sb.AppendLine("Non Resident Data"); 41 | sb.AppendLine(NonResidentData.ToString()); 42 | } 43 | else 44 | { 45 | sb.AppendLine("Resident Data"); 46 | sb.AppendLine(ResidentData.ToString()); 47 | } 48 | 49 | return sb.ToString(); 50 | } 51 | } -------------------------------------------------------------------------------- /MFT/Attributes/Data.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MFT.Attributes; 5 | 6 | public class Data : Attribute 7 | { 8 | public Data(byte[] rawBytes) : base(rawBytes) 9 | { 10 | if (IsResident) 11 | { 12 | var content = new byte[AttributeContentLength]; 13 | 14 | Buffer.BlockCopy(rawBytes, ContentOffset, content, 0, AttributeContentLength); 15 | 16 | ResidentData = new ResidentData(content); 17 | } 18 | else 19 | { 20 | NonResidentData = new NonResidentData(rawBytes); 21 | } 22 | } 23 | 24 | public ResidentData ResidentData { get; } 25 | public NonResidentData NonResidentData { get; } 26 | 27 | public override string ToString() 28 | { 29 | var sb = new StringBuilder(); 30 | 31 | sb.AppendLine("**** DATA ****"); 32 | 33 | sb.AppendLine(base.ToString()); 34 | 35 | sb.AppendLine(); 36 | 37 | if (ResidentData == null) 38 | { 39 | sb.AppendLine("Non Resident Data"); 40 | sb.AppendLine(NonResidentData.ToString()); 41 | } 42 | else 43 | { 44 | sb.AppendLine("Resident Data"); 45 | sb.AppendLine(ResidentData.ToString()); 46 | } 47 | 48 | return sb.ToString(); 49 | } 50 | } -------------------------------------------------------------------------------- /MFT/Attributes/ExtendedAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Text; 5 | using Serilog; 6 | 7 | namespace MFT.Attributes; 8 | 9 | public interface IEa 10 | { 11 | public string InternalName { get; } 12 | } 13 | 14 | public class ExtendedAttribute : Attribute 15 | { 16 | public ExtendedAttribute(byte[] rawBytes) : base(rawBytes) 17 | { 18 | Content = new byte[AttributeContentLength]; 19 | 20 | Buffer.BlockCopy(rawBytes, ContentOffset, Content, 0, AttributeContentLength); 21 | 22 | SubItems = new List(); 23 | 24 | ProcessContent(); 25 | } 26 | 27 | public byte[] Content { get; } 28 | 29 | public List SubItems { get; } 30 | 31 | private void ProcessContent() 32 | { 33 | if (Content.Length == 0) 34 | { 35 | return; 36 | } 37 | 38 | var index = 0; 39 | 40 | var chunks = new List(); 41 | 42 | while (index < Content.Length) 43 | { 44 | var size = BitConverter.ToInt32(Content, index); 45 | 46 | var buff = new byte[size]; 47 | Buffer.BlockCopy(Content, index, buff, 0, size); 48 | chunks.Add(buff); 49 | index += size; 50 | } 51 | 52 | foreach (var bytese in chunks) 53 | { 54 | index = 0; 55 | 56 | var nextOffset = BitConverter.ToUInt32(bytese, index); 57 | if (nextOffset == 0) 58 | { 59 | break; 60 | } 61 | 62 | index += 4; 63 | var flags = bytese[index]; 64 | index += 1; 65 | var nameLen = bytese[index]; 66 | index += 1; 67 | var eaLen = BitConverter.ToUInt16(bytese, index); 68 | index += 2; 69 | 70 | var name = Encoding.Unicode.GetString(bytese, index, nameLen); 71 | 72 | if (bytese[index + 1] != 0) 73 | { 74 | name = Encoding.ASCII.GetString(bytese, index, nameLen); 75 | } 76 | 77 | index += nameLen; 78 | index += 1; //null char 79 | 80 | // var defBuff = new byte[bytese.Length - index]; 81 | // Buffer.BlockCopy(bytese,index,defBuff,0,defBuff.Length); 82 | // File.WriteAllBytes($"D:\\Temp\\Maxim_EA)STUFF_MFT_wsl2\\EASAmples\\{name}_{Guid.NewGuid().ToString()}.bin",defBuff); 83 | 84 | switch (name) 85 | { 86 | case "$LXUID": 87 | case "$LXGID": 88 | case "$LXMOD": 89 | var lxuid = new byte[bytese.Length - index]; 90 | Buffer.BlockCopy(bytese, index, lxuid, 0, lxuid.Length); 91 | 92 | var lux = new LxXXX(lxuid, name, name); 93 | SubItems.Add(lux); 94 | break; 95 | case ".LONGNAME": 96 | var lnBuff = new byte[bytese.Length - index]; 97 | Buffer.BlockCopy(bytese, index, lnBuff, 0, lnBuff.Length); 98 | var ln = new LongName(lnBuff, name); 99 | SubItems.Add(ln); 100 | break; 101 | case "LXATTRB": 102 | var lbBuff = new byte[bytese.Length - index]; 103 | Buffer.BlockCopy(bytese, index, lbBuff, 0, lbBuff.Length); 104 | var lb = new Lxattrb(lbBuff, name); 105 | SubItems.Add(lb); 106 | //Debug.WriteLine(lb); 107 | break; 108 | case "LXXATTR": 109 | var lrBuff = new byte[bytese.Length - index]; 110 | Buffer.BlockCopy(bytese, index, lrBuff, 0, lrBuff.Length); 111 | var lr = new Lxattrr(lrBuff, name); 112 | SubItems.Add(lr); 113 | //Debug.WriteLine(lr); 114 | break; 115 | case "$KERNEL.PURGE.ESBCACHE": 116 | var kpEs = new byte[bytese.Length - index]; 117 | Buffer.BlockCopy(bytese, index, kpEs, 0, kpEs.Length); 118 | var esbCache = new PurgeEsbCache(kpEs, name); 119 | SubItems.Add(esbCache); 120 | //Debug.WriteLine(esbCache); 121 | //TODO FINISH 122 | break; 123 | case "$CI.CATALOGHINT": 124 | var ciCat = new byte[bytese.Length - index]; 125 | Buffer.BlockCopy(bytese, index, ciCat, 0, ciCat.Length); 126 | var catHint = new CatHint(ciCat, name); 127 | SubItems.Add(catHint); 128 | break; 129 | case "$KERNEL.PURGE.APPFIXCACHE": 130 | case "$KERNEL.PURGE.APPXFICACHE": 131 | var kpAppXFi = new byte[bytese.Length - index]; 132 | Buffer.BlockCopy(bytese, index, kpAppXFi, 0, kpAppXFi.Length); 133 | var appFix = new AppFixCache(kpAppXFi, name); 134 | SubItems.Add(appFix); 135 | break; 136 | case ".CLASSINFO": 137 | var clInfo = new byte[bytese.Length - index]; 138 | Buffer.BlockCopy(bytese, index, clInfo, 0, clInfo.Length); 139 | var clI = new ClassInfo(clInfo, name); 140 | SubItems.Add(clI); 141 | break; 142 | 143 | default: 144 | Log.Debug("Unknown EA with name: {Name}, Length: 0x{Length:X}", name, bytese.Length - index); 145 | //var defBuff = new byte[bytese.Length - index]; 146 | // Buffer.BlockCopy(bytese,index,defBuff,0,defBuff.Length); 147 | // File.WriteAllBytes($"D:\\Temp\\Maxim_EA)STUFF_MFT_wsl2\\EASAmples\\{name}_{Guid.NewGuid().ToString()}.bin",defBuff); 148 | break; 149 | } 150 | } 151 | } 152 | 153 | public override string ToString() 154 | { 155 | var sb = new StringBuilder(); 156 | 157 | sb.AppendLine("**** EXTENDED ATTRIBUTE ****"); 158 | 159 | sb.AppendLine(base.ToString()); 160 | 161 | var asAscii = Encoding.ASCII.GetString(Content); 162 | var asUnicode = Encoding.Unicode.GetString(Content); 163 | 164 | sb.AppendLine(); 165 | sb.AppendLine( 166 | $"Extended Attribute: {BitConverter.ToString(Content)}\r\n\r\nASCII: {asAscii}\r\nUnicode: {asUnicode}"); 167 | 168 | if (SubItems.Count > 0) 169 | { 170 | sb.AppendLine(); 171 | sb.AppendLine("Sub items"); 172 | } 173 | 174 | foreach (var subItem in SubItems) 175 | { 176 | sb.AppendLine(subItem.ToString()); 177 | } 178 | 179 | return sb.ToString(); 180 | } 181 | } 182 | 183 | public class LongName : IEa 184 | { 185 | public LongName(byte[] rawBytes, string internalName) 186 | { 187 | InternalName = internalName; 188 | 189 | var index = 0; 190 | 191 | index += 2; // unknown 192 | 193 | var size = BitConverter.ToInt16(rawBytes, index); 194 | index += 2; 195 | 196 | Name = Encoding.Unicode.GetString(rawBytes, index, size); 197 | } 198 | 199 | public string Name { get; } 200 | 201 | public string InternalName { get; } 202 | 203 | public override string ToString() 204 | { 205 | return $".LONGNAME: {Name}"; 206 | } 207 | } 208 | 209 | public class LxXXX : IEa 210 | { 211 | public LxXXX(byte[] rawBytes, string name, string internalName) 212 | { 213 | InternalName = internalName; 214 | Name = $"{name}: {BitConverter.ToString(rawBytes)}"; 215 | } 216 | 217 | public string Name { get; } 218 | 219 | public string InternalName { get; } 220 | 221 | public override string ToString() 222 | { 223 | return Name; 224 | } 225 | } 226 | 227 | public class ClassInfo : IEa 228 | { 229 | public ClassInfo(byte[] rawBytes, string internalName) 230 | { 231 | InternalName = internalName; 232 | // var index = 0; 233 | // 234 | // index += 2; // unknown 235 | // 236 | // var size = BitConverter.ToInt16(rawBytes, index); 237 | // index += 2; 238 | // 239 | // Name = Encoding.Unicode.GetString(rawBytes, index, size); 240 | } 241 | 242 | 243 | public string Name { get; } 244 | 245 | public string InternalName { get; } 246 | 247 | public override string ToString() 248 | { 249 | return ".ClassInfo: Not decoded"; 250 | } 251 | } 252 | 253 | public class CatHint : IEa 254 | { 255 | public CatHint(byte[] rawBytes, string internalName) 256 | { 257 | InternalName = internalName; 258 | var index = 0; 259 | 260 | Format = BitConverter.ToInt16(rawBytes, index); 261 | index += 2; 262 | 263 | var size = BitConverter.ToInt16(rawBytes, index); 264 | index += 2; 265 | 266 | Hint = Encoding.Unicode.GetString(rawBytes, index, size); 267 | } 268 | 269 | public short Format { get; } 270 | 271 | public string Hint { get; } 272 | 273 | public string InternalName { get; } 274 | 275 | public override string ToString() 276 | { 277 | return $"$CI.CATALOGHINT | Hint: {Hint}"; 278 | } 279 | } 280 | 281 | public class AppFixCache : IEa 282 | { 283 | public AppFixCache(byte[] rawBytes, string internalName) 284 | { 285 | InternalName = internalName; 286 | var tsraw = BitConverter.ToInt64(rawBytes, 0); 287 | if (tsraw < 0) 288 | { 289 | tsraw = 0; 290 | } 291 | 292 | Timestamp = DateTimeOffset.FromFileTime(tsraw).ToUniversalTime(); 293 | 294 | RemainingBytes = new byte[rawBytes.Length - 8]; 295 | Buffer.BlockCopy(rawBytes, 8, RemainingBytes, 0, RemainingBytes.Length); 296 | } 297 | 298 | public DateTimeOffset Timestamp { get; } 299 | public byte[] RemainingBytes { get; } 300 | 301 | public string InternalName { get; } 302 | 303 | public override string ToString() 304 | { 305 | return 306 | $"$KERNEL.PURGE.APPXFICACHE | Timestamp: {Timestamp:yyyy-MM-dd HH:mm:ss.fffffff} Remaining bytes: {BitConverter.ToString(RemainingBytes)}"; 307 | } 308 | } 309 | 310 | public class PurgeEsbCache : IEa 311 | { 312 | public PurgeEsbCache(byte[] rawBytes, string internalName) 313 | { 314 | InternalName = internalName; 315 | var index = 8; 316 | Timestamp = DateTimeOffset.FromFileTime(BitConverter.ToInt64(rawBytes, index)).ToUniversalTime(); 317 | index += 8; 318 | Timestamp2 = DateTimeOffset.FromFileTime(BitConverter.ToInt64(rawBytes, index)).ToUniversalTime(); 319 | index += 8; 320 | } 321 | 322 | 323 | public DateTimeOffset Timestamp { get; } 324 | public DateTimeOffset Timestamp2 { get; } 325 | 326 | public string InternalName { get; } 327 | 328 | public override string ToString() 329 | { 330 | return 331 | $"$KERNEL.PURGE.ESBCACHE | Timestamp: {Timestamp:yyyy-MM-dd HH:mm:ss.fffffff} Timestamp2: {Timestamp2:yyyy-MM-dd HH:mm:ss.fffffff}"; 332 | } 333 | } 334 | 335 | public class Lxattrr : IEa 336 | { 337 | public Lxattrr(byte[] rawBytes, string internalName) 338 | { 339 | InternalName = internalName; 340 | KeyValues = new Dictionary(); 341 | 342 | var index = 0; 343 | 344 | Format = BitConverter.ToInt16(rawBytes, index); 345 | index += 2; 346 | Version = BitConverter.ToInt16(rawBytes, index); 347 | index += 2; 348 | 349 | while (index < rawBytes.Length) 350 | { 351 | var offsetToNextRecord = BitConverter.ToInt32(rawBytes, index); 352 | index += 4; 353 | //index += 3; //unknown 354 | var valueSize = BitConverter.ToInt16(rawBytes, index); 355 | index += 2; 356 | var keySize = rawBytes[index]; 357 | index += 1; 358 | 359 | var keyName = Encoding.Unicode.GetString(rawBytes, index, keySize); 360 | index += keySize; 361 | var valueData = Encoding.Unicode.GetString(rawBytes, index, valueSize); 362 | index += valueSize; 363 | 364 | index += 1; //null terminator 365 | 366 | KeyValues.Add(keyName, valueData); 367 | 368 | if (offsetToNextRecord == 0) 369 | //we are out of data 370 | { 371 | break; 372 | } 373 | } 374 | } 375 | 376 | public Dictionary KeyValues { get; } 377 | 378 | public short Format { get; } 379 | public short Version { get; } 380 | public string InternalName { get; } 381 | 382 | public override string ToString() 383 | { 384 | var sb = new StringBuilder(); 385 | 386 | sb.AppendLine("LXXATTR"); 387 | 388 | foreach (var keyValue in KeyValues) 389 | { 390 | sb.AppendLine($"Key: {keyValue.Key} --> {keyValue.Value}"); 391 | } 392 | 393 | return sb.ToString(); 394 | } 395 | } 396 | 397 | public class Lxattrb : IEa 398 | { 399 | public Lxattrb(byte[] rawBytes, string internalName) 400 | { 401 | InternalName = internalName; 402 | 403 | var index = 0; 404 | Format = BitConverter.ToInt16(rawBytes, index); 405 | index += 2; 406 | Version = BitConverter.ToInt16(rawBytes, index); 407 | index += 2; 408 | Mode = BitConverter.ToInt32(rawBytes, index); 409 | index += 4; 410 | Uid = BitConverter.ToInt32(rawBytes, index); 411 | index += 4; 412 | Gid = BitConverter.ToInt32(rawBytes, index); 413 | index += 4; 414 | DeviceId = BitConverter.ToInt32(rawBytes, index); 415 | index += 4; 416 | LastAccessNanoSeconds = BitConverter.ToInt32(rawBytes, index); 417 | index += 4; 418 | ModifiedNanoSeconds = BitConverter.ToInt32(rawBytes, index); 419 | index += 4; 420 | InodeChangedNanoSeconds = BitConverter.ToInt32(rawBytes, index); 421 | index += 4; 422 | 423 | LastAccessTime = DateTimeOffset.FromUnixTimeSeconds(BitConverter.ToInt64(rawBytes, index)).ToUniversalTime(); 424 | index += 8; 425 | ModifiedTime = DateTimeOffset.FromUnixTimeSeconds(BitConverter.ToInt64(rawBytes, index)).ToUniversalTime(); 426 | index += 8; 427 | InodeChanged = DateTimeOffset.FromUnixTimeSeconds(BitConverter.ToInt64(rawBytes, index)).ToUniversalTime(); 428 | index += 8; 429 | } 430 | 431 | public short Format { get; } 432 | public short Version { get; } 433 | public int Mode { get; } 434 | public int Uid { get; } 435 | public int Gid { get; } 436 | public int DeviceId { get; } 437 | 438 | public int LastAccessNanoSeconds { get; } 439 | public int ModifiedNanoSeconds { get; } 440 | public int InodeChangedNanoSeconds { get; } 441 | 442 | public DateTimeOffset LastAccessTime { get; } 443 | public DateTimeOffset ModifiedTime { get; } 444 | public DateTimeOffset InodeChanged { get; } 445 | 446 | public string InternalName { get; } 447 | 448 | public override string ToString() 449 | { 450 | var sb = new StringBuilder(); 451 | 452 | sb.AppendLine("LXATTRB"); 453 | 454 | sb.AppendLine($"Format: 0x{Format:X}"); 455 | sb.AppendLine($"Version: 0x{Version:X}"); 456 | sb.AppendLine($"Mode: 0x{Mode:X}"); 457 | sb.AppendLine($"Uid/Gid: 0x{Uid:X}/0x{Gid:X}"); 458 | sb.AppendLine($"Device Id: 0x{DeviceId:X}"); 459 | 460 | //convert to seconds so we can use it later. 461 | //.net has no API for adding nanoseconds that works, so this is what we get 462 | var lastAccessSubSec = (LastAccessNanoSeconds / 1e+9).ToString(CultureInfo.InvariantCulture); 463 | var modifiedSubsec = (ModifiedNanoSeconds / 1e+9).ToString(CultureInfo.InvariantCulture); 464 | var inodeChangeSubsec = (InodeChangedNanoSeconds / 1e+9).ToString(CultureInfo.InvariantCulture); 465 | 466 | sb.AppendLine( 467 | $"Last Access Time: {LastAccessTime.ToUniversalTime():yyyy-MM-dd HH:mm:ss}.{(lastAccessSubSec.Length > 2 ? lastAccessSubSec.Substring(2) : "0000000")}"); 468 | sb.AppendLine( 469 | $"Modified Time: {ModifiedTime.ToUniversalTime():yyyy-MM-dd HH:mm:ss}.{modifiedSubsec.Substring(2)}"); 470 | sb.AppendLine( 471 | $"Inode Changed: {InodeChanged.ToUniversalTime():yyyy-MM-dd HH:mm:ss}.{inodeChangeSubsec.Substring(2)}"); 472 | 473 | return sb.ToString(); 474 | } 475 | } -------------------------------------------------------------------------------- /MFT/Attributes/ExtendedAttributeInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MFT.Attributes; 5 | 6 | public class ExtendedAttributeInformation : Attribute 7 | { 8 | public ExtendedAttributeInformation(byte[] rawBytes) : base(rawBytes) 9 | { 10 | var content = new byte[AttributeContentLength]; 11 | 12 | Buffer.BlockCopy(rawBytes, ContentOffset, content, 0, AttributeContentLength); 13 | 14 | EaSize = BitConverter.ToInt16(content, 0); 15 | NumberOfExtendedAttrWithNeedEaSet = BitConverter.ToInt16(content, 2); 16 | SizeOfEaData = BitConverter.ToInt32(content, 4); 17 | } 18 | 19 | public short EaSize { get; } 20 | public short NumberOfExtendedAttrWithNeedEaSet { get; } 21 | public int SizeOfEaData { get; } 22 | 23 | public override string ToString() 24 | { 25 | var sb = new StringBuilder(); 26 | 27 | sb.AppendLine("**** EXTENDED ATTRIBUTE INFORMATION ****"); 28 | 29 | sb.AppendLine(base.ToString()); 30 | 31 | sb.AppendLine(); 32 | sb.AppendLine("Extended Attribute Information"); 33 | 34 | sb.AppendLine( 35 | $"Ea Size: 0x{EaSize:X}, Number Of Extended Attributes With Need Ea Set: 0x{NumberOfExtendedAttrWithNeedEaSet:X} Size Of Ea Data: 0x{SizeOfEaData:X} "); 36 | 37 | return sb.ToString(); 38 | } 39 | } -------------------------------------------------------------------------------- /MFT/Attributes/FileInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using MFT.Other; 4 | using Serilog; 5 | 6 | namespace MFT.Attributes; 7 | 8 | [Flags] 9 | public enum NameTypes 10 | { 11 | Posix = 0x0, 12 | Windows = 0x1, 13 | Dos = 0x2, 14 | DosWindows = 0x3 15 | } 16 | 17 | public class FileInfo 18 | { 19 | public FileInfo(byte[] rawBytes) 20 | { 21 | var entryBytes = new byte[8]; 22 | 23 | Buffer.BlockCopy(rawBytes, 0, entryBytes, 0, 8); 24 | 25 | ParentMftRecord = new MftEntryInfo(entryBytes); 26 | 27 | var createdRaw = BitConverter.ToInt64(rawBytes, 0x8); 28 | if (createdRaw > 0) 29 | { 30 | try 31 | { 32 | CreatedOn = DateTimeOffset.FromFileTime(createdRaw).ToUniversalTime(); 33 | } 34 | catch (Exception) 35 | { 36 | Log.Warning("Invalid CreatedOn timestamp! Enable --debug for record information"); 37 | } 38 | } 39 | 40 | var contentModRaw = BitConverter.ToInt64(rawBytes, 0x10); 41 | if (contentModRaw > 0) 42 | { 43 | try 44 | { 45 | ContentModifiedOn = DateTimeOffset.FromFileTime(contentModRaw).ToUniversalTime(); 46 | } 47 | catch (Exception) 48 | { 49 | Log.Warning("Invalid ContentModifiedOn timestamp! Enable --debug for record information"); 50 | } 51 | } 52 | 53 | var recordModRaw = BitConverter.ToInt64(rawBytes, 0x18); 54 | if (recordModRaw > 0) 55 | { 56 | try 57 | { 58 | RecordModifiedOn = DateTimeOffset.FromFileTime(recordModRaw).ToUniversalTime(); 59 | } 60 | catch (Exception) 61 | { 62 | Log.Warning("Invalid RecordModifiedOn timestamp! Enable --debug for record information"); 63 | } 64 | } 65 | 66 | var lastAccessRaw = BitConverter.ToInt64(rawBytes, 0x20); 67 | if (lastAccessRaw > 0) 68 | { 69 | try 70 | { 71 | LastAccessedOn = DateTimeOffset.FromFileTime(lastAccessRaw).ToUniversalTime(); 72 | } 73 | catch (Exception) 74 | { 75 | Log.Warning("Invalid LastAccessedOn timestamp! Enable --debug for record information"); 76 | } 77 | } 78 | 79 | PhysicalSize = BitConverter.ToUInt64(rawBytes, 0x28); 80 | LogicalSize = BitConverter.ToUInt64(rawBytes, 0x30); 81 | 82 | Flags = (StandardInfo.Flag)BitConverter.ToInt32(rawBytes, 0x38); 83 | 84 | ReparseValue = BitConverter.ToInt32(rawBytes, 0x3c); 85 | 86 | NameLength = rawBytes[0x40]; 87 | NameType = (NameTypes)rawBytes[0x41]; 88 | 89 | FileName = Encoding.Unicode.GetString(rawBytes, 0x42, NameLength * 2); 90 | } 91 | 92 | public int ReparseValue { get; } 93 | public byte NameLength { get; } 94 | public NameTypes NameType { get; } 95 | public string FileName { get; } 96 | public ulong PhysicalSize { get; } 97 | public ulong LogicalSize { get; } 98 | public DateTimeOffset? CreatedOn { get; } 99 | public DateTimeOffset? ContentModifiedOn { get; } 100 | public DateTimeOffset? RecordModifiedOn { get; } 101 | public DateTimeOffset? LastAccessedOn { get; } 102 | public StandardInfo.Flag Flags { get; } 103 | public MftEntryInfo ParentMftRecord { get; } 104 | 105 | public override string ToString() 106 | { 107 | var sb = new StringBuilder(); 108 | 109 | sb.AppendLine(); 110 | 111 | sb.AppendLine( 112 | $"File name: {FileName} (Length: 0x{NameLength:X})\r\nFlags: {Flags.ToString().Replace(", ", "|")}, Name Type: {NameType}, " + 113 | $"Reparse Value: 0x{ReparseValue:X}, Physical Size: 0x{PhysicalSize:X}, Logical Size: 0x{LogicalSize:X}" + 114 | $"\r\nParent Mft Record: {ParentMftRecord}" + 115 | $"\r\n\r\nCreated On:\t\t{CreatedOn?.ToString(MftFile.DateTimeFormat)}" + 116 | $"\r\nContent Modified On:\t{ContentModifiedOn?.ToString(MftFile.DateTimeFormat)}" + 117 | $"\r\nRecord Modified On:\t{RecordModifiedOn?.ToString(MftFile.DateTimeFormat)}" + 118 | $"\r\nLast Accessed On:\t{LastAccessedOn?.ToString(MftFile.DateTimeFormat)}"); 119 | 120 | return sb.ToString(); 121 | } 122 | } -------------------------------------------------------------------------------- /MFT/Attributes/FileName.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MFT.Attributes; 5 | 6 | public class FileName : Attribute 7 | { 8 | public FileName(byte[] rawBytes) : base(rawBytes) 9 | { 10 | var content = new byte[rawBytes.Length - ContentOffset]; 11 | 12 | Buffer.BlockCopy(rawBytes, ContentOffset, content, 0, rawBytes.Length - ContentOffset); 13 | 14 | FileInfo = new FileInfo(content); 15 | } 16 | 17 | public FileInfo FileInfo { get; } 18 | 19 | 20 | public override string ToString() 21 | { 22 | var sb = new StringBuilder(); 23 | 24 | sb.AppendLine("**** FILE NAME ****"); 25 | 26 | sb.AppendLine(base.ToString()); 27 | 28 | //sb.AppendLine(); 29 | 30 | sb.AppendLine(FileInfo.ToString()); 31 | 32 | return sb.ToString(); 33 | } 34 | } -------------------------------------------------------------------------------- /MFT/Attributes/IndexAllocation.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace MFT.Attributes; 4 | 5 | public class IndexAllocation : Attribute 6 | { 7 | public IndexAllocation(byte[] rawBytes) : base(rawBytes) 8 | { 9 | NonResidentData = new NonResidentData(rawBytes); 10 | } 11 | 12 | public NonResidentData NonResidentData { get; } 13 | 14 | public override string ToString() 15 | { 16 | var sb = new StringBuilder(); 17 | 18 | sb.AppendLine("**** INDEX ALLOCATION ****"); 19 | 20 | sb.AppendLine(base.ToString()); 21 | 22 | sb.AppendLine(); 23 | 24 | sb.AppendLine( 25 | $"Non Resident Data: {NonResidentData}"); 26 | 27 | return sb.ToString(); 28 | } 29 | } -------------------------------------------------------------------------------- /MFT/Attributes/IndexNodeHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFT.Attributes; 4 | 5 | public class IndexNodeHeader 6 | { 7 | public enum IndexNodeFlag 8 | { 9 | HasIndexAllocation = 0x01 10 | } 11 | 12 | public IndexNodeHeader(byte[] rawBytes) 13 | { 14 | var index = 0; 15 | 16 | IndexValuesOffset = BitConverter.ToInt32(rawBytes, index); 17 | index += 4; 18 | IndexNodeSize = BitConverter.ToInt32(rawBytes, index); 19 | index += 4; 20 | AllocatedIndexNodeSize = BitConverter.ToInt32(rawBytes, index); 21 | index += 4; 22 | IndexNodeFlags = (IndexNodeFlag)BitConverter.ToInt32(rawBytes, index); 23 | } 24 | 25 | public int IndexValuesOffset { get; } 26 | public int IndexNodeSize { get; } 27 | public int AllocatedIndexNodeSize { get; } 28 | public IndexNodeFlag IndexNodeFlags { get; } 29 | 30 | 31 | public override string ToString() 32 | { 33 | return 34 | $"Index Values Offset: 0x{IndexValuesOffset:X} Index Node Size: 0x{IndexNodeSize:X} Allocated Index Node Size: 0x{AllocatedIndexNodeSize:X} Index Node Flags: {IndexNodeFlags.ToString().Replace(", ", "|")}"; 35 | } 36 | } -------------------------------------------------------------------------------- /MFT/Attributes/IndexRoot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using MFT.Other; 5 | 6 | namespace MFT.Attributes; 7 | 8 | public class IndexRoot : Attribute 9 | { 10 | public enum CollationTypes 11 | { 12 | Binary = 0x000000, 13 | Filename = 0x000001, 14 | Unicode = 0x000002, 15 | NtOfsUlong = 0x000010, 16 | NtOfsSid = 0x000011, 17 | NtOfsSecurityHash = 0x000012, 18 | NtOfsUlongs = 0x000013 19 | } 20 | 21 | [Flags] 22 | public enum IndexFlag 23 | { 24 | HasSubNode = 0x001, 25 | IsLast = 0x002 26 | } 27 | 28 | public IndexRoot(byte[] rawBytes) : base(rawBytes) 29 | { 30 | var index = (int)ContentOffset; 31 | 32 | IndexedAttributeType = (AttributeType)BitConverter.ToInt32(rawBytes, index); 33 | index += 4; 34 | 35 | CollationType = (CollationTypes)BitConverter.ToInt32(rawBytes, index); 36 | index += 4; 37 | 38 | EntrySize = BitConverter.ToInt32(rawBytes, index); 39 | index += 4; 40 | 41 | NumberClusterBlocks = BitConverter.ToInt32(rawBytes, index); 42 | index += 4; 43 | 44 | OffsetToFirstIndexEntry = BitConverter.ToInt32(rawBytes, index); 45 | index += 4; 46 | TotalSizeOfIndexEntries = BitConverter.ToInt32(rawBytes, index); 47 | index += 4; 48 | AllocatedSizeOfEntries = BitConverter.ToInt32(rawBytes, index); 49 | index += 4; 50 | 51 | Flags = (IndexFlag)rawBytes[index]; 52 | index += 1; 53 | 54 | 55 | index += 3; //padding 56 | 57 | //TODO verify this 58 | var mftInfoBytes = new byte[8]; 59 | Buffer.BlockCopy(rawBytes, index, mftInfoBytes, 0, 8); 60 | index += 8; 61 | 62 | MftRecord = new MftEntryInfo(mftInfoBytes); 63 | //end verify 64 | 65 | IndexEntries = new List(); 66 | 67 | while (index < rawBytes.Length) 68 | { 69 | var indexValSize = BitConverter.ToInt16(rawBytes, index); 70 | 71 | if (indexValSize == 0x10) 72 | //indicates no more index entries 73 | { 74 | break; 75 | } 76 | 77 | if (indexValSize > rawBytes.Length - index) 78 | { 79 | indexValSize = (short)(rawBytes.Length - index); 80 | } 81 | 82 | var buff = new byte[indexValSize]; 83 | Buffer.BlockCopy(rawBytes, index, buff, 0, indexValSize); 84 | 85 | var ie = new IndexEntry(buff); 86 | 87 | IndexEntries.Add(ie); 88 | 89 | index += indexValSize; 90 | } 91 | } 92 | 93 | public IndexFlag Flags { get; } 94 | public int TotalSizeOfIndexEntries { get; } 95 | public int AllocatedSizeOfEntries { get; } 96 | public int OffsetToFirstIndexEntry { get; } 97 | public List IndexEntries { get; } 98 | public MftEntryInfo MftRecord { get; } 99 | public AttributeType IndexedAttributeType { get; } 100 | public int EntrySize { get; } 101 | public int NumberClusterBlocks { get; } 102 | 103 | 104 | public CollationTypes CollationType { get; } 105 | 106 | 107 | public override string ToString() 108 | { 109 | var sb = new StringBuilder(); 110 | 111 | sb.AppendLine("**** INDEX ROOT ****"); 112 | 113 | sb.AppendLine(base.ToString()); 114 | 115 | sb.AppendLine(); 116 | 117 | sb.AppendLine( 118 | $"Indexed Attribute Type: {IndexedAttributeType} Entry Size: 0x{EntrySize:X} Number Cluster Blocks: 0x{NumberClusterBlocks:X} Collation Type: {CollationType} Index entries count: 0x{IndexEntries.Count:X} Mft Record: {MftRecord}"); 119 | 120 | sb.AppendLine(); 121 | sb.AppendLine("FileInfo Records Entries"); 122 | 123 | foreach (var ie in IndexEntries) 124 | { 125 | sb.AppendLine(ie.ToString()); 126 | } 127 | 128 | return sb.ToString(); 129 | } 130 | } -------------------------------------------------------------------------------- /MFT/Attributes/LoggedUtilityStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MFT.Attributes; 5 | 6 | public class LoggedUtilityStream : Attribute 7 | { 8 | public LoggedUtilityStream(byte[] rawBytes) : base(rawBytes) 9 | { 10 | var content = new byte[AttributeContentLength]; 11 | 12 | Buffer.BlockCopy(rawBytes, ContentOffset, content, 0, AttributeContentLength); 13 | 14 | //TODO decode content based on Name? $EFS, etc. 15 | ResidentData = new ResidentData(content); 16 | } 17 | 18 | public ResidentData ResidentData { get; } 19 | 20 | public override string ToString() 21 | { 22 | var sb = new StringBuilder(); 23 | 24 | sb.AppendLine("**** LOGGED UTILITY STREAM ****"); 25 | 26 | sb.AppendLine(base.ToString()); 27 | 28 | sb.AppendLine(); 29 | 30 | sb.AppendLine( 31 | $"Resident Data: {ResidentData}"); 32 | 33 | return sb.ToString(); 34 | } 35 | } -------------------------------------------------------------------------------- /MFT/Attributes/NonResidentData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using MFT.Other; 5 | 6 | namespace MFT.Attributes; 7 | 8 | public class NonResidentData 9 | { 10 | public NonResidentData(byte[] rawBytes) 11 | { 12 | StartingVirtualClusterNumber = BitConverter.ToUInt64(rawBytes, 0x10); 13 | EndingVirtualClusterNumber = BitConverter.ToUInt64(rawBytes, 0x18); 14 | 15 | var offsetToDataRuns = BitConverter.ToUInt16(rawBytes, 0x20); 16 | 17 | AllocatedSize = BitConverter.ToUInt64(rawBytes, 0x28); 18 | ActualSize = BitConverter.ToUInt64(rawBytes, 0x30); 19 | InitializedSize = BitConverter.ToUInt64(rawBytes, 0x38); 20 | 21 | var index = (int)offsetToDataRuns; //set index into bytes to start reading offsets 22 | 23 | DataRuns = new List(); 24 | 25 | var drStart = rawBytes[index]; 26 | 27 | while (drStart != 0) 28 | { 29 | var offsetLength = (byte)((drStart & 0xF0) >> 4); //left nibble 30 | var clusterLenByteCount = (byte)(drStart & 0x0F); //right nibble 31 | index += 1; 32 | 33 | var runLenBytes = new byte[8]; //length should never exceed 8, so start with 8 34 | Buffer.BlockCopy(rawBytes, index, runLenBytes, 0, clusterLenByteCount); 35 | 36 | index += clusterLenByteCount; 37 | 38 | var clusterRunLength = BitConverter.ToUInt64(runLenBytes, 0); 39 | 40 | var clusterBytes = new byte[8]; //length should never exceed 8, so start with 8 41 | 42 | //copy in what we have 43 | Buffer.BlockCopy(rawBytes, index, clusterBytes, 0, offsetLength); 44 | 45 | //negative offsets 46 | if (offsetLength > 0) 47 | { 48 | if (clusterBytes[offsetLength - 1] >= 0x80) 49 | { 50 | for (int i = offsetLength; i < clusterBytes.Length; i++) 51 | { 52 | clusterBytes[i] = 0xFF; 53 | } 54 | } 55 | } 56 | 57 | //we can safely get our cluster # 58 | var clusterNumber = BitConverter.ToInt64(clusterBytes, 0); 59 | 60 | index += offsetLength; 61 | 62 | var dr = new DataRun(clusterRunLength, clusterNumber); 63 | DataRuns.Add(dr); 64 | 65 | drStart = rawBytes[index]; 66 | } 67 | } 68 | 69 | public ulong StartingVirtualClusterNumber { get; } 70 | public ulong EndingVirtualClusterNumber { get; } 71 | public ulong AllocatedSize { get; } 72 | public ulong ActualSize { get; } 73 | public ulong InitializedSize { get; } 74 | public List DataRuns { get; } 75 | 76 | public override string ToString() 77 | { 78 | var sb = new StringBuilder(); 79 | 80 | sb.AppendLine(); 81 | sb.AppendLine( 82 | $"Starting Virtual Cluster #: 0x{StartingVirtualClusterNumber:X}, Ending Virtual Cluster #: 0x{EndingVirtualClusterNumber:X}, Allocated Size: 0x{AllocatedSize:X}, Actual Size: 0x{ActualSize:X}, Initialized Size: 0x{InitializedSize:X} "); 83 | 84 | sb.AppendLine(); 85 | sb.AppendLine("DataRuns Entries"); 86 | 87 | foreach (var dataRun in DataRuns) 88 | { 89 | sb.AppendLine(dataRun.ToString()); 90 | } 91 | 92 | return sb.ToString(); 93 | } 94 | } -------------------------------------------------------------------------------- /MFT/Attributes/ObjectId_.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace MFT.Attributes; 7 | 8 | public class ObjectId_ : Attribute 9 | { 10 | public ObjectId_(byte[] rawBytes) : base(rawBytes) 11 | { 12 | var content = new byte[AttributeContentLength]; 13 | 14 | Buffer.BlockCopy(rawBytes, ContentOffset, content, 0, AttributeContentLength); 15 | 16 | var residentData = new ResidentData(content); 17 | 18 | var guidRaw0 = new byte[16]; 19 | var guidRaw1 = new byte[16]; 20 | var guidRaw2 = new byte[16]; 21 | var guidRaw3 = new byte[16]; 22 | 23 | Buffer.BlockCopy(residentData.Data, 0x00, guidRaw0, 0, 16); 24 | ObjectId = new Guid(guidRaw0); 25 | 26 | if (residentData.Data.Length == 16) 27 | { 28 | return; 29 | } 30 | 31 | Buffer.BlockCopy(residentData.Data, 0x0A, guidRaw1, 0, 16); 32 | Buffer.BlockCopy(residentData.Data, 0x20, guidRaw2, 0, 16); 33 | Buffer.BlockCopy(residentData.Data, 0x30, guidRaw3, 0, 16); 34 | 35 | BirthVolumeId = new Guid(guidRaw1); 36 | BirthObjectId = new Guid(guidRaw2); 37 | DomainId = new Guid(guidRaw3); 38 | } 39 | 40 | public Guid BirthObjectId { get; } 41 | 42 | public Guid BirthVolumeId { get; } 43 | 44 | public Guid ObjectId { get; } 45 | 46 | public Guid DomainId { get; } 47 | 48 | private DateTimeOffset GetDateTimeOffsetFromGuid(Guid guid) 49 | { 50 | // offset to move from 1/1/0001, which is 0-time for .NET, to gregorian 0-time of 10/15/1582 51 | var gregorianCalendarStart = new DateTimeOffset(1582, 10, 15, 0, 0, 0, TimeSpan.Zero); 52 | const int versionByte = 7; 53 | const int versionByteMask = 0x0f; 54 | const int versionByteShift = 4; 55 | const byte timestampByte = 0; 56 | 57 | var bytes = guid.ToByteArray(); 58 | 59 | // reverse the version 60 | bytes[versionByte] &= versionByteMask; 61 | bytes[versionByte] |= 0x01 >> versionByteShift; 62 | 63 | var timestampBytes = new byte[8]; 64 | Array.Copy(bytes, timestampByte, timestampBytes, 0, 8); 65 | 66 | var timestamp = BitConverter.ToInt64(timestampBytes, 0); 67 | var ticks = timestamp + gregorianCalendarStart.Ticks; 68 | 69 | return new DateTimeOffset(ticks, TimeSpan.Zero); 70 | } 71 | 72 | 73 | // public Guid ObjectId { get; } 74 | // public Guid BirthVolumeId { get; } 75 | 76 | // public Guid BirthObjectId { get; } 77 | // public Guid DomainId { get; } 78 | // ObjectId = new Guid(br.ReadBytes(16)); 79 | // BirthVolumeId = new Guid(br.ReadBytes(16)); 80 | // BirthObjectId = new Guid(br.ReadBytes(16)); 81 | // DomainId = new Guid(br.ReadBytes(16)); 82 | 83 | public override string ToString() 84 | { 85 | var sb = new StringBuilder(); 86 | 87 | sb.AppendLine("**** OBJECT ID ****"); 88 | 89 | sb.AppendLine(base.ToString()); 90 | 91 | sb.AppendLine(); 92 | 93 | 94 | var tempMac = ObjectId.ToString().Split('-').Last(); 95 | var objectIdMacAddress = Regex.Replace(tempMac, ".{2}", "$0:"); 96 | var objectIdCreatedOn = GetDateTimeOffsetFromGuid(ObjectId); 97 | 98 | 99 | tempMac = BirthObjectId.ToString().Split('-').Last(); 100 | var birthVolumeIdMacAddress = Regex.Replace(tempMac, ".{2}", "$0:"); 101 | var birthVolumeIdCreatedOn = GetDateTimeOffsetFromGuid(BirthObjectId); 102 | 103 | var extra = 104 | $"\tBirth Volume Id MAC: {birthVolumeIdMacAddress}\r\n\tBirth Volume Id Created On: {birthVolumeIdCreatedOn.ToUniversalTime():yyyy-MM-dd HH:mm:ss.fffffff}\r\n"; 105 | if (BirthObjectId.ToString() == "00000000-0000-0000-0000-000000000000") 106 | { 107 | extra = string.Empty; 108 | } 109 | 110 | sb.AppendLine( 111 | $"Object Id: {ObjectId}\r\n\tObject Id MAC: {objectIdMacAddress}\r\n\tObject Id Created On: {objectIdCreatedOn.ToUniversalTime():yyyy-MM-dd HH:mm:ss.fffffff}\r\n" + 112 | $"Birth Volume Id: {BirthVolumeId}\r\n" + 113 | extra + 114 | $"Birth Object Id: {BirthObjectId}\r\n" + 115 | $"Domain Id: {DomainId}"); 116 | 117 | return sb.ToString(); 118 | } 119 | } -------------------------------------------------------------------------------- /MFT/Attributes/ReparsePoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Serilog; 4 | 5 | namespace MFT.Attributes; 6 | 7 | public class ReparsePoint : Attribute 8 | { 9 | public enum ReparsePointTag 10 | { 11 | ReservedZero, 12 | ReservedOne, 13 | ReservedTwo, 14 | DriverExtender, 15 | HierarchicalStorageManager2, 16 | SisFilterDriver, 17 | DistributedFileSystem, 18 | FilterManagerTestHarness, 19 | DistributedFileSystemR, 20 | MountPoint, 21 | SymbolicLink, 22 | Wim, 23 | Csv, 24 | HierarchicalStorageManager, 25 | DeDupe, 26 | Nfs, 27 | FilePlaceHolder, 28 | Wof, 29 | Wci, 30 | GlobalReparse, 31 | AppExeCLink, 32 | Hfs, 33 | Unhandled, 34 | OneDrive, 35 | Cloud, 36 | CloudRoot, 37 | CloudOnDemand, 38 | CloudRootOnDemand, 39 | Gvfs, 40 | IisCache, 41 | LxSymLink, 42 | WciTombstone, 43 | GvfsTombstone, 44 | AppXStrim 45 | } 46 | 47 | public ReparsePoint(byte[] rawBytes) : base(rawBytes) 48 | { 49 | if (AttributeContentLength == 0 || AttributeContentLength == 8) 50 | { 51 | SubstituteName = string.Empty; 52 | PrintName = string.Empty; 53 | 54 | return; 55 | } 56 | 57 | var content = new byte[AttributeContentLength]; 58 | 59 | Buffer.BlockCopy(rawBytes, ContentOffset, content, 0, AttributeContentLength); 60 | 61 | var tag = BitConverter.ToUInt32(content, 0); 62 | 63 | switch (tag) 64 | { 65 | case 0x00000000: 66 | Tag = ReparsePointTag.ReservedZero; 67 | break; 68 | case 0x00000001: 69 | Tag = ReparsePointTag.ReservedOne; 70 | break; 71 | case 0x00000002: 72 | Tag = ReparsePointTag.ReservedTwo; 73 | break; 74 | case 0x80000005: 75 | Tag = ReparsePointTag.DriverExtender; 76 | break; 77 | case 0x80000006: 78 | Tag = ReparsePointTag.HierarchicalStorageManager2; 79 | break; 80 | case 0x80000007: 81 | Tag = ReparsePointTag.SisFilterDriver; 82 | break; 83 | case 0x80000008: 84 | Tag = ReparsePointTag.Wim; 85 | break; 86 | case 0x80000009: 87 | Tag = ReparsePointTag.Csv; 88 | break; 89 | case 0x8000000a: 90 | Tag = ReparsePointTag.DistributedFileSystem; 91 | break; 92 | case 0x8000000b: 93 | Tag = ReparsePointTag.FilterManagerTestHarness; 94 | break; 95 | case 0x80000012: 96 | Tag = ReparsePointTag.DistributedFileSystemR; 97 | break; 98 | case 0xa0000003: 99 | Tag = ReparsePointTag.MountPoint; 100 | break; 101 | case 0xa000000c: 102 | Tag = ReparsePointTag.SymbolicLink; 103 | break; 104 | case 0xc0000004: 105 | Tag = ReparsePointTag.HierarchicalStorageManager; 106 | break; 107 | case 0x80000013: 108 | Tag = ReparsePointTag.DeDupe; 109 | break; 110 | case 0x80000014: 111 | Tag = ReparsePointTag.Nfs; 112 | break; 113 | case 0x80000015: 114 | Tag = ReparsePointTag.FilePlaceHolder; 115 | break; 116 | 117 | case 0x80000017: 118 | Tag = ReparsePointTag.Wof; 119 | break; 120 | case 0x80000018: 121 | Tag = ReparsePointTag.Wci; 122 | break; 123 | case 0x80000019: 124 | Tag = ReparsePointTag.GlobalReparse; 125 | break; 126 | case 0x8000001B: 127 | Tag = ReparsePointTag.AppExeCLink; 128 | break; 129 | case 0x8000001E: 130 | Tag = ReparsePointTag.Hfs; 131 | break; 132 | case 0x80000020: 133 | Tag = ReparsePointTag.Unhandled; 134 | break; 135 | case 0x80000021: 136 | Tag = ReparsePointTag.OneDrive; 137 | break; 138 | case 0x9000001A: 139 | Tag = ReparsePointTag.Cloud; 140 | break; 141 | case 0x9000101A: 142 | Tag = ReparsePointTag.CloudRoot; 143 | break; 144 | case 0x9000201A: 145 | Tag = ReparsePointTag.CloudOnDemand; 146 | break; 147 | 148 | case 0x9000301A: 149 | Tag = ReparsePointTag.CloudRootOnDemand; 150 | break; 151 | case 0x9000001C: 152 | Tag = ReparsePointTag.Gvfs; 153 | break; 154 | case 0xA0000010: 155 | Tag = ReparsePointTag.IisCache; 156 | break; 157 | case 0xA0000019: 158 | Tag = ReparsePointTag.GlobalReparse; 159 | break; 160 | case 0xA000001D: 161 | Tag = ReparsePointTag.LxSymLink; 162 | break; 163 | case 0xA000001F: 164 | Tag = ReparsePointTag.WciTombstone; 165 | break; 166 | 167 | case 0xA0000022: 168 | Tag = ReparsePointTag.GvfsTombstone; 169 | break; 170 | 171 | case 0xC0000014: 172 | Tag = ReparsePointTag.AppXStrim; 173 | break; 174 | } 175 | 176 | SubstituteName = string.Empty; 177 | PrintName = string.Empty; 178 | 179 | var subNameOffset = BitConverter.ToInt16(content, 8); 180 | var subNameSize = BitConverter.ToInt16(content, 10); 181 | 182 | if (subNameSize == 0) 183 | { 184 | Log.Debug("SubstituteName length is 0! Determining PrintName"); 185 | 186 | PrintName = Encoding.ASCII.GetString(content, 0xc, content.Length - 0xc).Trim('\0'); 187 | 188 | return; 189 | } 190 | 191 | var printNameOffset = BitConverter.ToInt16(content, 12); 192 | var printNameSize = BitConverter.ToInt16(content, 14); 193 | 194 | if (Tag != ReparsePointTag.SymbolicLink && Tag != ReparsePointTag.MountPoint) 195 | { 196 | return; 197 | } 198 | 199 | if (Tag == ReparsePointTag.SymbolicLink) 200 | { 201 | var symFlags = BitConverter.ToInt32(content, 16); 202 | 203 | if (symFlags == 0x1) 204 | { 205 | SymlinkFlagRelative = true; 206 | } 207 | 208 | subNameOffset += 4; 209 | } 210 | 211 | if (subNameSize > 0) 212 | { 213 | if (subNameOffset == 0) 214 | { 215 | subNameOffset = 0x10; 216 | } 217 | else 218 | { 219 | subNameOffset = 0x14; 220 | } 221 | 222 | SubstituteName = Encoding.Unicode.GetString(content, subNameOffset, subNameSize); 223 | } 224 | 225 | if (printNameSize > 0) 226 | { 227 | if (printNameOffset == 0) 228 | { 229 | printNameOffset = (short)(subNameOffset + subNameSize); 230 | } 231 | else 232 | { 233 | printNameOffset = (short)(subNameOffset + printNameOffset); 234 | } 235 | 236 | 237 | PrintName = Encoding.Unicode.GetString(content, printNameOffset, printNameSize); 238 | } 239 | } 240 | 241 | public string SubstituteName { get; } 242 | public string PrintName { get; } 243 | public ReparsePointTag Tag { get; } 244 | 245 | public bool SymlinkFlagRelative { get; } 246 | 247 | public override string ToString() 248 | { 249 | var sb = new StringBuilder(); 250 | 251 | sb.AppendLine("**** REPARSE POINT ****"); 252 | 253 | sb.AppendLine(base.ToString()); 254 | 255 | sb.AppendLine(); 256 | 257 | sb.AppendLine($"Substitute Name: {SubstituteName} Print Name: {PrintName} Tag: {Tag}"); 258 | 259 | return sb.ToString(); 260 | } 261 | } -------------------------------------------------------------------------------- /MFT/Attributes/ResidentData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MFT.Attributes; 5 | 6 | public class ResidentData 7 | { 8 | public ResidentData(byte[] rawBytes) 9 | { 10 | Data = rawBytes; 11 | } 12 | 13 | public byte[] Data { get; } 14 | 15 | public override string ToString() 16 | { 17 | var asAscii = Encoding.ASCII.GetString(Data); 18 | var asUnicode = Encoding.Unicode.GetString(Data); 19 | return $"Data: {BitConverter.ToString(Data)}\r\n\r\nASCII: {asAscii}\r\nUnicode: {asUnicode}"; 20 | } 21 | } -------------------------------------------------------------------------------- /MFT/Attributes/SKSecurityDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | // namespaces... 5 | 6 | namespace MFT.Attributes; 7 | 8 | // public classes... 9 | public class SkSecurityDescriptor 10 | { 11 | // public enums... 12 | [Flags] 13 | public enum ControlEnum 14 | { 15 | SeDaclAutoInherited = 0x0400, 16 | SeDaclAutoInheritReq = 0x0100, 17 | SeDaclDefaulted = 0x0008, 18 | SeDaclPresent = 0x0004, 19 | SeDaclProtected = 0x1000, 20 | SeGroupDefaulted = 0x0002, 21 | SeOwnerDefaulted = 0x0001, 22 | SeServerSecurity = 0x0080, 23 | SeDaclUntrusted = 0x0040, 24 | SeRmControlValid = 0x4000, 25 | SeSaclAutoInherited = 0x0800, 26 | SeSaclAutoInheritReq = 0x0200, 27 | SeSaclDefaulted = 0x0020, 28 | SeSaclPresent = 0x0010, 29 | SeSaclProtected = 0x2000, 30 | SeSelfRelative = 0x8000 31 | } 32 | 33 | private readonly uint _sizeDacl; 34 | private readonly uint _sizeGroupSid; 35 | private readonly uint _sizeOwnerSid; 36 | 37 | private readonly uint _sizeSacl; 38 | 39 | // public constructors... 40 | /// 41 | /// Initializes a new instance of the class. 42 | /// 43 | public SkSecurityDescriptor(byte[] rawBytes) 44 | { 45 | RawBytes = rawBytes; 46 | 47 | _sizeSacl = DaclOffset - SaclOffset; 48 | _sizeDacl = OwnerOffset - DaclOffset; 49 | _sizeOwnerSid = GroupOffset - OwnerOffset; 50 | _sizeGroupSid = (uint)(rawBytes.Length - GroupOffset); 51 | 52 | 53 | Padding = string.Empty; //TODO VERIFY ITS ALWAYS ZEROs 54 | } 55 | 56 | // public properties... 57 | public ControlEnum Control => (ControlEnum)BitConverter.ToUInt16(RawBytes, 0x02); 58 | 59 | public XAclRecord Dacl 60 | { 61 | get 62 | { 63 | if ((Control & ControlEnum.SeDaclPresent) == ControlEnum.SeDaclPresent) 64 | { 65 | //var rawDacla = RawBytes.Skip((int) DaclOffset).Take((int) sizeDacl).ToArray(); 66 | 67 | var rawDacl = new byte[_sizeDacl]; 68 | Buffer.BlockCopy(RawBytes, (int)DaclOffset, rawDacl, 0, (int)_sizeDacl); 69 | 70 | 71 | return new XAclRecord(rawDacl, XAclRecord.AclTypeEnum.Discretionary); 72 | } 73 | 74 | return null; //ncrunch: no coverage 75 | } 76 | } 77 | 78 | public uint DaclOffset => BitConverter.ToUInt32(RawBytes, 0x10); 79 | 80 | public uint GroupOffset => BitConverter.ToUInt32(RawBytes, 0x08); 81 | 82 | public string GroupSid 83 | { 84 | get 85 | { 86 | // var rawGroup = RawBytes.Skip((int) GroupOffset).Take((int) sizeGroupSid).ToArray(); 87 | 88 | var rawGroup = new byte[_sizeGroupSid]; 89 | Buffer.BlockCopy(RawBytes, (int)GroupOffset, rawGroup, 0, (int)_sizeGroupSid); 90 | 91 | return Helpers.ConvertHexStringToSidString(rawGroup); 92 | } 93 | } 94 | 95 | public Helpers.SidTypeEnum GroupSidType => Helpers.GetSidTypeFromSidString(GroupSid); 96 | 97 | public uint OwnerOffset => BitConverter.ToUInt32(RawBytes, 0x04); 98 | 99 | public string OwnerSid 100 | { 101 | get 102 | { 103 | // var rawOwner = RawBytes.Skip((int) OwnerOffset).Take((int) sizeOwnerSid).ToArray(); 104 | 105 | var rawOwner = new byte[_sizeOwnerSid]; 106 | Buffer.BlockCopy(RawBytes, (int)OwnerOffset, rawOwner, 0, (int)_sizeOwnerSid); 107 | 108 | return Helpers.ConvertHexStringToSidString(rawOwner); 109 | } 110 | } 111 | 112 | public Helpers.SidTypeEnum OwnerSidType => Helpers.GetSidTypeFromSidString(OwnerSid); 113 | 114 | public string Padding { get; } 115 | public byte[] RawBytes { get; } 116 | 117 | public byte Revision => RawBytes[0]; 118 | 119 | public XAclRecord Sacl 120 | { 121 | get 122 | { 123 | if ((Control & ControlEnum.SeSaclPresent) != ControlEnum.SeSaclPresent) 124 | { 125 | return null; 126 | } 127 | 128 | if (_sizeSacl > 1000) 129 | { 130 | return null; 131 | } 132 | 133 | var rawSacl = new byte[_sizeSacl]; 134 | Buffer.BlockCopy(RawBytes, (int)SaclOffset, rawSacl, 0, (int)_sizeSacl); 135 | 136 | if (rawSacl.Length == 0) 137 | { 138 | return null; 139 | } 140 | 141 | return new XAclRecord(rawSacl, XAclRecord.AclTypeEnum.Security); 142 | } 143 | } 144 | 145 | public uint SaclOffset => BitConverter.ToUInt32(RawBytes, 0x0c); 146 | 147 | // public methods... 148 | public override string ToString() 149 | { 150 | var sb = new StringBuilder(); 151 | 152 | sb.AppendLine($"Revision: 0x{Revision:X}"); 153 | sb.AppendLine($"Control: {Control}"); 154 | 155 | sb.AppendLine(); 156 | sb.AppendLine($"Owner offset: 0x{OwnerOffset:X}"); 157 | sb.AppendLine($"Owner SID: {OwnerSid}"); 158 | sb.AppendLine($"Owner SID Type: {OwnerSidType}"); 159 | 160 | sb.AppendLine(); 161 | sb.AppendLine($"Group offset: 0x{GroupOffset:X}"); 162 | sb.AppendLine($"Group SID: {GroupSid}"); 163 | sb.AppendLine($"Group SID Type: {GroupSidType}"); 164 | 165 | if (Dacl != null) 166 | { 167 | sb.AppendLine(); 168 | sb.AppendLine($"Dacl Offset: 0x{DaclOffset:X}"); 169 | sb.AppendLine($"DACL: {Dacl}"); 170 | } 171 | 172 | if (Sacl != null) 173 | { 174 | sb.AppendLine(); 175 | sb.AppendLine($"Sacl Offset: 0x{SaclOffset:X}"); 176 | sb.AppendLine($"SACL: {Sacl}"); 177 | } 178 | 179 | return sb.ToString(); 180 | } 181 | } -------------------------------------------------------------------------------- /MFT/Attributes/SecurityDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MFT.Attributes; 5 | 6 | public class SecurityDescriptor : Attribute 7 | { 8 | public SecurityDescriptor(byte[] rawBytes) : base(rawBytes) 9 | { 10 | if (IsResident) 11 | { 12 | var content = new byte[AttributeContentLength]; 13 | 14 | Buffer.BlockCopy(rawBytes, ContentOffset, content, 0, AttributeContentLength); 15 | 16 | ResidentData = new ResidentData(content); 17 | } 18 | else 19 | { 20 | NonResidentData = new NonResidentData(rawBytes); 21 | } 22 | 23 | if (IsResident == false) 24 | { 25 | return; 26 | } 27 | 28 | SecurityInfo = new SkSecurityDescriptor(ResidentData.Data); 29 | } 30 | 31 | public SkSecurityDescriptor SecurityInfo { get; } 32 | 33 | public ResidentData ResidentData { get; } 34 | 35 | public NonResidentData NonResidentData { get; } 36 | 37 | public override string ToString() 38 | { 39 | var sb = new StringBuilder(); 40 | 41 | sb.AppendLine("**** SECURITY DESCRIPTOR ****"); 42 | 43 | sb.AppendLine(base.ToString()); 44 | 45 | sb.AppendLine(); 46 | 47 | sb.AppendLine( 48 | $"Security Info: {SecurityInfo}"); 49 | 50 | if (IsResident) 51 | { 52 | sb.AppendLine($"Resident Data: {ResidentData}"); 53 | sb.AppendLine($"Security Info: {SecurityInfo}"); 54 | } 55 | else 56 | { 57 | sb.AppendLine($"Non Resident Data: {NonResidentData}"); 58 | } 59 | 60 | return sb.ToString(); 61 | } 62 | } -------------------------------------------------------------------------------- /MFT/Attributes/StandardInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Serilog; 4 | 5 | namespace MFT.Attributes; 6 | 7 | public class StandardInfo : Attribute 8 | { 9 | [Flags] 10 | public enum Flag 11 | { 12 | None = 0x00, 13 | ReadOnly = 0x01, 14 | Hidden = 0x02, 15 | System = 0x04, 16 | VolumeLabel = 0x08, 17 | Directory = 0x010, 18 | Archive = 0x020, 19 | Device = 0x040, 20 | Normal = 0x080, 21 | Temporary = 0x0100, 22 | SparseFile = 0x0200, 23 | ReparsePoint = 0x0400, 24 | Compressed = 0x0800, 25 | Offline = 0x01000, 26 | NotContentIndexed = 0x02000, 27 | Encrypted = 0x04000, 28 | IntegrityStream = 0x08000, 29 | Virtual = 0x010000, 30 | NoScrubData = 0x020000, 31 | RecallOnOpen = 0x040000, 32 | RecallOnDataAccess = 0x400000, 33 | Pinned = 0x80000, 34 | UnPinned = 0x100000, 35 | IsDirectory = 0x10000000, 36 | IsIndexView = 0x20000000 37 | } 38 | 39 | [Flags] 40 | public enum Flag2 41 | { 42 | None = 0x00, 43 | IsCaseSensitive = 0x01 44 | } 45 | 46 | public StandardInfo(byte[] rawBytes) : base(rawBytes) 47 | { 48 | var createdRaw = BitConverter.ToInt64(rawBytes, 0x18); 49 | if (createdRaw > 0) 50 | { 51 | try 52 | { 53 | CreatedOn = DateTimeOffset.FromFileTime(BitConverter.ToInt64(rawBytes, 0x18)).ToUniversalTime(); 54 | } 55 | catch (Exception) 56 | { 57 | Log.Warning("Invalid CreatedOn timestamp! Enable --debug for record information"); 58 | } 59 | } 60 | 61 | var contentModRaw = BitConverter.ToInt64(rawBytes, 0x20); 62 | if (contentModRaw > 0) 63 | { 64 | try 65 | { 66 | ContentModifiedOn = DateTimeOffset.FromFileTime(BitConverter.ToInt64(rawBytes, 0x20)).ToUniversalTime(); 67 | } 68 | catch (Exception) 69 | { 70 | Log.Warning("Invalid ContentModifiedOn timestamp! Enable --debug for record information"); 71 | } 72 | } 73 | 74 | var recordModRaw = BitConverter.ToInt64(rawBytes, 0x28); 75 | if (recordModRaw > 0) 76 | { 77 | try 78 | { 79 | RecordModifiedOn = DateTimeOffset.FromFileTime(BitConverter.ToInt64(rawBytes, 0x28)) 80 | .ToUniversalTime(); 81 | } 82 | catch (Exception) 83 | { 84 | Log.Warning("Invalid RecordModifiedOn timestamp! Enable --debug for record information"); 85 | } 86 | } 87 | 88 | var lastAccessRaw = BitConverter.ToInt64(rawBytes, 0x30); 89 | if (lastAccessRaw > 0) 90 | { 91 | try 92 | { 93 | LastAccessedOn = DateTimeOffset.FromFileTime(BitConverter.ToInt64(rawBytes, 0x30)).ToUniversalTime(); 94 | } 95 | catch (Exception) 96 | { 97 | Log.Warning("Invalid LastAccessedOn timestamp! Enable --debug for record information"); 98 | } 99 | } 100 | 101 | Flags = (Flag)BitConverter.ToInt32(rawBytes, 0x38); 102 | 103 | MaxVersion = BitConverter.ToInt32(rawBytes, 0x3C); 104 | Flags2 = (Flag2)BitConverter.ToInt32(rawBytes, 0x40); 105 | ClassId = BitConverter.ToInt32(rawBytes, 0x44); 106 | 107 | if (rawBytes.Length <= 0x48) 108 | { 109 | return; 110 | } 111 | 112 | OwnerId = BitConverter.ToInt32(rawBytes, 0x48); 113 | SecurityId = BitConverter.ToInt32(rawBytes, 0x4C); 114 | QuotaCharged = BitConverter.ToInt32(rawBytes, 0x50); 115 | UpdateSequenceNumber = BitConverter.ToInt64(rawBytes, 0x58); 116 | } 117 | 118 | public int MaxVersion { get; } 119 | public Flag2 Flags2 { get; } 120 | public int ClassId { get; } 121 | public int OwnerId { get; } 122 | public int SecurityId { get; } 123 | public int QuotaCharged { get; } 124 | public long UpdateSequenceNumber { get; } 125 | 126 | public DateTimeOffset? CreatedOn { get; } 127 | public DateTimeOffset? ContentModifiedOn { get; } 128 | public DateTimeOffset? RecordModifiedOn { get; } 129 | public DateTimeOffset? LastAccessedOn { get; } 130 | 131 | public Flag Flags { get; } 132 | 133 | public override string ToString() 134 | { 135 | var sb = new StringBuilder(); 136 | 137 | sb.AppendLine("**** STANDARD INFO ****"); 138 | 139 | sb.AppendLine(base.ToString()); 140 | 141 | sb.AppendLine(); 142 | 143 | sb.AppendLine( 144 | $"Flags: {Flags.ToString().Replace(", ", "|")}, Max Version: 0x{MaxVersion:X}, Flags 2: {Flags2.ToString().Replace(", ", "|")}, Class Id: 0x{ClassId:X}, " + 145 | $"Owner Id: 0x{OwnerId:X}, Security Id: 0x{SecurityId:X}, Quota Charged: 0x{QuotaCharged:X} " + 146 | $"\r\nUpdate Sequence #: 0x{UpdateSequenceNumber:X}" + 147 | $"\r\n\r\nCreated On:\t\t{CreatedOn?.ToString(MftFile.DateTimeFormat)}" + 148 | $"\r\nContent Modified On:\t{ContentModifiedOn?.ToString(MftFile.DateTimeFormat)}" + 149 | $"\r\nRecord Modified On:\t{RecordModifiedOn?.ToString(MftFile.DateTimeFormat)}" + 150 | $"\r\nLast Accessed On:\t{LastAccessedOn?.ToString(MftFile.DateTimeFormat)}"); 151 | 152 | return sb.ToString(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /MFT/Attributes/VolumeInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MFT.Attributes; 5 | 6 | public class VolumeInformation : Attribute 7 | { 8 | [Flags] 9 | public enum VolumeFlag 10 | { 11 | None = 0x0000, 12 | IsDirty = 0x0001, 13 | ResizeJournalLogFile = 0x0002, 14 | UpgradeOnNextMount = 0x0004, 15 | MountedOnNt4 = 0x0008, 16 | DeleteUsnUnderway = 0x0010, 17 | RepairObjectIDs = 0x0020, 18 | ModifiedByChkDsk = 0x8000 19 | } 20 | 21 | public VolumeInformation(byte[] rawBytes) : base(rawBytes) 22 | { 23 | var residentData = new ResidentData(rawBytes); 24 | 25 | //our data is in residentData.Data 26 | UnknownBytes = new byte[8]; 27 | Buffer.BlockCopy(residentData.Data, ContentOffset, UnknownBytes, 0, 8); 28 | 29 | MajorVersion = residentData.Data[ContentOffset + 0x8]; 30 | MinorVersion = residentData.Data[ContentOffset + 0x9]; 31 | 32 | VolumeFlags = (VolumeFlag)BitConverter.ToInt16(residentData.Data, ContentOffset + 0xA); 33 | } 34 | 35 | public byte[] UnknownBytes { get; } 36 | public int MajorVersion { get; } 37 | public int MinorVersion { get; } 38 | public VolumeFlag VolumeFlags { get; } 39 | 40 | public override string ToString() 41 | { 42 | var sb = new StringBuilder(); 43 | 44 | sb.AppendLine("**** VOLUME INFORMATION ****"); 45 | 46 | sb.AppendLine(base.ToString()); 47 | 48 | sb.AppendLine(); 49 | 50 | sb.AppendLine( 51 | $"Volume Flags: {VolumeFlags.ToString().Replace(", ", "|")} Major Version: 0x{MajorVersion:X} Minor Version: 0x{MinorVersion:X} Unknown Bytes: {BitConverter.ToString(UnknownBytes)} "); 52 | 53 | return sb.ToString(); 54 | } 55 | } -------------------------------------------------------------------------------- /MFT/Attributes/VolumeName.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace MFT.Attributes; 4 | 5 | public class VolumeName : Attribute 6 | { 7 | public VolumeName(byte[] rawBytes) : base(rawBytes) 8 | { 9 | var residentData = new ResidentData(rawBytes); 10 | 11 | VolName = string.Empty; 12 | 13 | if (residentData.Data.Length > 0) 14 | { 15 | VolName = Encoding.Unicode 16 | .GetString(residentData.Data, ContentOffset, residentData.Data.Length - ContentOffset) 17 | .TrimEnd('\0'); 18 | } 19 | } 20 | 21 | public string VolName { get; } 22 | 23 | public override string ToString() 24 | { 25 | var sb = new StringBuilder(); 26 | 27 | sb.AppendLine("**** VOLUME NAME ****"); 28 | 29 | sb.AppendLine(base.ToString()); 30 | 31 | sb.AppendLine(); 32 | 33 | sb.AppendLine($"Volume Name: {VolName}"); 34 | 35 | return sb.ToString(); 36 | } 37 | } -------------------------------------------------------------------------------- /MFT/Attributes/xACLRecord.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | // namespaces... 6 | 7 | namespace MFT.Attributes; 8 | 9 | // public classes... 10 | public class XAclRecord 11 | { 12 | // public enums... 13 | public enum AclTypeEnum 14 | { 15 | Security, 16 | Discretionary 17 | } 18 | 19 | // public constructors... 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public XAclRecord(byte[] rawBytes, AclTypeEnum aclTypetype) 24 | { 25 | RawBytes = rawBytes; 26 | 27 | AclType = aclTypetype; 28 | } 29 | 30 | // public properties... 31 | public ushort AceCount 32 | { 33 | get 34 | { 35 | if (RawBytes.Length == 0) 36 | { 37 | return 0; 38 | } 39 | 40 | return BitConverter.ToUInt16(RawBytes, 0x4); 41 | } 42 | } 43 | 44 | public List AceRecords 45 | { 46 | get 47 | { 48 | var index = 0x8; // the start of ACE structures 49 | 50 | var chunks = new List(); 51 | 52 | for (var i = 0; i < AceCount; i++) 53 | { 54 | if (index > RawBytes.Length) 55 | //ncrunch: no coverage 56 | { 57 | break; //ncrunch: no coverage 58 | } 59 | 60 | var aceSize = RawBytes[index + 2]; 61 | // var rawAce = RawBytes.Skip(index).Take(aceSize).ToArray(); 62 | 63 | var rawAce = new byte[aceSize]; 64 | Buffer.BlockCopy(RawBytes, index, rawAce, 0, aceSize); 65 | 66 | chunks.Add(rawAce); 67 | 68 | index += aceSize; 69 | } 70 | 71 | var records = new List(); 72 | 73 | foreach (var chunk in chunks) 74 | { 75 | if (chunk.Length <= 0) 76 | { 77 | continue; 78 | } 79 | 80 | var ace = new AceRecord(chunk); 81 | 82 | records.Add(ace); 83 | } 84 | 85 | return records; 86 | } 87 | } 88 | 89 | public byte AclRevision => RawBytes[0]; 90 | 91 | public ushort AclSize => BitConverter.ToUInt16(RawBytes, 0x2); 92 | 93 | public AclTypeEnum AclType { get; } 94 | public byte[] RawBytes { get; } 95 | 96 | public byte Sbz1 => RawBytes[1]; 97 | 98 | public ushort Sbz2 => BitConverter.ToUInt16(RawBytes, 0x6); 99 | 100 | // public methods... 101 | public override string ToString() 102 | { 103 | if (RawBytes.Length == 0) 104 | { 105 | return string.Empty; 106 | } 107 | 108 | var sb = new StringBuilder(); 109 | 110 | sb.AppendLine($"ACL Revision: 0x{AclRevision:X}"); 111 | sb.AppendLine($"ACL Size: 0x{AclSize:X}"); 112 | sb.AppendLine($"ACL Type: {AclType}"); 113 | sb.AppendLine($"Sbz1: 0x{Sbz1:X}"); 114 | sb.AppendLine($"Sbz2: 0x{Sbz2:X}"); 115 | 116 | sb.AppendLine($"ACE Records Count: {AceCount}"); 117 | 118 | sb.AppendLine(); 119 | 120 | var i = 0; 121 | foreach (var aceRecord in AceRecords) 122 | { 123 | sb.AppendLine($"------------ Ace record #{i} ------------"); 124 | sb.AppendLine(aceRecord.ToString()); 125 | i += 1; 126 | } 127 | 128 | return sb.ToString(); 129 | } 130 | } -------------------------------------------------------------------------------- /MFT/MFT.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | Eric R. Zimmerman 5 | $MFT Parser 6 | 1.5.1 7 | 8 | 10 9 | MIT 10 | Eric R. Zimmerman 11 | https://github.com/EricZimmerman/MFT 12 | https://github.com/EricZimmerman/MFT 13 | 14 | $MFT, $Boot, usn, $J, $I30, NTFS 15 | README.md 16 | icon.png 17 | True 18 | 19 | $(NoWarn);CS1591 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | all 33 | runtime; build; native; contentfiles; analyzers; buildtransitive 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /MFT/MftFile.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace MFT; 4 | 5 | public static class MftFile 6 | { 7 | public static string DateTimeFormat = "yyyy-MM-dd HH:mm:ss.fffffff"; 8 | 9 | public static Mft Load(string mftPath, bool recoverFromSlack) 10 | { 11 | if (File.Exists(mftPath) == false) 12 | { 13 | throw new FileNotFoundException($"'{mftPath}' not found"); 14 | } 15 | 16 | using var fs = new FileStream(mftPath, FileMode.Open, FileAccess.Read); 17 | return new Mft(fs,recoverFromSlack); 18 | } 19 | } -------------------------------------------------------------------------------- /MFT/Other/AdsInfo.cs: -------------------------------------------------------------------------------- 1 | using MFT.Attributes; 2 | 3 | namespace MFT.Other; 4 | 5 | public class AdsInfo 6 | { 7 | public AdsInfo(string name, ulong size, ResidentData residentData, NonResidentData nonResidentData, int attributeId) 8 | { 9 | Name = name; 10 | Size = size; 11 | ResidentData = residentData; 12 | NonResidentData = nonResidentData; 13 | AttributeId = attributeId; 14 | } 15 | 16 | public string Name { get; } 17 | public ulong Size { get; } 18 | 19 | public int AttributeId { get; } 20 | public ResidentData ResidentData { get; } 21 | public NonResidentData NonResidentData { get; } 22 | 23 | public override string ToString() 24 | { 25 | return $"Name: {Name} Size: 0x{Size:X}"; 26 | } 27 | } -------------------------------------------------------------------------------- /MFT/Other/AttributeInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MFT.Other; 5 | 6 | public class AttributeInfo 7 | { 8 | public AttributeInfo(byte[] rawBytes) 9 | { 10 | var buff = new byte[8]; 11 | 12 | FirstVirtualClusterNumber = BitConverter.ToUInt64(rawBytes, 0x8); 13 | 14 | Buffer.BlockCopy(rawBytes, 0x10, buff, 0, 0x8); 15 | 16 | EntryInfo = new MftEntryInfo(buff); 17 | 18 | var nameSize = rawBytes[0x6]; 19 | var nameOffset = rawBytes[0x7]; 20 | 21 | if (nameSize > 0) 22 | { 23 | Name = Encoding.Unicode.GetString(rawBytes, nameOffset, nameSize * 2); 24 | } 25 | } 26 | 27 | public ulong FirstVirtualClusterNumber { get; } 28 | 29 | public MftEntryInfo EntryInfo { get; } 30 | public string Name { get; } 31 | 32 | public override string ToString() 33 | { 34 | var name = string.Empty; 35 | if (Name != null) 36 | { 37 | name = $" Name: {Name}"; 38 | } 39 | 40 | return $"Entry info: {EntryInfo}{name} First Vcn: 0x{FirstVirtualClusterNumber:X}"; 41 | } 42 | } -------------------------------------------------------------------------------- /MFT/Other/DataRun.cs: -------------------------------------------------------------------------------- 1 | namespace MFT.Other; 2 | 3 | public class DataRun 4 | { 5 | public DataRun(ulong clustersInRun, long clusterOffset) 6 | { 7 | ClustersInRun = clustersInRun; 8 | ClusterOffset = clusterOffset; 9 | } 10 | 11 | public ulong ClustersInRun { get; } 12 | public long ClusterOffset { get; } 13 | 14 | public override string ToString() 15 | { 16 | return $"Cluster offset: 0x{ClusterOffset:X}, # clusters: 0x{ClustersInRun:X}"; 17 | } 18 | } -------------------------------------------------------------------------------- /MFT/Other/DirectoryNameMapValue.cs: -------------------------------------------------------------------------------- 1 | namespace MFT.Other; 2 | 3 | internal class DirectoryNameMapValue 4 | { 5 | public DirectoryNameMapValue(string name, string parentRecordKey, bool isDeleted) 6 | { 7 | Name = name; 8 | IsDeleted = isDeleted; 9 | ParentRecordKey = parentRecordKey; 10 | } 11 | 12 | public string Name { get; } 13 | public string ParentRecordKey { get; } 14 | public bool IsDeleted { get; } 15 | 16 | public override string ToString() 17 | { 18 | return $"{Name}, Parent key: {ParentRecordKey} Deleted: {IsDeleted}"; 19 | } 20 | } -------------------------------------------------------------------------------- /MFT/Other/ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using MFT.Attributes; 4 | 5 | namespace MFT.Other; 6 | 7 | public static class ExtensionMethods 8 | { 9 | public static int GetReferenceCount(this FileRecord record) 10 | { 11 | var fns = record.Attributes.Where(t => t.AttributeType == AttributeType.FileName); 12 | 13 | var hashes = new HashSet(); 14 | 15 | foreach (var attribute in fns) 16 | { 17 | var fn = (FileName)attribute; 18 | if (fn.FileInfo.NameType == NameTypes.Dos) 19 | { 20 | continue; 21 | } 22 | 23 | var key = $"{fn.FileInfo.FileName}-{fn.FileInfo.ParentMftRecord.GetKey()}"; 24 | 25 | hashes.Add(key); 26 | } 27 | 28 | return hashes.Count; 29 | } 30 | 31 | public static ReparsePoint GetReparsePoint(this FileRecord record) 32 | { 33 | var reparseAttr = 34 | record.Attributes.Where(t => 35 | t.AttributeType == AttributeType.ReparsePoint).ToList(); 36 | 37 | return (ReparsePoint)reparseAttr.FirstOrDefault(); 38 | } 39 | 40 | public static FileName GetFileNameAttributeFromFileRecord(this FileRecord fr) //, int attributeNumber = -1 41 | { 42 | // if (attributeNumber > -1) 43 | // { 44 | // var fin = fr.Attributes.SingleOrDefault(t => 45 | // t.AttributeType == AttributeType.FileName && ((FileName) t).AttributeNumber == attributeNumber); 46 | // 47 | // return (FileName) fin; 48 | // } 49 | var fi = fr.Attributes.FirstOrDefault(t => 50 | t.AttributeType == AttributeType.FileName && ((FileName)t).FileInfo.NameType == NameTypes.DosWindows); 51 | 52 | if (fi != null) 53 | { 54 | return (FileName)fi; 55 | } 56 | 57 | fi = fr.Attributes.FirstOrDefault(t => 58 | t.AttributeType == AttributeType.FileName && ((FileName)t).FileInfo.NameType == NameTypes.Windows); 59 | 60 | if (fi != null) 61 | { 62 | return (FileName)fi; 63 | } 64 | 65 | 66 | fi = fr.Attributes.FirstOrDefault(t => 67 | t.AttributeType == AttributeType.FileName && ((FileName)t).FileInfo.NameType == NameTypes.Posix); 68 | 69 | if (fi != null) 70 | { 71 | return (FileName)fi; 72 | } 73 | 74 | 75 | fi = fr.Attributes.SingleOrDefault(t => 76 | t.AttributeType == AttributeType.FileName && ((FileName)t).FileInfo.NameType == NameTypes.Dos); 77 | 78 | return (FileName)fi; 79 | } 80 | 81 | public static string GetKey(this MftEntryInfo mftInfo, bool asDecimal = false) 82 | { 83 | if (asDecimal) 84 | { 85 | return $"{mftInfo.MftEntryNumber}-{mftInfo.MftSequenceNumber}"; 86 | } 87 | 88 | return $"{mftInfo.MftEntryNumber:X8}-{mftInfo.MftSequenceNumber:X8}"; 89 | } 90 | 91 | public static string GetKey(this FileRecord record, bool asDecimal = false) 92 | { 93 | if (asDecimal) 94 | { 95 | if (record.IsDeleted()) 96 | { 97 | return $"{record.EntryNumber}-{record.SequenceNumber - 1}"; 98 | } 99 | 100 | return $"{record.EntryNumber}-{record.SequenceNumber}"; 101 | } 102 | 103 | 104 | if (record.IsDeleted()) 105 | { 106 | return $"{record.EntryNumber:X8}-{record.SequenceNumber - 1:X8}"; 107 | } 108 | 109 | return $"{record.EntryNumber:X8}-{record.SequenceNumber:X8}"; 110 | } 111 | 112 | public static bool IsDirectory(this FileRecord record) 113 | { 114 | if (record == null) 115 | { 116 | return false; 117 | } 118 | 119 | return (record.EntryFlags & FileRecord.EntryFlag.IsDirectory) == 120 | FileRecord.EntryFlag.IsDirectory; 121 | } 122 | 123 | public static bool IsDeleted(this FileRecord record) 124 | { 125 | return (record.EntryFlags & FileRecord.EntryFlag.InUse) != 126 | FileRecord.EntryFlag.InUse; 127 | } 128 | 129 | public static bool HasAds(this FileRecord record) 130 | { 131 | var dataAttrs = 132 | record.Attributes.Where(t => 133 | t.AttributeType == AttributeType.Data && t.NameSize > 0).ToList(); 134 | 135 | return dataAttrs.Count > 0; 136 | } 137 | 138 | public static List GetAlternateDataStreams(this FileRecord record) 139 | { 140 | var l = new List(); 141 | 142 | var dataAttrs = 143 | record.Attributes.Where(t => 144 | t.AttributeType == AttributeType.Data && t.NameSize > 0).ToList(); 145 | 146 | foreach (var attribute in dataAttrs) 147 | { 148 | var da = (Data)attribute; 149 | 150 | if (da.IsResident == false && da.NonResidentData.StartingVirtualClusterNumber > 0) 151 | { 152 | continue; 153 | } 154 | 155 | ulong size; 156 | if (da.IsResident) 157 | { 158 | size = (ulong)da.AttributeContentLength; 159 | } 160 | else 161 | { 162 | size = da.NonResidentData.ActualSize; 163 | } 164 | 165 | var adsi = new AdsInfo(da.Name, size, da.ResidentData, da.NonResidentData, da.AttributeNumber); 166 | 167 | l.Add(adsi); 168 | } 169 | 170 | return l; 171 | } 172 | 173 | public static ulong GetFileSize(this FileRecord record) 174 | { 175 | if (record.IsDirectory()) 176 | { 177 | return 0; 178 | } 179 | 180 | var fn = record.Attributes.FirstOrDefault(t => t.AttributeType == AttributeType.FileName); 181 | 182 | var datas = record.Attributes.Where(t => t.AttributeType == AttributeType.Data).ToList(); 183 | 184 | if (datas.Count >= 1) 185 | { 186 | var data = (Data)datas.First(); 187 | 188 | if (data.IsResident) 189 | { 190 | return (ulong)data.ResidentData.Data.LongLength; 191 | } 192 | 193 | return data.NonResidentData.ActualSize; 194 | } 195 | 196 | if (datas.Count != 0) 197 | { 198 | return 0; 199 | } 200 | 201 | 202 | if (fn != null) 203 | { 204 | var fna = (FileName)fn; 205 | return fna.FileInfo.LogicalSize; 206 | } 207 | 208 | return 0; 209 | } 210 | } -------------------------------------------------------------------------------- /MFT/Other/FixupData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace MFT.Other; 6 | 7 | public class FixupData 8 | { 9 | public FixupData(byte[] fixupDataRaw) 10 | { 11 | FixupExpected = BitConverter.ToInt16(fixupDataRaw, 0); 12 | FixupActual = new List(); 13 | 14 | var index = 2; 15 | 16 | while (index < fixupDataRaw.Length) 17 | { 18 | var b = new byte[2]; 19 | Buffer.BlockCopy(fixupDataRaw, index, b, 0, 2); 20 | FixupActual.Add(b); 21 | index += 2; 22 | } 23 | } 24 | 25 | /// 26 | /// the data expected at the end of each 512 byte chunk 27 | /// 28 | public short FixupExpected { get; } 29 | 30 | /// 31 | /// The actual bytes to be overlayed before processing a record, in order 32 | /// 33 | public List FixupActual { get; } 34 | 35 | public override string ToString() 36 | { 37 | var sb = new StringBuilder(); 38 | 39 | foreach (var bytese in FixupActual) 40 | { 41 | var bb = BitConverter.ToString(bytese); 42 | sb.Append($"{bb}|"); 43 | } 44 | 45 | var fua = sb.ToString().TrimEnd('|'); 46 | 47 | return $"Expected: {BitConverter.ToString(BitConverter.GetBytes(FixupExpected))} Fixup Actual: {fua}"; 48 | } 49 | } -------------------------------------------------------------------------------- /MFT/Other/IndexEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using MFT.Attributes; 4 | using Serilog; 5 | 6 | namespace MFT.Other; 7 | 8 | public class IndexEntry 9 | { 10 | public IndexEntry(byte[] rawBytes) 11 | { 12 | var index = 0; 13 | // var size = BitConverter.ToInt16(rawBytes, index); 14 | index += 2; 15 | 16 | var indexKeyDataSize = BitConverter.ToInt16(rawBytes, index); 17 | index += 2; 18 | 19 | var indexFlags = (IndexRoot.IndexFlag)BitConverter.ToInt32(rawBytes, index); 20 | index += 4; 21 | 22 | if ((indexFlags & IndexRoot.IndexFlag.IsLast) == IndexRoot.IndexFlag.IsLast) 23 | { 24 | return; 25 | } 26 | 27 | if (indexKeyDataSize == 0x10) 28 | //indicates no more index entries 29 | { 30 | return; 31 | } 32 | 33 | if (indexKeyDataSize <= 0x40) 34 | //too small to do anything with 35 | { 36 | return; 37 | } 38 | 39 | if (indexKeyDataSize > 0) 40 | { 41 | var mftInfoBytes = new byte[8]; 42 | Buffer.BlockCopy(rawBytes, index, mftInfoBytes, 0, 8); 43 | index += 8; 44 | 45 | ParentMftRecord = new MftEntryInfo(mftInfoBytes); 46 | 47 | var createdRaw = BitConverter.ToInt64(rawBytes, index); 48 | if (createdRaw > 0) 49 | { 50 | try 51 | { 52 | CreatedOn = DateTimeOffset.FromFileTime(createdRaw).ToUniversalTime(); 53 | } 54 | catch (Exception) 55 | { 56 | Log.Warning("Invalid CreatedOn timestamp. Enable --debug for more details"); 57 | } 58 | } 59 | 60 | index += 8; 61 | 62 | var contentModRaw = BitConverter.ToInt64(rawBytes, index); 63 | if (contentModRaw > 0) 64 | { 65 | try 66 | { 67 | ContentModifiedOn = DateTimeOffset.FromFileTime(contentModRaw).ToUniversalTime(); 68 | } 69 | catch (Exception) 70 | { 71 | Log.Warning("Invalid ContentModifiedOn timestamp. Enable --debug for more details"); 72 | } 73 | } 74 | 75 | index += 8; 76 | 77 | var recordModRaw = BitConverter.ToInt64(rawBytes, index); 78 | if (recordModRaw > 0) 79 | { 80 | try 81 | { 82 | RecordModifiedOn = DateTimeOffset.FromFileTime(recordModRaw).ToUniversalTime(); 83 | } 84 | catch (Exception) 85 | { 86 | Log.Warning("Invalid RecordModifiedOn timestamp. Enable --debug for more details"); 87 | } 88 | } 89 | 90 | index += 8; 91 | 92 | var lastAccessRaw = BitConverter.ToInt64(rawBytes, index); 93 | if (lastAccessRaw > 0) 94 | { 95 | try 96 | { 97 | LastAccessedOn = DateTimeOffset.FromFileTime(lastAccessRaw).ToUniversalTime(); 98 | } 99 | catch (Exception) 100 | { 101 | Log.Warning("Invalid LastAccessedOn timestamp. Enable --debug for more details"); 102 | } 103 | } 104 | 105 | index += 8; 106 | 107 | PhysicalSize = BitConverter.ToUInt64(rawBytes, index); 108 | index += 8; 109 | LogicalSize = BitConverter.ToUInt64(rawBytes, index); 110 | index += 8; 111 | 112 | Flags = (StandardInfo.Flag)BitConverter.ToInt32(rawBytes, index); 113 | index += 4; 114 | 115 | 116 | ReparseValue = BitConverter.ToInt32(rawBytes, index); 117 | index += 4; 118 | 119 | NameLength = rawBytes[index]; 120 | index += 1; 121 | NameType = (NameTypes)rawBytes[index]; 122 | index += 1; 123 | 124 | FileName = Encoding.Unicode.GetString(rawBytes, index, NameLength * 2); 125 | } 126 | 127 | //index += 2; //padding 128 | } 129 | 130 | public int ReparseValue { get; } 131 | public byte NameLength { get; } 132 | public NameTypes NameType { get; } 133 | public string FileName { get; } 134 | public ulong PhysicalSize { get; } 135 | public ulong LogicalSize { get; } 136 | public DateTimeOffset? CreatedOn { get; } 137 | public DateTimeOffset? ContentModifiedOn { get; } 138 | public DateTimeOffset? RecordModifiedOn { get; } 139 | public DateTimeOffset? LastAccessedOn { get; } 140 | public StandardInfo.Flag Flags { get; } 141 | 142 | public MftEntryInfo ParentMftRecord { get; } 143 | 144 | public override string ToString() 145 | { 146 | var sb = new StringBuilder(); 147 | 148 | sb.AppendLine(); 149 | 150 | sb.AppendLine( 151 | $"File name: {FileName} (Len:0x{NameLength:X}) Flags: {Flags.ToString().Replace(", ", "|")}, Name Type: {NameType} " + 152 | $"Reparse Value: 0x{ReparseValue:X} Physical Size: 0x{PhysicalSize:X}, Logical Size: 0x{LogicalSize:X}" + 153 | $"\r\nParent Mft Record: {ParentMftRecord} " + 154 | $"\r\nCreated On:\t\t{CreatedOn?.ToString(MftFile.DateTimeFormat)}" + 155 | $"\r\nContent Modified On:\t{ContentModifiedOn?.ToString(MftFile.DateTimeFormat)}" + 156 | $"\r\nRecord Modified On:\t{RecordModifiedOn?.ToString(MftFile.DateTimeFormat)}" + 157 | $"\r\nLast Accessed On:\t{LastAccessedOn?.ToString(MftFile.DateTimeFormat)}"); 158 | 159 | return sb.ToString(); 160 | } 161 | } -------------------------------------------------------------------------------- /MFT/Other/IndexEntryI30.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | 4 | namespace MFT.Other; 5 | 6 | public class IndexEntryI30 7 | { 8 | public enum OEntryFlag 9 | { 10 | HasSubNodes = 0x1, 11 | LastEntry = 0x2 12 | } 13 | 14 | public IndexEntryI30(byte[] rawBytes, long absoluteOffset, int pageNumber, bool fromSlack) 15 | { 16 | PageNumber = pageNumber; 17 | FromSlack = fromSlack; 18 | 19 | AbsoluteOffset = absoluteOffset; 20 | 21 | using var br = new BinaryReader(new MemoryStream(rawBytes)); 22 | 23 | var skipOffset = 0; 24 | if (fromSlack == false) 25 | { 26 | MftReferenceSelf = new MftEntryInfo(br.ReadBytes(8)); 27 | 28 | if (MftReferenceSelf.MftEntryNumber == 0) 29 | { 30 | return; 31 | } 32 | 33 | var indexEntrySize = br.ReadInt16(); 34 | var indexDataSize = br.ReadInt16(); 35 | Flag = (OEntryFlag)br.ReadInt32(); 36 | skipOffset = 8 + 2 + 2 + 4; 37 | } 38 | 39 | FileInfo = new Attributes.FileInfo(rawBytes.Skip(skipOffset).ToArray()); 40 | } 41 | 42 | public OEntryFlag Flag { get; } 43 | 44 | public int PageNumber { get; } 45 | public bool FromSlack { get; } 46 | 47 | public long AbsoluteOffset { get; } 48 | 49 | public MftEntryInfo MftReferenceSelf { get; } 50 | 51 | public Attributes.FileInfo FileInfo { get; } 52 | 53 | public string Md5 { get; set; } 54 | 55 | 56 | public override string ToString() 57 | { 58 | return 59 | $"Absolute offset: 0x{AbsoluteOffset:X} FromSlack: {FromSlack} Self MFT: {MftReferenceSelf} FileInfo: {FileInfo}"; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /MFT/Other/MftEntryInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MFT.Other; 4 | 5 | public class MftEntryInfo 6 | { 7 | public MftEntryInfo(byte[] rawEntryBytes) 8 | { 9 | if (rawEntryBytes.Length != 8) 10 | { 11 | throw new ArgumentException("rawEntryBytes must be 8 bytes long!"); 12 | } 13 | 14 | var sequenceNumber = BitConverter.ToUInt16(rawEntryBytes, 6); 15 | 16 | uint entryIndex; 17 | 18 | var entryIndex1 = BitConverter.ToUInt32(rawEntryBytes, 0); 19 | uint entryIndex2 = BitConverter.ToUInt16(rawEntryBytes, 4); 20 | 21 | if (entryIndex2 == 0) 22 | { 23 | entryIndex = entryIndex1; 24 | } 25 | else 26 | { 27 | entryIndex2 = entryIndex2 * 16777216; //2^24 28 | entryIndex = entryIndex1 + entryIndex2; 29 | } 30 | 31 | MftEntryNumber = entryIndex; 32 | MftSequenceNumber = sequenceNumber; 33 | } 34 | 35 | public uint MftEntryNumber { get; set; } 36 | 37 | public ushort MftSequenceNumber { get; set; } 38 | 39 | public override string ToString() 40 | { 41 | return $"Entry/seq: 0x{MftEntryNumber:X}-0x{MftSequenceNumber:X}"; 42 | } 43 | } -------------------------------------------------------------------------------- /MFT/Other/ParentMapEntry.cs: -------------------------------------------------------------------------------- 1 | namespace MFT.Other; 2 | 3 | public class ParentMapEntry 4 | { 5 | public ParentMapEntry(string fileName, string key, bool isDirectory) 6 | { 7 | FileName = fileName; 8 | Key = key; 9 | IsDirectory = isDirectory; 10 | } 11 | 12 | public string FileName { get; } 13 | public string Key { get; } 14 | public bool IsDirectory { get; } 15 | 16 | public override string ToString() 17 | { 18 | return $"{FileName} IsDir: {IsDirectory} Key: {Key}"; 19 | } 20 | } -------------------------------------------------------------------------------- /O/O.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using MFT.Other; 7 | using Serilog; 8 | 9 | namespace O; 10 | 11 | public class O 12 | { 13 | public O(Stream fileStream) 14 | { 15 | var pageSize = 0x1000; 16 | 17 | var rawBytes2 = new byte[fileStream.Length]; 18 | fileStream.Read(rawBytes2, 0, (int)fileStream.Length); 19 | 20 | var sig = 0x58444E49; 21 | 22 | var index2 = 0x0; 23 | 24 | while (index2 < rawBytes2.Length) 25 | { 26 | var index = 0x0; 27 | 28 | var rawBytes = new byte[pageSize]; 29 | Buffer.BlockCopy(rawBytes2, index2, rawBytes, 0, pageSize); 30 | 31 | var headerBytes = new byte[4]; 32 | 33 | fileStream.Seek(0, SeekOrigin.Begin); 34 | 35 | fileStream.Read(headerBytes, 0, 4); 36 | 37 | var sigActual = BitConverter.ToInt32(headerBytes, 0); 38 | 39 | if (sig != sigActual) 40 | { 41 | throw new Exception("Invalid header! Expected 'INDX' Signature."); 42 | } 43 | 44 | Entries = new List(); 45 | 46 | index += 4; 47 | 48 | var fixupOffset = BitConverter.ToInt16(rawBytes, index); 49 | index += 2; 50 | var numFixupPairs = BitConverter.ToInt16(rawBytes, index); 51 | index += 2; 52 | var logFileSequenceNumber = BitConverter.ToInt64(rawBytes, index); 53 | index += 8; 54 | var virtualClusterNumber = BitConverter.ToInt64(rawBytes, index); 55 | index += 8; 56 | 57 | var dataStartPosition = index; 58 | 59 | var indexValOffset = BitConverter.ToInt32(rawBytes, index); 60 | index += 4; 61 | var indexNodeSize = BitConverter.ToInt32(rawBytes, index); 62 | index += 4; 63 | var indexAllocatedSize = BitConverter.ToInt32(rawBytes, index); 64 | index += 4; 65 | var indexFlags = BitConverter.ToInt32(rawBytes, index); 66 | index += 4; 67 | 68 | var fixupTotalLength = numFixupPairs * 2; 69 | 70 | var fixupBuffer = new byte[fixupTotalLength]; 71 | Buffer.BlockCopy(rawBytes, fixupOffset, fixupBuffer, 0, fixupTotalLength); 72 | 73 | 74 | var fixupData = new FixupData(fixupBuffer); 75 | 76 | var fixupOk = true; 77 | 78 | //fixup verification 79 | var counter = 512; 80 | foreach (var bytese in fixupData.FixupActual) 81 | { 82 | //adjust the offset to where we need to check 83 | var fixupOffset1 = counter - 2; 84 | 85 | var expected = BitConverter.ToInt16(rawBytes, fixupOffset1); 86 | if (expected != fixupData.FixupExpected) 87 | { 88 | fixupOk = false; 89 | Log.Warning( 90 | "Fixup values do not match at 0x{FixupOffset1:X}. Expected: 0x{FixupExpected:X2}, actual: 0x{Expected:X2}", 91 | fixupOffset1, fixupData.FixupExpected, expected); 92 | } 93 | 94 | //replace fixup expected with actual bytes. bytese has actual replacement values in it. 95 | Buffer.BlockCopy(bytese, 0, rawBytes, fixupOffset1, 2); 96 | 97 | counter += 512; 98 | } 99 | 100 | index += fixupTotalLength; 101 | 102 | while (index % 8 != 0) 103 | { 104 | index += 1; 105 | } 106 | 107 | Log.Verbose("Overall offset: 0x{Index2:X} Starting new INDEX ENTRY AREA at subindex {Index:X}", index2, 108 | index); 109 | 110 | while (index < rawBytes.Length) 111 | { 112 | //var offsetToData = BitConverter.ToUInt16(rawBytes, index); 113 | //var sizeOfData = BitConverter.ToUInt16(rawBytes, index+2); 114 | var sizeOfIndexEntry = BitConverter.ToUInt16(rawBytes, index + 8); 115 | //var sizeOfIndexKey = BitConverter.ToUInt16(rawBytes, index+10); 116 | var flags = BitConverter.ToUInt16(rawBytes, index + 12); 117 | 118 | if (sizeOfIndexEntry == 0x10) 119 | { 120 | sizeOfIndexEntry = 0x58; 121 | } 122 | 123 | if (flags == 3 || sizeOfIndexEntry == 0 || index + sizeOfIndexEntry > rawBytes.Length) 124 | { 125 | break; 126 | } 127 | 128 | var buff = new byte[sizeOfIndexEntry]; 129 | Buffer.BlockCopy(rawBytes, index, buff, 0, 0x58); 130 | 131 | var oe = new OEntry(buff, index2 + index); 132 | 133 | index += sizeOfIndexEntry; 134 | 135 | if (oe.MftReference.MftEntryNumber == 0 && oe.MftReference.MftSequenceNumber == 0) 136 | { 137 | continue; 138 | } 139 | 140 | Entries.Add(oe); 141 | } 142 | 143 | index2 += pageSize; 144 | } 145 | } 146 | 147 | public List Entries { get; } 148 | } 149 | 150 | public class OEntry 151 | { 152 | public enum OEntryFlag 153 | { 154 | HasSubNodes = 0x1, 155 | LastEntry = 0x2 156 | } 157 | 158 | public OEntry(byte[] rawBytes, int absoluteOffset) 159 | { 160 | using var br = new BinaryReader(new MemoryStream(rawBytes)); 161 | AbsoluteOffset = absoluteOffset; 162 | OffsetToData = br.ReadUInt16(); 163 | DataSize = br.ReadUInt16(); 164 | br.ReadInt32(); //padding 165 | IndexEntrySize = br.ReadUInt16(); 166 | IndexKeySize = br.ReadUInt16(); 167 | Flags = (OEntryFlag)br.ReadInt16(); 168 | br.ReadInt16(); //padding 169 | ObjectId = new Guid(br.ReadBytes(16)); 170 | MftReference = new MftEntryInfo(br.ReadBytes(8)); 171 | BirthVolumeId = new Guid(br.ReadBytes(16)); 172 | BirthObjectId = new Guid(br.ReadBytes(16)); 173 | DomainId = new Guid(br.ReadBytes(16)); 174 | 175 | var tempMac = ObjectId.ToString().Split('-').Last(); 176 | ObjectIdMacAddress = Regex.Replace(tempMac, ".{2}", "$0:"); 177 | 178 | tempMac = BirthObjectId.ToString().Split('-').Last(); 179 | BirthVolumeIdMacAddress = Regex.Replace(tempMac, ".{2}", "$0:"); 180 | 181 | ObjectIdCreatedOn = GetDateTimeOffsetFromGuid(ObjectId); 182 | BirthVolumeIdCreatedOn = GetDateTimeOffsetFromGuid(BirthObjectId); 183 | } 184 | 185 | public int AbsoluteOffset { get; } 186 | public ushort OffsetToData { get; } 187 | public ushort DataSize { get; } 188 | public ushort IndexEntrySize { get; } 189 | public ushort IndexKeySize { get; } 190 | public OEntryFlag Flags { get; } 191 | 192 | public Guid ObjectId { get; } 193 | public MftEntryInfo MftReference { get; } 194 | public Guid BirthVolumeId { get; } 195 | public Guid BirthObjectId { get; } 196 | public Guid DomainId { get; } 197 | 198 | public string ObjectIdMacAddress { get; } 199 | public DateTimeOffset ObjectIdCreatedOn { get; } 200 | 201 | 202 | public string BirthVolumeIdMacAddress { get; } 203 | public DateTimeOffset BirthVolumeIdCreatedOn { get; } 204 | 205 | private DateTimeOffset GetDateTimeOffsetFromGuid(Guid guid) 206 | { 207 | // offset to move from 1/1/0001, which is 0-time for .NET, to gregorian 0-time of 10/15/1582 208 | var gregorianCalendarStart = new DateTimeOffset(1582, 10, 15, 0, 0, 0, TimeSpan.Zero); 209 | const int versionByte = 7; 210 | const int versionByteMask = 0x0f; 211 | const int versionByteShift = 4; 212 | const byte timestampByte = 0; 213 | 214 | var bytes = guid.ToByteArray(); 215 | 216 | // reverse the version 217 | bytes[versionByte] &= versionByteMask; 218 | bytes[versionByte] |= 0x01 >> versionByteShift; 219 | 220 | var timestampBytes = new byte[8]; 221 | Array.Copy(bytes, timestampByte, timestampBytes, 0, 8); 222 | 223 | var timestamp = BitConverter.ToInt64(timestampBytes, 0); 224 | var ticks = timestamp + gregorianCalendarStart.Ticks; 225 | 226 | return new DateTimeOffset(ticks, TimeSpan.Zero); 227 | } 228 | 229 | public override string ToString() 230 | { 231 | return 232 | $"Abs offset: {AbsoluteOffset} MFT Info: {MftReference} Object Id: {ObjectId} Birth Volume Id: {BirthVolumeId} Birth Object Id: {BirthObjectId} Domain Id: {DomainId} Flags: {Flags} ObjectId MAC: {ObjectIdMacAddress} ObjectIdCreatedOn: {ObjectIdCreatedOn.ToUniversalTime():yyyy-MM-dd HH:mm:ss.fffffff} BirthVolumeId MAC: {BirthVolumeIdMacAddress} BirthVolumeIdCreatedOn: {BirthVolumeIdCreatedOn.ToUniversalTime():yyyy-MM-dd HH:mm:ss.fffffff}"; 233 | } 234 | } -------------------------------------------------------------------------------- /O/O.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | MIT 5 | 6 | 10 7 | https://github.com/EricZimmerman/MFT 8 | https://github.com/EricZimmerman/MFT 9 | Eric R. Zimmerman 10 | O Parser 11 | Eric R. Zimmerman 12 | 1.5.1 13 | 14 | $MFT, $Boot, usn, $J, $I30, NTFS 15 | README.md 16 | icon.png 17 | True 18 | 19 | $(NoWarn);CS1591 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | all 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /O/OFile.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace O; 4 | 5 | public static class OFile 6 | { 7 | public static O Load(string siiFile) 8 | { 9 | if (File.Exists(siiFile) == false) 10 | { 11 | throw new FileNotFoundException($"'{siiFile}' not found"); 12 | } 13 | 14 | using var fs = new FileStream(siiFile, FileMode.Open, FileAccess.Read); 15 | return new O(fs); 16 | } 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MFT 2 | MFT parser 3 | 4 | Open Source Development funding and support provided by the following contributors: [SANS Institute](http://sans.org/) and [SANS DFIR](http://dfir.sans.org/). 5 | -------------------------------------------------------------------------------- /SDS/FixupData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Secure; 6 | 7 | public class FixupData 8 | { 9 | public FixupData(byte[] fixupDataRaw) 10 | { 11 | FixupExpected = BitConverter.ToInt16(fixupDataRaw, 0); 12 | FixupActual = new List(); 13 | 14 | var index = 2; 15 | 16 | while (index < fixupDataRaw.Length) 17 | { 18 | var b = new byte[2]; 19 | Buffer.BlockCopy(fixupDataRaw, index, b, 0, 2); 20 | FixupActual.Add(b); 21 | index += 2; 22 | } 23 | } 24 | 25 | /// 26 | /// the data expected at the end of each 512 byte chunk 27 | /// 28 | public short FixupExpected { get; } 29 | 30 | /// 31 | /// The actual bytes to be overlayed before processing a record, in order 32 | /// 33 | public List FixupActual { get; } 34 | 35 | public override string ToString() 36 | { 37 | var sb = new StringBuilder(); 38 | 39 | foreach (var bytese in FixupActual) 40 | { 41 | var bb = BitConverter.ToString(bytese); 42 | sb.Append($"{bb}|"); 43 | } 44 | 45 | var fua = sb.ToString().TrimEnd('|'); 46 | 47 | return $"Expected: {BitConverter.ToString(BitConverter.GetBytes(FixupExpected))} Fixup Actual: {fua}"; 48 | } 49 | } -------------------------------------------------------------------------------- /SDS/Sdh.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Secure; 4 | 5 | public class Sdh 6 | { 7 | public Sdh(Stream fileStream) 8 | { 9 | // var logger = LogManager.GetLogger("SDH"); 10 | // 11 | // var sig = 0x58444E49; 12 | // 13 | // // var index = 0x0; 14 | // 15 | // var headerBytes = new byte[4]; 16 | // 17 | // fileStream.Read(headerBytes, 0, 4); 18 | // 19 | // 20 | // var sigActual = BitConverter.ToInt32(headerBytes, 0); 21 | // 22 | // if (sig != sigActual) 23 | // { 24 | // { 25 | // throw new Exception("Invalid header! Expected 'INDX' Signature."); 26 | // } 27 | // } 28 | // 29 | // index += 4; 30 | // 31 | // var fixupOffset = BitConverter.ToInt16(rawBytes, index); 32 | // index += 2; 33 | // var numFixupPairs = BitConverter.ToInt16(rawBytes, index); 34 | // index += 2; 35 | // var logFileSequenceNumber = BitConverter.ToInt64(rawBytes, index); 36 | // index += 8; 37 | // var virtualClusterNumber = BitConverter.ToInt64(rawBytes, index); 38 | // index += 8; 39 | // 40 | // var dataStartPosition = index; 41 | // 42 | // var indexValOffset = BitConverter.ToInt32(rawBytes, index); 43 | // index += 4; 44 | // var indexNodeSize = BitConverter.ToInt32(rawBytes, index); 45 | // index += 4; 46 | // var indexAllocatedSize = BitConverter.ToInt32(rawBytes, index); 47 | // index += 4; 48 | // var indexFlags = BitConverter.ToInt32(rawBytes, index); 49 | // index += 4; 50 | // 51 | // var fixupTotalLength = numFixupPairs * 2; 52 | // 53 | // var fixupBuffer = new byte[fixupTotalLength]; 54 | // Buffer.BlockCopy(rawBytes, fixupOffset, fixupBuffer, 0, fixupTotalLength); 55 | // 56 | // 57 | // var fixupData = new FixupData(fixupBuffer); 58 | // 59 | // var fixupOk = true; 60 | // 61 | // //fixup verification 62 | // var counter = 512; 63 | // foreach (var bytese in fixupData.FixupActual) 64 | // { 65 | // //adjust the offset to where we need to check 66 | // var fixupOffset1 = counter - 2; 67 | // 68 | // var expected = BitConverter.ToInt16(rawBytes, fixupOffset1); 69 | // if (expected != fixupData.FixupExpected) 70 | // { 71 | // fixupOk = false; 72 | // logger.Warn( 73 | // $"Fixup values do not match at 0x{fixupOffset1:X}. Expected: 0x{fixupData.FixupExpected:X2}, actual: 0x{expected:X2}"); 74 | // } 75 | // 76 | // //replace fixup expected with actual bytes. bytese has actual replacement values in it. 77 | // Buffer.BlockCopy(bytese, 0, rawBytes, fixupOffset1, 2); 78 | // 79 | // counter += 512; 80 | // } 81 | // 82 | // index += fixupTotalLength; 83 | // 84 | // while (index % 8 != 0) 85 | // { 86 | // index += 1; 87 | // } 88 | // 89 | // //TODO finish? 90 | // 91 | // //figure out how to use indexAllocated ot at least indexnode size. do we need both? indexallocated + 0x18 is start of next index record 92 | // 93 | // while (index < dataStartPosition + indexAllocatedSize) 94 | // { 95 | // var startIndex = index; 96 | // 97 | // 98 | // var offsetToData = BitConverter.ToInt16(rawBytes, index); 99 | // index += 2; 100 | // var dataSize = BitConverter.ToInt16(rawBytes, index); 101 | // index += 2; 102 | // 103 | // index += 4; //padding 104 | // 105 | // var indexEntrySize = BitConverter.ToInt16(rawBytes, index); 106 | // index += 2; 107 | // var indexEntryKey = BitConverter.ToInt16(rawBytes, index); 108 | // index += 2; 109 | // 110 | // var flags = BitConverter.ToInt16(rawBytes, index); 111 | // index += 2; 112 | // 113 | // index += 2; //padding 114 | // 115 | // 116 | // var hash = BitConverter.ToInt32(rawBytes, index); 117 | // index += 4; 118 | // 119 | // Debug.WriteLine( 120 | // $"startIndex 0x {startIndex:X} OffsetToData: 0x {offsetToData:X} dataSize: 0x {dataSize:X} "); 121 | // } 122 | } 123 | } -------------------------------------------------------------------------------- /SDS/SdhFile.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Secure; 4 | 5 | public static class SdhFile 6 | { 7 | public static Sdh Load(string sdhFile) 8 | { 9 | if (File.Exists(sdhFile) == false) 10 | { 11 | throw new FileNotFoundException($"'{sdhFile}' not found"); 12 | } 13 | 14 | using var fs = new FileStream(sdhFile, FileMode.Open, FileAccess.Read); 15 | return new Sdh(fs); 16 | } 17 | } -------------------------------------------------------------------------------- /SDS/Sds.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using MFT.Attributes; 5 | using Serilog; 6 | 7 | namespace Secure; 8 | 9 | public class Sds 10 | { 11 | public static uint LastOffset; 12 | 13 | public Sds(Stream fileStream) 14 | { 15 | uint index = 0x0; 16 | 17 | SdsEntries = new List(); 18 | 19 | 20 | var rawBytes = new byte[fileStream.Length]; 21 | fileStream.Read(rawBytes, 0, (int)fileStream.Length); 22 | 23 | while (index < rawBytes.Length) 24 | { 25 | if (index + 16 > rawBytes.Length) 26 | //out of data 27 | { 28 | break; 29 | } 30 | 31 | var hash = BitConverter.ToUInt32(rawBytes, (int)index); 32 | var id = BitConverter.ToUInt32(rawBytes, (int)index + 4); 33 | var offset = BitConverter.ToUInt64(rawBytes, (int)index + 4 + 4); 34 | var size = BitConverter.ToUInt32(rawBytes, (int)index + 4 + 4 + 8); 35 | 36 | if (index == LastOffset && hash == 0x0 && id == 0x0 && offset == 0x0 && size == 0x0) 37 | { 38 | //nothing here, go to next page 39 | index += 0x40000; 40 | continue; 41 | } 42 | 43 | LastOffset = index; 44 | 45 | Log.Debug("LastOffset is 0x{LastOffset}", LastOffset); 46 | 47 | if (offset == 0 && size == 0 || offset > (ulong)rawBytes.Length) 48 | { 49 | //end of page, so get to start of next section 50 | while (index % 0x40000 != 0) 51 | { 52 | index += 1; 53 | } 54 | 55 | continue; 56 | } 57 | 58 | if (id > 0 && offset < (ulong)rawBytes.Length) //size < 0x2000 59 | { 60 | Log.Debug( 61 | "Starting index: 0x{LastOffset:X} Offset 0x{Offset:X} Hash: 0x{Hash:X} id: {Id} size 0x{Size:X}", 62 | LastOffset, offset, hash, id, size); 63 | 64 | var dataSize = size - 0x14; 65 | 66 | if (dataSize > rawBytes.Length - (int)LastOffset) 67 | { 68 | break; 69 | } 70 | 71 | var buff = new byte[dataSize]; 72 | Buffer.BlockCopy(rawBytes, (int)(offset + 0x14), buff, 0, (int)dataSize); 73 | 74 | var sk = new SkSecurityDescriptor(buff); 75 | Log.Verbose("{Sk}", sk); 76 | 77 | var sde = new SdsEntry(hash, id, offset, size, sk, LastOffset); 78 | 79 | SdsEntries.Add(sde); 80 | } 81 | 82 | if (size == 0) 83 | { 84 | size = 16; 85 | } 86 | 87 | index += size; 88 | 89 | //padding calculation 90 | while (index % 16 != 0) 91 | { 92 | index += 1; 93 | } 94 | } 95 | } 96 | 97 | public List SdsEntries { get; } 98 | } -------------------------------------------------------------------------------- /SDS/SdsEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MFT.Attributes; 3 | 4 | namespace Secure; 5 | 6 | public class SdsEntry 7 | { 8 | private readonly uint _hash; 9 | 10 | public SdsEntry(uint hash, uint id, ulong offset, uint size, SkSecurityDescriptor sk, ulong fileOffset) 11 | { 12 | _hash = hash; 13 | Id = id; 14 | Offset = offset; 15 | Size = size; 16 | SecurityDescriptor = sk; 17 | FileOffset = fileOffset; 18 | } 19 | 20 | 21 | public string Hash => GetHash(); 22 | 23 | public uint Id { get; } 24 | public ulong Offset { get; } 25 | public uint Size { get; } 26 | public ulong FileOffset { get; } 27 | 28 | public SkSecurityDescriptor SecurityDescriptor { get; } 29 | 30 | private string GetHash() 31 | { 32 | var b = BitConverter.GetBytes(_hash); 33 | return $"{b[0]:X2}{b[1]:X2}{b[2]:X2}{b[3]:X2}"; 34 | } 35 | } -------------------------------------------------------------------------------- /SDS/SdsFile.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Secure; 3 | 4 | namespace SDS; 5 | 6 | public static class SdsFile 7 | { 8 | public static Sds Load(string sdsFile) 9 | { 10 | if (File.Exists(sdsFile) == false) 11 | { 12 | throw new FileNotFoundException($"'{sdsFile}' not found"); 13 | } 14 | 15 | using var fs = new FileStream(sdsFile, FileMode.Open, FileAccess.Read); 16 | return new Sds(fs); 17 | } 18 | } -------------------------------------------------------------------------------- /SDS/Secure.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 10 6 | MIT 7 | https://github.com/EricZimmerman/MFT 8 | $SDS parser 9 | Eric R. Zimmerman 10 | https://github.com/EricZimmerman/MFT 11 | 1.5.1 12 | Eric R. Zimmerman 13 | 14 | $MFT, $Boot, usn, $J, $I30, NTFS 15 | README.md 16 | icon.png 17 | True 18 | 19 | $(NoWarn);CS1591 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | runtime; build; native; contentfiles; analyzers; buildtransitive 31 | 32 | 33 | all 34 | runtime; build; native; contentfiles; analyzers; buildtransitive 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /SDS/Sii.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | 5 | namespace Secure; 6 | 7 | public class Sii 8 | { 9 | public Sii(Stream fileStream) 10 | { 11 | //TODO finish? see Sdh for framework 12 | 13 | var sig = 0x58444E49; 14 | 15 | // var index = 0x0; 16 | 17 | var headerBytes = new byte[4]; 18 | 19 | fileStream.Read(headerBytes, 0, 4); 20 | 21 | var sigActual = BitConverter.ToInt32(headerBytes, 0); 22 | 23 | if (sig != sigActual) 24 | { 25 | throw new Exception("Invalid header! Expected 'INDX' Signature."); 26 | } 27 | 28 | // index += 4; 29 | 30 | // 4 4 Security descriptor hash 31 | // 8 4 Security descriptor identifier 32 | // 33 | // 12 8 Security descriptor data offset (in $SDS) 34 | // 35 | // 20 4 Security descriptor data size (in $SDS) 36 | 37 | var hashBuffer = new byte[4]; 38 | var idBuffer = new byte[4]; 39 | var offsetBuffer = new byte[8]; 40 | var sizeBuffer = new byte[4]; 41 | 42 | while (fileStream.Position < fileStream.Length) 43 | { 44 | var startIndex = fileStream.Position; 45 | 46 | fileStream.Read(hashBuffer, 0, 4); 47 | 48 | var hash = BitConverter.ToInt32(hashBuffer, 0); 49 | // index += 4; 50 | 51 | fileStream.Read(idBuffer, 0, 4); 52 | 53 | var id = BitConverter.ToInt32(idBuffer, 0); 54 | // index += 4; 55 | 56 | fileStream.Read(offsetBuffer, 0, 8); 57 | 58 | var offset = BitConverter.ToInt64(offsetBuffer, 0); 59 | // index += 8; 60 | 61 | fileStream.Read(sizeBuffer, 0, 4); 62 | 63 | var size = BitConverter.ToInt32(sizeBuffer, 0); 64 | // index += 4; 65 | 66 | Debug.WriteLine($"Hash: {hash} offset: 0x {offset:X} size 0x '{size:X}' startIndex 0x {startIndex}"); 67 | 68 | 69 | // index += 4; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /SDS/SiiFile.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Secure; 4 | 5 | public static class SiiFile 6 | { 7 | public static Sii Load(string siiFile) 8 | { 9 | if (File.Exists(siiFile) == false) 10 | { 11 | throw new FileNotFoundException($"'{siiFile}' not found"); 12 | } 13 | 14 | using var fs = new FileStream(siiFile, FileMode.Open, FileAccess.Read); 15 | return new Sii(fs); 16 | } 17 | } -------------------------------------------------------------------------------- /Usn/MFTInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Usn; 4 | 5 | public class MftInformation 6 | { 7 | public MftInformation(byte[] rawBytes) 8 | { 9 | if (rawBytes.Length != 8) 10 | { 11 | throw new ArgumentException("rawBytes must be 8 bytes long!"); 12 | } 13 | 14 | var sequenceNumber = BitConverter.ToUInt16(rawBytes, 6); 15 | 16 | ulong entryIndex = 0; 17 | 18 | ulong entryIndex1 = BitConverter.ToUInt32(rawBytes, 0); 19 | ulong entryIndex2 = BitConverter.ToUInt16(rawBytes, 4); 20 | 21 | if (entryIndex2 == 0) 22 | { 23 | entryIndex = entryIndex1; 24 | } 25 | else 26 | { 27 | entryIndex2 = entryIndex2 * 16777216; //2^24 28 | entryIndex = entryIndex1 + entryIndex2; 29 | } 30 | 31 | EntryNumber = entryIndex; 32 | SequenceNumber = sequenceNumber; 33 | } 34 | 35 | public ulong EntryNumber { get; set; } 36 | 37 | public uint SequenceNumber { get; set; } 38 | 39 | public override string ToString() 40 | { 41 | return $"Entry 0x{EntryNumber:X}, Seq 0x{SequenceNumber:X}"; 42 | } 43 | } -------------------------------------------------------------------------------- /Usn/Usn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Serilog; 5 | 6 | namespace Usn; 7 | 8 | public class Usn 9 | { 10 | private const int PageSize = 0x1000; 11 | 12 | public static uint LastOffset; 13 | 14 | public Usn(Stream fileStream, long startingOffset) 15 | { 16 | UsnEntries = new List(); 17 | 18 | fileStream.Seek(startingOffset, SeekOrigin.Begin); 19 | 20 | var lastGoodPageOffset = startingOffset; 21 | 22 | Log.Verbose("Beginning processing"); 23 | 24 | while (fileStream.Position < fileStream.Length) 25 | { 26 | Log.Verbose("Starting fileStream.Position 0x{Position:X8}", fileStream.Position); 27 | 28 | LastOffset = (uint)fileStream.Position; 29 | 30 | var calcBuff = new byte[8]; 31 | fileStream.Read(calcBuff, 0, 8); 32 | fileStream.Seek(-8, SeekOrigin.Current); //reverse to where we were 33 | 34 | var size = BitConverter.ToUInt32(calcBuff, 0); 35 | var majorVer = BitConverter.ToInt16(calcBuff, 4); //used for error checking 36 | 37 | if (size == 0) 38 | { 39 | Log.Verbose("Size is zero. Increasing index by 0x{PageSize:X}", PageSize); 40 | 41 | fileStream.Seek(lastGoodPageOffset + PageSize, SeekOrigin.Begin); 42 | 43 | lastGoodPageOffset += PageSize; 44 | 45 | continue; 46 | } 47 | 48 | if (size > PageSize) 49 | { 50 | Log.Verbose("Junk data found at 0x{Position:X8}. Increasing index by 0x{PageSize:X}", 51 | fileStream.Position, PageSize); 52 | 53 | lastGoodPageOffset += PageSize; 54 | 55 | fileStream.Seek(lastGoodPageOffset, SeekOrigin.Begin); 56 | 57 | continue; 58 | } 59 | 60 | if (size < 0x38 || size > 0x250 || majorVer != 2 61 | ) //~ minimum length, so jump to next page || max defined as max filename length (0xFF) + min length (it should not be bigger than this) 62 | { 63 | Log.Verbose( 64 | "Strange size or ver # incorrect at 0x{Position:X8}. Increasing index by 0x{PageSize:X}. Size: 0x{Size:X} version: {MajorVer}", 65 | fileStream.Position, PageSize, size, majorVer); 66 | 67 | fileStream.Seek(lastGoodPageOffset + PageSize, SeekOrigin.Begin); 68 | 69 | lastGoodPageOffset += PageSize; 70 | 71 | continue; 72 | } 73 | 74 | if (fileStream.Position % PageSize == 0) 75 | { 76 | Log.Debug("Setting lastGoodPageOffset to 0x{Position:X8}", fileStream.Position); 77 | 78 | lastGoodPageOffset = fileStream.Position; 79 | } 80 | 81 | Log.Verbose("Processing UsnEntry at 0x{Position:X}", startingOffset + fileStream.Position); 82 | 83 | var buff = new byte[size]; 84 | 85 | fileStream.Read(buff, 0, (int)size); 86 | 87 | var ue = new UsnEntry(buff, LastOffset); 88 | UsnEntries.Add(ue); 89 | } 90 | 91 | Log.Debug("Found {Count:N0} records", UsnEntries.Count); 92 | } 93 | 94 | public List UsnEntries { get; } 95 | } -------------------------------------------------------------------------------- /Usn/Usn.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 10 5 | 6 | https://github.com/EricZimmerman/MFT 7 | Eric R. Zimmerman 8 | USN $J parser 9 | https://github.com/EricZimmerman/MFT 10 | 1.5.1 11 | Eric R. Zimmerman 12 | MIT 13 | 14 | $MFT, $Boot, usn, $J, $I30, NTFS 15 | README.md 16 | icon.png 17 | True 18 | 19 | $(NoWarn);CS1591 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | all 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Usn/UsnEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Usn; 7 | 8 | public class UsnEntry 9 | { 10 | [Flags] 11 | public enum FileAttributeFlag 12 | { 13 | ReadOnly = 0x01, 14 | Hidden = 0x02, 15 | System = 0x04, 16 | VolumeLabel = 0x08, 17 | Directory = 0x010, 18 | Archive = 0x020, 19 | Device = 0x040, 20 | Normal = 0x080, 21 | Temporary = 0x0100, 22 | SparseFile = 0x0200, 23 | ReparsePoint = 0x0400, 24 | Compressed = 0x0800, 25 | Offline = 0x01000, 26 | NotContentIndexed = 0x02000, 27 | Encrypted = 0x04000, 28 | IntegrityStream = 0x08000, 29 | Virtual = 0x010000, 30 | NoScrubData = 0x020000, 31 | HasEa = 0x040000, 32 | IsDirectory = 0x10000000, 33 | IsIndexView = 0x20000000 34 | } 35 | 36 | [Flags] 37 | public enum UpdateReasonFlag : uint 38 | { 39 | [Description( 40 | "A user has either changed one or more file or directory attributes (for example, the read-only, hidden, system, archive, or sparse attribute), or one or more time stamps.")] 41 | BasicInfoChange = 0x00008000, 42 | 43 | [Description("The file or directory is closed.")] 44 | Close = 0x80000000, 45 | 46 | [Description("The compression state of the file or directory is changed from or to compressed.")] 47 | CompressionChange = 0x00020000, 48 | 49 | [Description("The file or directory is extended (added to).")] 50 | DataExtend = 0x00000002, 51 | 52 | [Description("The data in the file or directory is overwritten.")] 53 | DataOverwrite = 0x00000001, 54 | 55 | [Description("The file or directory is truncated.")] 56 | DataTruncation = 0x00000004, 57 | 58 | [Description( 59 | "The user made a change to the extended attributes of a file or directory. These NTFS file system attributes are not accessible to Windows-based applications.")] 60 | EaChange = 0x00000400, 61 | 62 | [Description("The file or directory is encrypted or decrypted.")] 63 | EncryptionChange = 0x00040000, 64 | 65 | [Description("The file or directory is created for the first time.")] 66 | FileCreate = 0x00000100, 67 | 68 | [Description("The file or directory is deleted.")] 69 | FileDelete = 0x00000200, 70 | 71 | [Description( 72 | "An NTFS file system hard link is added to or removed from the file or directory. An NTFS file system hard link, similar to a POSIX hard link, is one of several directory entries that see the same file or directory.")] 73 | HardLinkChange = 0x00010000, 74 | 75 | [Description( 76 | "A user changes the FILE_ATTRIBUTE_NOT_CONTENT_INDEXED attribute. That is, the user changes the file or directory from one where content can be indexed to one where content cannot be indexed, or vice versa. Content indexing permits rapid searching of data by building a database of selected content.")] 77 | IndexableChange = 0x00004000, 78 | 79 | [Description( 80 | "A user changed the state of the FILE_ATTRIBUTE_INTEGRITY_STREAM attribute for the given stream. On the ReFS file system, integrity streams maintain a checksum of all data for that stream, so that the contents of the file can be validated during read or write operations.")] 81 | IntegrityChange = 0x00800000, 82 | 83 | [Description("The one or more named data streams for a file are extended (added to).")] 84 | NamedDataExtend = 0x00000020, 85 | 86 | [Description("The data in one or more named data streams for a file is overwritten.")] 87 | NamedDataOverwrite = 0x00000010, 88 | 89 | [Description("The one or more named data streams for a file is truncated.")] 90 | NamedDataTruncation = 0x00000040, 91 | 92 | [Description("The object identifier of a file or directory is changed.")] 93 | ObjectIdChange = 0x00080000, 94 | 95 | [Description( 96 | "A file or directory is renamed, and the file name in the USN_RECORD structure is the new name.")] 97 | RenameNewName = 0x00002000, 98 | 99 | [Description( 100 | "The file or directory is renamed, and the file name in the USN_RECORD structure is the previous name.")] 101 | RenameOldName = 0x00001000, 102 | 103 | [Description( 104 | "The reparse point that is contained in a file or directory is changed, or a reparse point is added to or deleted from a file or directory.")] 105 | ReparsePointChange = 0x00100000, 106 | 107 | [Description("A change is made in the access rights to a file or directory.")] 108 | SecurityChange = 0x00000800, 109 | 110 | [Description("A named stream is added to or removed from a file, or a named stream is renamed.")] 111 | StreamChange = 0x00200000, 112 | 113 | [Description("The given stream is modified through a TxF transaction.")] 114 | TransactedChange = 0x00400000 115 | } 116 | 117 | [Flags] 118 | public enum UpdateSourceFlag 119 | { 120 | Na = 0x0, 121 | 122 | [Description( 123 | "The operation adds a private data stream to a file or directory. An example might be a virus detector adding checksum information. As the virus detector modifies the item, the system generates USN records. USN_SOURCE_AUXILIARY_DATA indicates that the modifications did not change the application data.")] 124 | AuxiliaryData = 0x00000002, 125 | 126 | [Description( 127 | "The operation provides information about a change to the file or directory made by the operating system. A typical use is when the Remote Storage system moves data from external to local storage.")] 128 | DataManagement = 0x00000001, 129 | 130 | [Description( 131 | "The operation is modifying a file to match the contents of the same file which exists in another member of the replica set.")] 132 | ReplicationManagement = 0x00000004, 133 | 134 | [Description( 135 | "The operation is modifying a file on client systems to match the contents of the same file that exists in the cloud.")] 136 | ClientReplicationManagement = 0x00000008 137 | } 138 | 139 | public UsnEntry(byte[] rawBytes, long offset) 140 | { 141 | OffsetToData = offset; 142 | Size = BitConverter.ToInt32(rawBytes, 0); 143 | MajorVersion = BitConverter.ToInt16(rawBytes, 4); 144 | MinorVersion = BitConverter.ToInt16(rawBytes, 6); 145 | 146 | var frb = new byte[8]; 147 | Buffer.BlockCopy(rawBytes, 8, frb, 0, 8); 148 | FileReference = new MftInformation(frb); 149 | 150 | var pfrb = new byte[8]; 151 | Buffer.BlockCopy(rawBytes, 16, pfrb, 0, 8); 152 | ParentFileReference = new MftInformation(pfrb); 153 | 154 | UpdateSequenceNumber = BitConverter.ToUInt64(rawBytes, 24); 155 | 156 | UpdateTimestamp = DateTimeOffset.FromFileTime(BitConverter.ToInt64(rawBytes, 32)).UtcDateTime; 157 | 158 | UpdateReasons = (UpdateReasonFlag)BitConverter.ToUInt32(rawBytes, 40); 159 | UpdateSources = (UpdateSourceFlag)BitConverter.ToUInt32(rawBytes, 44); 160 | 161 | SecurityDescriptorId = BitConverter.ToInt32(rawBytes, 48); 162 | FileAttributes = (FileAttributeFlag)BitConverter.ToInt32(rawBytes, 52); 163 | 164 | NameSize = BitConverter.ToInt16(rawBytes, 56); 165 | NameOffset = BitConverter.ToInt16(rawBytes, 58); 166 | 167 | Name = Encoding.Unicode.GetString(rawBytes, NameOffset, NameSize); 168 | } 169 | 170 | public int Size { get; } 171 | public short MajorVersion { get; } 172 | public short MinorVersion { get; } 173 | 174 | public MftInformation FileReference { get; } 175 | public MftInformation ParentFileReference { get; } 176 | 177 | public ulong UpdateSequenceNumber { get; } 178 | 179 | public DateTimeOffset UpdateTimestamp { get; } 180 | 181 | public UpdateReasonFlag UpdateReasons { get; } 182 | public UpdateSourceFlag UpdateSources { get; } 183 | 184 | public int SecurityDescriptorId { get; } 185 | public FileAttributeFlag FileAttributes { get; } 186 | 187 | public short NameSize { get; } 188 | public short NameOffset { get; } 189 | 190 | public string Name { get; } 191 | 192 | public long OffsetToData { get; set; } 193 | 194 | public override string ToString() 195 | { 196 | return 197 | $"Offset: 0x {OffsetToData:X} Name: {Name}, Usn: {UpdateSequenceNumber}, File ref: {FileReference} parent ref: {ParentFileReference}, Timestamp: {UpdateTimestamp:yyyy/MM/dd HH:mm:ss.fffffff}, Reasons: {UpdateReasons}, File attr: {FileAttributes}"; 198 | } 199 | 200 | public static string GetDescriptionFromEnumValue(Enum value) 201 | { 202 | var attribute = value.GetType() 203 | .GetField(value.ToString()) 204 | .GetCustomAttributes(typeof(DescriptionAttribute), false) 205 | .SingleOrDefault() as DescriptionAttribute; 206 | return attribute == null ? value.ToString() : attribute.Description; 207 | } 208 | } -------------------------------------------------------------------------------- /Usn/UsnFile.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Text; 4 | using Serilog; 5 | 6 | namespace Usn; 7 | 8 | public class UsnFile 9 | { 10 | public static Usn Load(string usnFilePath) 11 | { 12 | if (File.Exists(usnFilePath) == false) 13 | { 14 | throw new FileNotFoundException($"'{usnFilePath}' not found"); 15 | } 16 | 17 | long start = 0; 18 | 19 | using (var br = new BinaryReader(new FileStream(usnFilePath, FileMode.Open, FileAccess.Read))) 20 | { 21 | Log.Verbose("Binary reader open"); 22 | 23 | var firstByte = br.ReadByte(); 24 | br.BaseStream.Seek(0, SeekOrigin.Begin); 25 | 26 | if (firstByte != 0) 27 | { 28 | Log.Verbose("First byte is not zero. Reading everything"); 29 | 30 | return new Usn(br.BaseStream, 0); 31 | } 32 | 33 | Log.Verbose("Beginning data appears to be sparse"); 34 | //beginning is sparse, so we have to find the start of the data 35 | 36 | start = FindStartingOffset(br.BaseStream); 37 | } 38 | 39 | return new Usn(new FileStream(usnFilePath, FileMode.Open, FileAccess.Read), start); 40 | } 41 | 42 | private static bool CheckByteRangeAllZeros(byte[] buff) 43 | { 44 | return buff.All(b => b == 0); 45 | } 46 | 47 | public static long FindStartingOffset(Stream usnBytes) 48 | { 49 | long startIndex = 0; 50 | 51 | using (var br = new BinaryReader(usnBytes, Encoding.ASCII, true)) 52 | { 53 | Log.Verbose("Binary reader open"); 54 | 55 | var firstByte = br.ReadByte(); 56 | br.BaseStream.Seek(0, SeekOrigin.Begin); 57 | 58 | if (firstByte != 0) 59 | { 60 | return 0; 61 | } 62 | 63 | Log.Verbose("Beginning data appears to be sparse"); 64 | //beginning is sparse, so we have to find the start of the data 65 | 66 | long startOffset; 67 | long lastCheckedOffset = 0; 68 | 69 | long lastDataOffset = 0; 70 | 71 | var currentCheckOffset = br.BaseStream.Length / 2; 72 | 73 | while (true) 74 | { 75 | Log.Verbose( 76 | "currentCheckOffset: {CurrentCheckOffset:X}, lastCheckedOffset: 0x{LastCheckedOffset:X}, lastDataOffset: 0x{LastDataOffset:X}", 77 | currentCheckOffset, lastCheckedOffset, lastDataOffset); 78 | 79 | if (lastDataOffset > 0 && lastDataOffset - lastCheckedOffset < 300) 80 | { 81 | //we are close enough and will walk it out from here. 82 | startOffset = lastCheckedOffset; 83 | break; 84 | } 85 | 86 | br.BaseStream.Seek(currentCheckOffset, SeekOrigin.Begin); 87 | //0x90 bytes of 0s is good enough to know where to start 88 | var bcheck = br.ReadBytes(0x90); 89 | 90 | if (CheckByteRangeAllZeros(bcheck) == false) 91 | { 92 | //we are in data 93 | lastDataOffset = currentCheckOffset; 94 | 95 | //we know we didn't have any data at lastCheckedOffset the last time we looked, so go backwards half way between there and lastDataOffset 96 | currentCheckOffset = lastCheckedOffset + (currentCheckOffset - lastCheckedOffset) / 2; 97 | } 98 | else 99 | { 100 | //no data, so move forward 101 | lastCheckedOffset = currentCheckOffset; 102 | 103 | currentCheckOffset = currentCheckOffset + (br.BaseStream.Length - currentCheckOffset) / 2; 104 | } 105 | } 106 | 107 | br.BaseStream.Seek(startOffset, SeekOrigin.Begin); 108 | 109 | //ignore zeros until we get to the first size 110 | while (br.PeekChar() == 0) 111 | { 112 | br.ReadByte(); 113 | startOffset += 1; 114 | } 115 | 116 | 117 | startIndex = br.BaseStream.Position; 118 | } 119 | 120 | usnBytes.Seek(0, SeekOrigin.Begin); 121 | return startIndex; 122 | } 123 | } -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricZimmerman/MFT/50e4ea636a96359f1d5f0ee0f993802564ff6b86/icon.png --------------------------------------------------------------------------------