How can I tell whether two .NET DLLs are the same?
Asked Answered
H

5

13

I have the source for a DLL and I have a compiled version of it lying around somewhere.

If I compile the source it will have a different date to the already-compiled version.

How can I tell whether they are in fact the same and have merely been compiled at different times?

Himes answered 29/4, 2010 at 8:3 Comment(4)
Amount of bytes? though that isn't the most secure method I guess, not sureMarketa
if you have a certain change in mind, and want to check whether or not that is in a given dll file, there's always .net reflector; other than that, go with Kangkan's replyMargertmargery
@Bas: This is probably the best method. @David: How would .net reflector help?Himes
as i said, if you have a specific change in mind. for instance, if you want to be certain whether or not a certain feature has been rolled out from stage to production, and that is the reason why you want to know if two DLL's are the same, simply checking for that one feature would be the best way to be completely sure, and that's exactly what .net reflector is for. i'm entirely aware this might not be your situation, i was just throwing it in, in case it was your situation, and you weren't aware of the tool...Margertmargery
K
19

To compare two .dll files you can use ildasm or any other tool for geting IL code. I have created a sample with embedded ildasm in the dll file so that you can use it on every machine. When we disassemble an assembly we check if the ildasm.exe file exists in the executing assembly folder and if not the file is extracted there from our dll file. Using the ildasm file we get the IL code and save it to a temporary file. Then we need to remove the following three rows:

MVID - as I wrote before this is a unique GUID generated with every build

Image Base (The image base tells us as to where the program will be loaded in memory by the Windows loader.) - this is different with every build as well

Time-date stamp - the time and date when the ildasm is run

So we read the temp file content, remove these rows using the regexes, and then save the file content to the same file. Here is the Disassembler class:

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace FileHasher
{
    public class Disassembler
    {
        public static Regex regexMVID = new Regex("//\\s*MVID\\:\\s*\\{[a-zA-Z0-9\\-]+\\}", RegexOptions.Multiline | RegexOptions.Compiled);
        public static Regex regexImageBase = new Regex("//\\s*Image\\s+base\\:\\s0x[0-9A-Fa-f]*", RegexOptions.Multiline | RegexOptions.Compiled);
        public static Regex regexTimeStamp = new Regex("//\\s*Time-date\\s+stamp\\:\\s*0x[0-9A-Fa-f]*", RegexOptions.Multiline | RegexOptions.Compiled);

        private static readonly Lazy<Assembly> currentAssembly = new Lazy<Assembly>(() =>
        {
            return MethodBase.GetCurrentMethod().DeclaringType.Assembly;
        });

        private static readonly Lazy<string> executingAssemblyPath = new Lazy<string>(() =>
        {
            return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        });

        private static readonly Lazy<string> currentAssemblyFolder = new Lazy<string>(() =>
        {
            return Path.GetDirectoryName(currentAssembly.Value.Location);
        });

        private static readonly Lazy<string[]> arrResources = new Lazy<string[]>(() =>
        {
            return currentAssembly.Value.GetManifestResourceNames();
        });

        private const string ildasmArguments = "/all /text \"{0}\"";

        public static string ILDasmFileLocation
        {
            get
            {
                return Path.Combine(executingAssemblyPath.Value, "ildasm.exe");
            }
        }

        static Disassembler()
        {
            //extract the ildasm file to the executing assembly location
            ExtractFileToLocation("ildasm.exe", ILDasmFileLocation);
        }

        /// <summary>
        /// Saves the file from embedded resource to a given location.
        /// </summary>
        /// <param name="embeddedResourceName">Name of the embedded resource.</param>
        /// <param name="fileName">Name of the file.</param>
        protected static void SaveFileFromEmbeddedResource(string embeddedResourceName, string fileName)
        {
            if (File.Exists(fileName))
            {
                //the file already exists, we can add deletion here if we want to change the version of the 7zip
                return;
            }
            FileInfo fileInfoOutputFile = new FileInfo(fileName);

            using (FileStream streamToOutputFile = fileInfoOutputFile.OpenWrite())
            using (Stream streamToResourceFile = currentAssembly.Value.GetManifestResourceStream(embeddedResourceName))
            {
                const int size = 4096;
                byte[] bytes = new byte[4096];
                int numBytes;
                while ((numBytes = streamToResourceFile.Read(bytes, 0, size)) > 0)
                {
                    streamToOutputFile.Write(bytes, 0, numBytes);
                }

                streamToOutputFile.Close();
                streamToResourceFile.Close();
            }
        }

        /// <summary>
        /// Searches the embedded resource and extracts it to the given location.
        /// </summary>
        /// <param name="fileNameInDll">The file name in DLL.</param>
        /// <param name="outFileName">Name of the out file.</param>
        protected static void ExtractFileToLocation(string fileNameInDll, string outFileName)
        {
            string resourcePath = arrResources.Value.Where(resource => resource.EndsWith(fileNameInDll, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
            if (resourcePath == null)
            {
                throw new Exception(string.Format("Cannot find {0} in the embedded resources of {1}", fileNameInDll, currentAssembly.Value.FullName));
            }
            SaveFileFromEmbeddedResource(resourcePath, outFileName);
        }

        public static string GetDisassembledFile(string assemblyFilePath)
        {
            if (!File.Exists(assemblyFilePath))
            {
                throw new InvalidOperationException(string.Format("The file {0} does not exist!", assemblyFilePath));
            }

            string tempFileName = Path.GetTempFileName();
            var startInfo = new ProcessStartInfo(ILDasmFileLocation, string.Format(ildasmArguments, assemblyFilePath));
            startInfo.WindowStyle = ProcessWindowStyle.Hidden;
            startInfo.CreateNoWindow = true;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = true;

            using (var process = System.Diagnostics.Process.Start(startInfo))
            {
                string output = process.StandardOutput.ReadToEnd();
                process.WaitForExit();

                if (process.ExitCode > 0)
                {
                    throw new InvalidOperationException(
                        string.Format("Generating IL code for file {0} failed with exit code - {1}. Log: {2}",
                        assemblyFilePath, process.ExitCode, output));
                }

                File.WriteAllText(tempFileName, output);
            }

            RemoveUnnededRows(tempFileName);
            return tempFileName;
        }

        private static void RemoveUnnededRows(string fileName)
        {
            string fileContent = File.ReadAllText(fileName);
            //remove MVID
            fileContent = regexMVID.Replace(fileContent, string.Empty);
            //remove Image Base
            fileContent = regexImageBase.Replace(fileContent, string.Empty);
            //remove Time Stamp
            fileContent = regexTimeStamp.Replace(fileContent, string.Empty);
            File.WriteAllText(fileName, fileContent);
        }

        public static string DisassembleFile(string assemblyFilePath)
        {
            string disassembledFile = GetDisassembledFile(assemblyFilePath);
            try
            {
                return File.ReadAllText(disassembledFile);
            }
            finally
            {
                if (File.Exists(disassembledFile))
                {
                    File.Delete(disassembledFile);
                }
            }
        }
    }
}

Now you can compare the contents of these two IL codes. Another option is to generate hash codes of these files and compare them. Hese is a HashCalculator class: using System; using System.IO; using System.Reflection;

namespace FileHasher
{
    public class HashCalculator
    {
        public string FileName { get; private set; }

        public HashCalculator(string fileName)
        {
            this.FileName = fileName;
        }

        public string CalculateFileHash()
        {
            if (Path.GetExtension(this.FileName).Equals(".dll", System.StringComparison.InvariantCultureIgnoreCase)
                || Path.GetExtension(this.FileName).Equals(".exe", System.StringComparison.InvariantCultureIgnoreCase))
            {
                return GetAssemblyFileHash();
            }
            else
            {
                return GetFileHash();
            }
        }

        private string GetFileHash()
        {
            return CalculateHashFromStream(File.OpenRead(this.FileName));
        }

        private string GetAssemblyFileHash()
        {
            string tempFileName = null;
            try
            {
                //try to open the assembly to check if this is a .NET one
                var assembly = Assembly.LoadFile(this.FileName);
                tempFileName = Disassembler.GetDisassembledFile(this.FileName);
                return CalculateHashFromStream(File.OpenRead(tempFileName));
            }
            catch(BadImageFormatException)
            {
                return GetFileHash();
            }
            finally
            {
                if (File.Exists(tempFileName))
                {
                    File.Delete(tempFileName);
                }
            }
        }

        private string CalculateHashFromStream(Stream stream)
        {
            using (var readerSource = new System.IO.BufferedStream(stream, 1200000))
            {
                using (var md51 = new System.Security.Cryptography.MD5CryptoServiceProvider())
                {
                    md51.ComputeHash(readerSource);
                    return Convert.ToBase64String(md51.Hash);
                }
            }
        }
    }
}

You can find the full application source code on my blog here - Compare two dll files programmatically

Kolivas answered 5/11, 2012 at 17:26 Comment(5)
All your posts are links to your blog.Tetraspore
not sure why this got voted down - this is the only answer here that actually worksCervantes
when I used this I found that identical dll still had differences in guids. Is it safe to ignore all changes in guids?Reviere
@vasil-trifonov Time-date stamp is the time the executable/dll is built right ? Not the time which we ran Idasm.exe?Denumerable
@Denumerable I'm not quite sure about that, I don't remember now, you might be right, however we don't need that time when we compare the files.Kolivas
C
3

Both NDepend and a Plugin for Reflector allow you to compare assemblies.

Chemar answered 30/4, 2010 at 10:6 Comment(0)
J
1

You can use .NET Reflector to Disassamble the dll and compare it to the last code change you made to see if they are the same. If they are they you know that they are based on the same code.

Jampacked answered 29/4, 2010 at 14:44 Comment(0)
O
0

The basic comparison is of version and size of the DLL. Also, you can check if any file has a modified date beyond the date of the compiled dll.

Oneill answered 29/4, 2010 at 8:5 Comment(3)
See my question. If I compile the source it will have a different date to the already-compiled version.Himes
@Craig: If you read carefully, I mentioned that the last modified date of the source code file(s) should not be beyond the original dll's date. Of course, if the code has been saved with any change not in the functionality, then also the date of modification will change. It is sure that if you compile the new dll will have a different date. Its trivial.Oneill
OK, I see your point now. Yes, it would be good idea to look at the modified date of the source files.Himes
M
-1

compare the files in binary mode. Run the following from the command line:

fc /b file1.dll file2.dll

That will let you know if the files are identical, but they will probably not be unless they are compiled in exactly the same conditions, which, since you have the source code, is possible.

Morette answered 29/4, 2010 at 14:50 Comment(1)
This is not true, because the compiler always inserts the timestamp and a random MVID into the binary. Also the base address is very likely to change.Colvert

© 2022 - 2024 — McMap. All rights reserved.