realm / realm-dotnet

Realm is a mobile database: a replacement for SQLite & ORMs

Home Page:https://realm.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Realm Obfuscation (Unity + Local Database)

peachypixels opened this issue · comments

Problem

Hello!

I've recently integrated obfuscation into an application and hit a number of issues, mostly regarding Realm objects (i.e., IRealmObject)

The Realms are encrypted (no issues there) but I am also wanting to obfuscate Realm object & property names.

Initially I tried to use the obfuscation tool to achieve this (so the obfuscator changed the woven Realm 'proxy' classes) but this resulted in the following issue...

https://www.mongodb.com/docs/atlas/device-sdks/sdk/dotnet/troubleshooting/#resolve-a--no-properties-in-class--exception

After much trial and error, the only way I can obfuscate the Realm aspect of the app is to...

  1. Use MapTo attributes on Realm objects & properties
  2. Tell the obfuscator to ignore all Realm objects

But the limitation\issue with this approach is that...

  1. Mono builds (in Assembly-CSharp.dll) & IL2CPP builds (in global-metadata.dat) still contain the un-obfuscated names. I believe it's not too difficult (with the right knowledge\tools) to correlate the un-obfuscated & obfuscated names.
  2. Calculated properties (i.e., non-persisted) on the Realm objects are also not obfuscated.

The obfuscation tool that I'm using obfuscates mostly by means of attributes. I had hoped these would carry across to their counterparts on the woven 'proxies' but this doesn't seem to be the case.

So I'm wondering if there is a known workaround? If not, could this be considered for a feature request please?

Solution

Couldn't the woven 'proxies' be generated using the mapped names? They currently do, but only as attributes with literal strings. Class & property names still use their un-mapped names.

Alternatives

I'm considering a brute force approach of using explicit obfuscated names (for Realm objects & properties) but this means uncompiled code becomes far less readable and is really an absolute last resort.

How important is this improvement for you?

Would be a major improvement

Feature would mainly be used with

Local Database only

➤ PM Bot commented:

Jira ticket: RNET-1134

What tool are you using for obfuscation? I guess the main question is if it's running before or after the Realm weaver, because if it's running after, I would expect things to work just fine. One thing we do use reflection for is the generated RealmSchema field - if you exclude just that from obfuscation, do you still experience the same problems?

Hi Nikola,

Many thanks for the reply. The tool (that I'm currently evaluating) is the Beebyte obfuscator. There are others under consideration, but let's stick with this one for now.

If I extract the woven 'proxy' from the Mono DLL, the class & property names are clearly obfuscated, which I had assumed to mean the obfuscation was occurring after weaving (during build)

If it helps, I can send the 'proxy' of a test Realm object before (i.e., weaved in the Unity editor) and after (i.e., weaved during build). If so, let me know where to E-mail it to.

As for RealmSchema, I don't believe that would be the issue. The Realm package is (from what I can tell) stored in a separate assembly that is currently un-obfuscated. I wanted to get the basics working before turning to obfuscating third party tools.

Looking at the obfuscation translation table, the only reference to Realm (other than my own classes) is RealmModuleInitializer

Yeah, it'd be helpful if you could share those dlls to nikola.irinchev@mongodb.com.

I'm assuming you're using the source generator based models - i.e. your models implement IRealmObject rather than inherit from RealmObject. If that's the case, then in the generated class, there'll be a RealmSchema static property, which we're getting using reflection:

var schemaField = type.GetField("RealmSchema", BindingFlags.Public | BindingFlags.Static);
. I can't seem to find docs for Beebyte, but if it has the option to configure members that should be skipped, you could try and annotate the RealmSchema property. This is not a real solution as regenerating the file will remove your changes, but we could at least try and see if it works.

One thing I can see that won't work correctly is property change notifications. Those use strings generated at compile time, so if you take advantage of it, after obfuscation, the names in the property change event handler won't match the obfuscated names of the class.

OK, let me do some more research (with RealmSchema) and I'll message back in the next day or two.

In answer to your question, yes I'm using IRealmObject and not inheriting from RealmObject

I quickly looked at the Realm "C:\Users<user>\AppData\Local\Temp\VSGeneratedDocuments<id>\ClassName_generated.cs" proxy file and could not find the code you posted above.

I found the below though, is that what you're referring to?

public static Realms.Schema.ObjectSchema RealmSchema = new Realms.Schema.ObjectSchema.Builder("MappedClassName", ObjectSchema.ObjectType.RealmObject)
{
    Realms.Schema.Property.Primitive("MappedProperty1Name", Realms.RealmValueType.String, isPrimaryKey: true, indexType: IndexType.None, isNullable: true, managedName: "Property1Name"),
    Realms.Schema.Property.Primitive("MappedProperty2Nam>", Realms.RealmValueType.Data, isPrimaryKey: false, indexType: IndexType.None, isNullable: true, managedName: "Property2Name"),
}.Build();

That was from an IRealmObject decorated with MapTo attributes (for the obfuscated names)

As for the property change events, that may not be an issue here. I'm using mapped names that would otherwise match the obfuscated names. That said, I'm not using those events (yet) so haven't proved that theory.

Thanks again for the rapid response, it's much appreciated.

Hi Nikola,

I've looked a little deeper into the RealmSchema suggestion and I think you're right, this could be the issue.

Going on your code link above, Realm is looking for a field called "RealmSchema" by means of a magic string.

I rebuilt the app in question with Realm obfuscation enabled and the static property now looks like this (extracted from a Mono build using dotPeek)...

static PLHAHHKPCAB()
{
  ObjectSchema.Builder builder = new ObjectSchema.Builder("MappedClassName", (ObjectSchema.ObjectType) 0);
  builder.Add(Property.Primitive("MappedProperty1Name", (RealmValueType) 3, true, (IndexType) 0, true, nameof (NDNBBOCIAKN)));
  builder.Add(Property.Primitive("MappedProperty2Name", (RealmValueType) 4, false, (IndexType) 0, true, nameof (HHPHLHLODFC)));
  PLHAHHKPCAB.MPCIFIMONMC = builder.Build();
}

We can see the obfuscator has renamed "RealmSchema" to "MPCIFIMONMC" therefore triggering the exception at line 283 from the code link you posted above.

Ok, so to test the theory I need to compile the app and somehow exclude the "RealmSchema" field from obfuscation.

The obfuscator allows classes to be excluded by means of an editor setting or an attribute. Class members can only be excluded by means of an attribute. So the only way to exclude "RealmSchema" from obfuscation is by using an attribute. Unfortunately custom attributes don't carry across to the woven proxies and even if they did, the "RealmSchema" field is not something under control of the app.

Assuming the attribute could somehow be added, the obfuscator supports custom attributes [Skip] & [SkipRename] that do similar things. So one of those could be added, but obviously they couldn't exist in Realm production. It might be possible to inject one via code in the build pipeline, but I think that'll be a huge amount of work for a simple test. However the obfuscator also supports the standard dot net obfuscation attributes...

[System.Reflection.Obfuscation]
[System.Reflection.Obfuscation(ApplyToMembers=false)]
[System.Reflection.Obfuscation(Exclude=false)]

So Realm could theoretically decorate the "RealmSchema" field with a [System.Reflection.Obfuscation] attribute (that defaults to exclude true) and it'll work for everyone.

During the build process, Realm weaving certainly appears to run before obfuscation (according to the console). So even if I could add it before building, I expect it'll be overwritten during build weaving?

On-top of that, if I "Find All References" to MyRealmObject in Visual Studio, it shows the generated Realm proxy file, but it's marked as auto generated and is zero bytes. So it looks like there's some VS magic going on there and doesn't appear to be editable.

Other possibilities are maybe using nameof() instead of a magic string (to lookup the field) or allowing the magic string value to be customisable somehow.

Hopefully this all helps, but if there's anything else you need just say and I'll do what I can.

Hey sorry for the delay, I had a family thing end of last week. The problem with nameof is that we don't have access to the field - the code I linked to is in the Realm library, whereas the code we're looking up is in your library, so we cannot reference members directly (at the time we build Realm, we don't have access to everyone's codebases). Additionally, since RealmSchema is a static property, we cannot add it as abstract interface member since Realm needs to support .NET Standard 2.0 and there this is not supported.

I'll try and update the generator to add [System.Reflection.Obfuscation] to the generated RealmSchema member and will send you a test build to see if that will solve the problem for you, since we don't have access to the Beebyte obfuscator.

Hey @nirinchev

Many thanks for the update.

Understood about using nameof(). I dived deeper into the code yesterday and see what you mean. You're just dealing with Type at that point and trying to have even minimal strong typing, would likely involve substantial changes.

Thanks so much for taking the time to implement a potential workaround, it's really appreciated. I'm using the tarball package (V12.0.0) so not sure if it's possible to send an updated version of that? It might be easier to send the relevant updated DLL (i.e., in the library package cache) but have a feeling Unity will restore any cache files (from the package) if they detect hash deltas.

Either way, I'll send you a mail and include some extra obfuscator info that may be of help.