/* * PERWAPI - An API for Reading and Writing PE Files * * Copyright (c) Diane Corney, Queensland University of Technology, 2004. * * This program is free software; you can redistribute it and/or modify * it under the terms of the PERWAPI Copyright as included with this * distribution in the file PERWAPIcopyright.rtf. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY as is explained in the copyright notice. * * The author may be contacted at d.corney@qut.edu.au * * Version Date: 26/01/07 */ using System; using System.IO; using System.Collections; namespace QUT.PERWAPI { /**************************************************************************/ /**************************************************************************/ /**************************************************************************/ // Base Class for PE Files /**************************************************************************/ /**************************************************************************/ /**************************************************************************/ /// /// Base class for the PEFile (starting point) /// public class PEFile : Module { private string outputDir, fileName; private Stream outStream; private Assembly thisAssembly; PEWriter output; MetaDataOut metaData; System.IO.FileStream unmanagedResources; internal MetaDataTables metaDataTables; internal PEFileVersionInfo versionInfo; /*-------------------- Constructors ---------------------------------*/ /// /// Create a new PE File with the name "fileName". If "fileName" ends in ".dll" then /// the file is a dll, otherwise it is an exe file. This PE File has no assembly. /// /// Name for the output file. public PEFile(string fileName) : base(fileName) { //PrimitiveType.ClearAddedFlags(); // KJG 18-April-2005 - Now done in MetaDataOut this.fileName = fileName; metaData = new MetaDataOut(); versionInfo = new PEFileVersionInfo(); versionInfo.SetDefaults(fileName); } /// /// Create a new PE File with the name "fileName". If "fileName" ends in ".dll" then /// the file is a dll, otherwise it is an exe file. This file has an Assembly called /// "assemblyName". /// /// Name for the output file /// Name of the assembly public PEFile(string fileName, string assemblyName) : base(fileName) { //PrimitiveType.ClearAddedFlags(); // KJG 18-April-2005 - Now done in MetaDataOut this.fileName = fileName; thisAssembly = new Assembly(assemblyName, this); metaData = new MetaDataOut(); versionInfo = new PEFileVersionInfo(); versionInfo.SetDefaults(fileName); } /// /// Read a PE file and create all the data structures to represent it /// /// The file name of the PE file /// PEFile object representing "filename" public static PEFile ReadPEFile(string filename) { return PEReader.ReadPEFile(filename, false); } /// /// Read an existing PE File and return the exported interface /// (ie. anything that was specified as public). /// All the MetaData structures will be Refs. /// /// The name of the pe file /// The AssemblyRef or ModuleRef describing the exported interface of the specified file public static ReferenceScope ReadExportedInterface(string filename) { return PEReader.GetExportedInterface(filename); } public static PEFile ReadPublicClasses(string filename) { PEFile pefile = PEReader.ReadPEFile(filename, true); ArrayList newClasses = new ArrayList(); for (int i = 0; i < pefile.classes.Count; i++) { ClassDef aClass = (ClassDef)pefile.classes[i]; if (aClass.isPublic()) newClasses.Add(aClass); } pefile.classes = newClasses; return pefile; } /*---------------------------- public set and get methods ------------------------------*/ /// /// Get the version of .NET for this PE file /// /// .NET version public NetVersion GetNetVersion() { return versionInfo.netVersion; } /// /// Set the .NET version for this PE file /// /// .NET version public void SetNetVersion(NetVersion nVer) { versionInfo.netVersion = nVer; versionInfo.netVerString = MetaData.versions[(int)versionInfo.netVersion]; if ((nVer == NetVersion.Whidbey40) || (nVer == NetVersion.Whidbey41)) { versionInfo.tsMinVer = 1; } else { versionInfo.tsMinVer = 0; } if (nVer == NetVersion.Whidbey50) { versionInfo.tsMajVer = 2; } GenericParam.extraField = nVer <= NetVersion.Whidbey40; if (Diag.DiagOn && GenericParam.extraField) Console.WriteLine("Writing extra field for GenericParams"); } /// /// Get the .NET version for this PE file /// /// string representing the .NET version public string GetNetVersionString() { return versionInfo.netVerString.Trim(); } /// /// Make a descriptor for an external assembly to this PEFile (.assembly extern) /// /// the external assembly name /// a descriptor for this external assembly public AssemblyRef MakeExternAssembly(string assemName) { if (assemName.CompareTo(MSCorLib.mscorlib.Name()) == 0) return MSCorLib.mscorlib; return new AssemblyRef(assemName); } /// /// Make a descriptor for an external module to this PEFile (.module extern) /// /// the external module name /// a descriptor for this external module public ModuleRef MakeExternModule(string name) { return new ModuleRef(name); } /// /// Set the directory that the PE File will be written to. /// The default is the current directory. /// /// The directory to write the PE File to. public void SetOutputDirectory(string outputDir) { this.outputDir = outputDir; } /// /// Direct PE File output to an existing stream, instead of creating /// a new file. /// /// The output stream public void SetOutputStream(Stream output) { this.outStream = output; } /// /// Specify if this PEFile is a .dll or .exe /// public void SetIsDLL(bool isDll) { versionInfo.isDLL = isDll; if (isDll) versionInfo.characteristics = FileImage.dllCharacteristics; else versionInfo.characteristics = FileImage.exeCharacteristics; } /// /// Set the subsystem (.subsystem) (Default is Windows Console mode) /// /// subsystem value public void SetSubSystem(SubSystem subS) { versionInfo.subSystem = subS; } /// /// Set the flags (.corflags) /// /// the flags value public void SetCorFlags(CorFlags flags) { versionInfo.corFlags = flags; } public string GetFileName() { return fileName; } public void SetFileName(string filename) { this.fileName = filename; } /// /// Get a Meta Data Element from this PE file /// /// The meta data token for the required element /// The meta data element denoted by token public MetaDataElement GetElement(uint token) { if (buffer != null) return buffer.GetTokenElement(token); if (metaDataTables != null) return metaDataTables.GetTokenElement(token); return null; } /// /// Add a manifest resource to this PEFile /// public void AddUnmanagedResources(string resFilename) { if (!System.IO.File.Exists(resFilename)) throw (new FileNotFoundException("Unmanaged Resource File Not Found", resFilename)); unmanagedResources = System.IO.File.OpenRead(resFilename); throw new NotYetImplementedException("Unmanaged Resources are not yet implemented"); } /// /// Add a managed resource to this PEFile. The resource will be embedded in this PE file. /// /// The name of the managed resource /// The managed resource /// Access for the resource public void AddManagedResource(string resName, byte[] resBytes, bool isPublic) { resources.Add(new ManifestResource(this, resName, resBytes, isPublic)); } /// /// Add a managed resource from another assembly. /// /// The name of the resource /// The assembly where the resource is /// Access for the resource public void AddExternalManagedResource(string resName, AssemblyRef assem, bool isPublic) { resources.Add(new ManifestResource(this, resName, assem, 0, isPublic)); } /// /// Add a managed resource from another file in this assembly. /// /// The name of the resource /// The assembly where the resource is /// Access for the resource public void AddExternalManagedResource(string resName, ResourceFile resFile, uint offset, bool isPublic) { resources.Add(new ManifestResource(this, resName, resFile, offset, isPublic)); } /// /// Add a managed resource from another module in this assembly. /// /// The name of the resource /// The assembly where the resource is /// Access for the resource public void AddExternalManagedResource(string resName, ModuleRef mod, uint offset, bool isPublic) { resources.Add(new ManifestResource(this, resName, mod.modFile, offset, isPublic)); } /// /// Add a managed resource from another assembly. /// /// /// public void AddExternalManagedResource(ManifestResource mr, bool isPublic) { resources.Add(new ManifestResource(this, mr, isPublic)); } /// /// Find a resource /// /// The name of the resource /// The resource with the name "name" or null public ManifestResource GetResource(string name) { for (int i = 0; i < resources.Count; i++) { if (((ManifestResource)resources[i]).Name == name) return (ManifestResource)resources[i]; } return null; } public ManifestResource[] GetResources() { return (ManifestResource[])resources.ToArray(typeof(ManifestResource)); } /// /// Get the descriptor for this assembly. The PEFile must have been /// created with hasAssembly = true /// /// the descriptor for this assembly public Assembly GetThisAssembly() { return thisAssembly; } public AssemblyRef[] GetImportedAssemblies() { return buffer.GetAssemblyRefs(); } public string[] GetNamesOfImports() { return buffer.GetAssemblyRefNames(); } /*------------------------------------------ Output Methods -------------------------------*/ private void BuildMetaData() { BuildMDTables(metaData); if (thisAssembly != null) { thisAssembly.BuildMDTables(metaData); } metaData.BuildMDTables(); // DoCustomAttributes, BuildSignatures for each in metadata tables } /// /// Write out the PEFile (the "bake" function) /// /// include debug information public void WritePEFile(bool writePDB) { if (outStream == null) { if (outputDir != null) { if (!outputDir.EndsWith("\\")) fileName = outputDir + "\\" + fileName; else fileName = outputDir + fileName; } output = new PEWriter(versionInfo, fileName, metaData, writePDB); } else { // Check to make sure we have not been asked to write a PDB if (writePDB) throw new Exception("You can not write PDB data when writing to a stream. Please try writing to a file instead."); output = new PEWriter(versionInfo, outStream, metaData); } BuildMetaData(); output.MakeFile(versionInfo); } /// /// Makes the assembly debuggable by attaching the DebuggableAttribute /// to the Assembly. Call immediately before calling WritePEFile. /// /// set true to enable debugging, false otherwise /// set true to disable optimizations that affect debugging public void MakeDebuggable(bool allowDebug, bool suppressOpt) { ClassRef debugRef = null; MethodRef dCtor = null; Type[] twoBools = new Type[] { PrimitiveType.Boolean, PrimitiveType.Boolean }; debugRef = MSCorLib.mscorlib.GetClass("System.Diagnostics", "DebuggableAttribute"); if (debugRef == null) debugRef = MSCorLib.mscorlib.AddClass("System.Diagnostics", "DebuggableAttribute"); dCtor = debugRef.GetMethod(".ctor", twoBools); if (dCtor == null) { dCtor = debugRef.AddMethod(".ctor", PrimitiveType.Void, twoBools); dCtor.AddCallConv(CallConv.Instance); } Constant[] dbgArgs = new Constant[] { new BoolConst(allowDebug), new BoolConst(suppressOpt) }; thisAssembly.AddCustomAttribute(dCtor, dbgArgs); } /// /// Write out a CIL text file for this PE file /// /// include debug information public void WriteCILFile(bool debug) { string cilFile = fileName.Substring(0, fileName.IndexOf('.')) + ".il"; CILWriter writer = new CILWriter(cilFile, debug, this); writer.BuildCILInfo(); writer.WriteFile(debug); } internal void SetThisAssembly(Assembly assem) { if (Diag.DiagOn) Console.WriteLine("Setting fileScope to assembly " + assem.Name()); thisAssembly = assem; } internal void AddToResourceList(ManifestResource res) { resources.Add(res); } internal void AddToFileList() { } internal void SetDLLFlags(ushort dflags) { versionInfo.DLLFlags = dflags; } public void ReadPDB() { PDBReader reader = new PDBReader(this.fileName); foreach (ClassDef cDef in GetClasses()) foreach (MethodDef mDef in cDef.GetMethods()) { CILInstructions buffer = mDef.GetCodeBuffer(); PDBMethod meth = reader.GetMethod((int)mDef.Token()); if (meth == null) continue; // no symbols for this method PDBSequencePoint[] spList = meth.SequencePoints; MergeBuffer mergeBuffer = new MergeBuffer(buffer.GetInstructions()); PDBScope outer = meth.Scope; Scope root = ReadPDBScope(outer, mergeBuffer, null, mDef); buffer.currentScope = root; bool hasRootScope = mergeBuffer.hasRootScope(); if (!hasRootScope) mergeBuffer.Add(new OpenScope(root), (uint)0); foreach (PDBSequencePoint sp in spList) { PDBDocument doc = sp.Document; mergeBuffer.Add( new Line((uint)sp.Line, (uint)sp.Column, (uint)sp.EndLine, (uint)sp.EndColumn, SourceFile.GetSourceFile(doc.URL, doc.Language, doc.LanguageVendor, doc.DocumentType)), (uint)sp.Offset); } if (!hasRootScope) mergeBuffer.Add(new CloseScope(root), (uint)outer.EndOffset); buffer.SetInstructions(mergeBuffer.Instructions); } } private Scope ReadPDBScope(PDBScope scope, MergeBuffer mergeBuffer, Scope parent, MethodDef thisMeth) { Scope thisScope = new Scope(parent, thisMeth); if (parent != null) mergeBuffer.Add(new OpenScope(thisScope), (uint)scope.StartOffset); foreach (PDBVariable var in scope.Variables) thisScope.AddLocalBinding(var.Name, var.Address); foreach (PDBScope child in scope.Children) ReadPDBScope(child, mergeBuffer, thisScope, thisMeth); if (parent != null) mergeBuffer.Add(new CloseScope(thisScope), (uint)scope.EndOffset); return thisScope; } } /**************************************************************************/ internal class PEFileVersionInfo { private static char[] nulls = { '\0' }; internal bool fromExisting; internal ushort characteristics; internal bool isDLL; internal byte lMajor; internal byte lMinor; internal uint fileAlign; internal ushort osMajor; internal ushort osMinor; internal ushort userMajor; internal ushort userMinor; internal ushort subSysMajor; internal ushort subSysMinor; internal SubSystem subSystem; internal ushort DLLFlags = 0; internal ushort cliMajVer; internal ushort cliMinVer; internal CorFlags corFlags = CorFlags.CF_IL_ONLY; internal ushort mdMajVer; internal ushort mdMinVer; internal NetVersion netVersion; internal string netVerString; internal byte tsMajVer; internal byte tsMinVer; internal void SetDefaults(string name) { fromExisting = false; isDLL = name.EndsWith(".dll") || name.EndsWith(".DLL"); if (isDLL) { characteristics = FileImage.dllCharacteristics; } else { characteristics = FileImage.exeCharacteristics; } lMajor = MetaData.LMajors[0]; lMinor = 0; fileAlign = FileImage.minFileAlign; osMajor = 4; osMinor = 0; userMajor = 0; userMinor = 0; subSysMajor = 4; subSysMinor = 0; subSystem = SubSystem.Windows_CUI; DLLFlags = FileImage.DLLFlags; cliMajVer = 2; cliMinVer = 0; corFlags = CorFlags.CF_IL_ONLY; mdMajVer = 1; mdMinVer = 1; // MetaData Minor Version ECMA = 0, PEFiles = 1 netVersion = NetVersion.Everett; netVerString = MetaData.versions[0]; tsMajVer = 1; tsMinVer = 0; } internal void SetVersionFromString() { for (int i = 0; i < MetaData.versions.Length; i++) { if (MetaData.versions[i].Trim(nulls) == netVerString) { netVersion = (NetVersion)i; netVerString = MetaData.versions[i]; } } } } }