You wrote a clean license gate. It validates the key, checks the seat count, flips the app to "Pro." It compiles, it works, you ship it. And in an unprotected .NET assembly an attacker neutralises it in about the time it took you to read this paragraph — not because your logic is weak, but because the check ships as readable, editable IL, and the runtime executes whatever the assembly says. The lock is fine. It's bolted to a glass door.
Cracking a license isn't magic, and it isn't one technique. It's three, each targeting a different weakness, and each with a specific, well-understood defense. Understand the attack and the protection follows.
Anatomy of a license gate
Here's a representative check — nothing naïve, the shape most in-house licensing takes:
public bool IsLicensed()
{
if (!ValidateKey(_key)) return false; // signature / checksum
if (SeatsInUse > _license.Seats) return false; // seat count
return true;
}
public bool IsPro => IsLicensed() && _license.Tier >= Tier.Pro; Every one of the three attacks below starts from the decompiled form of this method — because, as covered in what a decompiler recovers, an unprotected build hands it over intact.
Attack 1 — Patch the check
The most common crack doesn't even read your logic; it edits one instruction. In IL, IsLicensed ends with a conditional branch that decides licensed-vs-not. The attacker opens the assembly in dnSpy, finds that branch, and inverts it — or replaces the entire method body with two bytes that mean return true. The opcodes are tiny and well known:
; invert the decision — flip the branch opcode:
brfalse.s (0x2C) → brtrue.s (0x2D) // one byte
; or blow the method away entirely — overwrite the body with:
ldc.i4.1 (0x17) ; push 1 (true)
ret (0x2A) ; return it
; "IsLicensed()" now unconditionally returns true. That's it. No understanding of your key format required. The app is now "licensed" against nothing.
Defense: anti-tamper. Nebula injects a load-time integrity check. At build time it hashes the protected method bodies and stores the digests, encrypted, in the assembly. At startup the runtime component rehashes the loaded code and compares. Change one byte — flip that branch, stub that method — and the hashes diverge, and the assembly refuses to run (or reacts however you configure). The patch attack is detected by the binary itself.
Attack 2 — Read the algorithm, write a key-gen
Patching produces a cracked copy. A key-gen produces something worse: keys that your own code accepts as genuine. If ValidateKey is readable, an attacker decompiles it, recovers the format and the checksum, and writes a generator. No files are modified — every install is "legitimately" licensed against forged keys, and anti-tamper never fires because nothing was tampered with.
private static bool ValidateKey(string key)
{
// groups of 5, Base32, last group is a checksum of the first three
var parts = key.Split('-');
int sum = 0;
foreach (var p in parts.Take(3))
foreach (char c in p) sum = sum * 33 + Decode(c);
return Base32(sum & 0x3FFFFFF) == parts[3]; // ← the whole scheme
} With that in hand the key-gen is a dozen lines. The problem isn't the algorithm's strength — it's that the algorithm is visible.
Defense: hide the algorithm. This is exactly what code virtualization is for. Virtualize ValidateKey and its IL is gone — replaced by a call into an embedded VM running custom bytecode. There's no readable checksum to reimplement; recovering it means first reverse-engineering the VM. Pair that with string encryption so the Base32 alphabet and magic masks aren't sitting in the #US heap, and control-flow flattening on the surrounding code, and rebuilding the scheme turns from an afternoon into a research project.
“Licensing is a security feature. Shipping its algorithm as readable IL is like printing the key-cutting instructions on the back of the lock.”
Attack 3 — Rip out the gate entirely
The subtlest attack ignores the check altogether. Rather than defeat IsLicensed, the attacker studies how IsPro unlocks features and simply calls the paid paths directly, or rebuilds the app with the licensing module deleted. If your gating is one boolean consulted in one place, removing it is trivial once the code is readable.
Defense: make the module unreadable and the wiring untraceable. Rename everything internal so there's no LicenseManager.IsPro to find; flatten the control flow so the gate isn't a clean branch to delete; and use reference (proxy-call) obfuscation so the calls into gated features route through injected, unbranded proxies — the attacker can't grep the call graph for "the unlock." And critically, don't let the client be the sole authority: pair Nebula's protection with server-side activation so the decision that matters is made where the attacker can't edit it.
The defense stack
A crack-resistant .NET license gate is never one trick; it's the layering:
- Anti-tamper so the binary can't be patched without detecting itself.
- Virtualization on the validator so the algorithm can't be read or reimplemented.
- String encryption + control-flow flattening so the scheme, its constants and its messages are hidden.
- Proxy-call obfuscation + renaming so the "unlock" path can't be located or lifted.
- Server-side activation for anything that must be authoritative — so the client isn't the only line of defense.
None of this makes cracking impossible — nothing client-side does. It makes it expensive enough that it isn't worth it, which for commercial software is the entire game.
Make your licensing hold up.
Nebula.NET hardens the code that enforces your license — anti-tamper, string encryption, control-flow, and Enterprise code virtualization.