diff --git a/ClrPhlib/include/ClrPhlib.h b/ClrPhlib/include/ClrPhlib.h index 8fde4478..79091cf7 100644 --- a/ClrPhlib/include/ClrPhlib.h +++ b/ClrPhlib/include/ClrPhlib.h @@ -159,6 +159,9 @@ namespace Dependencies { // Check if the PE is 32-bit bool IsArm32Dll(); + + // Check if the PE is a dot net + bool IsClrDll(); // return the processorArchiture of PE String^ GetProcessor(); diff --git a/ClrPhlib/src/managed/PE.cpp b/ClrPhlib/src/managed/PE.cpp index 7a0429f5..46e6cb34 100644 --- a/ClrPhlib/src/managed/PE.cpp +++ b/ClrPhlib/src/managed/PE.cpp @@ -201,6 +201,16 @@ bool PE::IsArm32Dll() return ((Properties->Machine & 0xffff) == IMAGE_FILE_MACHINE_ARMNT); } +bool PE::IsClrDll() +{ + PIMAGE_DATA_DIRECTORY dataDirectory; + if (NT_SUCCESS(PhGetMappedImageDataEntry( &m_Impl->m_PvMappedImage, IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, &dataDirectory))) + { + return dataDirectory->VirtualAddress != 0; + } + return false; +} + String^ PE::GetProcessor() { if ((Properties->Machine & 0xffff) == IMAGE_FILE_MACHINE_I386) diff --git a/DependenciesGui/DependencyWindow.xaml.cs b/DependenciesGui/DependencyWindow.xaml.cs index fe4599a5..f9b20b8f 100755 --- a/DependenciesGui/DependencyWindow.xaml.cs +++ b/DependenciesGui/DependencyWindow.xaml.cs @@ -586,6 +586,7 @@ public void InitializeView() var RootFilename = Path.GetFileName(this.Filename); var RootModule = new DisplayModuleInfo(RootFilename, this.Pe, ModuleSearchStrategy.ROOT); this.ProcessedModulesCache.Add(new ModuleCacheKey(RootFilename, this.Filename), RootModule); + this.ModulesList.AddModule(RootModule); ModuleTreeViewItem treeNode = new ModuleTreeViewItem(); DependencyNodeContext childTreeInfoContext = new DependencyNodeContext() @@ -765,16 +766,10 @@ private void ProcessAppInitDlls(Dictionary NewTreeContext } } - private void ProcessClrImports(Dictionary NewTreeContexts, PE AnalyzedPe, ImportContext ImportModule) + private void ProcessClrImports(Dictionary NewTreeContexts, PE AnalyzedPe) { - List PeImports = AnalyzedPe.GetImports(); - // only mscorre triggers clr parsing - string User32Filepath = Path.Combine(FindPe.GetSystemPath(this.Pe), "mscoree.dll"); - if (ImportModule.PeFilePath != User32Filepath) - { - return; - } + List PeImports = AnalyzedPe.GetImports(); var resolver = new DefaultAssemblyResolver(); resolver.AddSearchDirectory(RootFolder); @@ -938,17 +933,19 @@ private void ProcessPe(Dictionary NewTreeContexts, PE new // add warning for appv isv applications TriggerWarningOnAppvIsvImports(DllImport.Name); - NewTreeContexts.Add(DllImport.Name, ImportModule); - // AppInitDlls are triggered by user32.dll, so if the binary does not import user32.dll they are not loaded. ProcessAppInitDlls(NewTreeContexts, newPe, ImportModule); + } - // if mscoree.dll is imported, it means the module is a C# assembly, and we can use Mono.Cecil to enumerate its references - ProcessClrImports(NewTreeContexts, newPe, ImportModule); + // This should happen only if this is validated to be a C# assembly + if (newPe.IsClrDll()) + { + // We use Mono.Cecil to enumerate its references + ProcessClrImports(NewTreeContexts, newPe); } } @@ -980,6 +977,7 @@ private void ConstructDependencyTree(ModuleTreeViewItem RootNode, PE CurrentPE, BackgroundWorker bw = new BackgroundWorker(); bw.WorkerReportsProgress = true; // useless here for now + (Application.Current as App).StatusBarMessage = "Analyzing PE File " + CurrentPE.Filepath; bw.DoWork += (sender, e) => { @@ -1073,7 +1071,10 @@ private void ConstructDependencyTree(ModuleTreeViewItem RootNode, PE CurrentPE, // it's asynchronous (we would have to wait for all the background to finish and // use another Async worker to resolve). - if ((NewTreeContext.PeProperties != null) && (NewTreeContext.PeProperties.GetImports().Count > 0)) + // Some dot net dlls give 0 for GetImports() but they will always have imports + // that can be detected using the special CLR dll processing we do. + if ((NewTreeContext.PeProperties != null) && + (NewTreeContext.PeProperties.GetImports().Count > 0 || NewTreeContext.PeProperties.IsClrDll())) { ModuleTreeViewItem DummyEntry = new ModuleTreeViewItem(); DependencyNodeContext DummyContext = new DependencyNodeContext() @@ -1112,7 +1113,8 @@ private void ConstructDependencyTree(ModuleTreeViewItem RootNode, PE CurrentPE, } } - + + (Application.Current as App).StatusBarMessage = CurrentPE.Filepath + " Loaded successfully."; }; bw.RunWorkerAsync(); diff --git a/DependenciesGui/MainWindow.xaml.cs b/DependenciesGui/MainWindow.xaml.cs index 4369f1f3..967cd2ed 100755 --- a/DependenciesGui/MainWindow.xaml.cs +++ b/DependenciesGui/MainWindow.xaml.cs @@ -189,7 +189,8 @@ public void PopulateRecentFilesMenuItems() return; } - + // prevent the existing items appear multiple times + RecentsItems.Clear(); foreach (var RecentFilePath in Properties.Settings.Default.RecentFiles) { // Ignore empty dummy entries diff --git a/DependenciesLib/BinaryCache.cs b/DependenciesLib/BinaryCache.cs index 9112ec8e..b455623a 100644 --- a/DependenciesLib/BinaryCache.cs +++ b/DependenciesLib/BinaryCache.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Diagnostics; using Dependencies.ClrPh; using System.ComponentModel; +using System.Linq; namespace Dependencies { @@ -292,12 +293,7 @@ public class BinaryCacheImpl : BinaryCache { public BinaryCacheImpl(string ApplicationAppDataPath, int _MaxBinaryCount) { - BinaryDatabase = new Dictionary(); - FilepathDatabase = new Dictionary(); - BinaryDatabaseLock = new Object(); - LruCache = new List(); - MaxBinaryCount = _MaxBinaryCount; string platform = (IntPtr.Size == 8) ? "x64" : "x86"; @@ -307,12 +303,6 @@ public BinaryCacheImpl(string ApplicationAppDataPath, int _MaxBinaryCount) public override void Load() { - // "warm up" the cache - foreach (var CachedBinary in Directory.EnumerateFiles(BinaryCacheFolderPath)) - { - GetBinaryAsync(CachedBinary); - } - string System32Folder = Environment.GetFolderPath(Environment.SpecialFolder.System); string SysWow64Folder = Environment.GetFolderPath(Environment.SpecialFolder.SystemX86); @@ -340,53 +330,63 @@ public override void Load() { GetBinaryAsync(Path.Combine(SysWow64Folder, KnownDll)); } - } - } public override void Unload() { - // cut off the LRU cache - LruCache = LruCache.GetRange(0, Math.Min(LruCache.Count, MaxBinaryCount)); - - foreach (var CachedBinary in Directory.EnumerateFiles(BinaryCacheFolderPath)) + foreach (var kv in BinaryDatabase) { - string PeHash = GetBinaryHash(CachedBinary); + kv.Value.Unload(); + } + BinaryDatabase.Clear(); - if (LruCache.Find(Hash => (Hash == PeHash)) == null) + foreach (var file in new DirectoryInfo(BinaryCacheFolderPath).EnumerateFiles() + .OrderByDescending(fi => fi.LastAccessTime).Skip(MaxBinaryCount)) + { + try { - // Force map unloading before deleting file - if (BinaryDatabase.ContainsKey(PeHash)) - { - BinaryDatabase[PeHash].Unload(); - } + file.Delete(); + } + catch (System.UnauthorizedAccessException uae) + { + // The BinaryCache is shared among serveral Dependencies.exe instance + // so only the last one alive can clear the cache. + Debug.WriteLine("[BinaryCache] Could not unload file {0:s} : {1:s} ", file.FullName, uae); + } + } + } - try - { - File.Delete(CachedBinary); - } - catch (System.UnauthorizedAccessException uae) - { - // The BinaryCache is shared among serveral Dependencies.exe instance - // so only the last one alive can clear the cache. - Debug.WriteLine("[BinaryCache] Could not unload file {0:s} : {1:s} ", CachedBinary, uae); - } + private PE LoadCachedBinary(string peHash) + { + var cachedBinaryFile = Path.Combine(BinaryCacheFolderPath, peHash); + if (!NativeFile.Exists(cachedBinaryFile)) + { + return null; + } - } + PE cachedPE = new PE(cachedBinaryFile); + try + { + // update LastAccessTime to save LRU to disk + // note: Windows from Vista disable updating LastAccessTime by default, + // so we have to update it manually. + new FileInfo(cachedBinaryFile).LastAccessTime = DateTime.Now; } + catch { } + if (!cachedPE.Load()) + { + return null; + } - // flush the cache - BinaryDatabase.Clear(); - FilepathDatabase.Clear(); + return cachedPE; } public void GetBinaryAsync(string PePath, RunWorkerCompletedEventHandler Callback = null) { BackgroundWorker bw = new BackgroundWorker(); bw.DoWork += (sender, e) => { - GetBinary(PePath); }; @@ -395,7 +395,6 @@ public void GetBinaryAsync(string PePath, RunWorkerCompletedEventHandler Callbac bw.RunWorkerCompleted += Callback; } - bw.RunWorkerAsync(); } @@ -409,47 +408,39 @@ public override PE GetBinary(string PePath) return null; } - string Fullpath = Path.GetFullPath(PePath); - if (FilepathDatabase.ContainsKey(Fullpath)) - { - // TODO : update LRU cache - PE sShadowBinary = FilepathDatabase[Fullpath]; - sShadowBinary.Filepath = Fullpath; - return sShadowBinary; - } - string PeHash = GetBinaryHash(PePath); //Debug.WriteLine(String.Format("File {0:s} hash : {1:s} ", PePath, PeHash), "BinaryCache"); // A sync lock is mandatory here in order not to load twice the // same binary from two differents workers - lock (BinaryDatabaseLock) + PE cachedPE; + lock (BinaryDatabase) { - bool hit = BinaryDatabase.ContainsKey(PeHash); - - // Cache "miss" - if (!hit) + // Memory Cache "miss" + if (!BinaryDatabase.TryGetValue(PeHash, out cachedPE)) { - - string DestFilePath = Path.Combine(BinaryCacheFolderPath, PeHash); - if (!File.Exists(DestFilePath) && (DestFilePath != PePath)) + cachedPE = LoadCachedBinary(PeHash); + if (cachedPE == null) { - // Debug.WriteLine(String.Format("FileCopy from {0:s} to {1:s}", PePath, DestFilePath), "BinaryCache"); - NativeFile.Copy(PePath, DestFilePath); + // Disk Cache miss + string DestFilePath = Path.Combine(BinaryCacheFolderPath, PeHash); + if (!File.Exists(DestFilePath) && (DestFilePath != PePath)) + { + // Debug.WriteLine(String.Format("FileCopy from {0:s} to {1:s}", PePath, DestFilePath), "BinaryCache"); + NativeFile.Copy(PePath, DestFilePath); + } + cachedPE = LoadCachedBinary(PeHash); + if (cachedPE == null) + { + BinaryDatabase.Remove(PeHash); + return null; + } } - - PE NewShadowBinary = new PE(DestFilePath); - NewShadowBinary.Load(); - - LruCache.Add(PeHash); - BinaryDatabase.Add(PeHash, NewShadowBinary); - FilepathDatabase.Add(Fullpath, NewShadowBinary); + BinaryDatabase.Add(PeHash, cachedPE); } } - // Cache "Hit" - UpdateLru(PeHash); - PE ShadowBinary = BinaryDatabase[PeHash]; + PE ShadowBinary = cachedPE; ShadowBinary.Filepath = Path.GetFullPath(PePath); // convert any paths to an absolute one. Debug.WriteLine(String.Format("File {0:s} loaded from {1:s}", PePath, Path.Combine(BinaryCacheFolderPath, PeHash)), "BinaryCache"); @@ -461,28 +452,11 @@ protected string GetBinaryHash(string PePath) return NativeFile.GetPartialHashFile(PePath, 1024); } - protected void UpdateLru(string PeHash) - { - string MatchingHash = LruCache.Find(Hash => (Hash == PeHash)); - if (null == MatchingHash) - return; - - lock (BinaryDatabaseLock) - { - // prepend the matching item at the beginning of the list - LruCache.Remove(MatchingHash); - LruCache.Insert(0, MatchingHash); - } - } - #region Members - private List LruCache; - private Dictionary BinaryDatabase; - private Dictionary FilepathDatabase; - private Object BinaryDatabaseLock; + private readonly Dictionary BinaryDatabase; + private int MaxBinaryCount; private string BinaryCacheFolderPath; - private int MaxBinaryCount; #endregion Members } diff --git a/DependenciesLib/SxsManifest.cs b/DependenciesLib/SxsManifest.cs index efb8c19c..7aa86ed3 100644 --- a/DependenciesLib/SxsManifest.cs +++ b/DependenciesLib/SxsManifest.cs @@ -42,6 +42,31 @@ public SxsEntry(XElement SxsAssemblyIdentity, XElement SxsFile, string Folder) Type = ""; PublicKeyToken = ""; + string loadFrom = SxsFile.Attribute("loadFrom")?.Value?.ToString(); + if (!string.IsNullOrEmpty(loadFrom)) + { + loadFrom = Environment.ExpandEnvironmentVariables(loadFrom); + if (!System.IO.Path.IsPathRooted(loadFrom)) + { + loadFrom = System.IO.Path.Combine(Folder, loadFrom); + } + + // It's only a folder + if (loadFrom.EndsWith("\\") || loadFrom.EndsWith("/")) + { + Path = System.IO.Path.Combine(loadFrom, RelPath); + } + else + { + // It's also a dll name! + Path = loadFrom; + if (!Path.ToLower().EndsWith(".dll")) + { + Path += ".DLL"; + } + } + } + if (SxsAssemblyIdentity != null) { if (SxsAssemblyIdentity.Attribute("version") != null) diff --git a/README.md b/README.md index 5230ca88..6765d4d4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ # Dependencies - An open-source modern Dependency Walker [![Build status](https://ci.appveyor.com/api/projects/status/wtr5v8ksndbkkqxg?svg=true)](https://ci.appveyor.com/project/lucasg/dependencies) -### [Download here](https://github.com/lucasg/Dependencies/releases/download/v1.11.1/Dependencies_x64_Release.zip) - -#### [(If you're running an AV, use this download instead)](https://github.com/lucasg/Dependencies/releases/download/v1.11.1/Dependencies_x64_Release_.without.peview.exe.zip) +### [Download here](https://github.com/himeshsameera/Dependencies/releases/download/V2.0-alpha/Dependencies_V2.0-alpha_x64.zip) NB : due to [limitations on /clr compilation](https://msdn.microsoft.com/en-us/library/ffkc918h.aspx), `Dependencies` needs [Visual C++ Redistributable](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads) installed to run properly. @@ -13,10 +11,14 @@ NB : due to [limitations on /clr compilation](https://msdn.microsoft.com/en-us/l ## Overview + `Dependencies` is a rewrite of the legacy software [Dependency Walker](http://www.dependencywalker.com/) which was shipped along Windows SDKs, but whose development stopped around 2006. `Dependencies` can help Windows developers troubleshooting their dll load dependencies issues. ## Releases +* [v2.0-alpha](https://github.com/himeshsameera/Dependencies/releases/download/V2.0-alpha/Dependencies_V2.0-alpha_x64.zip) : + * Changes from HimeshSameera repository + * Fixed some issues with loading .NET dlls (mostly x64 ones) to Dependencies. * [v1.11](https://github.com/lucasg/Dependencies/releases/download/v1.11.1/Dependencies_x64_Release.zip) : * lots of bugfixes and incremental improvements * covid pandemic diff --git a/test/manifest-regress/Test-ManifestRegress.ps1 b/test/manifest-regress/Test-ManifestRegress.ps1 index 165cd4de..83f21d15 100644 --- a/test/manifest-regress/Test-ManifestRegress.ps1 +++ b/test/manifest-regress/Test-ManifestRegress.ps1 @@ -53,7 +53,7 @@ function Test-Executable { } else { - Write-Host -ForegroundColor Green "[+] $TestName test passed"; + Write-Host -ForegroundColor Green "[+] $TestName test passed"; } Write-Output "" @@ -72,4 +72,16 @@ Test-Executable -TestName "SystemSettings" -TestExecutable $SystemSettingsPath - # Double quotes in assemblyIdentity name attribute ! $DevicePairingFolderPath = Join-Path $RegressDir "DevicePairingFolder.dll"; -Test-Executable -TestName "DevicePairingFolder" -TestExecutable $DevicePairingFolderPath -Command "-manifest" -StringToFound '