Blog
News & engineering notes
Release announcements, deep-dives into how our tools work, and practical guides.
Decompiling C# expression trees: the lambda that became a tree of factory calls
The same `x => x + 1` compiles two completely different ways depending on the type it is assigned to. As a delegate it is a hidden method and an ldftn; as an Expression<Func<int, int>> it is a nest of Expression.Parameter, Expression.Constant, Expression.Add and Expression.Lambda calls with members referenced by ldtoken. Here is exactly what the compiler emits for an expression tree — captured variables as a Constant of a closure, string equality with its op_Equality, nested lambdas wrapped in Quote — and how a decompiler folds the factory calls back into the lambda you wrote.
Read article →Licensing a .NET app on Linux and macOS: no registry, no AppData — where the Keyright lease lives, what the fingerprint anchors to, and the systemd service that never relaunches
Most licensing guidance is written for Windows, and the Keyright SDK quietly hides that: the same Validate() and ActivateAsync() calls work on Linux and macOS. But the three things a licensing SDK depends on — a per-user data folder, a stable machine identity, and an app that relaunches often enough to refresh its lease — all look different off Windows. The lease lands in ~/.local/share, which a sandboxed Mac app or a systemd service with ProtectHome= cannot write. The machine fingerprint anchors on /etc/machine-id or the IOPlatformUUID instead of a registry value, and a golden image that forgot to clear machine-id makes a whole fleet one machine. And a daemon that starts once a quarter never gets the startup refresh a desktop app relies on, so its 14-day lease expires with the process still running. This post walks through where the SDK writes on each platform, what the fingerprint is built from and the two machine-id traps, a systemd unit with StateDirectory= and LoadCredential= that gives the SDK a home, the lease-refresh loop a long-running service needs, the file mode the lease deserves on a multi-user host, and the verification loop that proves it.
Read →Obfuscating a type you serialize with Newtonsoft.Json: [JsonProperty] is the contract, and TypeNameHandling's $type is a landmine
Newtonsoft.Json reads your DTO by its member names through reflection — Customer becomes "Customer" on the wire because the property is literally called Customer — so the moment an obfuscator renames it to a, the JSON your service produces turns into { "a": … } and every consumer and every stored document breaks, silently, at runtime. Json.NET has no compile-time source generator to escape that reflection walk, so pinning names with [JsonProperty] is not an optimization, it is the whole contract. And it hides a second, nastier trap: TypeNameHandling writes your type's assembly-qualified name into the document as $type, so renaming a type breaks every polymorphic payload a different build ever wrote — a failure no attribute fixes. This post pins down which names Json.NET binds to, why [JsonProperty] and [EnumMember] make the wire names constants the renamer cannot touch, why the deserializing constructor is a second name binding, and why TypeNameHandling needs a stable ISerializationBinder rather than a keep rule. It ends with the round-trip test, run against the protected build, that turns "serialization broke" into a failed test instead of a customer email.
Read →Decompiling C# string interpolation and interpolated string handlers
An interpolated string has no IL of its own — the compiler picks one of three completely different lowerings depending on your target framework, the hole types, and whether a method opted into a handler. Here is exactly what `$"..."` emits as string.Concat, as string.Format, and as the DefaultInterpolatedStringHandler dance, plus the custom-handler case where the string is never built at all, and where a decompiler can fold each one back to `$"..."`.
Read →Licensing an MSIX-packaged .NET app: AppData virtualization, the uninstall that deletes your lease cache, and where a Keyright license should actually live
MSIX is how Windows desktop apps get installed now — through winget, through enterprise deployment, through the Store — and it quietly changes three things a licensing SDK depends on. Writes to %LocalAppData% are redirected into a per-package folder the app never sees in a path. Uninstalling the package deletes that folder, so the cached activation lease, the local trial clock and the clock-tamper state disappear while the seat on the licensing service stays bound. And the install directory is read-only, which is exactly where a bundled site license should sit. This post walks through what the Keyright SDK writes and where it lands under a packaged app, why the machine fingerprint survives when the cache does not, what an uninstall/reinstall cycle does to seat accounting (nothing, thanks to idempotent activation — unless the customer meant to move), how to detect package identity and give the SDK explicit paths, how to ship a license inside the package, when to disable write virtualization in the manifest, and the test loop that proves it.
Read →Decompiling nullable value types: int? is a struct, a lifted operator is a HasValue check, and the box that erases Nullable<T>
int? looks like a type with a null in it, and in the IL it is neither: it is the generic struct System.Nullable<int>, two fields and no null anywhere, and every operation the language lets you write on it is rewritten by the compiler into calls on that struct. a + b on two int? values becomes a HasValue conjunction, two GetValueOrDefault calls, an add, and a newobj — or an initobj for the null result. a == b becomes a value comparison ANDed with a HasValue comparison. a == null becomes a single HasValue call. a ?? 5 becomes a conditional over HasValue. And boxing an int? does something no other struct does: the box instruction yields either a boxed int or a null reference, so Nullable<T> never exists on the heap at all. This post reads each of those lowerings in IL and follows a decompiler through folding them back into the operators the author wrote — why the HasValue and GetValueOrDefault pattern has to be recognised as a unit, why int? comes from the signature blob rather than any attribute (unlike string?), how unbox.any on a null reference quietly produces an empty Nullable, and what a decompiler gets wrong when it renders the lowering instead of the language.
Read →Site licenses: binding one Keyright key to the customer's domain instead of a machine, and what that binding can and cannot prove
An enterprise buying four hundred seats does not want four hundred activations, and you do not want the key that unlocks all of them walking out on a contractor's laptop. The classic answer is a site license — one key for the whole organisation — and the question nobody writes down is what the key is bound to, if not a machine. This post builds a site license on Keyright as a signed entitlement that names the customer's DNS domain, optionally pinned by the Windows domain SID: where those two identities come from (IPGlobalProperties.DomainName, not Environment.UserDomainName, and SecurityIdentifier.AccountDomainSid), why suffix matching is the right comparison for a forest with child domains, how the gate fails closed on an unjoined laptop, how the customer hands you the values without a round-trip to IT, and the honest threat model: a domain name is a policy fence a determined attacker can rename a VM around, a domain SID raises the bar to cloning a domain controller, and neither is a substitute for a seat count when you actually need one.
Read →Obfuscating code that uses dynamic: the runtime binder looks members up by name, so a renamed property is a RuntimeBinderException
A static call survives renaming because it is resolved by metadata token and the obfuscator rewrites the token with the member. A dynamic call survives nothing: the C# compiler lowers settings.Timeout into a call site whose binder is handed the string "Timeout", and at runtime Microsoft.CSharp walks the receiver's actual type looking for a member with exactly that name. Rename Timeout to a and the site still asks for "Timeout", the lookup fails, and the app throws RuntimeBinderException on a line that compiled fine and passed every test on the unprotected build. This post reads the IL of a dynamic site to show where the name lives (an ldstr feeding Binder.GetMember and Binder.InvokeMember inside a hidden <>o__ container), explains why string encryption neither helps nor hurts, why no renamer can safely fix it automatically (the receiver's type is not known until the call runs), which dynamic operations bind by name and which do not (indexers through Item, operators through op_ names, ExpandoObject keys never), the double-dispatch-by-dynamic pattern that silently breaks a whole visitor, and the three fixes in order of preference: replace dynamic with an interface, pin only the members a site reaches with [Obfuscation(Exclude = true)], or an exclude entry in nebula.config.json — plus the one test that catches it on the protected build.
Read →Decompiling an array initializer: the values live in a data blob and RuntimeHelpers.InitializeArray is the only clue
Write int[] x = { 1, 2, 3, 4, 5 } and you might expect five stelem instructions, one per element. For a small array that is roughly what you get, but past a threshold the C# compiler stops storing elements one at a time and does something stranger: it allocates the array, then calls RuntimeHelpers.InitializeArray with a handle to a static field whose bytes are the whole array packed end to end, and that field has no initializer in IL at all — its data lives in a data section of the PE, pointed at by a FieldRVA metadata row, typed as a synthetic struct in a hidden class whose only property is its byte length. A decompiler cannot read the literal off the IL, because the IL only holds a memcpy; it has to recognise the InitializeArray pattern, resolve the field token, follow the FieldRVA to the raw bytes, take the element type from the newarr that preceded it, and split the little-endian blob back into { 1, 2, 3, 4, 5 }. This post walks the IL and the metadata for a blob-initialised array, shows what Glass.NET reconstructs, explains why a string array never takes this path, and covers the ReadOnlySpan-of-byte case where there is no array at all — just a field handle and a CreateSpan.
Read →Decompiling a decimal constant: why 3.14m is not an IL constant, and the [DecimalConstant] attribute that rebuilds it
Every other numeric literal in C# — 42, 3.14, 1_000_000L — lands in the IL as an ldc instruction or in the metadata Constant table, a value the runtime loads directly. decimal cannot: it is a 128-bit struct, not a primitive the CLR has a load opcode for, so a const decimal or a default decimal parameter has nowhere native to live, and the compiler solves it with a trick that trips up anyone reading raw IL — it encodes the number into a [DecimalConstantAttribute] whose five constructor arguments (scale, sign, and three 32-bit integers) spell out the exact value. This post follows a decompiler through that reconstruction: why the Constant table rejects decimal, how the attribute's arguments map to the four ints a decimal is built from, why a const decimal field has no field initializer in any constructor, how a default decimal parameter differs from a default int one, and why mistaking the attribute for an ordinary annotation turns 3.14m back into new decimal(314, 0, 0, false, 2) instead of a literal.
Read →Decompiling extension methods: the [Extension] attribute is the only trace, the receiver was just the first argument, and the call that looks static is not
An extension method has no runtime existence as an extension. It is a plain static method whose first parameter happens to be marked, and a call to it — text.Trim().Count() — compiles to an ordinary static call, Enumerable.Count(string.Trim(text)), with no dot-syntax, no receiver, nothing to say it was ever written fluently. The one thing the compiler leaves behind is an [Extension] attribute: on the method, so the first parameter can be rendered with this, and on the containing static class, so a tool knows to look there at all. A decompiler has to read that attribute, decide that a static call whose target carries it should be printed as a member call on its first argument, and get the precedence right so a chain of ten LINQ operators comes back as a chain and not a ten-deep nest of static calls. This post walks the IL for a defined extension method and for a call site, shows what Glass.NET reconstructs, explains why the attribute — not the call — is the source of truth, and covers the newer C# 14 extension-block shape whose members compile to the same static methods under a different skin.
Read →Decompiling volatile: the modreq on the field, the prefix on every access, and the Volatile.Read call that looks the same but is not
The volatile keyword has no runtime of its own. It compiles into two separate pieces of metadata that live in different places: a required modifier, modreq(IsVolatile), welded into the field's signature, and a one-byte volatile. prefix stamped in front of every ldfld and stfld that touches the field. A decompiler has to read both, agree with itself about which one is authoritative, and then not confuse the result with the two things that look identical in C# but compile completely differently — Volatile.Read on an ordinary field, which is a plain call with no prefix and no modifier, and Interlocked.Increment on a volatile field, which takes the field by address and therefore carries the modifier but not the prefix. This post walks the IL for all three, shows what Glass.NET reconstructs from each, explains why the field reference token itself has to carry the modreq or the access does not even resolve, and covers the one case C# cannot spell — a volatile. prefix with no volatile field behind it — and the call Glass emits for it.
Read →What HTTP status does a failed license check deserve? 402, 403, Problem Details, and a header the client can read
When a licensed API decides a caller is not allowed to run, the worst thing it can do is return a bare 500 with a stack trace, and the second worst is a 200 with { "ok": false } that no client ever notices. This post works out the HTTP contract a license gate actually owes its callers: why a license failure is not a 401 (the caller is authenticated fine — it is the licence that lapsed), when 403 is right and when 402 Payment Required says more, how to carry the machine-readable reason in an RFC 9457 application/problem+json body with extension members a client can branch on, and the one response header — never the key — that lets a CLI or a sister service react without parsing prose. It builds the gate as an ASP.NET Core IEndpointFilter that maps Keyright's LicenseStatus onto status codes through IProblemDetailsService, adds Retry-After when the license server is briefly unreachable and the grace window is still open, and ends with a WebApplicationFactory test that asserts the status, the problem type and that the signed key never appears in the body.
Read →License state as a health check: mapping a Keyright license onto Healthy, Degraded and Unhealthy without restart loops
A headless .NET service has no About box to show its license in, so the only place operators ever see license state is the monitoring dashboard — which means the license has to become a health check. The naive version wires the license straight into the readiness probe, an expired key starts returning 503, the orchestrator pulls the instance out of rotation and then kills it, the replacement comes up with the same expired key, and you have built a restart loop out of a billing event. This post reads what the three HealthStatus values actually mean to an orchestrator, maps Keyright's LicenseStatus onto them so a valid-but-expiring key is Degraded rather than Unhealthy, keeps license state off the liveness probe entirely so a lapse never triggers a kill, caches the validation so a per-second probe does not re-verify a signature every time, and exposes the detail operators need through the health-check data bag. It ends with the tag layout that separates the license endpoint from readiness and liveness, and the test that proves an expired key degrades without taking the process down.
Read →One license key, three products: the product claim, suite bundles as entitlements, and the gate that lets a Lite key unlock Pro
You ship a Designer, a Renderer and a CLI, and your customer wants one key for all of them, not three. The obvious shortcut — give every app the same product slug and let the signature do the rest — is the shortcut that quietly sells your most expensive app for the price of your cheapest one, because a key that verifies for the product line verifies in every app on the line. This post reads what the product claim on a Keyright license actually checks (it is an audience check, not a feature check), lays out the three ways to license a suite, shows the hole in the shared-slug design with the exact gate that opens it, and builds the version that holds: one slug for the line, per-app membership as namespaced entitlements, and a gate in every app that asks "am I in this bundle?" before it asks anything else. It ends with the upgrade path from a single-app key to a suite key, and the two-client fallback for teams that already shipped one slug per app.
Read →Logging a license check: structured events an operator can read, and never the key itself
The license check is the one event that decides whether your software is allowed to run, and it is almost always logged either as nothing at all or as a disaster — a log.LogInformation("license: {License}", info) that serialises the whole object and prints the signed key, the signature and the licensee's email into a file that gets shipped to a log aggregator and shared in a support ticket. This post builds the middle path: a single structured event per verification, emitted through a source-generated LoggerMessage so the redaction rule lives in exactly one place, with the key reduced to a short fingerprint that lets an operator correlate without ever carrying the secret. It maps Keyright's LicenseStatus onto log levels so an invalid license is an Error you can alert on and a healthy one is quiet Information, assigns a stable EventId range so a dashboard can count license failures without string-matching, wraps the verification in a logging scope so every downstream line carries the fingerprint, keeps the event off the per-request hot path, and ends with the test — a FakeLogger assertion — that proves the raw key never reaches the sink.
Read →Obfuscating an app that registers itself by convention: assembly-scanning DI binds on type names, so renaming unregisters your services
A great many .NET apps never write out their DI registrations. They scan an assembly at startup — with Scrutor, or a hand-rolled reflection loop — and register every type whose name ends in "Service", "Handler" or "Repository". That convention is a string match against the type name, and renaming obfuscation changes every one of those names, so the predicate t.Name.EndsWith("Handler") matches nothing, the services are silently never registered, and the app throws "Unable to resolve service for type ..." on the first request — a run-time failure with a green build, far from the line that caused it. This post separates the two ways a scan can find a type: by its name, which does not survive rename, and by its shape — assignable to a marker interface, decorated with an attribute, derived from a base — which does, because those are metadata-token identities the rename rewrites consistently on both sides. It shows the Scrutor registrations that break and the equivalent ones that survive, explains why AsImplementedInterfaces is fine while the name predicate is not, draws the line Nebula draws, gives both the recommended refactor and the exclude rule for when you cannot refactor, and ends with a startup smoke test that fails the obfuscated build fast instead of at the first request.
Read →Obfuscating a COM-visible .NET assembly: the GUID is the contract, the DispId is the contract, and the class name is the ProgID unless you pin it
A COM-visible .NET assembly is consumed by code that does not know it is .NET — a VBA macro, a VBScript, a legacy C++ host — and that consumer binds to it through a contract made of three things that are not where a C# developer keeps the public API: a GUID on the class and the interface, a DispId on each member, and a ProgID that defaults to the namespace-qualified type name. Obfuscate it the way you would obfuscate a library and the GUIDs survive (they are attribute constants), but the ProgID moves the moment you rename the class, and a late-bound client that does CreateObject("Acme.Widget") and calls members by name through IDispatch breaks on both the moved ProgID and the renamed member names. This post reads what a COM client actually resolves at each stage — ProgID to CLSID to IDispatch member — draws the line between what Nebula renames (the implementation, the engine) and what is pinned (the GUIDs, the DispIds, the member names late binding uses, the ProgID), shows the [ProgId] and explicit-interface layout that shrinks the pinned surface, gives the config, and ends with the register-and-call check in both a late-bound script and an early-bound tlb client.
Read →Obfuscating a PowerShell binary module: the cmdlet name lives in an attribute, but the parameter names are your property names
A PowerShell binary module is an ordinary .NET assembly that PowerShell loads by reflection, and it is a peculiar thing to obfuscate because its public contract is not where C# developers expect it. The cmdlet name — Get-Widget — comes from the [Cmdlet] attribute, so the class that implements it can be renamed to anything. But the parameter names — -Name, -Force, -Path — are literally the names of your public properties: rename them and every script, every tab completion and every Get-Help page breaks. The same is true of the objects the cmdlet writes to the pipeline, whose property names are the column headers of Format-Table and the targets of Select-Object. This post reads exactly what the PowerShell runtime reflects on when it imports a module and binds parameters, draws the line between what Nebula can rename (the cmdlet class, the engine behind it) and what is pinned (parameter properties, output properties, the attributes), shows the "thin cmdlet, fat engine" layout that makes the pinned surface small, gives the config, and ends with the import-and-invoke check that actually proves the protected module works.
Read →Obfuscating a type you serialize with System.Text.Json: [JsonPropertyName] is the contract, the renamed property is not
Reflection-based System.Text.Json reads your DTO by its property names — Order becomes "order" on the wire because the property is literally called Order — which means the moment an obfuscator renames that property to a, the JSON your service produces turns into { "a": …, "b": … } and every consumer and every round-trip breaks, silently, at runtime where no build step can catch it. This post pins down exactly which names the serializer binds to and which it does not: why a bare auto-property is a landmine, why [JsonPropertyName("order")] is the fix that makes the wire name a constant the renamer cannot touch, why a parameterized [JsonConstructor] matches parameters to properties by name so the constructor arguments are pinned too, how [JsonExtensionData] and enum [JsonStringEnumMemberName] behave, and why the source-generated JsonSerializerContext changes the picture entirely. It shows the Nebula configuration that keeps the contract intact — attribute the names, let the members rename — and the round-trip test that proves a protected build still deserializes what it serialized.
Read →Decompiling default interface methods: a method body inside an interface, the class that never overrode it, and the cast you cannot drop
A default interface method breaks the oldest rule a decompiler knows about interfaces — that they hold signatures, not bodies. Since C# 8 an interface can ship a working implementation, and a class that implements the interface can provide nothing at all and still get the behaviour. That leaves three puzzles for Glass.NET: a virtual instance method with a body sitting in an interface (not abstract, not a class method), a class whose metadata contains no override and no MethodImpl yet behaves as though it had one, and a call site that must go through the interface type because the member is invisible on the class. This post reads the IL for all three — the newslot virtual with a body, the absent MethodImpl, the callvirt against the interface — and shows how Glass rebuilds the default implementation, leaves the implementing class correctly empty, and keeps the interface cast that the language actually requires.
Read →Decompiling exception filters: the catch (e) when (...) that becomes a filter region and the endfilter that runs it
A catch block with a type is one of the simplest things a decompiler reads — a handler clause with a type token, done. A catch with a when clause is one of the trickiest, because it is a different kind of exception region entirely. The filter has no type token; the type test moves inside a separate block of IL that runs while the stack is still unwound, evaluates the when condition, and ends in an endfilter that returns 1 to run the handler or 0 to keep searching. Reconstructing catch (IOException e) when (e.HResult == 5) means recognising that two-block structure, lifting the exception object the runtime pushed onto the stack into the e variable, pulling the type test back out of the filter as the catch type, and reading the rest as the when expression — then knowing that exceptions thrown inside the filter are swallowed, which is a real language guarantee visible in the IL. This post reads the exact IL a filtered catch emits, walks the endfilter protocol, and shows how Glass.NET rebuilds the clause.
Read →Decompiling fixed: pinned locals, the null-or-empty array guard, and GetPinnableReference
There is no fixed statement in IL. What the C# compiler emits instead is a local variable with the pinned modifier in its signature, a few instructions that compute a pointer from whatever you pinned — with guards you never wrote — and a store that clears the local at the end of the block. There is no try/finally, because a pin is a property of a local's lifetime rather than a region of code. The initializer is lowered four different ways depending on what you pinned: an array gets a null-or-empty check and ldelema of element zero, a string gets either the legacy OffsetToStringData arithmetic or a call to GetPinnableReference, a Span or any type with a GetPinnableReference method gets that call on its address, and &field or &local gets a plain ldflda or ldloca. This post reads the real IL for each shape, explains why an empty array yields a null pointer rather than an exception, and shows how Glass.NET anchors on the pinned local to fold the machinery back into the one-line fixed you wrote.
Read →Decompiling optional and named arguments: default values in the Constant table and the temporaries the compiler emits for reordering
An optional parameter has no runtime cost at the call site you wrote — the default is not stored in the method, it is substituted by the compiler into every caller. So in IL there is no sign of a default argument except at the call: a literal pushed where you wrote nothing. The default value itself lives far away, in the Constant metadata table attached to the parameter, next to an Optional flag; a decompiler reading the method has to go find it there to print = 0 or = null. Named arguments are stranger still: they vanish entirely when they are in order, and when they are out of order the compiler rewrites them into in-order positional pushes — unless evaluating them out of order has side effects, in which case it spills each argument into a temporary first to preserve left-to-right evaluation, leaving a block of stores that look like code you wrote. This post reads the IL and the metadata for both, shows the Constant table rows behind a default, walks the reordering temporaries, and explains how Glass.NET recovers the optional defaults and decides when a named argument is worth printing.
Read →Clock skew in license verification: why a brand-new key fails on a slow laptop, and the leeway your verify needs
A customer activates a license at 9:00, and your app rejects it as "not yet valid." Nothing is forged, nothing is expired — their laptop clock is four minutes behind the machine that issued the key, so the NotBefore claim sits in their future. This post is about the honest-clock problem (not the attacker rolling the clock back, which is a different post): the few minutes of disagreement between the issuer's clock and the verifier's clock that turn a correct license into a spurious failure at the worst possible moment — right after purchase. It reads the three time claims on a Keyright license (IssuedAt, NotBefore, Expiry), shows the naive comparison that breaks, and builds the leeway-aware verify: a small symmetric tolerance on NotBefore and Expiry, why the tolerance is asymmetric in intent, and why elapsed time inside a session should come off a monotonic clock, not the wall clock you just decided you cannot fully trust.
Read →Licensing a .NET component library: the developer license, the key that compiles into your customer's app, and what the end user never sees
A component library has two audiences and only one of them is your customer. The developer who buys your PDF engine or charting control holds the license; the end users who run the apps built with it have never heard of you, must never be asked for a key, and run on machines you cannot activate. That changes where a license is checked and what travels inside the shipped binary. This post builds the design most control vendors converge on with Keyright: a perpetual, non-node-locked developer license verified at build time by an MSBuild target in your NuGet package, embedded into the customer's assembly as a signed attribute, and verified again at runtime offline against the public key in your library — with an updates-until entitlement instead of an expiry so the customer's app never stops, an audience claim so a license lifted from one app does not light up another, and a watermark-grade licensee name signed into every binary that uses it.
Read →Verifying a Keyright license on Native AOT: no reflection, a trimmed verify path, and the public key as a ReadOnlySpan<byte>
Native AOT and full trimming reward the one thing a license check should already be: a pure function over bytes. But the conveniences that make an SDK pleasant on the JIT — a reflection-based JSON serializer, loading a key by name, pulling the signing algorithm from a string — are exactly what the trimmer removes and the AOT compiler refuses to generate code for. This post takes a Keyright offline verify and makes it AOT-clean end to end: embedding the public key as a ReadOnlySpan<byte> that compiles to a pointer into the data section with no allocation, importing it with ImportSubjectPublicKeyInfo and verifying with ECDsa.VerifyData — both trim-safe — and reading the signed payload with a Utf8JsonReader instead of a reflection serializer, so the whole path carries no RequiresDynamicCode or RequiresUnreferencedCode and produces zero trim warnings. It reads the IL of the span-over-static-data optimization, shows the one JsonSerializer call that would have poisoned the publish, and explains why a license verifier is the easiest part of your app to get to AOT.
Read →Renewal nudges that don't nag: driving "expires in N days" from LicenseKind, not a date in your code
A countdown banner is easy to get wrong in two opposite ways: it nags a perpetual customer who has nothing to renew, or it stays silent until a subscription has already lapsed and the app has failed closed to Free. Both bugs come from the same mistake — computing the message from the raw license file instead of from the validated result. This post builds the renewal nudge the right way, off Keyright's LicenseInfo: using Kind to tell a perpetual license apart from a subscription and a trial, DaysRemaining and IsExpiringSoon to decide when to speak, and the fact that an expired subscription validates to Free — so the nudge must fire while the license is still valid, not after. It reads the small amount of logic behind Kind, shows why a perpetual license has a null ExpiryUtc and must never show a countdown, and wires a banner that renews the quietly-expiring customer and leaves everyone else alone.
Read →Obfuscating an F# assembly: discriminated unions, the [CompilationMapping] attribute, and what FSharp.Core reflects on
F# compiles to the same IL as C#, so a renamer can obfuscate it — but F# leans on a layer of compiler-emitted metadata that C# has no equivalent for, and a renamer that does not understand it quietly breaks things that still build. A discriminated union is not a sum type to the CLR; it is an abstract class with one nested subclass per case, a Tag integer, and a [CompilationMapping(SourceConstructFlags.SumType)] attribute that FSharp.Core reflection reads to recover the cases. Rename the case types blindly, or strip that attribute as "unused metadata", and FSharpType.GetUnionCases returns nothing, %A printing stops naming your cases, and any F#-aware serializer loses the shape — all with a clean compile. This post reads the IL a union, a record and a module actually emit, shows exactly which names and attributes are load-bearing because FSharp.Core observes them at runtime, and configures Nebula to rename the logic while preserving the reflection contract F# depends on.
Read →Obfuscating readonly structs, in parameters, and ref returns: the modreqs and compiler attributes Nebula must not strip
A readonly struct, an in parameter, and a ref readonly return are not just source conveniences — they are encoded in metadata as custom modifiers and compiler-emitted attributes that the C# compiler reads back to decide whether a call site makes a hidden defensive copy, which overload wins, and whether a value can be passed by reference at all. A modreq(InAttribute) on a parameter is part of its signature, so dropping it changes the method's identity and breaks binding; stripping IsReadOnlyAttribute from a struct makes every in-call to it start copying; removing IsByRefLike lets a ref struct be boxed. An obfuscator that treats all this as decorative metadata to be renamed or discarded produces assemblies that still load but behave differently, or that fail to bind against callers it does not control. This post reads the IL of each feature, shows exactly which pieces are signature and which are attribute, walks the three ways a careless renamer corrupts them, and explains how Nebula preserves the modreqs and the well-known compiler attributes verbatim while still renaming everything that is genuinely yours.
Read →Obfuscating a Roslyn source generator: why the generator renames freely but the attribute and the public constructor are pinned
A source generator is a strange thing to obfuscate: it is a .NET Standard 2.0 assembly that the compiler loads by reflection and runs inside your customer's build, and it is where your most valuable compile-time logic lives — exactly the code you ship and would rather not hand to a decompiler. The good news is that Roslyn discovers generators by attribute, not by name, so Nebula can rename the generator type and its whole private surface with no effect on discovery. The parts that are pinned are small and specific: the [Generator] attribute must survive, the public parameterless constructor must stay public (the compiler instantiates it with Activator.CreateInstance), and the interface method it calls must keep its slot. This post reads what the GeneratorDriver actually reflects on, draws the line between what renames and what is load-bearing, shows the Nebula config, and clears up the one thing people get backwards — obfuscating the generator does not obfuscate the code it emits.
Read →Obfuscating virtual and interface members: override chains, explicit implementations, and the MethodImpl table that ties them together
Renaming a private method is a local edit. Renaming a virtual method is not, because its name is part of how the CLR wires dispatch: an override reuses its base slot by matching name and signature at type-load time, an implicit interface implementation is found the same way, and only an explicit implementation is bound by a MethodImpl row that carries tokens instead of text. Get the grouping wrong and you either get a TypeLoadException the moment the type loads — or, worse, nothing at all, while the override silently detaches and the base implementation runs instead. This post reads the IL of each binding, shows which ones are by-name and which are by-table, walks the three ways a naive renamer breaks them, and explains how Nebula groups a virtual method with every override and implementation it is connected to — across types and interfaces — so the whole slot is renamed to one name or kept intact when any member of it crosses into an assembly you do not control.
Read →Decompiling anonymous types: the generated generic class behind new { a, b }
An anonymous type has no name you wrote, but it is a real class in the metadata — a generic one the compiler synthesizes, named something like <>f__AnonymousType0`2, with a readonly property per member, a constructor that takes them all, and overridden Equals, GetHashCode and ToString. The trick that makes it reusable is that the generated class is generic over its property types, so two new { } sites with the same property names in the same order share one backing type instantiated with different type arguments. This post reads the generated class and the newobj call site in IL, shows where the property names actually live (on the generated type’s properties, not on the <Name>j__TPar type parameters), walks the structural Equals and the hash-combine GetHashCode the compiler writes, and explains how Glass.NET recognizes the pattern and folds it back into new { Name = ..., Total = ... } instead of leaving you an unspeakable generic type.
Read →Decompiling control flow: how Glass.NET rebuilds loops and if/else from a pile of branches
IL has no while, no for, no else — it has basic blocks and branch instructions: br, brtrue, brfalse, switch. A for loop and a goto-spaghetti rewrite of the same loop can compile to identical IL, so a decompiler cannot read the structure back; it has to reconstruct it. This post walks the real pipeline Glass.NET runs: split the method into basic blocks at every branch and branch target, build a control-flow graph, compute dominators to find which block guards which, detect natural loops from back edges to the block that dominates them, and structure the forward branches into if/else — then explains the honest hard case, irreducible control flow, where no clean loop exists and a decompiler must either duplicate code or fall back to labels and goto.
Read →Decompiling foreach: the enumerator, the hidden try/finally, and when it is really a for loop
There is no foreach in IL. The C# compiler lowers it to one of three completely different shapes depending on what you iterate — a call to GetEnumerator with a while loop and a hidden try/finally that disposes the enumerator, a struct-enumerator version with no dispose at all, or, over an array or span, a plain index-counted for loop with no enumerator in sight. A decompiler sees only the lowered code and has to recognize which pattern it is looking at to print foreach back instead of the machinery. This post reads the real IL for each case, shows why the Dispose lives in a finally the source never wrote, why the struct enumerator skips it, and how Glass.NET matches the shape — the GetEnumerator/MoveNext/get_Current trio, the try/finally, the IDisposable check — to recover the loop you actually wrote.
Read →Choosing a signature algorithm for your license keys: Ed25519, ECDSA and RSA compared
A license key is a signed payload, and the signature algorithm you pick is not an interchangeable detail — it decides how long the key your customer has to type is, how fast every launch verifies it, and whether a single mistake in your issuing code can leak the private key. RSA-2048 signatures are 256 bytes, which turns a typeable key into a wall of base32; ECDSA P-256 is a third of that but carries a nonce that, reused, hands an attacker your key; Ed25519 is 64 bytes, deterministic, and has no nonce to get wrong. This post weighs the three on the axes that actually matter for offline-first licensing — signature and public-key size, verification cost on a cold start, determinism and failure modes, and what each one looks like in System.Security.Cryptography — and explains why Keyright signs with Ed25519 by default.
Read →Pinning your verification public key: the attacker who just swaps your key for theirs
Your app verifies a license because a signature checks out against a public key embedded in the assembly. But that key is data — a base64 constant any attacker who can edit your DLL can find and overwrite with a key they control, so their self-signed licenses now verify and yours is forgotten. This is trust-anchor substitution, and it is the single highest-leverage attack on offline-first licensing. This post shows the attack at the IL level — the ldstr that loads the key, the one byte-range a patch touches — then raises the cost of it: fingerprinting the key against a value woven into control flow instead of standing alone, verifying it in more than one place, carrying multiple keys with AdditionalPublicKeysBase64 so a single swap is not enough, and pairing the client check with strong-naming, anti-tamper and report-home so a tampered build is both harder to make and visible when it ships.
Read →Designing a license key humans can type: base32, segment grouping, and a check digit that catches typos
A Keyright license is signed bytes, but the thing a customer reads off an invoice, types into an activation dialog, or dictates over the phone is a string — and base64 is a terrible string for that job. It is case-sensitive, it uses + and / which get mangled in email and shells, and a single mistyped character produces a signature failure with no hint of where the mistake was. This post builds the transport encoding Keyright actually uses: Crockford base32 so the alphabet survives a phone call and a shift key, five-character segments so the eye can track position, and a mod-37 check symbol that catches every single-character error and almost every transposition before you ever run the expensive signature verification. It shows the real encode and decode in C#, the normalization that turns a sloppily typed key back into canonical bytes, and why the check digit is an ergonomics feature and never a security one.
Read →Obfuscating enums: why the names survive renaming, and how ToString() and switch leak them
Rename an enum and its members and a decompiler still prints LicenseTier.Pro, not a.b — because an enum is a sealed class whose members are public const literal fields, and the moment your code calls ToString() or Enum.Parse the runtime reads those field names straight out of the metadata at runtime. Renaming them to a, b, c is legal and costs you nothing except the one thing you wanted: a round-trip through the name breaks, because "Pro" no longer matches a field called a. This post reads the IL an enum compiles to, shows where the names live (the Field table and #Strings heap, not the values), walks exactly what breaks when you rename — ToString, Enum.Parse, [Flags] formatting, JSON string serialization, Enum.GetNames — and how Nebula scopes enum handling so the values that gate your logic are protected while the names your protocol depends on stay intact.
Read →Obfuscating generics: renaming type parameters, and the constraints and reflection that pin the names down
A generic type parameter like TResult is a name in the metadata — a row in the GenericParam table with a string in #Strings — so a renamer can turn it into T0 and a decompiler will faithfully print T0, which is exactly the harmless outcome you want. But generics are where naive renaming quietly breaks things, because the CLR identifies an instantiation by position and arity, not by name, while your reflection code identifies a type parameter by its name. This post reads what a generic method and class actually compile to — the GenericParam and GenericParamConstraint tables, the MethodSpec and TypeSpec that record an instantiation by index — shows why renaming the parameter is free but renaming a constraint type is not, and walks the reflection calls (Type.GetGenericArguments().Name, MakeGenericType, GetMethod on an open generic) that read names back and break when a renamer touches the wrong thing.
Read →Renaming to collisions: how Nebula reuses one name across members, and why the CLR tells them apart by signature
A naive renamer gives every member a unique short name — a, b, c, d — and stops there. It is leaving value on the table, because .NET does not identify a member by its name. A method is identified by its name together with its signature, a field by its name and its declaring type, and the runtime binds every call through a metadata token resolved at load time, not by re-matching strings. That means you can rename dozens of unrelated methods to the single name a, so long as their signatures differ, and the assembly still loads, verifies and runs. This post reads the MethodDef and String heap metadata to show why collision renaming is legal, walks the exact rules that bound it — same-signature clashes, virtual override chains, explicit interface implementations — and explains how Nebula packs names down to a tiny reused set that both shrinks the string heap and turns a decompiler’s output into an unreadable wall of identically named members.
Read →Decompiling C# events: the CompareExchange loop behind += and -=
One line of C# — public event EventHandler Changed; — becomes four things in the assembly: a private delegate field, an add_Changed and a remove_Changed accessor, and an Event table row that ties them together through MethodSemantics. The accessors are not simple assignments either: since C# 4 the compiler emits a lock-free Interlocked.CompareExchange loop around Delegate.Combine and Delegate.Remove, and older binaries carry a [MethodImpl(Synchronized)] variant instead. A decompiler has to recognise both shapes to fold the four pieces back into one declaration, keep an explicit add/remove when the author wrote one, and render the ?.Invoke raise site rather than the dup/brtrue dance it compiles to. Here is the metadata, the IL, and what Glass.NET makes of it.
Read →Decompiling a?.b?.c: how the null-conditional operator becomes branches and one shared skip label
The null-conditional operator reads like a single tidy symbol — order?.Customer?.Name — but there is no ?. instruction in IL. The C# compiler lowers it to ordinary null checks and branches, and the detail that makes a chain decompile cleanly is that every link short-circuits to the same shared target, not to a nest of its own. This post reads the real IL for a two-link chain, shows why the first receiver is checked with a bare brtrue while an intermediate result is dup-ed and popped, walks through the Nullable lifting that turns s?.Length into an int? with an initobj on the null side and a Nullable..ctor on the other, covers the ?? and ??= lowerings that share the same short-circuit shape, and explains how Glass.NET recognizes the pattern and folds the branches back into ?. and ?? instead of leaving you a maze of labels.
Read →Decompiling params collections: where did my array go?
For twenty years a params parameter meant one thing in IL: the caller built a newarr and filled it. C# 13 generalised params to any collection type, and the headline case — params ReadOnlySpan<T> — allocates nothing on the heap at all. The caller stacks the arguments into a compiler-generated inline-array buffer and wraps a span around it; params List<T> routes through a collection builder instead. The decompile tell is entirely at the call site, not the signature. Here is what each form emits, how a decompiler folds the buffer back into a plain call, and why a params ReadOnlySpan<T> that shows a newarr is a cold-path fallback worth noticing.
Read →Decompiling a switch on strings: the hidden hash jump table the compiler builds
A switch over an integer compiles to one IL switch instruction. A switch over a string cannot — there is no jump table for text — so once you have more than a handful of cases the C# compiler quietly rewrites it into a two-stage dispatch: hash the string with a synthesized ComputeStringHash, branch on the hash to a candidate, then confirm with an ordinal String.Equals before jumping to the arm. This post reads the real IL for a large string switch, shows the <PrivateImplementationDetails>.ComputeStringHash FNV helper and the second dense switch over a case index, explains why the equality check is not optional, and walks through how Glass.NET recognizes the whole shape and folds it back into switch (s) { case "...": } instead of leaving you a page of hash constants.
Read →Verifying a license key is not free: caching the signature check without caching a stale answer
A signed Keyright license is verified with an RSA signature check, and that check costs real microseconds. Call Validate() on every request and you have put public-key crypto on your hot path for no reason — the signature has not changed since startup. But you cannot simply cache the result forever either, because the answer is time-dependent: an entitlement can expire at noon, a lease can be reaped, a renewal can widen the plan. This post separates the two halves of a license check — the expensive, cacheable signature verification and the cheap, must-be-per-call evaluation of the license at the current instant — measures what each one costs, and shows a two-tier memoization that keeps the crypto off the hot path without ever returning an answer that is one minute stale.
Read →A license is a capability token: Keyright entitlements as ASP.NET Core authorization policies
Most apps check a license once at startup and scatter IsEnabled calls through the code. In an ASP.NET Core service that is a mistake — you already have an authorization pipeline built to answer "is this caller allowed to do this?", and a licensed entitlement is exactly that question. Here is how to project Keyright entitlements onto the authorization system: a requirement that names an entitlement, a handler that reads the validated license, a policy provider that conjures policies on demand, and [Authorize(Policy = "ent:export")] on the endpoints that need it — all fail-closed, all offline.
Read →Licensing a .NET CLI tool: headless activation, KEYRIGHT_LICENSE, and runners with no "Register" dialog
A command-line tool has no settings screen, often runs on a machine no human sits at, and is driven by scripts that only read exit codes. That breaks the desktop licensing script — paste a key into a dialog, cache a lease, carry on. Here is how to license a dotnet tool or build task with Keyright without a UI: a `license` subcommand that activates and reports, the six places the SDK looks for a license and which CLI idiom maps to each, fail-closed gating that never prompts when stdin is not a terminal, and the ephemeral-CI-runner problem solved with an offline license file carried in KEYRIGHT_LICENSE instead of a seat per job.
Read →Obfuscating async/await and iterators: why the state machine survives renaming (and the one binding a naive renamer breaks)
Open a decompiled, obfuscated assembly and the scariest thing you see is a MoveNext full of flattened control flow and fields called <>1__state. People assume renaming must have broken their async code. It did not — because every link between an async method, its generated state-machine type, and the AsyncTaskMethodBuilder is a metadata token, not a name lookup, so renaming the type and its fields to a and b changes nothing the runtime reads. But there is one thing a careless obfuscator genuinely can break: MoveNext, SetStateMachine, Current and Dispose are interface methods, and renaming them without rewriting the interface binding (the MethodImpl) leaves the builder calling a method that no longer implements IAsyncStateMachine. This post reads the real lowering of an async method and an iterator, shows which names are free and which binding must be preserved, and explains the metadata-hardening trade: strip the AsyncStateMachine attribute for a release build and know it costs you async debug stepping, not correctness.
Read →Obfuscating custom attributes: what the metadata keeps, what renaming breaks, and the reflection that still works
A custom attribute is not a comment the compiler throws away — it is a row in the CustomAttribute metadata table that points, by token, at a constructor, and carries a blob of positional and named arguments. That structure decides exactly what obfuscation can and cannot touch. Rename the attribute class and the token-based GetCustomAttribute<T>() keeps working, because tokens do not care about names; rename it and the string-keyed GetCustomAttributes().First(a => a.GetType().Name == "FeatureGate") breaks, because it was reading a name you just deleted. Named arguments are the subtle part: they reference a field or property by its string name inside the blob, so a renamer that moves one without the other produces an assembly that throws TypeLoadException at load. This post reads the real metadata, shows which reflection patterns survive a rename and which do not, and explains how Nebula keeps attribute usages consistent while still stripping the names an attacker would read.
Read →Obfuscating an assembly your tests still have to pass: InternalsVisibleTo and friend assemblies
Renaming internals is where obfuscation earns its keep — but [InternalsVisibleTo] quietly turns some of those internals into a contract. A test project, a sibling assembly, or a generated mock reaches your internal types by name, and that name only exists at compile time. Obfuscate the producer alone and the friend stops compiling, or worse, a reflection-based mock fails at runtime. Here is exactly what [InternalsVisibleTo] does in metadata, why Nebula.NET preserves the single-assembly case for you, and how to keep a genuine friend assembly green by obfuscating the pair together.
Read →Obfuscating source-generated code: why System.Text.Json contexts, [GeneratedRegex] and [LoggerMessage] survive renaming — and the one name they leak
Source generators run inside the compiler and emit ordinary C# into your assembly, so the IL they produce is indistinguishable from code you typed. The useful consequence for obfuscation is that generators resolve names at compile time: a System.Text.Json context captures "Name" as a string literal and reaches the property through a delegate, the configuration binder switches on literal keys, and a GeneratedRegex is pure type identity. Rename everything and the JSON, the bound options and the regex keep working — unlike their reflection-based equivalents. What generators do leave behind is literals: the regex pattern, the log message template, and a nameof() that bakes your original method name into an EventId. Here is what each generator emits, what Nebula.NET renames safely, and what to encrypt or exclude.
Read →One license, four runtimes: verifying the same Keyright key from .NET, Node, Python and Java
A product line is rarely one language — a .NET desktop app, a Node CLI, a Python data tool, a JVM service, all sold as one product. The lazy answer gives each its own licensing and leaves the customer juggling four keys. The better one: a single tenant signs a license with one RSA private key, every component embeds the same public key, and four SDKs verify the identical signed license and lease format offline. This post shows the same initialize-validate-gate flow in C#, JavaScript, Python and Java side by side, explains why the machine fingerprint is byte-compatible across all of them so one seat covers every component on a box, and covers the two things that are genuinely per-component — the local lease cache and the license-resolution path — plus how a key rotation rolls across the whole line at once.
Read →Decompiling init-only setters and required members: the modreq and attributes that make them
init and required are two of the smallest-looking C# keywords and two of the easiest for a decompiler to lose. An init accessor is just a set accessor whose signature carries a required modifier — modreq(IsExternalInit) — and a decompiler that does not look at the modifier shows it as an ordinary set, quietly handing you a type whose properties can be reassigned after construction. required is worse: it is three separate attributes working together — RequiredMemberAttribute on each member, RequiredMemberAttribute on the type, and CompilerFeatureRequired on the type — plus a SetsRequiredMembers on any constructor that satisfies them, and a decompiler that drops them produces code whose callers are no longer forced to set anything. This post reads the IL and the metadata for both features, shows the exact byte-level shape of the modreq and the attribute blobs, walks through how Glass.NET reconstructs init, required, and [SetsRequiredMembers], and explains why skipping the step silently changes a type's public contract.
Read →Decompiling operator overloads: op_ names, checked operators, and the conversions that only compile as operators
A user-defined operator is a static method with a reserved name and the specialname flag — op_Addition, op_Implicit, op_Equality — and mapping it back to an operator declaration looks like a table lookup. Three things make it harder than the table. User-defined conversions may be overloaded on return type alone, which is legal IL and illegal C#: two op_Explicit methods with identical parameters differ only in what they return, so a decompiler that emits them as ordinary methods produces source that cannot compile. C# 11 added checked operators — op_CheckedAddition, op_CheckedExplicit — that must be rendered with the checked keyword and paired with their unchecked twin, or every checked(a + b) call site decompiles to a method call. And op_True/op_False, op_Increment and the C# 14 instance compound-assignment operators each compile to call patterns that have to be folded back into syntax rather than shown as invocations. This post reads the IL for each, shows the call sites that drive the reconstruction, and walks how Glass.NET turns them back into the operators the author wrote.
Read →Decompiling ref fields and scoped: how C# 11 ref structs look in IL
A ref field is the first time a C# field stores a reference rather than a value, and it compiles to metadata that earlier decompilers never had to read. The field's signature carries a byref element type — ELEMENT_TYPE_BYREF inside a FIELD signature, which was illegal before .NET 7 — and the ref-safety rules that keep it from outliving what it points at are encoded in modreq and attributes, not in IL you can execute. scoped is the loudest of those: it is a ScopedRefAttribute on a parameter, with no runtime effect at all, that a decompiler either surfaces as the scoped keyword or drops, quietly changing the method's ref-safety contract. This post reads the raw signatures and attribute blobs for a ref field, a ref struct that holds one, and a scoped ref parameter, shows where the ref escape rules live in metadata, and walks through how Glass.NET reconstructs ref, ref readonly, scoped and the [UnscopedRef] escape hatch so the decompiled source compiles with the same safety the author wrote.
Read →Decompiling the new lock: when the compiler stops calling Monitor
For twenty years lock (obj) lowered to Monitor.Enter/Exit with a lockTaken flag. C# 13 changed that: lock on a System.Threading.Lock lowers to EnterScope() and a Scope ref struct disposed in a finally — a completely different IL shape. Here is exactly what each form generates, how a decompiler tells them apart and folds both back into lock, and the one decompile tell that exposes a silent bug: a Lock boxed to object that quietly falls back to Monitor.
Read →When a VM gets cloned: detecting duplicate activations in node-locked licensing
A machine fingerprint identifies a machine — and a cloned VM is, by construction, the same machine twice. Snapshot-restore, golden images and autoscaled instances all reproduce the exact hardware and OS attributes a node-lock hashes, so two running copies present one identical fingerprint and node-locking cannot tell them apart. The fix is not a better fingerprint; it is activation identity the server owns plus a per-run instance nonce the client cannot clone meaningfully, so two copies of one activation reveal themselves the moment they both phone home. This post reads the SDK and the server policy for exactly that: why the fingerprint is defeated, how an activation record and a runtime instance id separate a legitimate migration from a live clone, and how Keyright decides newest-wins versus flag-and-hold without locking out a customer who simply moved.
Read →One license, many clocks: per-entitlement expiry with Keyright
A license usually has a single expiry date, but real contracts rarely do: a base plan renews annually while a trial add-on runs 30 days and a promotional feature ends with the quarter. Baking one date into the whole license forces you to either over-grant or reissue constantly. Here is how to give each entitlement its own expiry in a single signed Keyright license — the payload shape, the SDK that reads a per-feature clock offline, how to enforce it at the capability boundary, and the grace rules that keep a lapsed add-on from nuking the whole app.
Read →Deactivating a license and moving it to a new machine: receipts, offline proofs, and transfer limits
Freeing a seat sounds like the easy half of node-locking — the customer clicks "deactivate this device" and the seat is available again. But a deactivation is a claim the client makes about its own state, and the client is the one party you cannot trust. Online, the problem is ordering and retries: release on the server before or after wiping locally, and what happens when one of the two fails. Offline, the problem is proof: a machine with no network has to hand the customer something that convinces the server a seat was really given up. And for dead machines — the laptop that fell in a lake — the problem is abuse: every forced release is a chance to multiply seats. This post reads the Keyright SDK and server policy for all three: the idempotent online release, the HMAC deactivation receipt bound to the activation's instance secret, why the lease TTL bounds what a lying client can gain, and the transfer cooldown and rolling limits that keep "I lost my machine" from becoming a seat generator.
Read →Turning on license enforcement without locking out your customers: audit mode first
The scariest deploy in any licensing project is the one that flips enforcement from off to on. Up to that point the gate has never said no to anyone; the moment it does, every mis-provisioned key, every clock that is a day off, every customer who activated behind a proxy you did not anticipate becomes a support ticket — all at once, in production. This post shows how to avoid the big-bang cutover with audit mode: a gate that evaluates the license exactly as it will in enforce mode, records the decision it would have made, and then allows the call anyway. You run it across your installed base, read the would-have-blocked population, fix the data, and only then flip to enforce — per cohort, driven by a mode the signed license carries so no client can turn it off. With a decision record, an enforcement-mode enum the payload signs, a staged rollout, and the metrics that tell you it is safe to block.
Read →Obfuscating an Avalonia app: compiled bindings, the ViewLocator convention, and what the XAML compiler already made rename-safe
Avalonia is not WPF with a different logo, and the difference matters the moment you obfuscate it. Avalonia compiles your .axaml to IL at build time, so a compiled binding is a typed property accessor that references the getter by metadata token — it survives a full rename untouched — while a reflection binding is a string the runtime resolves by name and silently comes back empty. The thing that breaks first is neither: it is the ViewLocator in the default MVVM template, which finds a view by taking the view model's type name, replacing "ViewModel" with "View" and calling Type.GetType — a convention that a rename turns into a "Not Found" TextBlock or an InvalidCastException. This post reads what the XAML compiler actually emits for a compiled binding, shows why INotifyPropertyChanged still matches after renaming, walks the two Avalonia-generated types the runtime locates with Assembly.GetType by their literal names, and gives the Nebula configuration and the DataTemplate-based view mapping that let you flatten the whole app without touching the parts Avalonia resolves by name.
Read →Obfuscating a gRPC service without breaking the contract on the wire
gRPC puts two kinds of names in your assembly, and obfuscation has to treat them as opposites. The names that travel on the wire — the service path, the method path, the field numbers in a protobuf message — are a contract every peer depends on, and renaming them breaks interop as surely as changing a URL. The names that never leave the process — your C# method names, your message property names, your DI registrations — are free to disappear. The catch is that which kind a given name is depends entirely on whether you went contract-first with protoc or code-first with protobuf-net.Grpc, because code-first derives the wire identity from your .NET names by reflection, turning the free names back into load-bearing ones. This post reads the generated code and the metadata to show exactly where the wire identity lives in each style, why protobuf field numbers make message members safe to rename, and how Nebula pins the contract surface while renaming everything behind it.
Read →Obfuscating a plugin host without breaking the plugin contract
A plugin host has a split personality that obfuscation has to respect exactly. The contract assembly — the interfaces, abstract bases and attributes that third-party plugins compile against — is a published ABI: rename a member of it and every plugin already shipped against the old names fails to bind, the same way renaming a public API breaks its callers. But the host that loads those plugins is your most valuable code and the thing you most want renamed. The two live in one solution and an ordinary obfuscation pass cannot tell them apart. This post reads the exact binding points — the interface type and method names an AssemblyLoadContext resolves at load time, the [Export]/[Import] contract names MEF matches on, and the attribute type names a host scans for — shows why each one is on the ABI and must survive, and how Nebula pins the contract surface by its shape while flattening the host behind it.
Read →Obfuscating a WinForms app without losing your designer resources
WinForms has a trap WPF does not: the designer wires every form to an embedded .resources stream whose name is derived from the form type's full name, and rebinds controls to data by property-name string. Rename the wrong thing and your icons vanish or a bound field goes blank — at runtime, with no compile error. Here is exactly what the designer emits, why renaming a Form type breaks ComponentResourceManager, and how to protect a WinForms build with Nebula.NET so the internals get hardened and the designer keeps working.
Read →Decompiling C# 12 inline arrays and stackalloc: fixed buffers without the unsafe
C# 12 inline arrays give you a fixed-size, value-type buffer that lives inline in its containing struct — the thing C used to call `int buf[8]` — but in IL it is a one-field struct decorated with [InlineArray(8)], accessed through runtime intrinsics and Span<T> rather than any array instruction. stackalloc, meanwhile, compiles to a raw localloc that a naive decompiler shows as a pointer and a pile of stind opcodes. Get either wrong and the decompiler emits unsafe pointer arithmetic where the source had a clean indexer, or invents an array that was never allocated. Here is exactly what the compiler emits for an [InlineArray] type, how element access lowers to a PrivateImplementationDetails helper over a Span, what localloc and the Span(void*, int) constructor look like in IL, and how Glass.NET reads both back into the inline-array declarations and stackalloc expressions you actually wrote.
Read →Decompiling function pointers: calli, delegate*, and where the calling convention hides
A delegate* is not a delegate — there is no object, no Invoke method, no allocation. It compiles to a raw code address pushed on the stack and a calli instruction that calls through it. So a decompiler has nothing to lean on: no type name, no method to follow, just an indirect call and a standalone signature it must read to know the argument types and the calling convention. And the convention — managed, Cdecl, Stdcall, Thiscall — is not a keyword in the IL; it is encoded as modopt types bolted onto the signature. Here is exactly how a function pointer is laid down, how calli names its own signature, where the unmanaged convention actually lives, and how Glass.NET rebuilds the delegate* you wrote.
Read →Decompiling nullable reference types: where string? actually lives in the metadata
string and string? are the same type to the runtime — System.String, one TypeRef, no difference in any signature. The question mark exists only at compile time, and the compiler has to smuggle it into the assembly somewhere so that consumers of your library get the right warnings. It does that with two embedded attributes, NullableAttribute and NullableContextAttribute, a three-value byte vocabulary, a preorder flattening of generic type trees into byte arrays, and an aggressive compression scheme that omits whatever matches the enclosing default. This post reads those bytes directly from the IL, shows the exact blob encoding for the single-byte and array forms, walks through how a decompiler reconstructs ?, notnull and class? from them, and explains why skipping this step silently changes a library's public contract.
Read →Decompiling static abstract interface members: how generic math compiles
C# 11 let interfaces declare static abstract members, and the BCL used it to build generic math — write Sum<T>(span) once and it runs over int, double, or your own type. But the feature leans on a corner of the ECMA spec most decompilers never had to handle: a constrained call to a *static* interface method, resolved to the type argument at runtime. Get that wrong and the decompiler emits a call to a method that looks like it has no receiver, or invents a cast that is not there. Here is exactly what the compiler emits for T.Zero and a + b over a generic constraint, why the constrained. prefix on a non-virtual static call is the whole trick, and how Glass.NET reads it back into the operators and static members you actually wrote.
Read →Encrypting embedded resources: the data your obfuscator forgets
You renamed every type, encrypted every string, and virtualized the hot methods — and then someone opened your assembly in a resource viewer and read your embedded SQL, your licensing key material, and the full text of your error messages straight out of the .resources blob. Managed resources are not code, so method-level protection never touches them; they sit in the manifest as length-prefixed values anyone can dump. This is how embedded resources are actually stored in a PE, why renaming leaves them untouched, and how Nebula encrypts them at build time and transparently decrypts them through a resolver so your calls to ResourceManager and GetManifestResourceStream keep working unchanged.
Read →License heartbeats and lease reaping: counting concurrent seats when clients crash
A floating license sells N seats shared across a team, so the server has to know how many are in use *right now*. That is easy until a client dies the wrong way — a crash, a killed VM, a yanked network cable — and never returns its seat. Refcount on checkout and the seat is leaked forever; your customer pays for ten and can run six. The fix is to stop trusting checkout/checkin as a matched pair and treat every seat as a lease with a time-to-live that the client must keep alive with heartbeats. Here is the lease-and-heartbeat protocol in Keyright: the signed short-TTL lease, the renew loop on the client, the reaper that reclaims dead seats on the server, and the clock and grace rules that keep the count honest without punishing a flaky network.
Read →Versioning your license payload: adding claims without breaking the clients already in the field
A signed license is a set of claims frozen into bytes, and the moment you ship a client that reads it, that byte layout becomes a contract you cannot unilaterally change. Then product asks for a new claim — a seat cap, a region lock, a feature list — and you discover that half your installed base is running a parser that has never seen it. This post walks through how Keyright-style license payloads evolve safely: a schema version the signature covers, open-world parsing that keeps unknown claims verifiable, a critical-claims list so an old client refuses what it cannot enforce rather than silently ignoring it, schema negotiation at activation so the issuing side never emits what a client cannot read, and the golden-file tests that stop a format change from reaching production unnoticed.
Read →Named-user licensing: binding entitlements to a person, not a machine
Machine binding answers "which computer is this?" — but plenty of products are sold per *seat*, not per box. A developer with a laptop, a desktop, and a CI runner is one paying user, not three activations. Named-user licensing flips the anchor: the license belongs to an identity, and devices are leaves that hang off it under a cap you control. Here is how to model a signed identity claim in Keyright, exchange a sign-in for a short-lived device-scoped lease, enforce a device limit without pinning the user to one box, and let a user roll a device off a full seat — with the real C# for issuing, binding, and verifying it.
Read →When nameof and [CallerMemberName] leak your renamed members
You renamed every type and method, then opened the obfuscated binary and found your original names sitting in plain string literals. The culprit is not a bug in the obfuscator — it is the compiler. nameof(OrderService) bakes the literal "OrderService" into IL at compile time, long before renaming runs, and [CallerMemberName] does the same with the original method name as a default parameter value. Rename the member and the literal still says the old name, so a decompiler reads your map for free and any reflection that expected the runtime name silently breaks. Here is exactly where these literals come from in IL, why renaming cannot touch them on its own, and how Nebula.NET's string encryption and rename-aware handling close the leak.
Read →Obfuscating an ASP.NET Core app: controllers, DI, model binding, and what must stay public
ASP.NET Core finds almost everything by name at runtime — controller types by suffix, actions by method name, routes from [controller] and [action] tokens, query and form parameters from parameter names, JSON properties from property names, hub methods from strings, options classes from configuration keys. Rename any of those and the app still compiles, still starts, and returns 404 or an empty DTO. This post maps every reflection-driven seam in an ASP.NET Core app — MVC, Minimal APIs, Razor views and tag helpers, SignalR, the options pattern, hosted services — shows what the renamed metadata actually looks like, and builds a nebula.config.json that renames and hardens the services and domain while pinning exactly the surface the framework observes.
Read →Obfuscating P/Invoke and DllImport code without breaking native interop
Renaming is the backbone of obfuscation, and it breaks exactly where your managed code stops being self-contained — at the boundary with native code. A [DllImport] method, a delegate you marshal to a function pointer, a struct you blit across the boundary: each carries a name or a layout that something outside the CLR depends on, and rename it blindly and the call fails at runtime with a MarshalDirectiveException or a silently corrupted struct. This is why native interop survives obfuscation only when the tool understands the marshaller: which names are load-bearing, why EntryPoint decouples the managed name from the native one, how field order and StructLayout must be preserved, and how Nebula renames everything around the boundary while leaving the boundary itself intact.
Read →Storing an activated license on the client: DPAPI, tamper-evident caching, and what not to trust
Activation gives the client a signed lease; then the question nobody plans for arrives — where do you put it? Drop it in a plain file and a user edits the expiry; encrypt it badly and you have tied the app to one machine by accident. The cache is for availability, not authenticity: the signature still decides whether a lease is real, so the store's only jobs are to survive a restart, resist casual editing, and refuse a lease that was copied from another machine. Here is how to seal a cached lease with DPAPI where it exists, bind it to the device with an HMAC so an edited or transplanted cache is detected, and why you must never trust the cached expiry over the signed one.
Read →Decompiling the dynamic keyword: reading call-site IL in Glass.NET
One line of C# with dynamic compiles into a cached call site, a binder, an array of argument-info flags and an invocation through a delegate field — a dozen members of generated scaffolding for a single method call. Here is what the compiler actually emits for dynamic dispatch, how a naive decompiler shows you the raw CallSite machinery, and how Glass.NET recognizes the pattern and gives you back the one line you wrote.
Read →Decompiling local functions in Glass.NET: name mangling and ref-struct closures
A local function is not a lambda, and the compiler proves it in the IL. There is no delegate allocation and no closure class — a non-capturing local function becomes a plain static method with a mangled name like <Run>g__Add|0_0, and a capturing one becomes a method that takes its captured variables by ref in a value-type display struct, so nothing lands on the heap. Here is exactly what the C# compiler emits for both kinds, how a decompiler that does not recognize the pattern hands you the mangled method and the ref struct, and how Glass.NET folds it back into the local function you wrote.
Read →Decompiling string interpolation: reading the handler back to $"..."
Modern C# does not compile $"Hello {name}" into string.Format anymore — it lowers to a DefaultInterpolatedStringHandler with AppendLiteral and AppendFormatted calls. Here is exactly what that looks like in IL, how Glass.NET pattern-matches the sequence back into an interpolated string with its format and alignment intact, and the one case where it honestly cannot.
Read →Decompiling tuples: where the names (count, name) actually live
A ValueTuple has no field called count or name — those are Item1 and Item2, and the type system knows nothing about your names. The names you wrote live in a [TupleElementNames] attribute the compiler attaches to the method signature, field or property, as an array of strings with nulls for the unnamed slots. A decompiler that ignores that attribute hands you Item1/Item2; one that reads it restores (int count, string name). Here is exactly how tuple names are encoded, why they survive on signatures but vanish on locals, and how Glass.NET puts them back.
Read →Licensing containers and autoscaled .NET services with Keyright
Machine-bound licensing assumes a machine: a stable box with a fingerprint you can lease a seat to. A container has none of that — the image is identical across replicas, the host is ephemeral, and an autoscaler spins instances up and down faster than any activation flow can keep up. Here is how to license a horizontally-scaled .NET service with Keyright: activate once at the deployment boundary, ship a signed lease into every replica, validate offline with no per-instance network call, and decide up front whether a seat means a deployment or a running instance.
Read →Designing a license gate: where to check, what to cache, and when to fail closed
A single Validate() call is the easy part of licensing. The hard part is architecture: where in your app the check lives, how often it runs, what you cache between runs, and — the question that decides whether a bug locks out a paying customer or hands your software away for free — which failures fail closed and which fail open. Here is a fail-safe license gate for .NET built on Keyright, with a status-by-status decision table and a cached snapshot that survives offline.
Read →Selling updates: how a license decides which versions it may run
A perpetual license is forever; "one year of updates" is not — and the two live in the same key. Release coverage is how a license says which versions it is entitled to run, as signed version-range and date-window grants that the build checks locally, offline, with no reactivation. Here is how a coverage grant is shaped, how the entitlement date (not the install date) decides a hotfix, how the signed block rides along with the lease, and how your build enforces it without a single network call on the hot path.
Read →Obfuscating an Entity Framework Core app without breaking your data layer
EF Core maps your CLR types to tables by their names. Rename an entity or a property with an obfuscator and, by default, the schema EF expects moves with it — your DbContext now looks for a table called "a" with a column "c", migrations stop matching, and string-based property access throws at runtime. The fix is not to exclude your whole data layer from obfuscation. It is to understand exactly which names EF reads at runtime, make your mapping explicit so the schema is decoupled from the CLR names, and then let Nebula.NET rename the members freely. Here is the full picture, with the rename rules and the EF patterns that make it safe.
Read →Obfuscating a multi-targeted assembly: one config across net48 and net8.0
A library that targets net48;net8.0 builds into two different assemblies, with two different reference sets and sometimes two different sets of IL. Protecting only one of them ships an unobfuscated build. Here is how Nebula.NET treats each target framework as its own obfuscation run while sharing one policy — and the per-TFM gotchas that bite if you do not.
Read →Re-signing a .NET assembly after obfuscation: strong names and Authenticode
Renaming rewrites your assembly, which invalidates the strong name and the Authenticode signature it was built with — a protected build that ships unsigned, or worse ships with a now-broken signature, will fail to load or fail to verify. Nebula.NET re-signs for you as the last step of the pipeline: a new strong-name signature over the obfuscated image, cross-assembly public-key tokens rewritten so interdependent assemblies still resolve, then an Authenticode signature on top. Here is the exact order, how to feed the key to a build server without checking it in, and what InternalsVisibleTo breaks if you forget.
Read →Keeping obfuscated names stable across releases with Nebula's seed map
By default every obfuscation run renames from scratch, so a method that was "a" in 1.4.0 becomes "q" in 1.4.1 and every archived deobfuscation map rots the moment you ship. Nebula.NET's seedMapFile fixes the output in place: unchanged members keep the obfuscated name they had last release, only new code gets new names. Here is how the symbol map works, why stable names make crash symbolication and binary diffs tractable, and how to wire incremental obfuscation into CI.
Read →Testing the code that lives behind a license check
Your paid features sit behind a license gate — but how do you test them without a live issuing service, a real key, or the wall clock? Here is a two-layer strategy for Keyright: a thin seam you mock for fast unit tests, and a handful of committed, test-key-signed fixtures that exercise the real SDK verification path fully offline and deterministically in CI.
Read →Turning a DLL back into a buildable project with Glass.NET
Reading decompiled C# in a window is one thing; getting a .csproj you can open, grep, refactor and compile is another. Here is how Glass.NET's project export reconstructs a whole assembly — source, references, resources — what it handles cleanly, where compiler-generated code needs a hand, and how to script it from the CLI.
Read →Rotating your license signing keys without locking out customers
Your license keys and leases are only as trustworthy as the key that signs them. Here is how to roll that signing key to a new one with zero downtime — overlapping trust with AdditionalPublicKeysBase64, a staged rollout, and how leases and offline files make the transition safe.
Read →Report-home: knowing when a protected .NET build is attacked in the field
Anti-tamper and anti-debug guards react on the user's machine — but you never find out they fired. Nebula.NET's report-home (tamperReportUrl, Pro) POSTs a minimal event to your own endpoint the moment a guard trips, so an attack on a shipped build becomes a signal you can act on. Here is how it works and how to build the receiver.
Read →Canonical signing of license payloads: why field order and encoding decide whether your signature verifies
A signature is computed over bytes, not over fields — so if your issuing service and your client serialize the license payload even slightly differently, verification fails on a perfectly genuine license. This is how to canonicalize a payload once (stable field order, invariant number and date formatting, UTF-8, base64url) so one signature verifies identically in .NET, Node, Python, and Java.
Read →Decompiling C# 12: collection expressions and primary constructors
Collection expressions and primary constructors look like single syntactic ideas, but the compiler lowers them to very different IL depending on the target type and whether a parameter is captured. Here is exactly what `[1, 2, 3]`, spreads and a primary constructor emit, and where a decompiler can fold the sugar back versus where it can only show the lowered form.
Read →Obfuscating .NET Native AOT and single-file apps
Publishing as a single file or with Native AOT is packaging and compilation, not protection. Here is what each actually does to your code, why single-file apps still ship fully-recoverable managed IL, what Native AOT does and does not hide — and why obfuscation has to run on the IL before the bundle is built or the AOT compiler removes it.
Read →Auto-Hardening Your License Checks
The method that verifies your license is the single highest-value target in your app — crack it and everything else is moot. Nebula.NET can find the methods that call your licensing SDK and automatically apply the strongest protection your edition allows to exactly those methods, so the gate is hardened without you hand-listing it. Here is how auto-protect works.
Read →Decompiling async iterators: when await and yield share one state machine
An async IAsyncEnumerable method mixes await and yield return — so the compiler generates a single state machine that is both an async state machine and an iterator. Here is the shape it lowers to, how the state field drives both await-resumes and yield points, and how a decompiler recognizes the pattern to rebuild await foreach.
Read →Decompiling C# pattern matching and switch expressions
A switch expression is not a construct in IL — it lowers to a chain of type tests, null checks and branches, ending in a thrown SwitchExpressionException for the unmatched case. Here is exactly what the compiler emits for type, property, relational and logical patterns, and how a decompiler reads the shape back into a switch.
Read →Decompiling using: the try/finally the compiler writes for you
A using statement has no IL of its own — the compiler lowers it to a try/finally that calls Dispose, and await using to a try/finally that awaits DisposeAsync. Here is exactly what that generated code looks like, the null-check and struct-enumerator subtleties, and how a decompiler folds it back into using.
Read →Obfuscation and performance: which transforms cost, and how to scope them
Not all .NET protection has the same runtime cost. Renaming is free; string and constant encryption add a tiny per-use decode; control-flow flattening adds dispatch overhead; method encryption and virtualization are heavy per call. Here is the honest cost model for each transform and how to scope protection so you defend what matters without slowing a hot path.
Read →What your app should do when the license server is down
A license check that fails hard the moment the server is unreachable turns your outage — or the customer's flaky Wi-Fi — into their outage. Here is how to design grace windows, choose fail-open vs fail-closed per tier, and keep a transient network blip from locking out paying customers.
Read →Reference Obfuscation in .NET: Hiding the Call Graph
Rename everything and a decompiler still reads your intent from the calls you make: File.ReadAllText, RSA.Create, your own CheckLicense. Reference (proxy-call) obfuscation routes those calls through generated proxies so the static call site no longer names the target. Here is how it works in IL, what it defeats, and what it costs.
Read →How to Test Your Software Licensing Integration
Licensing bugs ship because the only path anyone exercises is "valid key, server up, clock correct." Here is how to put expiry, revocation, offline grace and seat limits under automated test: inject the clock, fake the license client, and drive every edge from xUnit so CI catches the lockouts before your customers do.
Read →Shipping a Time-Limited Demo of Your .NET App
Sometimes you want a build that simply stops working after a date — a conference demo, an evaluation copy, a pre-release that must not run in production forever. Nebula.NET can bake an expiry into the assembly itself, enforced from a module initializer before your code runs, with a reaction you choose. Here is how it works and when to use it instead of server licensing.
Read →Watermarking .NET assemblies to trace who leaked a build
Obfuscation stops a reader from understanding your code; it does nothing to tell you which customer leaked it. A per-licensee watermark does — a covert, unique marker baked into each build that survives casual copying and maps a leaked assembly back to the account it was issued to. Here is what a good watermark needs to be, where it lives, and how it complements obfuscation and licensing.
Read →Anatomy of a signed license key: why it cannot be forged
A good license key is not a random string checked against a list — it is a signed claims payload the app can verify offline with a public key it ships. Here is what lives inside a signed key, why the private/public split makes it unforgeable, how verification works without a server, and what signing does and does not protect against.
Read →Constant encryption in .NET: hiding the numbers decompilers read for free
String encryption gets the attention, but the numeric literals in your code — thresholds, opcodes, magic numbers, offsets — compile to ldc instructions a decompiler reads instantly. Here is how numeric constants leak logic, how constant encryption replaces them with runtime-decoded values, and where it fits alongside string encryption and control-flow protection.
Read →Decompiling C# records: the generated members that give them away
A record is a class plus a precise set of compiler-generated members — value equality, a clone method, a Deconstruct, a PrintMembers/ToString, init-only setters. A decompiler recognizes that fingerprint and rebuilds the record keyword. Here is exactly what the compiler emits for a record and how the pattern is read back.
Read →Decompiling yield return: how iterator blocks become state machines
An iterator method with yield return has no direct IL representation — the compiler rewrites it into a generated class that implements IEnumerator and resumes inside a MoveNext switch. Here is the shape of that state machine, how a decompiler recognizes it, and how yield return and yield break are recovered from the generated code.
Read →Runtime patching: the attack your file-integrity check never sees
Anti-tamper that hashes the DLL on disk catches a patched file — but not a method rewritten in memory at runtime. Libraries like Harmony detour your methods without touching the file, which is how modern license bypasses and cheats work. Here is how in-memory patching works and what actually detects it.
Read →Trusting the clock: how licensing survives date rollback
Trials and subscriptions expire on a date — but the device clock is attacker-controlled, and rolling it back is the oldest trick for reviving an expired license. Here is why you cannot trust local time, and the high-water-mark and server-time techniques that defend expiry without punishing honest users.
Read →Designing a machine fingerprint that is stable but not brittle
Node-locking binds a license to a device, which means you need a fingerprint that is unique enough to identify a machine yet stable enough to survive a RAM upgrade. Here is how to build one from weighted hardware signals, match it with tolerance, hash it for privacy, and handle the VM and hardware-change cases that trip up naive implementations.
Read →Anti-Debugging in .NET: How It Works and Where It Ends
A debugger is how an attacker watches your decrypted strings and breakpoints your license check. Here are the managed, native and timing-based ways a .NET app can detect one — with real code — and an honest account of why anti-debugging is a speed bump, not a wall.
Read →Converting a Trial to a Paid License Without a Reinstall
A trial user who pays should become a licensed user in place — same install, same data, no key to re-enter. Here is how clean trial-to-paid conversion works: upgrading the key in place, refreshing the cached entitlements, and designing trial expiry so paying simply restores what was there.
Read →What Lambdas and Closures Decompile To: Display Classes and Captured Variables
Decompile a method that uses a lambda and you find a type named <>c__DisplayClass with your local variables as fields. Here is how the C# compiler turns closures into classes, why a capturing lambda allocates and a non-capturing one does not, and what the generated code tells you about a closure bug.
Read →Decompiling Without a PDB: What You Lose and What a Decompiler Recovers
A release DLL ships without its PDB, yet a decompiler still reconstructs readable C#. Here is the split: what lives in the assembly metadata (and survives), what lives only in the PDB (local names, line numbers), and how a decompiler fills the gap when the symbols are missing.
Read →Reading obfuscated stack traces: renaming maps and crash deobfuscation
Renaming obfuscation turns your production stack traces into a wall of a, b, c — unless you keep the renaming map. Here is how the map works, how to restore a readable trace from an obfuscated crash, and how to keep the map safe without shipping it.
Read →How .NET metadata tables work — and how a decompiler reads them
Every .NET assembly carries a small relational database of its own structure: the metadata tables. Here is what TypeDef, MethodDef and the coded indexes actually contain, how a metadata token addresses a row, and how a decompiler walks the tables to turn a DLL back into types and methods.
Read →License Revocation: Making a Key Stop Working, Online and Offline
Issuing a license is easy; making one stop working after a refund, a leak or a chargeback is the harder half. Here is how revocation actually works — online check-in that fails closed, signed revocation lists that reach offline clients, and how to bound the exposure window you can never fully close.
Read →What Obfuscation Breaks: Reflection, Serialization, and What to Exclude
The first time you obfuscate a working app it can break in surprising ways: JSON comes out wrong, reflection returns null, data binding stops. The cause is always the same — something resolved a renamed member by its name. Here is exactly what breaks, why, and how to exclude the right things without weakening protection.
Read →Automating License Delivery with Fulfillment Webhooks
A paid order should mint a license with no human in the loop. Here is how fulfillment webhooks work end to end: verifying the signature over the raw body, making delivery idempotent per order, and mapping paid, refund and cancellation events to issue, revoke and renew.
Read →What async/await Decompiles To: Reading the Generated State Machine
Decompile an async method and you do not see async/await — you see a compiler-generated struct with a MoveNext() switch. Here is how the C# compiler lowers async into a state machine, how to read the <Method>d__N type a decompiler shows you, and how a good decompiler reconstructs the original.
Read →Obfuscation Beyond Renaming: Why Renamed .NET Still Reads Like Source
Identifier renaming is the weakest obfuscation layer: a decompiler on rename-only code still shows your logic, strings and constants verbatim. Here is what renaming does and does not hide, and the layers — string encryption, constant masking, control-flow flattening and virtualization — that actually make .NET hard to read.
Read →Entitlements and Feature Flags, Driven by the License
How to gate features and enforce numeric limits by license tier: signed entitlement templates, reading flags and limits offline in your app, enforcing at the capability boundary, and changing what a tier unlocks without shipping a new build.
Read →Licensing for Air-Gapped and Offline Environments
How air-gapped license activation works: signed offline license files, manual activation flows, offline expiry and grace, and revocation with no network.
Read →How to Safely Analyze a Suspicious .NET Assembly
A defensive guide to statically analyzing an untrusted .NET DLL without running it: decompile, inspect APIs and resources, spot obfuscation, stay safe.
Read →Anti-Tamper for .NET: Stop Patched and Cracked Builds
Anti-tamper for .NET detects a modified or patched assembly and a debugger at runtime. Here is how self-integrity and anti-debug checks work, and their honest limits.
Read →Control Flow Obfuscation in .NET: Why Renaming Isn't Enough
Renaming hides names; control flow obfuscation hides logic. Here is how control-flow flattening and opaque predicates work in .NET, with a before/after example.
Read →dnSpy Alternative in 2026: A Maintained .NET Decompiler
dnSpy is archived and unmaintained. Here are the best dnSpy alternatives in 2026 for decompiling, reading and debugging .NET — free and actively developed.
Read →Floating (Concurrent) Licenses Explained
How a floating (concurrent) license works versus node-locked and named-user: the concurrency ceiling, check-in/check-out, and when each fits.
Read →ILSpy Alternative: When You Want More
ILSpy is an excellent free .NET decompiler. An honest look at ILSpy alternatives in 2026 — where users want more UX and navigation, and how Glass compares.
Read →Obfuscate .NET in Azure DevOps Pipelines
A concrete Azure DevOps YAML pipeline that obfuscates your .NET app on Release only, licenses just the build agent, and keeps the symbol map as an artifact.
Read →Protecting a .NET SDK or Class Library You Ship to Customers
Obfuscating a .NET class library or SDK is different: the public API must stay callable while internals are protected. Here is how to exclude the surface and ship safely.
Read →How to Recover Lost Source Code from a .NET DLL
Lost the repo but still have the DLL? How to recover source code from a .NET DLL by decompiling it back to a buildable C# project — with honest caveats.
Read →de4dot and .NET Deobfuscation: What Actually Stops It
Automated deobfuscators like de4dot strip simple renaming in one click. Here is what raises the cost: control flow, string and method encryption, and virtualization.
Read →Self-Hosted vs Cloud Licensing: How to Choose
Self-hosted licensing vs a managed cloud license server: the real trade-offs in control, air-gap, compliance, uptime and operational burden.
Read →How to Stop License Key Sharing and Piracy
How to stop license key sharing and prevent license piracy: node-locking, activation limits, seat and device caps, re-validation and revocation.
Read →Usage-Based (Metered) Licensing for Software
How usage-based (metered) licensing works: credits, quotas, metering API calls or seats, prepaid credit blocks, and reconciling usage offline.
Read →How to View the IL of a .NET Assembly (and Why)
How to view the IL of a .NET assembly, what CIL actually is, and when reading IL beats decompiled C# — with a small, correct C#-to-IL worked example.
Read →How to debug a .NET app when you don't have the source
No source for that NuGet package or legacy DLL? A .NET decompiler turns the assembly back into readable C# you can navigate, compare across versions and export to a project — so you can find the bug without the original code.
Read →Free .NET obfuscator: what you actually get (and what you don't)
A free .NET obfuscator gives you identifier renaming, some string encryption and basic control-flow protection — the paid-tier features are full string encryption, anti-tamper, method encryption, code virtualization and CI integration. Here is an honest breakdown, and where Nebula.NET's free edition fits.
Read →How to license your software: a practical guide
A practical, end-to-end guide to licensing your software: pick a licensing model, issue signed license keys instead of a homegrown string, deliver them to customers, verify offline and activate online, enforce seats and node-locking, gate features with entitlements, and handle trials, renewals and revocation.
Read →How reverse engineering a .NET app works (and how to make it not worth it)
Reverse engineering a .NET app is easy because IL decompiles cleanly back to C# — strings, license checks and logic are all readable. Here is how it works, and the defensive layers that raise the cost.
Read →Software license types explained: perpetual, subscription, floating, trial
A clear reference to the main software license types — perpetual, subscription/term, floating/concurrent, node-locked, trial, and volume/site — with when to use each, the trade-offs, a comparison table, and how each maps to what Keyright supports.
Read →How to add a free trial to your software (that doesn't leak)
Time-limited trials done right: issue a signed, auto-expiring key, node-lock it to one trial per machine, make it revocable and tracked, convert to paid without re-keying, and offer self-service trials from your own site — instead of a registry flag anyone can reset.
Read →License key generator for .NET: why you shouldn't roll your own
A real .NET license system isn't a random key string — it's a signed license verified against an embedded public key, with expiry, node-lock, entitlements and revocation. Here's why a homegrown key generator fails and what a proper signed-license system looks like.
Read →Method encryption in .NET: how it protects your most sensitive code
Method encryption stores a method's IL encrypted in the assembly and re-emits it at runtime, so a decompiler sees only a stub — no readable C# or IL. Here is how it works, when to use it, and its trade-offs.
Read →A dotPeek alternative: Glass.NET, a free .NET decompiler
Glass.NET is a free .NET decompiler built on the ILSpy engine, with a navigation-first workflow, project export and an exposure report. Here is an honest comparison with JetBrains dotPeek.
Read →Node-locking and seat management for software licenses
How to bind a license to a machine with a stable fingerprint and swap tolerance, enforce a seat count across N machines, let customers self-deactivate a seat, and why only online activation makes seats and revocation real.
Read →How to add software licensing to a .NET app
A practical how-to for adding licensing to a .NET app: issue a signed key, embed a public key, verify offline, gate features by tier and entitlements, activate online for seats, and handle trials and revocation.
Read →Build vs buy: software licensing for .NET (2026)
An honest breakdown of the real cost of rolling your own .NET licensing — signing keys, node-locking, seats, revocation, trials, a customer portal — versus adopting a licensing service like Keyright.
Read →A Cryptolens alternative for software licensing
Cryptolens is an established, .NET-focused licensing service. Here's a fair comparison with Keyright — offline-first signed licenses, per-tenant signing keys, entitlements and self-service trials, with a free tier to try first.
Read →How to decompile a .NET DLL to C# (2026 guide)
Decompile any .NET DLL or EXE back to readable C# for free. A step-by-step guide plus an honest ILSpy vs dnSpy vs dotPeek vs Glass.NET comparison.
Read →How to protect .NET code from decompilation (2026)
A practical, honest guide to protecting .NET code from decompilation: why C# is trivially reversible, and the obfuscation layers that actually raise the cost.
Read →How to inspect a NuGet package's code before you trust it
A NuGet package ships compiled DLLs, not the source you read on GitHub — so the only way to audit the exact code you are about to run is to decompile it. Here is how to decompile a NuGet package back to C#, what red flags to look for, and how a free decompiler makes the review fast.
Read →A Keygen alternative for .NET licensing
Keygen is a capable, language-agnostic licensing API. Here's an honest comparison with Keyright — where each fits, and why a .NET-first team shipping offline signed licenses might prefer Keyright.
Read →How to obfuscate a Blazor WebAssembly app on .NET 8/9/10 (WebCil)
Modern Blazor WebAssembly wraps your real .NET assemblies in WebCil, fingerprints them and guards them with integrity hashes — so post-publish obfuscators break the app. Here is why, and how to protect a Blazor WASM app the correct way: obfuscate at build time, before the SDK packages WebCil.
Read →Offline license validation with signed keys, explained
How offline license validation works: RSA signatures signed server-side and verified client-side against an embedded public key, node-locking to a machine fingerprint, expiry, revocation lists, and why failing closed matters.
Read →A Dotfuscator alternative for .NET in 2026
Dotfuscator is the veteran enterprise .NET obfuscator, but its quote-based pricing and enterprise focus don't fit every team. Here's an honest look at where it shines, where it doesn't, and how Nebula.NET compares as a modern alternative.
Read →An Eazfuscator.NET alternative in 2026
Eazfuscator.NET is a polished, MSBuild-native .NET obfuscator with virtualization. Here's a fair comparison with Nebula.NET — where each fits, and how to choose for your team.
Read →Glass.NET vs ILSpy, dnSpy and dotPeek: a decompiler you navigate
A feature-by-feature look at Glass.NET against ILSpy, dnSpy, dotPeek and .NET Reflector — same proven decompiler engine, but a navigation-first workflow, a full assembly-information view, project export and an exposure report. Free, and actively developed.
Read →A .NET Reactor alternative in 2026
.NET Reactor is a capable, affordable obfuscator with native compilation (NecroBit) and built-in licensing. Here's an honest comparison with Nebula.NET — where each is strong, and how to decide.
Read →How to obfuscate a .NET MAUI app (without breaking it)
A .NET MAUI app ships your managed assemblies to every device, where they decompile back to C#. Here's what's exposed on each platform, how to obfuscate a MAUI app with Nebula.NET, and the XAML-binding and iOS gotchas to avoid.
Read →How to protect a Blazor WebAssembly app from decompilation
Blazor WebAssembly ships your real .NET assemblies to the browser, where anyone can download and decompile them. Here is why Blazor is the most exposed .NET target, why you can't just obfuscate the DLLs, and how Nebula.NET protects a Blazor WASM app end to end.
Read →Nebula.NET 1.1: code virtualization, Blazor WASM protection, and beating de4dot
Nebula.NET 1.1 adds code virtualization, Blazor WebAssembly protection, and hardening built specifically to defeat automated deobfuscators like de4dot.
Read →How to Obfuscate a .NET App in GitHub Actions (CI/CD)
Obfuscate your .NET app automatically in GitHub Actions: a step-by-step CI/CD workflow that protects your Release build on the runner, licenses only the build server, and keeps developer builds clean.
Read →Obfuscate only on your build server (keep developer builds clean)
Protect only the official, shippable builds — developer machines build unobfuscated and debuggable. A step-by-step CI setup with Nebula.NET.
Read →How to obfuscate a NuGet package before you publish it
Ship a protected .NET library on NuGet without breaking the public API your consumers depend on — a step-by-step guide with Nebula.NET.
Read →How to obfuscate a WPF app without breaking XAML data binding
A step-by-step guide to protecting a WPF desktop application with Nebula.NET — and keeping bindings, commands and converters working.
Read →Best .NET Obfuscators in 2026: Top 5 Comparison
Best .NET obfuscators for 2026, compared honestly: ConfuserEx, Dotfuscator, Eazfuscator.NET, .NET Reactor, and Nebula.NET. Real protection vs. renaming tricks.
Read →Looking for a ConfuserEx alternative? What to know in 2026
ConfuserEx is popular but effectively unmaintained. Here is what that means for your .NET protection, what to look for in a modern alternative, and your options.
Read →Does obfuscation actually protect .NET code?
A straight answer on what .NET obfuscation can and cannot do — why .NET is easy to decompile, what obfuscation changes, and how to build realistic, layered protection.
Read →Introducing Glass.NET — a free .NET decompiler you can navigate
Glass.NET is a free .NET decompiler that turns any assembly into clean, readable C# — with fast Go to Definition, Find All References, Go To All, call/type hierarchy, C#/IL views and project export. A modern ILSpy, dnSpy and .NET Reflector alternative.
Read →How to obfuscate a .NET assembly (step-by-step, 2026)
A practical guide to obfuscating .NET assemblies — what to protect, how to do it with Nebula.NET, and how to verify your app still runs identically afterward.
Read →Can someone decompile my .NET app? Stopping dnSpy and ILSpy
Yes — tools like dnSpy and ILSpy decompile .NET binaries in seconds. Here is how decompilation works and how to make your assemblies far harder to read and debug.
Read →How to protect C# source code from being decompiled
Compiled C# can be turned back into readable source in seconds. Here is why that happens and the practical steps to protect your C# code before you ship it.
Read →Adding licensing to your .NET app? Protect the license check too
A license key is only as strong as the code that verifies it. Learn the common .NET licensing approaches and why obfuscation and anti-tamper are essential to make them hold.
Read →Hardcoded strings are leaking your .NET app’s secrets
Literal strings in a .NET binary are readable in seconds with any decompiler — exposing API endpoints, keys and logic. Here is why string encryption matters and how it works.
Read →Which Nebula.NET edition is right for you?
Free, Licensed, or Enterprise? A practical guide to choosing the right Nebula.NET edition for your project, team, and threat model.
Read →Introducing Nebula.NET
Our first product is here — a .NET obfuscation and hardening suite built for correctness, with a CLI, desktop GUI and CI integration.
Read →