BitMono Obfuscator

repository·main·Indexed 20 days ago

https://github.com/bitmono-project/bitmono

A free, open-source C# obfuscator for .NET and Mono applications, including Unity Engine. It provides advanced protection layers to break reverse-engineering tools like dnSpy and ILSpy. BitMono can be used as a standalone CLI tool, a .NET Global Tool, a NuGet package for MSBuild automation, or via a web interface. It supports a plugin system for custom protections and integrates with GitHub Actions for CI/CD pipelines.

Tokens
25.9K
Snippets
70
Records
141
Agent score
69%

What's inside BitMono

  1. Overview of BitMono Obfuscator

    main

    BitMono is a free, open-source C# obfuscator designed primarily for Mono, but compatible with various .NET environments including .NET Framework, .NET, and Unity Engine. It uses AsmResolver for assembly handling.

    BitMono aims to break common reverse-engineering tools such as dnSpy, dnlib, AsmResolver, MonoCecil, ILSpy, PEBear, DetectItEasy, and CFF Explorer through various protection layers.

    Key characteristics of an obfuscated application include:

    • Appearing as a C++ application while remaining a C# application.
    • Causing decompilers to crash or break when analyzing types.
    • Breaking IL code and making types invisible or resulting in 'no code' visibility.
  2. Overview of BitMono

    main

    BitMono is a versatile obfuscation tool designed for protecting .NET assemblies. It can be used as a standalone tool for quick obfuscation (via a two-click process) or integrated as an engine to build custom obfuscation solutions for specific plugins or purposes.

    Many technical questions regarding assembly manipulation in BitMono are addressed by the AsmResolver library. If you encounter low-level assembly issues, consult the AsmResolver documentation.

  3. Use cases for BitMono

    main

    BitMono is a code obfuscator and protector designed for developers who need to protect their intellectual property or software logic. Common use cases include:

    • Custom Protection Development: Building your own protection logic without having to write a low-level engine from scratch or relying on the aging ConfuserEx engine.
    • Intellectual Property Protection: Increasing the difficulty for reversers to understand and extract your code.
    • Anti-Cheat/Anti-Tamper: Protecting game cheats or sensitive software logic against anti-cheat systems or unauthorized modifications.
    • Security against 'Skids': Preventing low-effort tampering or unauthorized use of your software.
    • Extensibility: Because BitMono uses clean architecture and AsmResolver as its assembly manipulator, it is designed to be easily forked and extended by the community.
  4. How BitMono protection works

    main
    BitMono works by modifying the Portable Executable (PE) file metadata. This process makes the file unrecognizable to common decompilers and analysis tools like 'Detect It Easy'. By altering the metadata, tools may misidentify the file (for example, as an MS-DOS Executable), preventing them from successfully opening or analyzing the assembly. This protection relies on the fact that the Mono runtime does not require the specific metadata that decompilers use to function.
  5. Understand IL2CPP-compatible vs. skipped protections

    main

    In Unity IL2CPP builds, only protections that affect the metadata or AOT-compile into C++ are effective.

    IL2CPP-compatible protections (Kept):

    • FullRenamer: Renamed names are written cloaked into global-metadata.dat.
    • NoNamespaces: Clears namespaces in the metadata.
    • StringsEncryption: Removes plaintext strings from metadata; the decryptor is AOT-compiled to C++.
    • AntiDebugBreakpoints: Pure managed timing checks that AOT-compile and run at runtime.

    Protections skipped on IL2CPP (to prevent build breakage): UnmanagedString, CallToCalli, DotNetHook, BitMethodDotnet, ObjectReturnType, AntiDe4dot, BillionNops, AntiILdasm, BitTimeDateStamp, AntiDecompiler, BitMono, BitDotNet, BitDecompiler.

  6. How CallToCalli protection works

    main
    The CallToCalli protection works by replacing standard call opcodes with calli opcodes. Instead of calling a method by its metadata name, the protection forces the runtime to call the method using its direct function pointer. This makes it significantly harder for static analysis tools to resolve call graphs and identify which methods are being invoked.
  7. Extending BitMono with Plugins

    main

    BitMono supports a plugin system that allows you to add custom protections without rebuilding the entire project. You can drop your own protection implementations into a plugins folder.

    For detailed implementation instructions, refer to the official plugin developer guide.

  8. Decide when to mark a protection as IL2CPP incompatible

    main

    Because BitMono obfuscates code before il2cpp.exe converts managed code to C++, your decision to mark a protection depends on its output:

    • Do NOT mark it if the protection performs managed metadata/IL edits that il2cpp.exe can parse (e.g., renaming, clearing namespaces, or swapping ldstr for a managed decryptor call). These changes will persist in the resulting global-metadata.dat.
    • DO mark it if the protection produces output il2cpp.exe cannot handle, such as emitting native code, calli instructions, or packing the PE. These would either break the conversion process or affect the managed PE that IL2CPP eventually discards.
  9. Use StringsEncryption to protect string literals

    main

    The StringsEncryption protection uses AES encryption to secure string literals within your application. This prevents attackers from easily finding sensitive strings (like API keys or internal identifiers) via static analysis.

    Key Effects:

    • Anti-Decompiler: When combined with AntiDecompiler, it can cause tools like dnSpy to crash when attempting to analyze the classes using the encrypted strings.
    • RVA Obfuscation: It sets the Relative Virtual Address (RVA) of the underlying byte[] to 0, making it harder to locate the encrypted data in memory.

    Warning: This protection significantly impacts application performance due to the decryption overhead at runtime. Use it sparingly for sensitive strings only.

    Protection Type: Protection
  10. Use Protection Presets to configure BitMono

    main

    A preset is a predefined protection level that enables a curated set of protections. Instead of manually toggling every individual protection in protections.json, you can select a preset to quickly apply a specific security profile.

    Important: Presets are chosen explicitly by the user and are never auto-detected. Because BitMono cannot automatically distinguish between different runtimes (e.g., Mono/Unity vs. .NET Core/Framework), you must ensure your chosen preset is compatible with your target runtime. BitMono will issue a warning if a preset enables protections intended for a different runtime, but it will not block the process.

    {
      "Preset": "Balanced"
    }
  11. Configure AOT-safe obfuscation settings

    main

    Native AOT relies heavily on metadata for reflection and internal bookkeeping. To prevent runtime crashes or build failures, follow these guidelines:

    • Exclude Reflection Members: Use criticals.json or the [Obfuscation(Exclude = true)] attribute to exclude members read by reflection. Ensure ReflectionMembersObfuscationExclude is enabled.
    • Generic Instantiations: Ensure you are using a current BitMono build; FullRenamer now correctly rewrites references for members reached through generic instantiations (e.g., Foo<int>().Bar()) to prevent AOT/JIT crashes.
    • Error Handling: If you encounter No entrypoint module during ilc, this is a known SDK-level limitation regarding third-party hooks, not a BitMono configuration error.
  12. How AntiDebugBreakpoints protection works

    main
    The AntiDebugBreakpoints protection works by injecting timing checks directly into method bodies. It monitors the time elapsed since the last execution of a specific method. If the elapsed time exceeds a predefined constant value (indicating a debugger might be pausing execution or stepping through code), the protection triggers a program crash to prevent debugging.