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

Obfuscate .NET dlls #4202

Closed
MichaelRumpler opened this issue Jan 30, 2020 · 13 comments
Closed

Obfuscate .NET dlls #4202

MichaelRumpler opened this issue Jan 30, 2020 · 13 comments
Assignees
Labels

Comments

@MichaelRumpler
Copy link

I have a Xamarin.Forms solution with multiple projects. Several of them need to be obfuscated (I use Babel.NET) before the app bundle is built. In order to make it a bit more complicated, the obfuscator also merges some dlls together into one and has to delete the merged, unobfuscated dlls which must not be included in the final aab anymore.

This all used to work a few months ago in some older Xamarin.Android version. I included an Obfuscate.targets file from my csproj file:

<Import Project="Obfuscate.targets" Condition=" '$(Configuration)' == 'Release' " />

And in that .targets file I declared two targets which did the work:

<Target Name="Obfuscate" AfterTargets="_CopyIntermediateAssemblies">

and

<Target Name="DeleteMergedAssemblies" BeforeTargets="_PrepareAssemblies">

Unfortunately those targets starting with underline I depend upon are internal and may change with every new Xamarin version. But I couldn't make it work with anything other.

This is exactly my problem now. The new build process does not contain the target _CopyIntermediateAssemblies anymore and I get an error.

The target "_CopyIntermediateAssemblies" listed in an AfterTargets attribute at "...\Obfuscate.targets (5,27)" does not exist in the project, and will be ignored.

So my question is:

  • How can I trigger the obfuscator to run after all projects have been compiled
  • and how can I delete the dlls which have been merged before the linker is started

Everything should of course use the new shiny features like d8, r8 and app bundles.
I can build the aab without problem if I disable obfuscation, but this is a requirement for my app.

Version Information

Microsoft Visual Studio Professional 2019
Version 16.4.3
VisualStudio.16.Release/16.4.3+29709.97
Microsoft .NET Framework
Version 4.8.03752

Xamarin 16.4.000.308 (d16-4@4755fb3)
Xamarin Designer 16.4.0.475 (remotes/origin/d16-4@ac250f5aa)
Xamarin Templates 16.4.25 (579ee62)
Xamarin.Android SDK 10.1.3.7 (d16-4/d66aed0)
Xamarin.Android Reference Assemblies and MSBuild support.
Mono: fd9f379
Java.Interop: xamarin/java.interop/d16-4@c4e569f
ProGuard: xamarin/proguard@905836d
SQLite: xamarin/sqlite@46204c4
Xamarin.Android Tools: xamarin/xamarin-android-tools/master@9f4ed4b

@gugavaro
Copy link
Contributor

@jonathanpeppers did we change anything on the targets that could cause that?

@gugavaro gugavaro added this to the Under Consideration milestone Jan 31, 2020
@jonathanpeppers
Copy link
Member

_CopyIntermediateAssemblies does not exist anymore. The leading underscore _ in MSBuild means it is a "private" target that might move or be renamed between releases.

We have some new extension points you can use now documented here:

https://github.com/xamarin/xamarin-android/blob/master/Documentation/guides/BuildProcess.md#build-extension-points

An example of some of the recent changes and how we worked around it:
xamarin/XamarinAndroidXMigration@fce0562

@MichaelRumpler
Copy link
Author

The missing linksrc folder will also be a problem. I always ran the obfuscator in that directory so that it could find all dependencies. If you don't copy them all to one folder anymore, in which variable can I find a list of all dependencies? Is that ResolvedAssemblies?

And how do I remove the merged assemblies from the list? Do I remove them from ResolvedUserAssemblies? Currently I also get an "System.IO.FileNotFoundException: Could not load assembly" for the merged dlls in GenerateJavaStubs.

When is the best time to run the obfuscator? My old approach was to run it after everything had been copied to linksrc, but if that is not done anymore...
I don't know if AfterGenerateAndroidManifest would be very helpful there.

I also attach my diagnostic log here - should've done that before.
Build_APK.zip

@jonathanpeppers
Copy link
Member

I would review the change for AndroidX.Migration as this specifically worked around the linksrc directory problem: xamarin/XamarinAndroidXMigration@fce0562

@MichaelRumpler
Copy link
Author

I did that and changed the obfuscation task to use all the directories from @(ResolvedAssemblies) instead of linksrc. Obfuscation now runs AfterTarget Build. The obfuscator overwrites my main assembly in bin\Release and after the obfuscation I remove the merged dlls from ResolvedAssemblies and ResolvedUserAssemblies like you did for AndroidXMigration.

	<ItemGroup>
		<ResolvedAssemblies Remove="@(MergeAssemblies)" />
		<ResolvedUserAssemblies Remove="@(MergeAssemblies)" />
	</ItemGroup>

But when the build process continues, it still uses the merged assemblies afterwards. I also had to remove the merged assemblies from _ResolvedAssemblies, _ResolvedUserAssemblies, ReferenceDependencyPaths and _ReferenceDependencyPaths.
Now at last the merged assemblies do not pop up in the log after obfuscation and they are not included in the .aab in base/lib/arm64-v8a/libaot-assemblyname.dll.so.

I don't know how I can check my main assembly with an .so extension from that directory, but I also found it in the .aab in base/root/assemblies. And unfortunately that version was not obfuscated !!! In the projects bin\Release folder the dll did get obfuscated.

What else do I have to do in order to include the right file in the .aab?

@jonathanpeppers
Copy link
Member

AndroidX is running the target earlier:

https://github.com/xamarin/XamarinAndroidXMigration/blob/ea130ca0d0e9b3e20edccec02364f36da11ada6b/source/Xamarin.AndroidX.Migration/BuildTasks/Xamarin.AndroidX.Migration.props#L8-L16

So you are probably obfuscating the assemblies after they are used by some steps.

You might try running your target:

    <PropertyGroup>
        <BeforeGenerateAndroidManifest>
            $(BeforeGenerateAndroidManifest);
            YourTarget;
        </BeforeGenerateAndroidManifest>
    </PropertyGroup>

Then try just changing the two item groups:

https://github.com/xamarin/XamarinAndroidXMigration/blob/5a0691c3dd94308b4c5de8d35c6b85b7b021e478/source/Xamarin.AndroidX.Migration/BuildTasks/Xamarin.AndroidX.Migration.targets#L180-L184

I don't know if this is an option, but I would have argued not to obfuscate at all -- obfuscated IL (or AOT'd assembly) is still readable and able to be reverse engineered.

@MichaelRumpler
Copy link
Author

MichaelRumpler commented Feb 11, 2020

If I run it BeforeGenerateAndroidManifest and only remove the merged dlls from ResolvedAssemblies and ResolvedUserAssemblies, then it builds, but the .aab still contains the merged dlls.

If I also remove them from the other properties I listed above, then I get a System.IO.FileNotFoundException: Could not load assembly for one of the merged dlls which should not be used anymore.

I also tried actually deleting the merged dlls from the harddisk and copying the obfuscated dlls to obj\Release\90 and obj\Release\90\android\assets, but then I got an System.IO.FileNotFoundException: Could not load assembly 'netstandard, Version=2.0.0.0.
Both FileNotFoundExceptions happened in task GenerateJavaStubs.

Obfuscation does help. Although I expect AOT to revert most of the control flow obfuscation simply renaming all the symbols (classes, method names, ...) makes it almost impossible to get back to readable code.

@jonathanpeppers
Copy link
Member

@MichaelRumpler can you share a sample app with your custom target?

@MichaelRumpler
Copy link
Author

Thanks! I hope you can test it. Unfortunately the feature which merges several dlls together is a premium feature of the Babel Obfuscator (http://www.babelfor.net/products/obfuscator). I needed to buy the enterprise version of it.

I tried to keep the project structure as it is in my real solution. So the names, target frameworks and dependencies are from my real app. But most of the projects only contain a Class1.cs which holds references to the dependencies so that no whole dll is linked out.

The interesting stuff can be found in the folder RoyalMobileApps.XF\RoyalMobileApps.XF.Android. I usually start building the release version with Build_APK.bat and the file Obfuscate.targets does all the obfuscation stuff.
Some of my old tries are still commented out at the end of the Obfuscate.targets file.

ObfuscateRepro.zip

@jonathanpeppers
Copy link
Member

Do you know how to get a trial key for this?

BABEL A valid license could not be found. Please contact http://www.babelfor.net for assistance. 

@MichaelRumpler
Copy link
Author

I sent them an email on Friday but didn't get a reply yet.
You can use mine, but please delete it after this issue gets fixed.
Unzip this file with the password I sent you via DM on Twitter and save the file in C:\Program Files\Babel
babel.zip

@jonathanpeppers
Copy link
Member

I tried your sample, and it seemed like I needed to add:

https://github.com/xamarin/XamarinAndroidXMigration/blob/ea130ca0d0e9b3e20edccec02364f36da11ada6b/source/Xamarin.AndroidX.Migration/BuildTasks/Xamarin.AndroidX.Migration.props#L9-L12

        <_BeforeLinkAssemblies>
            $(_BeforeLinkAssemblies);
            Obfuscate;
        </_BeforeLinkAssemblies>

Since, it looks like we had to use this private property in AndroidX.Migration, we might need to rename this to $(BeforeLinkAssemblies). 🤔

@MichaelRumpler
Copy link
Author

Yes, that was the key! Thank you very much. Now everything works as expected.

Here is again what I did:

In my .csproj I load a Obfuscate.targets file only in Release mode:

<Import Project="Obfuscate.targets" Condition=" '$(Configuration)' == 'Release' " />

In the beginning I had this stuff in my .csproj directly, but as it got too much, I didn't want to litter the .csproj with it.

The Obfuscate.targets file is something like this:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<PropertyGroup>
		<_BeforeLinkAssemblies>
			$(_BeforeLinkAssemblies);
			Obfuscate;
		</_BeforeLinkAssemblies>
	</PropertyGroup>

As Jonathan pointed out above, _BeforeLinkAssemblies executes the obfuscator at the exact right moment in the build process so that the rest works.

	<Target Name="Obfuscate">
		<ItemGroup>
			<MainAndroidAssembly Remove="@(MainAndroidAssembly)" />
			<MainAndroidAssembly Include="@(ResolvedAssemblies)" Condition="'%(Filename)' == '$(AssemblyName)'" KeepMetadata="none" />
		</ItemGroup>

I could've also hardcoded the MainAndroidAssembly, but searching within ResolvedAssemblies makes sure that it also works when this runs somewhere else than usual.
KeepMetadata="none" is not needed, but it makes the diagnostic log much more readable.

		<ItemGroup>
			<MergeAssemblies Remove="@(MergeAssemblies)" />
			<MergeAssemblies Include="@(ResolvedAssemblies)" Condition="'%(Filename)' == 'MergedAssembly1' Or '%(Filename)' == 'MergedAssembly2' Or '%(Filename)' == 'MergedAssembly3'" KeepMetadata="none" />
		</ItemGroup>

This gets the files with complete path to the merged assemblies. The obfuscator merges them directly into the MainAndroidAssembly so that these are not needed anymore.

		<ItemGroup>
			<SearchDirectories Remove="@(SearchDirectories)" />
			<SearchDirectories Include="@(ResolvedAssemblies -> '%(RootDir)%(Directory)')" KeepMetadata="none" />
		</ItemGroup>

SearchDirectories is a list of all folders where the dependencies of my solution are saved. Previously the build process copied them together into one folder (linksrc), but this has been removed.

		<ObfuscateTask />

Now call the obfuscator. I use BabelFor.Net, but I won't list all the parameters for calling it here, as it is not relevant for any other obfuscators.

		<ItemGroup>
			<ResolvedAssemblies Remove="@(MergeAssemblies)" />
			<ResolvedUserAssemblies Remove="@(MergeAssemblies)" />
		</ItemGroup>
	</Target>
</Project>

After the obfuscation, the merged assemblies must be removed from ResolvedAssemblies and ResolvedUserAssemblies so that they are not included in the bundle/package.

@ghost ghost locked as resolved and limited conversation to collaborators Jun 4, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants