private string use32bitAssembly(Dictionary <string, Assembly> assemblyLocations) { if (assemblyLocations != null && assemblyLocations.Count > 0) { string osFolder = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.System)); foreach (Assembly a in assemblyLocations.Values) { try { if (a.GlobalAssemblyCache) { if (a.Location.StartsWith(osFolder, StringComparison.OrdinalIgnoreCase)) { continue; } } AssemblyDetails ad = AssemblyDetails.FromFile(a.Location); if (ad != null) { if (ad.CPUVersion == CPUVersion.x86) { return(a.FullName); } } } catch { } } } return(string.Empty); }
/// <summary> /// Get the AssemblyDetails of a .NET 2.0+ assembly. /// </summary> /// <param name="file">The file to get the AssemblyDetails of.</param> /// <returns>An AssemblyDetails object for .NET 2.0+ assemblies. Null otherwise.</returns> public static AssemblyDetails FromFile(string file) { AssemblyDetails assembInfo = new AssemblyDetails(); using (FileStream s = new FileStream(file, FileMode.Open, FileAccess.Read)) { using (BinaryReader r = new BinaryReader(s)) { byte[] bytes = r.ReadBytes(2); // Verify file starts with "MZ" signature. if ((bytes[0] != 0x4d) || (bytes[1] != 0x5a)) { // Not a PE file. return(null); } // Partion II, 25.2.1 // OFFSET_TO_PE_HEADER_OFFSET = 0x3c s.Seek(0x3c, SeekOrigin.Begin); // read the offset to the PE Header uint offset = r.ReadUInt32(); // go to the beginning of the PE Header s.Seek(offset, SeekOrigin.Begin); bytes = r.ReadBytes(4); // Verify PE header starts with 'PE\0\0'. if ((bytes[0] != 0x50) || (bytes[1] != 0x45) || (bytes[2] != 0) || (bytes[3] != 0)) { // Not a PE file. return(null); } // It's a PE file, verify that it has the right "machine" code. // Partion II, 25.2.2 // ushort machineCode = r.ReadUInt16(); // IMAGE_FILE_MACHINE_AMD64 (aka x64) = 0x8664 // IMAGE_FILE_MACHINE_I386 (aka x86) = 0x14c if (!(machineCode == 0x014c || machineCode == 0x8664)) { // Invalid or unrecognized PE file of some kind. return(null); } // Locate the PE_OPTIONAL_HEADER // The PE_FILE_HEADER is 20bytes long we already // read the 2 byte machine code (hence 18byte seek) s.Seek(18, SeekOrigin.Current); ushort magic = r.ReadUInt16(); switch (magic) { case 0x10b: // PE32 // set to AnyCPU for now - we'll check later if image is x86 specific assembInfo.CPUVersion = CPUVersion.AnyCPU; break; case 0x20b: // PE32+ (aka x64) assembInfo.CPUVersion = CPUVersion.x64; break; default: // unknown assembly type return(null); } // Read the SectionAlignment & FileAlignment for // conversion from RVA to file address s.Seek(30, SeekOrigin.Current); uint sectionAlignment = r.ReadUInt32(); uint fileAlignment = r.ReadUInt32(); // go to 'NumberOfRvaAndSizes' in the PE Header // at 92/108 from start of PE Header for PE32/PE32+ s.Seek(magic == 0x10b ? 52 : 68, SeekOrigin.Current); // verify that the number of data directories is 0x10. uint numDataDirs = r.ReadUInt32(); // Partition II, 25.2.3.2 if (numDataDirs != 0x10) // Partition II, 25.2.3.2 { // Invalid or unrecognized PE file of some kind. return(null); } // Go to the CLR Runtime Header // at 208/224 from start of PE Header for PE32/PE32+ s.Seek(112, SeekOrigin.Current); // Check for the existence of a non-null CLI header. // If found, this is an assembly of some kind, otherwise // it's a native PE file of one kind or another. uint rvaCLIHeader = r.ReadUInt32(); // Partition II, 25.2.3.3, CLI Header (rva) // uint cliHeaderSize = UIntFromBytes(pPEOptionalHeader + 212); // Partition II, 25.2.3.3, CLI Header (size) if (rvaCLIHeader == 0) { // Not an assembly. return(null); } // Partition II, 25.3.3 (CLI Header) // Go to the begginning of the CLI header (RVA -> file address) /* * -> Converting from Relative Virtual Address to File Address: * * FA = RVA - sectionAlignment + fileAlignment * * The section alignment in memory is sectionAlignment (usually 0x2000), * and since the RVA for the CLR header is 2008, on subtracting 2000 from * 2008, the difference comes to 8. Thus, the CLR header is placed 8 bytes * away from the start of the section. * * A file on disk has the alignment of fileAlignment (usually 512 bytes). * Therefore, the first section would start at position 512 from the start * of the file. As the CLR is 8 bytes away from the section start, 8 is added * to 512, (section start for a file on disk), thereby arriving at a value of 520. * The next 72 bytes (0x48) are picked up from this position, since they * constitute the CLR header, and they are loaded at location 0x4002008. * */ // Also, skip the CLI header size = 4 bytes s.Seek((rvaCLIHeader - sectionAlignment + fileAlignment) + 4, SeekOrigin.Begin); ushort majorVersion = r.ReadUInt16(); ushort minorVersion = r.ReadUInt16(); // 2.5 means the file is a .NET Framework 2.0+ assembly if (!(majorVersion == 2 && minorVersion == 5)) { return(null); } // RVA for the MetaData (we'll read the metadata later) uint rvaMetaData = r.ReadUInt32(); s.Seek(4, SeekOrigin.Current); // skip the size // Partition II, 25.3.3.1 // read the CLI flags uint cliFlags = r.ReadUInt32(); // Detect if compiled with Platform Target of "x86" // COMIMAGE_FLAGS_32BITREQUIRED = 0x2; if (assembInfo.CPUVersion == CPUVersion.AnyCPU && (cliFlags & 0x2) == 0x2) { assembInfo.CPUVersion = CPUVersion.x86; } // Detect if the assembly is built with a strong name // CLI_FLAG_STRONG_NAME_SIGNED = 0x8; assembInfo.StrongName = ((cliFlags & 0x8) == 0x8); s.Seek((rvaMetaData - sectionAlignment + fileAlignment) + 12, SeekOrigin.Begin); // Read the framework version required (meta data - Partition II, 24.2.1 - pg 200) // read the version string length int versionLen = r.ReadInt32(); char[] versionStr = r.ReadChars(versionLen); // read the .NET framework version required from the meta-data //Note: we only read the first 2 numbers of the version - if you want to // detect beta vs. rc vs. rtm, then read the whole version. // We assume no one will be stupid enough to use beta created exes/dlls in the wild. if (versionStr[1] == '2' && versionStr[3] == '0') { assembInfo.FrameworkVersion = FrameworkVersion.Net2_0; } else if (versionStr[1] == '4' && versionStr[3] == '0') { assembInfo.FrameworkVersion = FrameworkVersion.Net4_0; } else { assembInfo.FrameworkVersion = FrameworkVersion.Unknown; } } } return(assembInfo); }