├── .gitattributes
├── README.md
├── FileHunter
├── Properties
│ └── AssemblyInfo.cs
├── FileHunter.csproj
└── Program.cs
├── LICENSE
├── FileHunter.sln
└── .gitignore
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FileHunter
2 |
3 | 此项目为钓鱼上线后,快速收集受害者机器可能存在敏感信息的文件列表,方便横向渗透,在内网渗透中更方便查找密码本等细节性问题,更快速的扩大内网攻击成果。
--------------------------------------------------------------------------------
/FileHunter/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("FileCollection")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("FileCollection")]
13 | [assembly: AssemblyCopyright("Copyright © 2023")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // 将 ComVisible 设置为 false 会使此程序集中的类型
18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("b9f7493a-acfc-48f3-bb86-8cde1ea1ff9c")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
33 | //通过使用 "*",如下所示:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Adminxe
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 |
--------------------------------------------------------------------------------
/FileHunter.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.4.33213.308
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileHunter", "FileHunter\FileHunter.csproj", "{B9F7493A-ACFC-48F3-BB86-8CDE1EA1FF9C}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {B9F7493A-ACFC-48F3-BB86-8CDE1EA1FF9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {B9F7493A-ACFC-48F3-BB86-8CDE1EA1FF9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {B9F7493A-ACFC-48F3-BB86-8CDE1EA1FF9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {B9F7493A-ACFC-48F3-BB86-8CDE1EA1FF9C}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {FB82537C-5B09-494F-B607-A24A5881D917}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/FileHunter/FileHunter.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {B9F7493A-ACFC-48F3-BB86-8CDE1EA1FF9C}
8 | Exe
9 | FileHunter
10 | FileHunter
11 | v3.5
12 | 512
13 | true
14 |
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 | true
26 |
27 |
28 | AnyCPU
29 | none
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 | true
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.tlog
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
298 | *.vbp
299 |
300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
301 | *.dsw
302 | *.dsp
303 |
304 | # Visual Studio 6 technical files
305 | *.ncb
306 | *.aps
307 |
308 | # Visual Studio LightSwitch build output
309 | **/*.HTMLClient/GeneratedArtifacts
310 | **/*.DesktopClient/GeneratedArtifacts
311 | **/*.DesktopClient/ModelManifest.xml
312 | **/*.Server/GeneratedArtifacts
313 | **/*.Server/ModelManifest.xml
314 | _Pvt_Extensions
315 |
316 | # Paket dependency manager
317 | .paket/paket.exe
318 | paket-files/
319 |
320 | # FAKE - F# Make
321 | .fake/
322 |
323 | # CodeRush personal settings
324 | .cr/personal
325 |
326 | # Python Tools for Visual Studio (PTVS)
327 | __pycache__/
328 | *.pyc
329 |
330 | # Cake - Uncomment if you are using it
331 | # tools/**
332 | # !tools/packages.config
333 |
334 | # Tabs Studio
335 | *.tss
336 |
337 | # Telerik's JustMock configuration file
338 | *.jmconfig
339 |
340 | # BizTalk build output
341 | *.btp.cs
342 | *.btm.cs
343 | *.odx.cs
344 | *.xsd.cs
345 |
346 | # OpenCover UI analysis results
347 | OpenCover/
348 |
349 | # Azure Stream Analytics local run output
350 | ASALocalRun/
351 |
352 | # MSBuild Binary and Structured Log
353 | *.binlog
354 |
355 | # NVidia Nsight GPU debugger configuration file
356 | *.nvuser
357 |
358 | # MFractors (Xamarin productivity tool) working folder
359 | .mfractor/
360 |
361 | # Local History for Visual Studio
362 | .localhistory/
363 |
364 | # Visual Studio History (VSHistory) files
365 | .vshistory/
366 |
367 | # BeatPulse healthcheck temp database
368 | healthchecksdb
369 |
370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
371 | MigrationBackup/
372 |
373 | # Ionide (cross platform F# VS Code tools) working folder
374 | .ionide/
375 |
376 | # Fody - auto-generated XML schema
377 | FodyWeavers.xsd
378 |
379 | # VS Code files for those working on multiple tools
380 | .vscode/*
381 | !.vscode/settings.json
382 | !.vscode/tasks.json
383 | !.vscode/launch.json
384 | !.vscode/extensions.json
385 | *.code-workspace
386 |
387 | # Local History for Visual Studio Code
388 | .history/
389 |
390 | # Windows Installer files from build outputs
391 | *.cab
392 | *.msi
393 | *.msix
394 | *.msm
395 | *.msp
396 |
397 | # JetBrains Rider
398 | *.sln.iml
399 |
--------------------------------------------------------------------------------
/FileHunter/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using Microsoft.Win32.SafeHandles;
6 | using System.Runtime.InteropServices;
7 | using System.Security.Principal;
8 |
9 | namespace FileHunter
10 | {
11 | class Program
12 | {
13 | static void Main(string[] args)
14 | {
15 | var allDrives = DriveInfo.GetDrives();
16 |
17 | StringBuilder sb = new StringBuilder();
18 |
19 | foreach (var driveToAnalyze in allDrives)
20 | {
21 | if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator))
22 | {
23 | NtfsReader ntfsReader = new NtfsReader(driveToAnalyze, NtfsReader.RetrieveMode.All);
24 | IEnumerable nodes =
25 | ntfsReader.GetNodes(driveToAnalyze.Name);
26 | foreach (NtfsReader.INode node in nodes)
27 | {
28 | if (CheckFile(node.Name,node.FullName))
29 | sb.AppendLine(((node.Attributes & NtfsReader.Attributes.Directory) != 0 ? "Dir;" : "File;") + node.FullName);
30 | }
31 | }
32 | else
33 | {
34 | string path = driveToAnalyze.ToString();
35 | List nameList = new List();
36 | Director(path, nameList);
37 | foreach (var node in nameList)
38 | {
39 | sb.AppendLine(node);
40 | }
41 | }
42 |
43 | }
44 | File.WriteAllText("1.txt", sb.ToString());
45 | Console.WriteLine("Over!");
46 | Console.ReadLine();
47 | }
48 |
49 | public static void Director(string dir, List list)
50 | {
51 | try
52 | {
53 | DirectoryInfo d = new DirectoryInfo(dir);
54 | FileInfo[] files = d.GetFiles();
55 | DirectoryInfo[] directs = d.GetDirectories();
56 | foreach (FileInfo f in files)
57 | {
58 | if (CheckFile(f.Name,f.FullName))
59 | {
60 | list.Add("File;" + f.FullName);
61 | }
62 | }
63 | foreach (DirectoryInfo dd in directs)
64 | {
65 | if (CheckFile(dd.Name, dd.Name))
66 | {
67 | list.Add("Dir;" + dd.FullName);
68 | }
69 | Director(dd.FullName, list);
70 | }
71 | }
72 | catch { }
73 | }
74 |
75 | public static string[] blacklist = new string[]
76 | {
77 | ".exe",".dll",".png",".jpg",".bmp",".xml",".bin",".dat",".manifest","locale","winsxs","windows\\sys"
78 | };
79 |
80 | public static string[] whitelist = new string[]
81 | {
82 | ".ini","pass","conf","account","login","pwd","user","oa","root","密码","账号","账户","配置","服务器","数据库","备忘","常用","通讯录"
83 | };
84 |
85 | public static bool CheckFile(string name, string fullname)
86 | {
87 | foreach (var item in blacklist)
88 | {
89 | if (fullname.ToLower().Contains(item))
90 | {
91 | return false;
92 | }
93 | }
94 | foreach (var item in whitelist)
95 | {
96 |
97 | if (name.ToLower().Contains(item))
98 | {
99 | return true;
100 | }
101 | }
102 | return false;
103 | }
104 | }
105 |
106 |
107 | public sealed partial class NtfsReader : IDisposable
108 | {
109 | public NtfsReader(DriveInfo driveInfo, RetrieveMode retrieveMode)
110 | {
111 | if (driveInfo == null)
112 | throw new ArgumentNullException("driveInfo");
113 |
114 | _driveInfo = driveInfo;
115 | _retrieveMode = retrieveMode;
116 |
117 | StringBuilder builder = new StringBuilder(1024);
118 | GetVolumeNameForVolumeMountPoint(_driveInfo.RootDirectory.Name, builder, builder.Capacity);
119 |
120 | string volume = builder.ToString().TrimEnd(new char[] { '\\' });
121 |
122 | _volumeHandle =
123 | CreateFile(
124 | volume,
125 | FileAccess.Read,
126 | FileShare.All,
127 | IntPtr.Zero,
128 | FileMode.Open,
129 | 0,
130 | IntPtr.Zero
131 | );
132 |
133 | if (_volumeHandle == null || _volumeHandle.IsInvalid)
134 | throw new IOException(
135 | string.Format(
136 | "Unable to open volume {0}. Make sure it exists and that you have Administrator privileges.",
137 | driveInfo
138 | )
139 | );
140 |
141 | using (_volumeHandle)
142 | {
143 | InitializeDiskInfo();
144 |
145 | _nodes = ProcessMft();
146 | }
147 |
148 | _nameIndex = null;
149 | _volumeHandle = null;
150 |
151 | GC.Collect();
152 | }
153 |
154 | public IDiskInfo DiskInfo
155 | {
156 | get { return _diskInfo; }
157 | }
158 |
159 | public List GetNodes(string rootPath)
160 | {
161 | List nodes = new List();
162 |
163 | UInt32 nodeCount = (UInt32)_nodes.Length;
164 | for (UInt32 i = 0; i < nodeCount; ++i)
165 | if (_nodes[i].NameIndex != 0 && GetNodeFullNameCore(i).StartsWith(rootPath, StringComparison.InvariantCultureIgnoreCase))
166 | nodes.Add(new NodeWrapper(this, i, _nodes[i]));
167 |
168 | return nodes;
169 | }
170 |
171 | public byte[] GetVolumeBitmap()
172 | {
173 | return _bitmapData;
174 | }
175 |
176 | #region IDisposable Members
177 |
178 | public void Dispose()
179 | {
180 | if (_volumeHandle != null)
181 | {
182 | _volumeHandle.Dispose();
183 | _volumeHandle = null;
184 | }
185 | }
186 |
187 | #endregion
188 | private string GetNodeFullNameCore(UInt32 nodeIndex)
189 | {
190 | UInt32 node = nodeIndex;
191 |
192 | Stack fullPathNodes = new Stack();
193 | fullPathNodes.Push(node);
194 |
195 | UInt32 lastNode = node;
196 | while (true)
197 | {
198 | UInt32 parent = _nodes[node].ParentNodeIndex;
199 |
200 | if (parent == ROOTDIRECTORY)
201 | break;
202 |
203 | if (parent == lastNode)
204 | throw new InvalidDataException("Detected a loop in the tree structure.");
205 |
206 | fullPathNodes.Push(parent);
207 |
208 | lastNode = node;
209 | node = parent;
210 | }
211 |
212 | StringBuilder fullPath = new StringBuilder();
213 | fullPath.Append(_driveInfo.Name.TrimEnd(new char[] { '\\' }));
214 |
215 | while (fullPathNodes.Count > 0)
216 | {
217 | node = fullPathNodes.Pop();
218 |
219 | fullPath.Append(@"\");
220 | fullPath.Append(GetNameFromIndex(_nodes[node].NameIndex));
221 | }
222 |
223 | return fullPath.ToString();
224 | }
225 | [DllImport("kernel32", CharSet = CharSet.Auto, BestFitMapping = false)]
226 | private static extern bool GetVolumeNameForVolumeMountPoint(String volumeName, StringBuilder uniqueVolumeName, int uniqueNameBufferCapacity);
227 |
228 | [DllImport("kernel32", CharSet = CharSet.Auto, BestFitMapping = false)]
229 | private static extern SafeFileHandle CreateFile(string lpFileName, FileAccess fileAccess, FileShare fileShare, IntPtr lpSecurityAttributes, FileMode fileMode, int dwFlagsAndAttributes, IntPtr hTemplateFile);
230 |
231 | [DllImport("kernel32", CharSet = CharSet.Auto)]
232 | private static extern bool ReadFile(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, ref NativeOverlapped lpOverlapped);
233 |
234 | [Serializable]
235 | private enum FileMode : int
236 | {
237 | Append = 6,
238 | Create = 2,
239 | CreateNew = 1,
240 | Open = 3,
241 | OpenOrCreate = 4,
242 | Truncate = 5
243 | }
244 |
245 | [Serializable, Flags]
246 | private enum FileShare : int
247 | {
248 | None = 0,
249 | Read = 1,
250 | Write = 2,
251 | Delete = 4,
252 | All = Read | Write | Delete
253 | }
254 |
255 | [Serializable, Flags]
256 | private enum FileAccess : int
257 | {
258 | Read = 1,
259 | ReadWrite = 3,
260 | Write = 2
261 | }
262 |
263 | [StructLayout(LayoutKind.Sequential)]
264 | private struct NativeOverlapped
265 | {
266 | public IntPtr privateLow;
267 | public IntPtr privateHigh;
268 | public UInt64 Offset;
269 | public IntPtr EventHandle;
270 |
271 | public NativeOverlapped(UInt64 offset)
272 | {
273 | Offset = offset;
274 | EventHandle = IntPtr.Zero;
275 | privateLow = IntPtr.Zero;
276 | privateHigh = IntPtr.Zero;
277 | }
278 | }
279 | #region Ntfs Structures
280 |
281 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
282 | private unsafe struct BootSector
283 | {
284 | fixed byte AlignmentOrReserved1[3];
285 | public UInt64 Signature;
286 | public UInt16 BytesPerSector;
287 | public byte SectorsPerCluster;
288 | fixed byte AlignmentOrReserved2[26];
289 | public UInt64 TotalSectors;
290 | public UInt64 MftStartLcn;
291 | public UInt64 Mft2StartLcn;
292 | public UInt32 ClustersPerMftRecord;
293 | public UInt32 ClustersPerIndexRecord;
294 | }
295 |
296 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
297 | private struct VolumeData
298 | {
299 | public UInt64 VolumeSerialNumber;
300 | public UInt64 NumberSectors;
301 | public UInt64 TotalClusters;
302 | public UInt64 FreeClusters;
303 | public UInt64 TotalReserved;
304 | public UInt32 BytesPerSector;
305 | public UInt32 BytesPerCluster;
306 | public UInt32 BytesPerFileRecordSegment;
307 | public UInt32 ClustersPerFileRecordSegment;
308 | public UInt64 MftValidDataLength;
309 | public UInt64 MftStartLcn;
310 | public UInt64 Mft2StartLcn;
311 | public UInt64 MftZoneStart;
312 | public UInt64 MftZoneEnd;
313 | }
314 |
315 | private enum RecordType : uint
316 | {
317 | File = 0x454c4946, //'FILE' in ASCII
318 | }
319 |
320 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
321 | private struct RecordHeader
322 | {
323 | public RecordType Type; /* File type, for example 'FILE' */
324 | public UInt16 UsaOffset; /* Offset to the Update Sequence Array */
325 | public UInt16 UsaCount; /* Size in words of Update Sequence Array */
326 | public UInt64 Lsn; /* $LogFile Sequence Number (LSN) */
327 | }
328 |
329 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
330 | private struct INodeReference
331 | {
332 | public UInt32 InodeNumberLowPart;
333 | public UInt16 InodeNumberHighPart;
334 | public UInt16 SequenceNumber;
335 | };
336 |
337 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
338 | private struct FileRecordHeader
339 | {
340 | public RecordHeader RecordHeader;
341 | public UInt16 SequenceNumber; /* Sequence number */
342 | public UInt16 LinkCount; /* Hard link count */
343 | public UInt16 AttributeOffset; /* Offset to the first Attribute */
344 | public UInt16 Flags; /* Flags. bit 1 = in use, bit 2 = directory, bit 4 & 8 = unknown. */
345 | public UInt32 BytesInUse; /* Real size of the FILE record */
346 | public UInt32 BytesAllocated; /* Allocated size of the FILE record */
347 | public INodeReference BaseFileRecord; /* File reference to the base FILE record */
348 | public UInt16 NextAttributeNumber; /* Next Attribute Id */
349 | public UInt16 Padding; /* Align to 4 UCHAR boundary (XP) */
350 | public UInt32 MFTRecordNumber; /* Number of this MFT Record (XP) */
351 | public UInt16 UpdateSeqNum; /* */
352 | };
353 |
354 | private enum AttributeType : uint
355 | {
356 | AttributeInvalid = 0x00, /* Not defined by Windows */
357 | AttributeStandardInformation = 0x10,
358 | AttributeAttributeList = 0x20,
359 | AttributeFileName = 0x30,
360 | AttributeObjectId = 0x40,
361 | AttributeSecurityDescriptor = 0x50,
362 | AttributeVolumeName = 0x60,
363 | AttributeVolumeInformation = 0x70,
364 | AttributeData = 0x80,
365 | AttributeIndexRoot = 0x90,
366 | AttributeIndexAllocation = 0xA0,
367 | AttributeBitmap = 0xB0,
368 | AttributeReparsePoint = 0xC0, /* Reparse Point = Symbolic link */
369 | AttributeEAInformation = 0xD0,
370 | AttributeEA = 0xE0,
371 | AttributePropertySet = 0xF0,
372 | AttributeLoggedUtilityStream = 0x100
373 | };
374 |
375 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
376 | private struct Attribute
377 | {
378 | public AttributeType AttributeType;
379 | public UInt32 Length;
380 | public byte Nonresident;
381 | public byte NameLength;
382 | public UInt16 NameOffset;
383 | public UInt16 Flags; /* 0x0001 = Compressed, 0x4000 = Encrypted, 0x8000 = Sparse */
384 | public UInt16 AttributeNumber;
385 | }
386 |
387 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
388 | private unsafe struct AttributeList
389 | {
390 | public AttributeType AttributeType;
391 | public UInt16 Length;
392 | public byte NameLength;
393 | public byte NameOffset;
394 | public UInt64 LowestVcn;
395 | public INodeReference FileReferenceNumber;
396 | public UInt16 Instance;
397 | public fixed UInt16 AlignmentOrReserved[3];
398 | };
399 |
400 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
401 | private struct AttributeFileName
402 | {
403 | public INodeReference ParentDirectory;
404 | public UInt64 CreationTime;
405 | public UInt64 ChangeTime;
406 | public UInt64 LastWriteTime;
407 | public UInt64 LastAccessTime;
408 | public UInt64 AllocatedSize;
409 | public UInt64 DataSize;
410 | public UInt32 FileAttributes;
411 | public UInt32 AlignmentOrReserved;
412 | public byte NameLength;
413 | public byte NameType; /* NTFS=0x01, DOS=0x02 */
414 | public char Name;
415 | };
416 |
417 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
418 | private struct AttributeStandardInformation
419 | {
420 | public UInt64 CreationTime;
421 | public UInt64 FileChangeTime;
422 | public UInt64 MftChangeTime;
423 | public UInt64 LastAccessTime;
424 | public UInt32 FileAttributes; /* READ_ONLY=0x01, HIDDEN=0x02, SYSTEM=0x04, VOLUME_ID=0x08, ARCHIVE=0x20, DEVICE=0x40 */
425 | public UInt32 MaximumVersions;
426 | public UInt32 VersionNumber;
427 | public UInt32 ClassId;
428 | public UInt32 OwnerId; // NTFS 3.0 only
429 | public UInt32 SecurityId; // NTFS 3.0 only
430 | public UInt64 QuotaCharge; // NTFS 3.0 only
431 | public UInt64 Usn; // NTFS 3.0 only
432 | };
433 |
434 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
435 | private struct ResidentAttribute
436 | {
437 | public Attribute Attribute;
438 | public UInt32 ValueLength;
439 | public UInt16 ValueOffset;
440 | public UInt16 Flags; // 0x0001 = Indexed
441 | };
442 |
443 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
444 | private unsafe struct NonResidentAttribute
445 | {
446 | public Attribute Attribute;
447 | public UInt64 StartingVcn;
448 | public UInt64 LastVcn;
449 | public UInt16 RunArrayOffset;
450 | public byte CompressionUnit;
451 | public fixed byte AlignmentOrReserved[5];
452 | public UInt64 AllocatedSize;
453 | public UInt64 DataSize;
454 | public UInt64 InitializedSize;
455 | public UInt64 CompressedSize; // Only when compressed
456 | };
457 |
458 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
459 | private struct Fragment
460 | {
461 | public UInt64 Lcn; // Logical cluster number, location on disk.
462 | public UInt64 NextVcn; // Virtual cluster number of next fragment.
463 |
464 | public Fragment(UInt64 lcn, UInt64 nextVcn)
465 | {
466 | Lcn = lcn;
467 | NextVcn = nextVcn;
468 | }
469 | }
470 |
471 | #endregion
472 |
473 | #region Private Classes
474 |
475 | private sealed class Stream
476 | {
477 | public UInt64 Clusters; // Total number of clusters.
478 | public UInt64 Size; // Total number of bytes.
479 | public AttributeType Type;
480 | public int NameIndex;
481 | public List _fragments;
482 |
483 | public Stream(int nameIndex, AttributeType type, UInt64 size)
484 | {
485 | NameIndex = nameIndex;
486 | Type = type;
487 | Size = size;
488 | }
489 |
490 | public List Fragments
491 | {
492 | get
493 | {
494 | if (_fragments == null)
495 | _fragments = new List(5);
496 |
497 | return _fragments;
498 | }
499 | }
500 | }
501 |
502 | ///
503 | /// Node struct for file and directory entries
504 | ///
505 | ///
506 | /// We keep this as small as possible to reduce footprint for large volume.
507 | ///
508 | private struct Node
509 | {
510 | public Attributes Attributes;
511 | public UInt32 ParentNodeIndex;
512 | public UInt64 Size;
513 | public int NameIndex;
514 | }
515 |
516 | ///
517 | /// Contains extra information not required for basic purposes.
518 | ///
519 | private struct StandardInformation
520 | {
521 | public UInt64 CreationTime;
522 | public UInt64 LastAccessTime;
523 | public UInt64 LastChangeTime;
524 |
525 | public StandardInformation(
526 | UInt64 creationTime,
527 | UInt64 lastAccessTime,
528 | UInt64 lastChangeTime
529 | )
530 | {
531 | CreationTime = creationTime;
532 | LastAccessTime = lastAccessTime;
533 | LastChangeTime = lastChangeTime;
534 | }
535 | }
536 |
537 | ///
538 | /// Add some functionality to the basic stream
539 | ///
540 | private sealed class FragmentWrapper : IFragment
541 | {
542 | StreamWrapper _owner;
543 | Fragment _fragment;
544 |
545 | public FragmentWrapper(StreamWrapper owner, Fragment fragment)
546 | {
547 | _owner = owner;
548 | _fragment = fragment;
549 | }
550 |
551 | #region IFragment Members
552 |
553 | public ulong Lcn
554 | {
555 | get { return _fragment.Lcn; }
556 | }
557 |
558 | public ulong NextVcn
559 | {
560 | get { return _fragment.NextVcn; }
561 | }
562 |
563 | #endregion
564 | }
565 |
566 | ///
567 | /// Add some functionality to the basic stream
568 | ///
569 | private sealed class StreamWrapper : IStream
570 | {
571 | NtfsReader _reader;
572 | NodeWrapper _parentNode;
573 | int _streamIndex;
574 |
575 | public StreamWrapper(NtfsReader reader, NodeWrapper parentNode, int streamIndex)
576 | {
577 | _reader = reader;
578 | _parentNode = parentNode;
579 | _streamIndex = streamIndex;
580 | }
581 |
582 | #region IStream Members
583 |
584 | public string Name
585 | {
586 | get
587 | {
588 | return _reader.GetNameFromIndex(_reader._streams[_parentNode.NodeIndex][_streamIndex].NameIndex);
589 | }
590 | }
591 |
592 | public UInt64 Size
593 | {
594 | get
595 | {
596 | return _reader._streams[_parentNode.NodeIndex][_streamIndex].Size;
597 | }
598 | }
599 |
600 | public IList Fragments
601 | {
602 | get
603 | {
604 | //if ((_reader._retrieveMode & RetrieveMode.Fragments) != RetrieveMode.Fragments)
605 | // throw new NotSupportedException("The fragments haven't been retrieved. Make sure to use the proper RetrieveMode.");
606 |
607 | IList fragments =
608 | _reader._streams[_parentNode.NodeIndex][_streamIndex].Fragments;
609 |
610 | if (fragments == null || fragments.Count == 0)
611 | return null;
612 |
613 | List newFragments = new List();
614 | foreach (Fragment fragment in fragments)
615 | newFragments.Add(new FragmentWrapper(this, fragment));
616 |
617 | return newFragments;
618 | }
619 | }
620 |
621 | #endregion
622 | }
623 |
624 | ///
625 | /// Add some functionality to the basic node
626 | ///
627 | private sealed class NodeWrapper : INode
628 | {
629 | NtfsReader _reader;
630 | UInt32 _nodeIndex;
631 | Node _node;
632 | string _fullName;
633 |
634 | public NodeWrapper(NtfsReader reader, UInt32 nodeIndex, Node node)
635 | {
636 | _reader = reader;
637 | _nodeIndex = nodeIndex;
638 | _node = node;
639 | }
640 |
641 | public UInt32 NodeIndex
642 | {
643 | get { return _nodeIndex; }
644 | }
645 |
646 | public UInt32 ParentNodeIndex
647 | {
648 | get { return _node.ParentNodeIndex; }
649 | }
650 |
651 | public Attributes Attributes
652 | {
653 | get { return _node.Attributes; }
654 | }
655 |
656 | public string Name
657 | {
658 | get { return _reader.GetNameFromIndex(_node.NameIndex); }
659 | }
660 |
661 | public UInt64 Size
662 | {
663 | get { return _node.Size; }
664 | }
665 |
666 | public string FullName
667 | {
668 | get
669 | {
670 | if (_fullName == null)
671 | _fullName = _reader.GetNodeFullNameCore(_nodeIndex);
672 |
673 | return _fullName;
674 | }
675 | }
676 |
677 | public IList Streams
678 | {
679 | get
680 | {
681 | if (_reader._streams == null)
682 | throw new NotSupportedException("The streams haven't been retrieved. Make sure to use the proper RetrieveMode.");
683 |
684 | Stream[] streams = _reader._streams[_nodeIndex];
685 | if (streams == null)
686 | return null;
687 |
688 | List newStreams = new List();
689 | for (int i = 0; i < streams.Length; ++i)
690 | newStreams.Add(new StreamWrapper(_reader, this, i));
691 |
692 | return newStreams;
693 | }
694 | }
695 |
696 | #region INode Members
697 |
698 | public DateTime CreationTime
699 | {
700 | get
701 | {
702 | if (_reader._standardInformations == null)
703 | throw new NotSupportedException("The StandardInformation haven't been retrieved. Make sure to use the proper RetrieveMode.");
704 |
705 | return DateTime.FromFileTimeUtc((Int64)_reader._standardInformations[_nodeIndex].CreationTime);
706 | }
707 | }
708 |
709 | public DateTime LastChangeTime
710 | {
711 | get
712 | {
713 | if (_reader._standardInformations == null)
714 | throw new NotSupportedException("The StandardInformation haven't been retrieved. Make sure to use the proper RetrieveMode.");
715 |
716 | return DateTime.FromFileTimeUtc((Int64)_reader._standardInformations[_nodeIndex].LastChangeTime);
717 | }
718 | }
719 |
720 | public DateTime LastAccessTime
721 | {
722 | get
723 | {
724 | if (_reader._standardInformations == null)
725 | throw new NotSupportedException("The StandardInformation haven't been retrieved. Make sure to use the proper RetrieveMode.");
726 |
727 | return DateTime.FromFileTimeUtc((Int64)_reader._standardInformations[_nodeIndex].LastAccessTime);
728 | }
729 | }
730 |
731 | #endregion
732 | }
733 |
734 | ///
735 | /// Simple structure of available disk informations.
736 | ///
737 | private sealed class DiskInfoWrapper : IDiskInfo
738 | {
739 | public UInt16 BytesPerSector;
740 | public byte SectorsPerCluster;
741 | public UInt64 TotalSectors;
742 | public UInt64 MftStartLcn;
743 | public UInt64 Mft2StartLcn;
744 | public UInt32 ClustersPerMftRecord;
745 | public UInt32 ClustersPerIndexRecord;
746 | public UInt64 BytesPerMftRecord;
747 | public UInt64 BytesPerCluster;
748 | public UInt64 TotalClusters;
749 |
750 | #region IDiskInfo Members
751 |
752 | ushort IDiskInfo.BytesPerSector
753 | {
754 | get { return BytesPerSector; }
755 | }
756 |
757 | byte IDiskInfo.SectorsPerCluster
758 | {
759 | get { return SectorsPerCluster; }
760 | }
761 |
762 | ulong IDiskInfo.TotalSectors
763 | {
764 | get { return TotalSectors; }
765 | }
766 |
767 | ulong IDiskInfo.MftStartLcn
768 | {
769 | get { return MftStartLcn; }
770 | }
771 |
772 | ulong IDiskInfo.Mft2StartLcn
773 | {
774 | get { return Mft2StartLcn; }
775 | }
776 |
777 | uint IDiskInfo.ClustersPerMftRecord
778 | {
779 | get { return ClustersPerMftRecord; }
780 | }
781 |
782 | uint IDiskInfo.ClustersPerIndexRecord
783 | {
784 | get { return ClustersPerIndexRecord; }
785 | }
786 |
787 | ulong IDiskInfo.BytesPerMftRecord
788 | {
789 | get { return BytesPerMftRecord; }
790 | }
791 |
792 | ulong IDiskInfo.BytesPerCluster
793 | {
794 | get { return BytesPerCluster; }
795 | }
796 |
797 | ulong IDiskInfo.TotalClusters
798 | {
799 | get { return TotalClusters; }
800 | }
801 |
802 | #endregion
803 | }
804 |
805 | #endregion
806 |
807 | #region Constants
808 |
809 | private const UInt64 VIRTUALFRAGMENT = 18446744073709551615; // _UI64_MAX - 1 */
810 | private const UInt32 ROOTDIRECTORY = 5;
811 |
812 | private readonly byte[] BitmapMasks = new byte[] { 1, 2, 4, 8, 16, 32, 64, 128 };
813 |
814 | #endregion
815 |
816 | SafeFileHandle _volumeHandle;
817 | DiskInfoWrapper _diskInfo;
818 | Node[] _nodes;
819 | StandardInformation[] _standardInformations;
820 | Stream[][] _streams;
821 | DriveInfo _driveInfo;
822 | List _names = new List();
823 | RetrieveMode _retrieveMode;
824 | byte[] _bitmapData;
825 |
826 | //preallocate a lot of space for the strings to avoid too much dictionary resizing
827 | //use ordinal comparison to improve performance
828 | //this will be deallocated once the MFT reading is finished
829 | Dictionary _nameIndex = new Dictionary(128 * 1024, StringComparer.Ordinal);
830 |
831 | #region Events
832 |
833 | ///
834 | /// Raised once the bitmap data has been read.
835 | ///
836 | public event EventHandler BitmapDataAvailable;
837 |
838 | private void OnBitmapDataAvailable()
839 | {
840 | if (BitmapDataAvailable != null)
841 | BitmapDataAvailable(this, EventArgs.Empty);
842 | }
843 |
844 | #endregion
845 |
846 | #region Helpers
847 |
848 | ///
849 | /// Allocate or retrieve an existing index for the particular string.
850 | ///
851 | ///
852 | /// In order to mimize memory usage, we reuse string as much as possible.
853 | ///
854 | private int GetNameIndex(string name)
855 | {
856 | int existingIndex;
857 | if (_nameIndex.TryGetValue(name, out existingIndex))
858 | return existingIndex;
859 |
860 | _names.Add(name);
861 | _nameIndex[name] = _names.Count - 1;
862 |
863 | return _names.Count - 1;
864 | }
865 |
866 | ///
867 | /// Get the string from our stringtable from the given index.
868 | ///
869 | private string GetNameFromIndex(int nameIndex)
870 | {
871 | return nameIndex == 0 ? null : _names[nameIndex];
872 | }
873 |
874 | private Stream SearchStream(List streams, AttributeType streamType)
875 | {
876 | //since the number of stream is usually small, we can afford O(n)
877 | foreach (Stream stream in streams)
878 | if (stream.Type == streamType)
879 | return stream;
880 |
881 | return null;
882 | }
883 |
884 | private Stream SearchStream(List streams, AttributeType streamType, int streamNameIndex)
885 | {
886 | //since the number of stream is usually small, we can afford O(n)
887 | foreach (Stream stream in streams)
888 | if (stream.Type == streamType &&
889 | stream.NameIndex == streamNameIndex)
890 | return stream;
891 |
892 | return null;
893 | }
894 |
895 | #endregion
896 |
897 | #region File Reading Wrappers
898 |
899 | private unsafe void ReadFile(byte* buffer, int len, UInt64 absolutePosition)
900 | {
901 | ReadFile(buffer, (UInt64)len, absolutePosition);
902 | }
903 |
904 | private unsafe void ReadFile(byte* buffer, UInt32 len, UInt64 absolutePosition)
905 | {
906 | ReadFile(buffer, (UInt64)len, absolutePosition);
907 | }
908 |
909 | private unsafe void ReadFile(byte* buffer, UInt64 len, UInt64 absolutePosition)
910 | {
911 | NativeOverlapped overlapped = new NativeOverlapped(absolutePosition);
912 |
913 | uint read;
914 | if (!ReadFile(_volumeHandle, (IntPtr)buffer, (uint)len, out read, ref overlapped))
915 | throw new Exception("Unable to read volume information");
916 |
917 | if (read != (uint)len)
918 | throw new Exception("Unable to read volume information");
919 | }
920 |
921 | #endregion
922 |
923 | #region Ntfs Interpretor
924 |
925 | ///
926 | /// Read the next contiguous block of information on disk
927 | ///
928 | private unsafe bool ReadNextChunk(
929 | byte* buffer,
930 | UInt32 bufferSize,
931 | UInt32 nodeIndex,
932 | int fragmentIndex,
933 | Stream dataStream,
934 | ref UInt64 BlockStart,
935 | ref UInt64 BlockEnd,
936 | ref UInt64 Vcn,
937 | ref UInt64 RealVcn
938 | )
939 | {
940 | BlockStart = nodeIndex;
941 | BlockEnd = BlockStart + bufferSize / _diskInfo.BytesPerMftRecord;
942 | if (BlockEnd > dataStream.Size * 8)
943 | BlockEnd = dataStream.Size * 8;
944 |
945 | UInt64 u1 = 0;
946 |
947 | int fragmentCount = dataStream.Fragments.Count;
948 | while (fragmentIndex < fragmentCount)
949 | {
950 | Fragment fragment = dataStream.Fragments[fragmentIndex];
951 |
952 | /* Calculate Inode at the end of the fragment. */
953 | u1 = (RealVcn + fragment.NextVcn - Vcn) * _diskInfo.BytesPerSector * _diskInfo.SectorsPerCluster / _diskInfo.BytesPerMftRecord;
954 |
955 | if (u1 > nodeIndex)
956 | break;
957 |
958 | do
959 | {
960 | if (fragment.Lcn != VIRTUALFRAGMENT)
961 | RealVcn = RealVcn + fragment.NextVcn - Vcn;
962 |
963 | Vcn = fragment.NextVcn;
964 |
965 | if (++fragmentIndex >= fragmentCount)
966 | break;
967 |
968 | } while (fragment.Lcn == VIRTUALFRAGMENT);
969 | }
970 |
971 | if (fragmentIndex >= fragmentCount)
972 | return false;
973 |
974 | if (BlockEnd >= u1)
975 | BlockEnd = u1;
976 |
977 | ulong position =
978 | (dataStream.Fragments[fragmentIndex].Lcn - RealVcn) * _diskInfo.BytesPerSector *
979 | _diskInfo.SectorsPerCluster + BlockStart * _diskInfo.BytesPerMftRecord;
980 |
981 | ReadFile(buffer, (BlockEnd - BlockStart) * _diskInfo.BytesPerMftRecord, position);
982 |
983 | return true;
984 | }
985 |
986 | ///
987 | /// Gather basic disk information we need to interpret data
988 | ///
989 | private unsafe void InitializeDiskInfo()
990 | {
991 | byte[] volumeData = new byte[512];
992 |
993 | fixed (byte* ptr = volumeData)
994 | {
995 | ReadFile(ptr, volumeData.Length, 0);
996 |
997 | BootSector* bootSector = (BootSector*)ptr;
998 |
999 | if (bootSector->Signature != 0x202020205346544E)
1000 | throw new Exception("This is not an NTFS disk.");
1001 |
1002 | DiskInfoWrapper diskInfo = new DiskInfoWrapper();
1003 | diskInfo.BytesPerSector = bootSector->BytesPerSector;
1004 | diskInfo.SectorsPerCluster = bootSector->SectorsPerCluster;
1005 | diskInfo.TotalSectors = bootSector->TotalSectors;
1006 | diskInfo.MftStartLcn = bootSector->MftStartLcn;
1007 | diskInfo.Mft2StartLcn = bootSector->Mft2StartLcn;
1008 | diskInfo.ClustersPerMftRecord = bootSector->ClustersPerMftRecord;
1009 | diskInfo.ClustersPerIndexRecord = bootSector->ClustersPerIndexRecord;
1010 |
1011 | if (bootSector->ClustersPerMftRecord >= 128)
1012 | diskInfo.BytesPerMftRecord = ((ulong)1 << (byte)(256 - (byte)bootSector->ClustersPerMftRecord));
1013 | else
1014 | diskInfo.BytesPerMftRecord = diskInfo.ClustersPerMftRecord * diskInfo.BytesPerSector * diskInfo.SectorsPerCluster;
1015 |
1016 | diskInfo.BytesPerCluster = (UInt64)diskInfo.BytesPerSector * (UInt64)diskInfo.SectorsPerCluster;
1017 |
1018 | if (diskInfo.SectorsPerCluster > 0)
1019 | diskInfo.TotalClusters = diskInfo.TotalSectors / diskInfo.SectorsPerCluster;
1020 |
1021 | _diskInfo = diskInfo;
1022 | }
1023 | }
1024 |
1025 | ///
1026 | /// Used to check/adjust data before we begin to interpret it
1027 | ///
1028 | private unsafe void FixupRawMftdata(byte* buffer, UInt64 len)
1029 | {
1030 | FileRecordHeader* ntfsFileRecordHeader = (FileRecordHeader*)buffer;
1031 |
1032 | if (ntfsFileRecordHeader->RecordHeader.Type != RecordType.File)
1033 | return;
1034 |
1035 | UInt16* wordBuffer = (UInt16*)buffer;
1036 |
1037 | UInt16* UpdateSequenceArray = (UInt16*)(buffer + ntfsFileRecordHeader->RecordHeader.UsaOffset);
1038 | UInt32 increment = (UInt32)_diskInfo.BytesPerSector / sizeof(UInt16);
1039 |
1040 | UInt32 Index = increment - 1;
1041 |
1042 | for (int i = 1; i < ntfsFileRecordHeader->RecordHeader.UsaCount; i++)
1043 | {
1044 | /* Check if we are inside the buffer. */
1045 | if (Index * sizeof(UInt16) >= len)
1046 | throw new Exception("USA data indicates that data is missing, the MFT may be corrupt.");
1047 |
1048 | // Check if the last 2 bytes of the sector contain the Update Sequence Number.
1049 | if (wordBuffer[Index] != UpdateSequenceArray[0])
1050 | throw new Exception("USA fixup word is not equal to the Update Sequence Number, the MFT may be corrupt.");
1051 |
1052 | /* Replace the last 2 bytes in the sector with the value from the Usa array. */
1053 | wordBuffer[Index] = UpdateSequenceArray[i];
1054 | Index = Index + increment;
1055 | }
1056 | }
1057 |
1058 | ///
1059 | /// Decode the RunLength value.
1060 | ///
1061 | private static unsafe Int64 ProcessRunLength(byte* runData, UInt32 runDataLength, Int32 runLengthSize, ref UInt32 index)
1062 | {
1063 | Int64 runLength = 0;
1064 | byte* runLengthBytes = (byte*)&runLength;
1065 | for (int i = 0; i < runLengthSize; i++)
1066 | {
1067 | runLengthBytes[i] = runData[index];
1068 | if (++index >= runDataLength)
1069 | throw new Exception("Datarun is longer than buffer, the MFT may be corrupt.");
1070 | }
1071 | return runLength;
1072 | }
1073 |
1074 | ///
1075 | /// Decode the RunOffset value.
1076 | ///
1077 | private static unsafe Int64 ProcessRunOffset(byte* runData, UInt32 runDataLength, Int32 runOffsetSize, ref UInt32 index)
1078 | {
1079 | Int64 runOffset = 0;
1080 | byte* runOffsetBytes = (byte*)&runOffset;
1081 |
1082 | int i;
1083 | for (i = 0; i < runOffsetSize; i++)
1084 | {
1085 | runOffsetBytes[i] = runData[index];
1086 | if (++index >= runDataLength)
1087 | throw new Exception("Datarun is longer than buffer, the MFT may be corrupt.");
1088 | }
1089 |
1090 | //process negative values
1091 | if (runOffsetBytes[i - 1] >= 0x80)
1092 | while (i < 8)
1093 | runOffsetBytes[i++] = 0xFF;
1094 |
1095 | return runOffset;
1096 | }
1097 |
1098 | ///
1099 | /// Read the data that is specified in a RunData list from disk into memory,
1100 | /// skipping the first Offset bytes.
1101 | ///
1102 | private unsafe byte[] ProcessNonResidentData(
1103 | byte* RunData,
1104 | UInt32 RunDataLength,
1105 | UInt64 Offset, /* Bytes to skip from begin of data. */
1106 | UInt64 WantedLength /* Number of bytes to read. */
1107 | )
1108 | {
1109 | /* Sanity check. */
1110 | if (RunData == null || RunDataLength == 0)
1111 | throw new Exception("nothing to read");
1112 |
1113 | if (WantedLength >= UInt32.MaxValue)
1114 | throw new Exception("too many bytes to read");
1115 |
1116 | /* We have to round up the WantedLength to the nearest sector. For some
1117 | reason or other Microsoft has decided that raw reading from disk can
1118 | only be done by whole sector, even though ReadFile() accepts it's
1119 | parameters in bytes. */
1120 | if (WantedLength % _diskInfo.BytesPerSector > 0)
1121 | WantedLength += _diskInfo.BytesPerSector - (WantedLength % _diskInfo.BytesPerSector);
1122 |
1123 | /* Walk through the RunData and read the requested data from disk. */
1124 | UInt32 Index = 0;
1125 | Int64 Lcn = 0;
1126 | Int64 Vcn = 0;
1127 |
1128 | byte[] buffer = new byte[WantedLength];
1129 |
1130 | fixed (byte* bufPtr = buffer)
1131 | {
1132 | while (RunData[Index] != 0)
1133 | {
1134 | /* Decode the RunData and calculate the next Lcn. */
1135 | Int32 RunLengthSize = (RunData[Index] & 0x0F);
1136 | Int32 RunOffsetSize = ((RunData[Index] & 0xF0) >> 4);
1137 |
1138 | if (++Index >= RunDataLength)
1139 | throw new Exception("Error: datarun is longer than buffer, the MFT may be corrupt.");
1140 |
1141 | Int64 RunLength =
1142 | ProcessRunLength(RunData, RunDataLength, RunLengthSize, ref Index);
1143 |
1144 | Int64 RunOffset =
1145 | ProcessRunOffset(RunData, RunDataLength, RunOffsetSize, ref Index);
1146 |
1147 | // Ignore virtual extents.
1148 | if (RunOffset == 0 || RunLength == 0)
1149 | continue;
1150 |
1151 | Lcn += RunOffset;
1152 | Vcn += RunLength;
1153 |
1154 | /* Determine how many and which bytes we want to read. If we don't need
1155 | any bytes from this extent then loop. */
1156 | UInt64 ExtentVcn = (UInt64)((Vcn - RunLength) * _diskInfo.BytesPerSector * _diskInfo.SectorsPerCluster);
1157 | UInt64 ExtentLcn = (UInt64)(Lcn * _diskInfo.BytesPerSector * _diskInfo.SectorsPerCluster);
1158 | UInt64 ExtentLength = (UInt64)(RunLength * _diskInfo.BytesPerSector * _diskInfo.SectorsPerCluster);
1159 |
1160 | if (Offset >= ExtentVcn + ExtentLength)
1161 | continue;
1162 |
1163 | if (Offset > ExtentVcn)
1164 | {
1165 | ExtentLcn = ExtentLcn + Offset - ExtentVcn;
1166 | ExtentLength = ExtentLength - (Offset - ExtentVcn);
1167 | ExtentVcn = Offset;
1168 | }
1169 |
1170 | if (Offset + WantedLength <= ExtentVcn)
1171 | continue;
1172 |
1173 | if (Offset + WantedLength < ExtentVcn + ExtentLength)
1174 | ExtentLength = Offset + WantedLength - ExtentVcn;
1175 |
1176 | if (ExtentLength == 0)
1177 | continue;
1178 |
1179 | ReadFile(bufPtr + ExtentVcn - Offset, ExtentLength, ExtentLcn);
1180 | }
1181 | }
1182 |
1183 | return buffer;
1184 | }
1185 |
1186 | ///
1187 | /// Process each attributes and gather information when necessary
1188 | ///
1189 | private unsafe void ProcessAttributes(ref Node node, UInt32 nodeIndex, byte* ptr, UInt64 BufLength, UInt16 instance, int depth, List streams, bool isMftNode)
1190 | {
1191 | Attribute* attribute = null;
1192 | for (uint AttributeOffset = 0; AttributeOffset < BufLength; AttributeOffset = AttributeOffset + attribute->Length)
1193 | {
1194 | attribute = (Attribute*)(ptr + AttributeOffset);
1195 |
1196 | // exit the loop if end-marker.
1197 | if ((AttributeOffset + 4 <= BufLength) &&
1198 | (*(UInt32*)attribute == 0xFFFFFFFF))
1199 | break;
1200 |
1201 | //make sure we did read the data correctly
1202 | if ((AttributeOffset + 4 > BufLength) || attribute->Length < 3 ||
1203 | (AttributeOffset + attribute->Length > BufLength))
1204 | throw new Exception("Error: attribute in Inode %I64u is bigger than the data, the MFT may be corrupt.");
1205 |
1206 | //attributes list needs to be processed at the end
1207 | if (attribute->AttributeType == AttributeType.AttributeAttributeList)
1208 | continue;
1209 |
1210 | /* If the Instance does not equal the AttributeNumber then ignore the attribute.
1211 | This is used when an AttributeList is being processed and we only want a specific
1212 | instance. */
1213 | if ((instance != 65535) && (instance != attribute->AttributeNumber))
1214 | continue;
1215 |
1216 | if (attribute->Nonresident == 0)
1217 | {
1218 | ResidentAttribute* residentAttribute = (ResidentAttribute*)attribute;
1219 |
1220 | switch (attribute->AttributeType)
1221 | {
1222 | case AttributeType.AttributeFileName:
1223 | AttributeFileName* attributeFileName = (AttributeFileName*)(ptr + AttributeOffset + residentAttribute->ValueOffset);
1224 |
1225 | if (attributeFileName->ParentDirectory.InodeNumberHighPart > 0)
1226 | throw new NotSupportedException("48 bits inode are not supported to reduce memory footprint.");
1227 |
1228 | //node.ParentNodeIndex = ((UInt64)attributeFileName->ParentDirectory.InodeNumberHighPart << 32) + attributeFileName->ParentDirectory.InodeNumberLowPart;
1229 | node.ParentNodeIndex = attributeFileName->ParentDirectory.InodeNumberLowPart;
1230 |
1231 | if (attributeFileName->NameType == 1 || node.NameIndex == 0)
1232 | node.NameIndex = GetNameIndex(new string(&attributeFileName->Name, 0, attributeFileName->NameLength));
1233 |
1234 | break;
1235 |
1236 | case AttributeType.AttributeStandardInformation:
1237 | AttributeStandardInformation* attributeStandardInformation = (AttributeStandardInformation*)(ptr + AttributeOffset + residentAttribute->ValueOffset);
1238 |
1239 | node.Attributes |= (Attributes)attributeStandardInformation->FileAttributes;
1240 |
1241 | if ((_retrieveMode & RetrieveMode.StandardInformations) == RetrieveMode.StandardInformations)
1242 | _standardInformations[nodeIndex] =
1243 | new StandardInformation(
1244 | attributeStandardInformation->CreationTime,
1245 | attributeStandardInformation->FileChangeTime,
1246 | attributeStandardInformation->LastAccessTime
1247 | );
1248 |
1249 | break;
1250 |
1251 | case AttributeType.AttributeData:
1252 | node.Size = residentAttribute->ValueLength;
1253 | break;
1254 | }
1255 | }
1256 | else
1257 | {
1258 | NonResidentAttribute* nonResidentAttribute = (NonResidentAttribute*)attribute;
1259 |
1260 | //save the length (number of bytes) of the data.
1261 | if (attribute->AttributeType == AttributeType.AttributeData && node.Size == 0)
1262 | node.Size = nonResidentAttribute->DataSize;
1263 |
1264 | if (streams != null)
1265 | {
1266 | //extract the stream name
1267 | int streamNameIndex = 0;
1268 | if (attribute->NameLength > 0)
1269 | streamNameIndex = GetNameIndex(new string((char*)(ptr + AttributeOffset + attribute->NameOffset), 0, (int)attribute->NameLength));
1270 |
1271 | //find or create the stream
1272 | Stream stream =
1273 | SearchStream(streams, attribute->AttributeType, streamNameIndex);
1274 |
1275 | if (stream == null)
1276 | {
1277 | stream = new Stream(streamNameIndex, attribute->AttributeType, nonResidentAttribute->DataSize);
1278 | streams.Add(stream);
1279 | }
1280 | else if (stream.Size == 0)
1281 | stream.Size = nonResidentAttribute->DataSize;
1282 |
1283 | //we need the fragment of the MFTNode so retrieve them this time
1284 | //even if fragments aren't normally read
1285 | if (isMftNode || (_retrieveMode & RetrieveMode.Fragments) == RetrieveMode.Fragments)
1286 | ProcessFragments(
1287 | ref node,
1288 | stream,
1289 | ptr + AttributeOffset + nonResidentAttribute->RunArrayOffset,
1290 | attribute->Length - nonResidentAttribute->RunArrayOffset,
1291 | nonResidentAttribute->StartingVcn
1292 | );
1293 | }
1294 | }
1295 | }
1296 |
1297 | if (streams != null && streams.Count > 0)
1298 | node.Size = streams[0].Size;
1299 | }
1300 |
1301 | ///
1302 | /// Process fragments for streams
1303 | ///
1304 | private unsafe void ProcessFragments(
1305 | ref Node node,
1306 | Stream stream,
1307 | byte* runData,
1308 | UInt32 runDataLength,
1309 | UInt64 StartingVcn)
1310 | {
1311 | if (runData == null)
1312 | return;
1313 |
1314 | /* Walk through the RunData and add the extents. */
1315 | uint index = 0;
1316 | Int64 lcn = 0;
1317 | Int64 vcn = (Int64)StartingVcn;
1318 | int runOffsetSize = 0;
1319 | int runLengthSize = 0;
1320 |
1321 | while (runData[index] != 0)
1322 | {
1323 | /* Decode the RunData and calculate the next Lcn. */
1324 | runLengthSize = (runData[index] & 0x0F);
1325 | runOffsetSize = ((runData[index] & 0xF0) >> 4);
1326 |
1327 | if (++index >= runDataLength)
1328 | throw new Exception("Error: datarun is longer than buffer, the MFT may be corrupt.");
1329 |
1330 | Int64 runLength =
1331 | ProcessRunLength(runData, runDataLength, runLengthSize, ref index);
1332 |
1333 | Int64 runOffset =
1334 | ProcessRunOffset(runData, runDataLength, runOffsetSize, ref index);
1335 |
1336 | lcn += runOffset;
1337 | vcn += runLength;
1338 |
1339 | /* Add the size of the fragment to the total number of clusters.
1340 | There are two kinds of fragments: real and virtual. The latter do not
1341 | occupy clusters on disk, but are information used by compressed
1342 | and sparse files. */
1343 | if (runOffset != 0)
1344 | stream.Clusters += (UInt64)runLength;
1345 |
1346 | stream.Fragments.Add(
1347 | new Fragment(
1348 | runOffset == 0 ? VIRTUALFRAGMENT : (UInt64)lcn,
1349 | (UInt64)vcn
1350 | )
1351 | );
1352 | }
1353 | }
1354 |
1355 | ///
1356 | /// Process an actual MFT record from the buffer
1357 | ///
1358 | private unsafe bool ProcessMftRecord(byte* buffer, UInt64 length, UInt32 nodeIndex, out Node node, List streams, bool isMftNode)
1359 | {
1360 | node = new Node();
1361 |
1362 | FileRecordHeader* ntfsFileRecordHeader = (FileRecordHeader*)buffer;
1363 |
1364 | if (ntfsFileRecordHeader->RecordHeader.Type != RecordType.File)
1365 | return false;
1366 |
1367 | //the inode is not in use
1368 | if ((ntfsFileRecordHeader->Flags & 1) != 1)
1369 | return false;
1370 |
1371 | UInt64 baseInode = ((UInt64)ntfsFileRecordHeader->BaseFileRecord.InodeNumberHighPart << 32) + ntfsFileRecordHeader->BaseFileRecord.InodeNumberLowPart;
1372 |
1373 | //This is an inode extension used in an AttributeAttributeList of another inode, don't parse it
1374 | if (baseInode != 0)
1375 | return false;
1376 |
1377 | if (ntfsFileRecordHeader->AttributeOffset >= length)
1378 | throw new Exception("Error: attributes in Inode %I64u are outside the FILE record, the MFT may be corrupt.");
1379 |
1380 | if (ntfsFileRecordHeader->BytesInUse > length)
1381 | throw new Exception("Error: in Inode %I64u the record is bigger than the size of the buffer, the MFT may be corrupt.");
1382 |
1383 | //make the file appear in the rootdirectory by default
1384 | node.ParentNodeIndex = ROOTDIRECTORY;
1385 |
1386 | if ((ntfsFileRecordHeader->Flags & 2) == 2)
1387 | node.Attributes |= Attributes.Directory;
1388 |
1389 | ProcessAttributes(ref node, nodeIndex, buffer + ntfsFileRecordHeader->AttributeOffset, length - ntfsFileRecordHeader->AttributeOffset, 65535, 0, streams, isMftNode);
1390 |
1391 | return true;
1392 | }
1393 |
1394 | ///
1395 | /// Process the bitmap data that contains information on inode usage.
1396 | ///
1397 | private unsafe byte[] ProcessBitmapData(List streams)
1398 | {
1399 | UInt64 Vcn = 0;
1400 | UInt64 MaxMftBitmapBytes = 0;
1401 |
1402 | Stream bitmapStream = SearchStream(streams, AttributeType.AttributeBitmap);
1403 | if (bitmapStream == null)
1404 | throw new Exception("No Bitmap Data");
1405 |
1406 | foreach (Fragment fragment in bitmapStream.Fragments)
1407 | {
1408 | if (fragment.Lcn != VIRTUALFRAGMENT)
1409 | MaxMftBitmapBytes += (fragment.NextVcn - Vcn) * _diskInfo.BytesPerSector * _diskInfo.SectorsPerCluster;
1410 |
1411 | Vcn = fragment.NextVcn;
1412 | }
1413 |
1414 | byte[] bitmapData = new byte[MaxMftBitmapBytes];
1415 |
1416 | fixed (byte* bitmapDataPtr = bitmapData)
1417 | {
1418 | Vcn = 0;
1419 | UInt64 RealVcn = 0;
1420 |
1421 | foreach (Fragment fragment in bitmapStream.Fragments)
1422 | {
1423 | if (fragment.Lcn != VIRTUALFRAGMENT)
1424 | {
1425 | ReadFile(
1426 | bitmapDataPtr + RealVcn * _diskInfo.BytesPerSector * _diskInfo.SectorsPerCluster,
1427 | (fragment.NextVcn - Vcn) * _diskInfo.BytesPerSector * _diskInfo.SectorsPerCluster,
1428 | fragment.Lcn * _diskInfo.BytesPerSector * _diskInfo.SectorsPerCluster
1429 | );
1430 |
1431 | RealVcn = RealVcn + fragment.NextVcn - Vcn;
1432 | }
1433 |
1434 | Vcn = fragment.NextVcn;
1435 | }
1436 | }
1437 |
1438 | return bitmapData;
1439 | }
1440 |
1441 | ///
1442 | /// Begin the process of interpreting MFT data
1443 | ///
1444 | private unsafe Node[] ProcessMft()
1445 | {
1446 | //64 KB seems to be optimal for Windows XP, Vista is happier with 256KB...
1447 | uint bufferSize =
1448 | (Environment.OSVersion.Version.Major >= 6 ? 256u : 64u) * 1024;
1449 |
1450 | byte[] data = new byte[bufferSize];
1451 |
1452 | fixed (byte* buffer = data)
1453 | {
1454 | //Read the $MFT record from disk into memory, which is always the first record in the MFT.
1455 | ReadFile(buffer, _diskInfo.BytesPerMftRecord, _diskInfo.MftStartLcn * _diskInfo.BytesPerSector * _diskInfo.SectorsPerCluster);
1456 |
1457 | //Fixup the raw data from disk. This will also test if it's a valid $MFT record.
1458 | FixupRawMftdata(buffer, _diskInfo.BytesPerMftRecord);
1459 |
1460 | List mftStreams = new List();
1461 |
1462 | if ((_retrieveMode & RetrieveMode.StandardInformations) == RetrieveMode.StandardInformations)
1463 | _standardInformations = new StandardInformation[1]; //allocate some space for $MFT record
1464 |
1465 | Node mftNode;
1466 | if (!ProcessMftRecord(buffer, _diskInfo.BytesPerMftRecord, 0, out mftNode, mftStreams, true))
1467 | throw new Exception("Can't interpret Mft Record");
1468 |
1469 | //the bitmap data contains all used inodes on the disk
1470 | _bitmapData =
1471 | ProcessBitmapData(mftStreams);
1472 |
1473 | OnBitmapDataAvailable();
1474 |
1475 | Stream dataStream = SearchStream(mftStreams, AttributeType.AttributeData);
1476 |
1477 | UInt32 maxInode = (UInt32)_bitmapData.Length * 8;
1478 | if (maxInode > (UInt32)(dataStream.Size / _diskInfo.BytesPerMftRecord))
1479 | maxInode = (UInt32)(dataStream.Size / _diskInfo.BytesPerMftRecord);
1480 |
1481 | Node[] nodes = new Node[maxInode];
1482 | nodes[0] = mftNode;
1483 |
1484 | if ((_retrieveMode & RetrieveMode.StandardInformations) == RetrieveMode.StandardInformations)
1485 | {
1486 | StandardInformation mftRecordInformation = _standardInformations[0];
1487 | _standardInformations = new StandardInformation[maxInode];
1488 | _standardInformations[0] = mftRecordInformation;
1489 | }
1490 |
1491 | if ((_retrieveMode & RetrieveMode.Streams) == RetrieveMode.Streams)
1492 | _streams = new Stream[maxInode][];
1493 |
1494 | /* Read and process all the records in the MFT. The records are read into a
1495 | buffer and then given one by one to the InterpretMftRecord() subroutine. */
1496 |
1497 | UInt64 BlockStart = 0, BlockEnd = 0;
1498 | UInt64 RealVcn = 0, Vcn = 0;
1499 |
1500 | ulong totalBytesRead = 0;
1501 | int fragmentIndex = 0;
1502 | int fragmentCount = dataStream.Fragments.Count;
1503 | for (UInt32 nodeIndex = 1; nodeIndex < maxInode; nodeIndex++)
1504 | {
1505 | // Ignore the Inode if the bitmap says it's not in use.
1506 | if ((_bitmapData[nodeIndex >> 3] & BitmapMasks[nodeIndex % 8]) == 0)
1507 | continue;
1508 |
1509 | if (nodeIndex >= BlockEnd)
1510 | {
1511 | if (!ReadNextChunk(
1512 | buffer,
1513 | bufferSize,
1514 | nodeIndex,
1515 | fragmentIndex,
1516 | dataStream,
1517 | ref BlockStart,
1518 | ref BlockEnd,
1519 | ref Vcn,
1520 | ref RealVcn))
1521 | break;
1522 |
1523 | totalBytesRead += (BlockEnd - BlockStart) * _diskInfo.BytesPerMftRecord;
1524 | }
1525 |
1526 | FixupRawMftdata(
1527 | buffer + (nodeIndex - BlockStart) * _diskInfo.BytesPerMftRecord,
1528 | _diskInfo.BytesPerMftRecord
1529 | );
1530 |
1531 | List streams = null;
1532 | if ((_retrieveMode & RetrieveMode.Streams) == RetrieveMode.Streams)
1533 | streams = new List();
1534 |
1535 | Node newNode;
1536 | if (!ProcessMftRecord(
1537 | buffer + (nodeIndex - BlockStart) * _diskInfo.BytesPerMftRecord,
1538 | _diskInfo.BytesPerMftRecord,
1539 | nodeIndex,
1540 | out newNode,
1541 | streams,
1542 | false))
1543 | continue;
1544 |
1545 | nodes[nodeIndex] = newNode;
1546 |
1547 | if (streams != null)
1548 | _streams[nodeIndex] = streams.ToArray();
1549 | }
1550 |
1551 | return nodes;
1552 | }
1553 | }
1554 |
1555 | #endregion
1556 | [Flags]
1557 | public enum RetrieveMode
1558 | {
1559 | Minimal = 0,
1560 | StandardInformations = 1,
1561 | Streams = 2,
1562 | Fragments = 4,
1563 | All = StandardInformations | Streams | Fragments,
1564 | }
1565 | public interface IStream
1566 | {
1567 | string Name { get; }
1568 | UInt64 Size { get; }
1569 | IList Fragments { get; }
1570 | }
1571 | public interface IFragment
1572 | {
1573 | UInt64 Lcn { get; }
1574 |
1575 | UInt64 NextVcn { get; }
1576 | }
1577 | public interface INode
1578 | {
1579 | Attributes Attributes { get; }
1580 | UInt32 NodeIndex { get; }
1581 | UInt32 ParentNodeIndex { get; }
1582 | string Name { get; }
1583 | UInt64 Size { get; }
1584 | string FullName { get; }
1585 | IList Streams { get; }
1586 |
1587 | DateTime CreationTime { get; }
1588 | DateTime LastChangeTime { get; }
1589 | DateTime LastAccessTime { get; }
1590 | }
1591 | public interface IDiskInfo
1592 | {
1593 | UInt16 BytesPerSector { get; }
1594 | byte SectorsPerCluster { get; }
1595 | UInt64 TotalSectors { get; }
1596 | UInt64 MftStartLcn { get; }
1597 | UInt64 Mft2StartLcn { get; }
1598 | UInt32 ClustersPerMftRecord { get; }
1599 | UInt32 ClustersPerIndexRecord { get; }
1600 | UInt64 BytesPerMftRecord { get; }
1601 | UInt64 BytesPerCluster { get; }
1602 | UInt64 TotalClusters { get; }
1603 | }
1604 | [Flags]
1605 | public enum Attributes : uint
1606 | {
1607 | ReadOnly = 1,
1608 | Hidden = 2,
1609 | System = 4,
1610 | Directory = 16,
1611 | Archive = 32,
1612 | Device = 64,
1613 | Normal = 128,
1614 | Temporary = 256,
1615 | SparseFile = 512,
1616 | ReparsePoint = 1024,
1617 | Compressed = 2048,
1618 | Offline = 4096,
1619 | NotContentIndexed = 8192,
1620 | Encrypted = 16384,
1621 | }
1622 | public static class Algorithms
1623 | {
1624 | public static IDictionary> AggregateByFragments(IEnumerable nodes, UInt32 minimumFragments)
1625 | {
1626 | Dictionary> fragmentsAggregate = new Dictionary>();
1627 |
1628 | foreach (INode node in nodes)
1629 | {
1630 | IList streams = node.Streams;
1631 | if (streams == null || streams.Count == 0)
1632 | continue;
1633 |
1634 | IList fragments = streams[0].Fragments;
1635 | if (fragments == null)
1636 | continue;
1637 |
1638 | UInt32 fragmentCount = (UInt32)fragments.Count;
1639 |
1640 | if (fragmentCount < minimumFragments)
1641 | continue;
1642 |
1643 | List nodeList;
1644 | fragmentsAggregate.TryGetValue(fragmentCount, out nodeList);
1645 |
1646 | if (nodeList == null)
1647 | {
1648 | nodeList = new List();
1649 | fragmentsAggregate[fragmentCount] = nodeList;
1650 | }
1651 |
1652 | nodeList.Add(node);
1653 | }
1654 |
1655 | return fragmentsAggregate;
1656 | }
1657 | }
1658 | }
1659 | }
1660 |
--------------------------------------------------------------------------------