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.


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)); }


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 } } }


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.