Completing the anonymization of modules

Creating an "anonymized" module isn't of much use if your programs can't use it. So in this modification of Roslyn a test is made to see if an anonymized module has been referenced, and if so to change the reference to the anonymized name.

Note: The code shown below is somewhat obsolete. A newer version is available for download - see this article.

src\Compilers\Core\Portable\PEWriter\MetadataWriter.cs

This source file is part of the CodeAnalysis project in Roslyn. In the Visual Studio Solution Explorer it can be found under CodeAnalysis - PEWriter.

Compared with the previous step, a few more lines of code have been added, and a Debug.Assert() statement has been removed.

private void PopulateAssemblyRefTableRows() { var assemblyRefs = this.GetAssemblyRefs(); metadata.SetCapacity(TableIndex.AssemblyRef, assemblyRefs.Count); foreach (var identity in assemblyRefs) { //Yacks05: If necessary, modify reference to a Merlinia assembly to use the // anonymized assembly name string s = GetAssemblyName(identity.Name); // reference has token, not full public key metadata.AddAssemblyReference( //name: GetStringHandleForPathAndCheckLength(identity.Name), name: GetStringHandleForPathAndCheckLength(s), version: identity.Version, culture: metadata.GetOrAddString(identity.CultureName), publicKeyOrToken: metadata.GetOrAddBlob(identity.PublicKeyToken), flags: (AssemblyFlags)((int)identity.ContentType << 9) | (identity.IsRetargetable ? AssemblyFlags.Retargetable : 0), hashValue: default(BlobHandle)); } } private void PopulateAssemblyTableRows() { if (!EmitAssemblyDefinition) { return; } var sourceAssembly = module.SourceAssemblyOpt; Debug.Assert(sourceAssembly != null); var flags = sourceAssembly.AssemblyFlags & ~AssemblyFlags.PublicKey; if (!sourceAssembly.Identity.PublicKey.IsDefaultOrEmpty) { flags |= AssemblyFlags.PublicKey; } //Yacks04: Anonymize the assembly name if applicable string s = module.Name; if (EmittingAnonymized()) s = module.CommonCompilation._YacksData.GetModuleName(); metadata.AddAssembly( flags: flags, hashAlgorithm: sourceAssembly.HashAlgorithm, version: sourceAssembly.Identity.Version, publicKey: metadata.GetOrAddBlob(sourceAssembly.Identity.PublicKey), //name: GetStringHandleForPathAndCheckLength(module.Name, module), name: GetStringHandleForPathAndCheckLength(s, module), culture: metadata.GetOrAddString(sourceAssembly.Identity.CultureName)); }

src\Compilers\Core\Portable\PEWriter\MetadataWriter.Yacks.cs

This is a source file which has been added to the CodeAnalysis project.

// Copyright (c) Merlinia A/S. All Rights Reserved. Licensed under the Apache License, Version 2.0. (Just to be compatible with the Microsoft Roslyn license.) using System; using System.Collections.Immutable; using System.Diagnostics; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using Microsoft.CodeAnalysis; using LiteDB; using Merlinia.Yacks; using Merlinia.Yacks.ProjectMetadata; namespace Microsoft.Cci { /// <summary> /// This source file contains some code that has been added to the Roslyn MetadataWriter class. /// But to keep the inline added code to a minimum the added code is placed in methods in this /// source file and called from the main MetadataWriter.cs source file. /// </summary> internal partial class MetadataWriter { /// <summary> /// Method to modify a reference to a Merlinia assembly to use the anonymized assembly name if /// necessary, /// </summary> private string GetAssemblyName(string assemblyName) { // Test if necessary to convert assembly name to anonymized assembly name if (!EmittingAnonymized() || !assemblyName.StartsWith(Yacks_CompilationData.CMerlinia, StringComparison.Ordinal) || assemblyName.StartsWith(YacksCore.CMerliniaYacksCore, StringComparison.Ordinal) || module.CommonCompilation._YacksData.YacksProjectDirectory == null) return assemblyName; // Open yacksProjectDirectory database (it should exist) using (LiteDatabase liteDatabase = new LiteDatabase(module.CommonCompilation._YacksData.YacksProjectDirectory)) { // Get YacksProjectInfo for this assembly, if it exists in database (it should) LiteCollection<YacksProjectInfo> yacksProjects = Compilation.GetProjectCollection(liteDatabase); YacksProjectInfo projectInfo = Compilation.ReadProjectInfo(yacksProjects, assemblyName); // Return the anonymized assembly name Debug.Assert(projectInfo != null); return module.CommonCompilation._YacksData.GetModuleName(projectInfo.ModuleNumber); } } /// <summary> /// Method to test for a Code Analysis attribute, and if so to indicate that it can be omitted /// from the output module if an "anonymized" module is currently being built. This is done on /// the assumption that they are not necessary, and because they may contain unobfuscated /// method parameter names /// </summary> private bool OmitCodeAnalysisAttributes(ICustomAttribute customAttribute) { if (!EmittingAnonymized()) return false; string attributeName = GetAttributeName(customAttribute); if (attributeName == null) return false; return attributeName == "SuppressMessageAttribute"; } /// <summary> /// Method to test for one of the AssemblyInfo attributes that should be "censored" to avoid /// providing information about the purpose of the module. This is only done when building an /// "anonymized" module. /// </summary> private bool AnonymizeAssemblyAttribute(ICustomAttribute customAttribute) { if (!EmittingAnonymized()) return false; string attributeName = GetAttributeName(customAttribute); if (attributeName == null) return false; return attributeName == "AssemblyTitleAttribute" || attributeName == "AssemblyDescriptionAttribute" || attributeName == "AssemblyProductAttribute"; } /// <summary> /// Method to get and check AttributeData.Name, given an ICustomAttribute object. /// </summary> /// <returns>AttributeData.Name, or null if something wrong</returns> private static string GetAttributeName(ICustomAttribute customAttribute) { AttributeData attributeData = customAttribute as AttributeData; if (attributeData == null) return null; Debug.Assert(attributeData.AttributeClass != null && attributeData.AttributeClass.Name != null); return attributeData.AttributeClass.Name; } /// <summary> /// Method to test if Yacks modifications are in effect and if an "anonymized" module is /// currently being built. /// </summary> private bool EmittingAnonymized() { return module.CommonCompilation._YacksData != null && module.CommonCompilation._YacksData.EmittingAnonymized; } /// <summary> /// Method to perform some kludgy processing needed to get loading the reference to the static /// field "s" in the static objects that implement disguised strings to work. /// </summary> /// <returns>true = kludgy processing done, false = not disguised string reference</returns> private bool ProcessPossibleDisguisedStringReference(int pseudoToken, ImmutableArray<byte> generatedIL, ref int localOffset, BlobWriter blobWriter) { // Test if the pseudo/fake token is actually a (negative) stringy number, exit if not if (pseudoToken >= 0) return false; Yacks_CompilationData yacksData = module.CommonCompilation._YacksData; Debug.Assert(yacksData != null); // Replace the ldstr opcode with ldsfld opcode in BlobWriter's data area Debug.Assert(ReadByte(generatedIL, localOffset - 1) == (byte)ILOpCode.Ldstr); blobWriter.Offset = localOffset - 1; blobWriter.WriteByte((byte)ILOpCode.Ldsfld); // Look up the fake/pseudo token for the "s" field, convert it to a handle and emit it int fakeToken; if (!yacksData.StringyNumberToFakeTokenNumber.TryGetValue(-pseudoToken, out fakeToken)) Debug.Assert(false, "stringy number not in dictionary"); blobWriter.WriteInt32( MetadataTokens.GetToken(ResolveEntityHandleFromPseudoToken(fakeToken))); localOffset += 4; return true; // No further processing for this opcode } } }

Testing

As a very simple test I compiled a program TestDynamicBitArrays, which tests parts of the Merlinia.CommonClasses.MArrays library assembly. Both the non-anonymized and the anonymized versions of the two programs worked.

This screen shot shows the non-anonymized and the anonymized versions of the TestDynamicBitArrays program as displayed by JetBrains dotPeek.

ModRos 5 Snap1

You must login to post a comment.
Loading comment... The comment will be refreshed after 00:00.

Be the first to comment.