diff --git a/exeinfo/Program.cs b/exeinfo/Program.cs index aa75684..995037a 100644 --- a/exeinfo/Program.cs +++ b/exeinfo/Program.cs @@ -179,6 +179,58 @@ namespace exeinfo { recognized = true; Console.Write(peExe.Information); + + if(((PE)peExe).Versions != null) + foreach(PE.Version vers in ((PE)peExe).Versions) + { + Console.WriteLine("\tVersion resource {0}:", vers.Name); + Console.WriteLine("\t\tFile version: {0}", vers.FileVersion); + Console.WriteLine("\t\tProduct version: {0}", vers.ProductVersion); + Console.WriteLine("\t\tFile type: {0}", PE.Version.TypeToString(vers.FileType)); + if(vers.FileType == PE.VersionFileType.VFT_DRV) + Console.WriteLine("\t\tFile subtype: {0} driver", + PE.Version.DriverToString(vers.FileSubtype)); + else if(vers.FileType == PE.VersionFileType.VFT_DRV) + Console.WriteLine("\t\tFile subtype: {0} font", PE.Version.FontToString(vers.FileSubtype)); + else if(vers.FileSubtype > 0) + Console.WriteLine("\t\tFile subtype: {0}", (uint)vers.FileSubtype); + Console.WriteLine("\t\tFile flags: {0}", vers.FileFlags); + Console.WriteLine("\t\tFile OS: {0}", PE.Version.OsToString(vers.FileOS)); + + foreach(KeyValuePair> strByLang in vers.StringsByLanguage) + { + string cultureName; + string encodingName; + + try + { + cultureName = new CultureInfo(Convert.ToInt32(strByLang.Key.Substring(0, 4), 16)) + .DisplayName; + } + catch + { + cultureName = + $"unsupported culture 0x{Convert.ToInt32(strByLang.Key.Substring(0, 4), 16):X4}"; + } + + try + { + encodingName = Encoding + .GetEncoding(Convert.ToInt32(strByLang.Key.Substring(4), 16)) + .EncodingName; + } + catch + { + encodingName = + $"unsupported encoding 0x{Convert.ToInt32(strByLang.Key.Substring(4), 16):X4}"; + } + + Console.WriteLine("\t\tStrings for {0} in codepage {1}:", cultureName, encodingName); + foreach(KeyValuePair strings in strByLang.Value) + Console.WriteLine("\t\t\t{0}: {1}", strings.Key, strings.Value); + } + } + if(peExe.Strings != null && peExe.Strings.Any()) { diff --git a/libexeinfo/PE/Consts.cs b/libexeinfo/PE/Consts.cs index 6cce0dc..53f3a05 100644 --- a/libexeinfo/PE/Consts.cs +++ b/libexeinfo/PE/Consts.cs @@ -34,5 +34,13 @@ namespace libexeinfo const ushort SIGNATURE = 0x00004550; const ushort PE32 = COFF.ZMAGIC; internal const ushort PE32Plus = 0x20b; + /// + /// Signature for a + /// + const string FIXED_FILE_INFO_SIG = "VS_VERSION_INFO"; + /// + /// Signature for list of name=value strings inside a version resource + /// + const string STRING_FILE_INFO = "StringFileInfo"; } } \ No newline at end of file diff --git a/libexeinfo/PE/Enums.cs b/libexeinfo/PE/Enums.cs index a374471..8312899 100644 --- a/libexeinfo/PE/Enums.cs +++ b/libexeinfo/PE/Enums.cs @@ -176,5 +176,92 @@ namespace libexeinfo /// IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16 } - } + + /// + /// Version file flags. + /// + [Flags] + public enum VersionFileFlags : uint + { + VS_FF_DEBUG = 0x00000001, + VS_FF_INFOINFERRED = 0x00000010, + VS_FF_PATCHED = 0x00000004, + VS_FF_PRERELEASE = 0x00000002, + VS_FF_PRIVATEBUILD = 0x00000008, + VS_FF_SPECIALBUILD = 0x00000020 + } + + /// + /// Version file operating system. + /// + public enum VersionFileOS : uint + { + VOS_DOS = 0x00010000, + VOS_NT = 0x00040000, + VOS_WINDOWS16 = 0x00000001, + VOS_WINDOWS32 = 0x00000004, + VOS_OS216 = 0x00020000, + VOS_OS232 = 0x00030000, + VOS_PM16 = 0x00000002, + VOS_PM32 = 0x00000003, + VOS_UNKNOWN = 0x00000000, + + // Combinations, some have no sense + VOS_DOS_NT = 0x00050000, + VOS_DOS_WINDOWS16 = 0x00010001, + VOS_DOS_WINDOWS32 = 0x00010004, + VOS_DOS_PM16 = 0x00010002, + VOS_DOS_PM32 = 0x00010003, + VOS_NT_WINDOWS16 = 0x00040001, + VOS_NT_WINDOWS32 = 0x00040004, + VOS_NT_PM16 = 0x00040002, + VOS_NT_PM32 = 0x00040003, + VOS_OS216_WINDOWS16 = 0x00020001, + VOS_OS216_WINDOWS32 = 0x00020004, + VOS_OS216_PM16 = 0x00020002, + VOS_OS216_PM32 = 0x00020003, + VOS_OS232_WINDOWS16 = 0x00030001, + VOS_OS232_WINDOWS32 = 0x00030004, + VOS_OS232_PM16 = 0x00030002, + VOS_OS232_PM32 = 0x00030003 + } + + /// + /// Version file subtype. + /// + public enum VersionFileSubtype : uint + { + VFT2_UNKNOWN = 0x00000000, + // Drivers + VFT2_DRV_COMM = 0x0000000A, + VFT2_DRV_DISPLAY = 0x00000004, + VFT2_DRV_INSTALLABLE = 0x00000008, + VFT2_DRV_KEYBOARD = 0x00000002, + VFT2_DRV_LANGUAGE = 0x00000003, + VFT2_DRV_MOUSE = 0x00000005, + VFT2_DRV_NETWORK = 0x00000006, + VFT2_DRV_PRINTER = 0x00000001, + VFT2_DRV_SOUND = 0x00000009, + VFT2_DRV_SYSTEM = 0x00000007, + VFT2_DRV_VERSIONED_PRINTER = 0x0000000C, + // Fonts + VFT2_FONT_RASTER = 0x00000001, + VFT2_FONT_TRUETYPE = 0x00000003, + VFT2_FONT_VECTOR = 0x00000002 + } + + /// + /// Version file type. + /// + public enum VersionFileType : uint + { + VFT_APP = 0x00000001, + VFT_DLL = 0x00000002, + VFT_DRV = 0x00000003, + VFT_FONT = 0x00000004, + VFT_STATIC_LIB = 0x00000007, + VFT_UNKNOWN = 0x00000000, + VFT_VXD = 0x00000005 + } + } } \ No newline at end of file diff --git a/libexeinfo/PE/PE.cs b/libexeinfo/PE/PE.cs index afccef5..211cd03 100644 --- a/libexeinfo/PE/PE.cs +++ b/libexeinfo/PE/PE.cs @@ -53,6 +53,7 @@ namespace libexeinfo COFF.SectionHeader[] sectionHeaders; public ResourceNode WindowsResourcesRoot; WindowsHeader64 winHeader; + public Version[] Versions; /// /// Initializes a new instance of the class. @@ -387,9 +388,14 @@ namespace libexeinfo // TODO: Decode BeOS resource format } else + { WindowsResourcesRoot = GetResourceNode(BaseStream, rsrc.pointerToRawData, rsrc.virtualAddress, rsrc.pointerToRawData, 0, null, 0); + Versions = GetVersions().ToArray(); + + strings.AddRange(from v in Versions from s in v.StringsByLanguage from k in s.Value select k.Value); + } sectionHeaders = newSectionHeaders.Values.OrderBy(s => s.pointerToRawData).ToArray(); Segment[] segments = new Segment[sectionHeaders.Length]; diff --git a/libexeinfo/PE/Structs.cs b/libexeinfo/PE/Structs.cs index 3c12754..c427816 100644 --- a/libexeinfo/PE/Structs.cs +++ b/libexeinfo/PE/Structs.cs @@ -459,5 +459,39 @@ namespace libexeinfo public byte[] data; public int level; } + + /// + /// Node in a version resource + /// + class VersionNode + { + public ushort wValueLength; + public ushort wLength; + public ushort wType; + public VersionNode[] children; + public byte[] rgbData; + public string szName; + } + + /// + /// Fixed file version info + /// + [StructLayout(LayoutKind.Sequential)] + public struct FixedFileInfo + { + public uint dwSignature; + public uint dwStrucVersion; + public uint dwFileVersionMS; + public uint dwFileVersionLS; + public uint dwProductVersionMS; + public uint dwProductVersionLS; + public uint dwFileFlagsMask; + public uint dwFileFlags; + public uint dwFileOS; + public uint dwFileType; + public uint dwFileSubtype; + public uint dwFileDateMS; + public uint dwFileDateLS; + } } } \ No newline at end of file diff --git a/libexeinfo/PE/Version.cs b/libexeinfo/PE/Version.cs new file mode 100644 index 0000000..103d7ad --- /dev/null +++ b/libexeinfo/PE/Version.cs @@ -0,0 +1,331 @@ +// +// Version.cs +// +// Author: +// Natalia Portillo +// +// Copyright (c) 2017-2018 Copyright © Claunia.com +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using libexeinfo.Windows; + +namespace libexeinfo +{ + public partial class PE + { + /// + /// Gets all the version resources from this instance + /// + /// The decoded version resources. + public List GetVersions() + { + return (from node in WindowsResourcesRoot.children + where node.id == (uint)ResourceTypes.RT_VERSION + from ids in node.children + from lang in ids.children + select new Version(lang.data, lang.name)).ToList(); + } + + /// + /// Represents a version ("RT_VERSION") resource + /// + public class Version + { + /// + /// Initializes a new instance of the class. + /// + /// Resource data. + /// Resource name. + public Version(byte[] data, string resourceName = null) + { + if(data == null || data.Length < 5) return; + + Name = resourceName; + + StringsByLanguage = new Dictionary>(); + + VersionNode root = GetNode(data, 0, out int rootLength); + DecodeNode(root, null, null); + } + + /// + /// This contains a list of all name=value strings pairs sorted by language + /// + /// List of all name=value strings pairs sorted by language. + public Dictionary> StringsByLanguage { get; } + + /// + /// File version. + /// + /// The file version. + public string FileVersion { get; set; } + + /// + /// Product version. + /// + /// The product version. + public string ProductVersion { get; set; } + + /// + /// File flags. + /// + /// The file flags. + public VersionFileFlags FileFlags { get; set; } + + /// + /// File operating system. + /// + /// The file operating system. + public VersionFileOS FileOS { get; set; } + + /// + /// File type. + /// + /// The type of the file. + public VersionFileType FileType { get; set; } + + /// + /// File subtype. + /// + /// The file subtype. + public VersionFileSubtype FileSubtype { get; set; } + + /// + /// File date. + /// + /// The file date. + public DateTime FileDate { get; set; } + + /// + /// Resource name + /// + /// The resource name. + public string Name { get; } + + static VersionNode GetNode(byte[] data, int startPosition, out int nodeLength) + { + nodeLength = 0; + + VersionNode node = new VersionNode + { + wLength = BitConverter.ToUInt16(data, startPosition + nodeLength), + wValueLength = BitConverter.ToUInt16(data, startPosition + nodeLength + 2), + wType = BitConverter.ToUInt16(data, startPosition + nodeLength + 4) + }; + nodeLength += 6; + + MemoryStream nameMs = new MemoryStream(); + while(true) + { + if(data[startPosition + nodeLength] == 0 && data[startPosition + nodeLength + 1] == 0) + break; + + nameMs.WriteByte(data[startPosition + nodeLength]); + nameMs.WriteByte(data[startPosition + nodeLength + 1]); + nodeLength+=2; + } + + node.szName = Encoding.Unicode.GetString(nameMs.ToArray()); + nodeLength+=2; + + if(nodeLength % 4 > 0) nodeLength += 4 - nodeLength % 4; + + int factor = node.wType == 1 ? 2 : 1; + + node.rgbData = new byte[node.wValueLength * factor]; + Array.Copy(data, startPosition + nodeLength, node.rgbData, 0, node.rgbData.Length); + nodeLength += node.rgbData.Length; + if(nodeLength % 4 > 0) nodeLength += 4 - nodeLength % 4; + + string foo = Encoding.Unicode.GetString(node.rgbData); + + List children = new List(); + + while(nodeLength < node.wLength) + { + children.Add(GetNode(data, startPosition + nodeLength, out int childLength)); + nodeLength += childLength; + } + + if(children.Count > 0) node.children = children.ToArray(); + + return node; + } + + void DecodeNode(VersionNode node, string parent, string grandparent) + { + if(node.szName == FIXED_FILE_INFO_SIG) + { + IntPtr infoPtr = Marshal.AllocHGlobal(node.wValueLength); + Marshal.Copy(node.rgbData, 0, infoPtr, node.wValueLength); + FixedFileInfo info = (FixedFileInfo)Marshal.PtrToStructure(infoPtr, typeof(FixedFileInfo)); + Marshal.FreeHGlobal(infoPtr); + + FileVersion = + $"{(info.dwFileVersionMS & 0xFFFF0000) >> 16}.{info.dwFileVersionMS & 0xFFFF:D2}.{(info.dwFileVersionLS & 0xFFFF0000) >> 16}.{info.dwFileVersionLS & 0xFFFF}"; + ProductVersion = + $"{(info.dwProductVersionMS & 0xFFFF0000) >> 16}.{info.dwProductVersionMS & 0xFFFF:D2}.{(info.dwProductVersionLS & 0xFFFF0000) >> 16}.{info.dwProductVersionLS & 0xFFFF}"; + FileFlags = (VersionFileFlags)(info.dwFileFlags & info.dwFileFlagsMask); + FileOS = (VersionFileOS)info.dwFileOS; + FileType = (VersionFileType)info.dwFileType; + FileSubtype = (VersionFileSubtype)info.dwFileSubtype; + FileDate = DateTime.FromFileTime(info.dwFileDateMS * 0x100000000 + info.dwFileDateLS); + } + + if(parent == STRING_FILE_INFO) + { + Dictionary strings = new Dictionary(); + StringsByLanguage.Add(node.szName, strings); + } + + if(grandparent == STRING_FILE_INFO) + if(StringsByLanguage.TryGetValue(parent, out Dictionary strings)) + { + strings.Add(node.szName, Encoding.Unicode.GetString(node.rgbData)); + } + + if(node.children == null) return; + + foreach(VersionNode n in node.children) DecodeNode(n, node.szName, parent); + } + + /// + /// Converts a to string + /// + /// The string. + /// + /// + /// + public static string TypeToString(VersionFileType type) + { + switch(type) + { + case VersionFileType.VFT_APP: return "Application"; + case VersionFileType.VFT_DLL: return "Dynamic-link library"; + case VersionFileType.VFT_DRV: return "Device driver"; + case VersionFileType.VFT_FONT: return "Font"; + case VersionFileType.VFT_STATIC_LIB: return "Static-link library"; + case VersionFileType.VFT_UNKNOWN: return "Unknown"; + case VersionFileType.VFT_VXD: return "Virtual device"; + default: return $"Unknown type code {(uint)type}"; + } + } + + /// + /// Converts a to string, considering file type to be a driver + /// + /// The string. + /// + /// + /// + public static string DriverToString(VersionFileSubtype subtype) + { + switch(subtype) + { + case VersionFileSubtype.VFT2_DRV_COMM: return "Communications"; + case VersionFileSubtype.VFT2_DRV_DISPLAY: return "Display"; + case VersionFileSubtype.VFT2_DRV_INSTALLABLE: return "Installable"; + case VersionFileSubtype.VFT2_DRV_KEYBOARD: return "Keyboard"; + case VersionFileSubtype.VFT2_DRV_LANGUAGE: return "Language"; + case VersionFileSubtype.VFT2_DRV_MOUSE: return "Mouse"; + case VersionFileSubtype.VFT2_DRV_NETWORK: return "Network"; + case VersionFileSubtype.VFT2_DRV_PRINTER: return "Printer"; + case VersionFileSubtype.VFT2_DRV_SOUND: return "Sound"; + case VersionFileSubtype.VFT2_DRV_SYSTEM: return "System"; + case VersionFileSubtype.VFT2_DRV_VERSIONED_PRINTER: return "Versioned"; + case VersionFileSubtype.VFT2_UNKNOWN: return "Unknown"; + default: return $"Unknown type code {(uint)subtype}"; + } + } + + /// + /// Converts a to string, considering file type to be a font + /// + /// The string. + /// + /// + /// + public static string FontToString(VersionFileSubtype subtype) + { + switch(subtype) + { + case VersionFileSubtype.VFT2_FONT_RASTER: return "Raster"; + case VersionFileSubtype.VFT2_FONT_TRUETYPE: return "TrueType"; + case VersionFileSubtype.VFT2_FONT_VECTOR: return "Vector"; + case VersionFileSubtype.VFT2_UNKNOWN: return "Unknown"; + default: return $"Unknown type code {(uint)subtype}"; + } + } + + /// + /// Converts a to string + /// + /// The string. + /// + /// + /// + public static string OsToString(VersionFileOS os) + { + switch(os) + { + case VersionFileOS.VOS_DOS: return "DOS"; + case VersionFileOS.VOS_NT: return "Windows NT"; + case VersionFileOS.VOS_WINDOWS16: return "16-bit Windows"; + case VersionFileOS.VOS_WINDOWS32: return "32-bit Windows"; + case VersionFileOS.VOS_OS216: return "16-bit OS/2"; + case VersionFileOS.VOS_OS232: return "32-bit OS/2"; + case VersionFileOS.VOS_PM16: return "16-bit Presentation Manager"; + case VersionFileOS.VOS_PM32: return "32-bit Presentation Manager"; + case VersionFileOS.VOS_UNKNOWN: return "Unknown"; + case VersionFileOS.VOS_DOS_NT: return "DOS running under Windows NT"; + case VersionFileOS.VOS_DOS_WINDOWS16: return "16-bit Windows running under DOS"; + case VersionFileOS.VOS_DOS_WINDOWS32: return "32-bit Windows running under DOS"; + case VersionFileOS.VOS_DOS_PM16: return "16-bit Presentation Manager running under DOS"; + case VersionFileOS.VOS_DOS_PM32: return "32-bit Presentation Manager running under DOS"; + case VersionFileOS.VOS_NT_WINDOWS16: return "16-bit Windows running under Windows NT"; + case VersionFileOS.VOS_NT_WINDOWS32: return "32-bit Windows running under Windows NT"; + case VersionFileOS.VOS_NT_PM16: + return "16-bit Presentation Manager running under Windows NT"; + case VersionFileOS.VOS_NT_PM32: + return "32-bit Presentation Manager running under Windows NT"; + case VersionFileOS.VOS_OS216_WINDOWS16: return "16-bit Windows running under 16-bit OS/2"; + case VersionFileOS.VOS_OS216_WINDOWS32: return "32-bit Windows running under 16-bit OS/2"; + case VersionFileOS.VOS_OS216_PM16: + return "16-bit Presentation Manager running under 16-bit OS/2"; + case VersionFileOS.VOS_OS216_PM32: + return "32-bit Presentation Manager running under 16-bit OS/2"; + case VersionFileOS.VOS_OS232_WINDOWS16: return "16-bit Windows running under 32-bit OS/2"; + case VersionFileOS.VOS_OS232_WINDOWS32: return "32-bit Windows running under 32-bit OS/2"; + case VersionFileOS.VOS_OS232_PM16: + return "16-bit Presentation Manager running under 32-bit OS/2"; + case VersionFileOS.VOS_OS232_PM32: + return "32-bit Presentation Manager running under 32-bit OS/2"; + default: return $"Unknown OS code {(uint)os}"; + } + } + } + } +} \ No newline at end of file diff --git a/libexeinfo/libexeinfo.csproj b/libexeinfo/libexeinfo.csproj index e891144..c2e57a4 100644 --- a/libexeinfo/libexeinfo.csproj +++ b/libexeinfo/libexeinfo.csproj @@ -67,6 +67,7 @@ +