godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Provide obfuscators support for C# #1407

Open Bleuzen opened 4 years ago

Bleuzen commented 4 years ago

Describe the project you are working on:

Any C# / Mono project

Describe the problem or limitation you are having in your project:

Currently nothing is done to obfuscate the C# assemblies on export with Godot Mono. This means it is easy to recovery the full source code of the game for anyone with a C# decompiler. Of course this hurts commercial projects, since they get easy to crack.

Describe the feature / enhancement and how it helps to overcome the problem or limitation:

There are some C# obfuscators out there, which try to solve (or at least improve) the problem. (for example https://www.obfuscar.com/)

The feature request is to Godot to support applying an obfuscator on game export.

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:

Godot will the the obfuscator program on the C# assembly when the user exports the game.

If this enhancement will not be used often, can it be worked around with a few lines of script?:

I don't think so.

Is there a reason why this should be core and not an add-on in the asset library?:

Many users are complaining about it and want to better protect their game resources (and scripts) for commercial projects. See: https://github.com/godotengine/godot/issues/24716

Alternative

An even better way to solve this problem and better protect the source code would be to convert C# to C++ and compile it to a native binary, just like Unity does (https://docs.unity3d.com/Manual/IL2CPP.html). This will protect the code much better and is much harder to reverse engineer. But it also may be harder to implement in Godot.

Zireael07 commented 4 years ago

Uh, you know Unity's code can be deobfuscated? I know at least one project offhand that does it, trivially, one-click and you get the code...

Bleuzen commented 4 years ago

Uh, you know Unity's code can be deobfuscated? I know at least one project offhand that does it, trivially, one-click and you get the code...

Well, nothing is unhackable. But to my understanding, it is much easier to modify a pure C# assembly than to modify a native binary. So sure, it is not perfect, but it improves things. Same goes for obfuscators. Someone with enough time and knowledge will still be able to crack and modify the game. But it gets harder.

Armynator commented 3 years ago

Uh, you know Unity's code can be deobfuscated? I know at least one project offhand that does it, trivially, one-click and you get the code...

Don't confuse deobfuscation with decompilation. Yes, Unity builds (even IL2CPP builds) can be decompiled easily. Mono builds are insanely easy to decompile and to modify. IL2CPP builds are slightly more complicated, but still pretty readable and easy to modify as well.

However, this is where obfuscation comes into play. Classes, Fields, Method names etc. will be renamed to random characters or something similar, making the decompiled code a lot harder to read. There are also obfuscators available for Unity which will add fake code into classes, making it even more confusing for anyone who is trying to reverse engineer it.

In Godot I tried to obfuscate the exported C# assembly with Obfuscar in a few different ways, with no success. It seems like C# scripts will break completely already once just simple things like field and/or method names are getting obfuscated. Maybe Godot relies on Exported field names or something?

In Unity on the other hand we have multiple third party assets to do the job for us, including Obfuscar. It's free, open source, and yes, it has Unity support.

I also want to make this clear before someone brings this up again: I know that obfuscating game code won't make it safe from cheaters. I know that anything stored/running locally can be decompiled and modified sooner or later. This request is not about making Godot games totally cheat-proof, this is about adding a minimum of security against anyone trying to take a closer look at the game code, and against anyone who is trying to write simple cheats by modifying the .NET assembly or by using reflection.

Anyone who ever made cheats or mods for .NET games before probably agrees with me that heavy obfuscation can be a massive pain in the ***. Especially if every new build/game update randomizes all method and class names again, and adds different fake code everywhere. If a developer obfuscates code like this, C# games are no longer that easy to modify for cheats. In fact, at that point its probably easier to write cheats directly in assembler language, which takes quite a bit more time and effort.

Another sidenote: I'm mostly talking about competetive multiplayer/MMO games here. Of course such games are protected against cheating pretty well already if everything is server-based, but there is no need to let any random curious person take a clear look at the game or network logic if it can be prevented by reasonable countermeasures.

markhughes commented 3 years ago

Is there a way to at least add a post build step to the C# binaries?

Calinou commented 3 years ago

Is there a way to at least add a post build step to the C# binaries?

Not that I know of.

kienvn commented 3 years ago

Can the C# assemblies encrypted like GDScript? image

Calinou commented 3 years ago

Can the C# assemblies encrypted like GDScript? image

That option only works for GDScript. It doesn't affect C# or GDNative.

Note that the master branch offers full PCK encryption, instead of being limited to scripts.

kienvn commented 3 years ago

Can the C# assemblies encrypted like GDScript? image

That option only works for GDScript. It doesn't affect C# or GDNative.

Note that the master branch offers full PCK encryption, instead of being limited to scripts.

Yes, but can we create a same feature like this for C# library?

Calinou commented 3 years ago

Yes, but can we create a same feature like this for C# library?

C# obfuscation should be left to third-party tools which will do a better job at it. All Godot could do is call one of these tools when exporting the project (in a way similar to rcedit), but nobody knows how to call them yet.

kienvn commented 3 years ago

C# assembly was loaded from data, so we can encrypt data, and decrypt data when load: https://github.com/godotengine/godot/blob/fdccc0b2195813d7874a7d9b97fff46081772e1e/modules/mono/mono_gd/gd_mono_assembly.cpp#L231

Bleuzen commented 3 years ago

@kienvn Just a side note: encryption doesn't offer much protection. The key is in the executable and the algorithm is open source, so it is easy to decrypt and get the full original source code back. If Godot is big enough, I guess at some point there could even be tools for it to decrypt Godot games one click. That's why I wouldn't care much about encryption, the benefit is just tiny anyway. And that's why this issue is specifically about obfuscation.

BluesM18A1 commented 2 years ago

An even better way to solve this problem and better protect the source code would be to convert C# to C++ and compile it to a native binary, just like Unity does (https://docs.unity3d.com/Manual/IL2CPP.html). This will protect the code much better and is much harder to reverse engineer. But it also may be harder to implement in Godot.

IIRC (correct me if I'm wrong) Unity's IL2CPP feature also brings a lot of performance gains and can make deployment significantly easier for certain platforms. I'd be very interested in seeing a feature like this come to Godot. Maybe worth making a different proposal over?

Calinou commented 2 years ago

IIRC (correct me if I'm wrong) Unity's IL2CPP feature also brings a lot of performance gains and can make deployment significantly easier for certain platforms. I'd be very interested in seeing a feature like this come to Godot. Maybe worth making a different proposal over?

Godot's C# integration will change significantly in 4.x, so it may be too early to know whether it's feasible yet.

Armynator commented 2 years ago

IIRC (correct me if I'm wrong) Unity's IL2CPP feature also brings a lot of performance gains and can make deployment significantly easier for certain platforms.

Probably not worth it. While IL2CPP is about 3-5x faster than Mono, it's also quite equal to the performance of .NET Core/.NET 5 standalone applications, so it most likely would make more sense to implement .NET 6 directly instead of creating a complicated AOT compiler just for performance gains. (A proposal for .NET Core support exists already: https://github.com/godotengine/godot-proposals/issues/793)

Also keep in mind that this wouldn't really fix this proposal, as IL2CPP builds still include a big load of metadata so methods can be found by string at runtime, be it for events, reflection or whatever. So doing it exactly like Unity won't fix this issue.

The best option still is proper obfuscator support in my opinion. I've tried to obfuscate the DLL manually in Godot 3.4rc1 in a few different ways again, but no luck. As soon as field or method names change in the DLL, the C# scripts will break without any error or warning.

It's pretty sad because Godots Webassembly way to do C# in the Browser is much better than Unity's double-compiled WebGL mess. But as long as any random person out there can download the .pck files of web exports containing all the game assets and C# scripts in clear text, we simply can't use this engine. It's like inviting everyone to write cheats or server emulators for your game. That's just not acceptable for us, especially since it could be prevented quite easily with an automated obfuscator workflow. (Unity supports automated obfuscator workflows perfectly fine btw, we are currently doing it like this: compiled C# DLL -> C# Obfuscator -> obfuscated C# DLL -> IL2CPP -> obfuscated C++ code/native binary file with unreadable/nonsense metadata)

goman64 commented 2 years ago

I've tried to obfuscate the DLL manually in Godot 3.4rc1 in a few different ways again, but no luck. As soon as field or method names change in the DLL, the C# scripts will break without any error or warning.

I managed to obfuscate the DLL in Godot 3.4.4. I used godotpcktool 1.7 to unpack. I obfuscated mygame.dll with maximum obfuscation settings in most cases using RedGate SmartAssembly (I'm not affiliated with this product). Optionally, I removed mygame.pdb which supposedly contained debug symbols and then repacked.

In my C# scripts, I made sure that all the functions that handle signals are public void. Otherwise, the obfuscated assembly was complaining that it couldn't find the functions.

Armynator commented 1 year ago

It looks like Obfuscar is working fine with Godot 4.0 on a .NET 6 build. I've tested private & public class, field and method renames, including exported fields and signal methods. Most infos are written directly into the DLL by Godot now, so it doesn't break the game anymore like it did with Mono before. Everything it needs (exported fields, signal methods, other data, ...) is found by reference inside the DLL file now. (identified by strings (which can be made harder to read with Obfuscar))

A few more notes:

Sure, this isn't perfect yet, but way better than the unreliable (reflection-based?) Mono version from before. I've also seen that NativeAOT support is under consideration for the future. I'm not sure how much metadata is included in NativeAOT builds, but I hope that we could get some official obfuscation implementation as hook/build step, so we can post-process the DLL in any way we want before it's getting compiled by NativeAOT. That way it would be similar to Unity with IL2CPP. (Build C# DLL -> obfuscate/post-process -> NativeAOT->native code with obfuscated/nonsense metadata)

If you want to test Obfuscar yourself, remember to skip the GodotPlugins namespace as mentioned above. Here is a config snippet for it: `<SkipNamespace name="GodotPlugins" />`

I haven't tested other obfuscators (with fake code injection) yet, but I assume that they will work just fine like Obfuscar, at least if they can be configured properly.