Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal const string is null when obfuscating Release build #36

Open
TooMuchBlue opened this issue Apr 4, 2019 · 4 comments
Open

internal const string is null when obfuscating Release build #36

TooMuchBlue opened this issue Apr 4, 2019 · 4 comments
Labels
bug Something isn't working

Comments

@TooMuchBlue
Copy link

Summary:
An internal const string returns null when compiled for Release and obfuscated. It behaves normally when compiled for Debug and obfuscated, and when compiled for Release and not obfuscated.

Description:
The code below is a portion of a shared helper library used in multiple projects. The library is compiled into a single DLL which I want to obfuscate to protect the propietary code inside, but still use it across a variety of projects. Most of the code in the library is static.

MyBuildInfo.cs:

namespace MyBuildInfo
{
  internal static class Details
  {
    internal const string AssemblyFileVersion = "1.0.13533.1000";
  }
}

Common.cs:

namespace My.Library
{
  public static class Common
  {
    public static string CommonAssemblyFileVersion()
    {
      return MyBuildInfo.Details.AssemblyFileVersion;
    }
  }
}

When used in a larger project, we often want to display build/configuration/version information about the library to the user of the outer project to aid in error reporting. As such, the library has methods like CommonAssemblyFileVersion() which report build information about the shared library. MyBuildInfo is autogenerated from the source code repository and is used in other projects (including the outer project). The library provides the method in order to avoid name collisions and confusion.

I'm using a .crproj file to control obfuscation options. I don't have any [Obfuscate] attributes in the library project. My crproj uses template "none" and at least this protection:

<protection id="constants">
  <argument name="mode" value="normal" />
  <argument name="elements" value="S" />
</protection>

When I compile for Debug, then obfuscate, CommonAssemblyFileVersion() returns the expected value. When I compile for Release, then obfuscate, the same method returns null. (Naturally, the method returns the correct value at all times when not obfuscated.)

I'm calling the library like this:

  string x = Common.CommonAssemblyConfiguration();
  txtConfiguration.Text = string.Format("{0} {1}", x.Length, x);

When this fails, I get a NullReferenceException on x.Length. (I could deal with that if it was normal, but it doesn't help me get the data I need.)

Here are the Debug and Release build configuration settings from my csproj:

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>..\bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <RunCodeAnalysis>true</RunCodeAnalysis>
    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
    <DocumentationFile>..\bin\Debug\McGladrey.Library.XML</DocumentationFile>
    <Prefer32Bit>false</Prefer32Bit>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>..\bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DocumentationFile>..\bin\Release\McGladrey.Library.XML</DocumentationFile>
    <Prefer32Bit>false</Prefer32Bit>
  </PropertyGroup>

Additional details:

  • Windows 10 version 1809
  • Visual Studio Pro 2017, version 15.9.3
  • Class Library project, targeting .NET Framework 4
  • Language version: "C# latest major version (default)"
  • The expected value is still present in the obfuscated Release build. I can see it when I decompile using ILSpy,
  • The problem with the string property is unaffected by including or excluding elements N, I, P. I haven't checked if properties of those types are similarly affected, though.
  • When I changed CommonAssemblyFileVersion() to assign the value to a local string variable, then return that, the problem may have gone away...I think. It could be this blocks certain optimizations, or it may have been that I tested that with a Debug build.
  • Marking this particular constant so it is not obfuscated is an option, but there are many other internal constants that I still want to protect.
  • To ensure the problem isn't caused by Visual Studio debug mode, I also tested by running my outer project directly. The behavior is identical.
@TooMuchBlue
Copy link
Author

IL from the obfuscated release build. (Pardon the right-to-left text. Yay for Unicode!)

.class public auto ansi abstract sealed beforefieldinit My.Library.Common
	extends [mscorlib]System.Object
{
	// Methods
	.method public hidebysig static 
		string CommonAssemblyFileVersion () cil managed 
	{
		// Method begins at RVA 0x3ec0
		// Code size 13 (0xd)
		.maxstack 8

		// return global::<Module>.‏‏‪‮​‭‪‫‬​‎​‏‫‮‍‎‎​‍‌‮‪‮<string>(1283113728u);
		IL_0000: ldc.i4 1283113728
		// (no C# code)
		IL_0005: br.s IL_0007

		IL_0007: call !!0 '<Module>'::'‏‏‪‮​‭‪‫‬​‎​‏‫‮‍‎‎​‍‌‮‪‮'<string>(uint32)
		IL_000c: ret
	} // end of method Common::CommonAssemblyFileVersion
} // end of class My.Library.Common

@mkaring
Copy link

mkaring commented Apr 7, 2019

I am currently tracking this error in my fork as well. Are you able to produce a minimal working example to produce the error? I believe that there is a problem in the decryption method for the protected constants that causes the method to return null for some invocations. How ever I believe that it depends on the other protected constants in the assembly, because the issue seems to disappear in case the affected code is extracted into an minimal example, even if the code of the affected method still looks the same.

@TooMuchBlue
Copy link
Author

TooMuchBlue commented Apr 8, 2019

I would love to produce a MWE, but it's not going to happen this week, probably not the next.

What I can do is produce the IL for the Debug and Release builds, pre-obfuscation. Some of the optimization that happens in a Release build will remove interim variables and rearrange code. Perhaps that's a contributing factor.

If you think this is valuable, I'll find the time to generate the IL and add it here.

@XenocodeRCE XenocodeRCE added the bug Something isn't working label Apr 8, 2019
@mkaring
Copy link

mkaring commented Apr 8, 2019

If you could attach or send the obfuscated assembly or extract the IL code of the string decoder along with it's broken invocation and the attached data array, that would help in case the problem is where I expect it to be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants