Skip to content
← Alle Beiträge
· Delta1 Labs Obfuskierung.NETLeitfaden

Code obfuskieren, der Code erzeugt: Reflection.Emit, DynamicMethod und Ausdrucksbäume

Wenn Ihre Anwendung zur Laufzeit IL erzeugt — DynamicMethod, TypeBuilder, Expression.Compile — kann die Umbenennung sie auf eine Weise zerstören, vor der der Compiler nie warnt. Hier steht genau, welche Codegenerierungs-Muster die Obfuskierung überstehen und welche Keep-Regeln brauchen.

Serialisierer, ORMs, Dependency-Injection-Container, Mapper und Regel-Engines machen unter der Haube denselben Trick: Sie schreiben zur Laufzeit IL, statt Ihre Methoden direkt aufzurufen, weil eine generierte, spezialisierte Methode weit schneller ist als Reflexion bei jedem Aufruf. .NET bietet dafür drei Hauptwege — DynamicMethod + ILGenerator, TypeBuilder über Reflection.Emit und Expression.Compile über LINQ-Ausdrucksbäume. Alle drei interagieren mit der Obfuskierung auf eine Weise, vor der der Compiler nicht warnen kann, denn der Bruch tritt zur Laufzeit auf, in Code, der zur Compile-Zeit nicht existierte. Hier ist das genaue Modell dessen, was sicher ist und was nicht.

Der entscheidende Unterschied: der Generator wird ausgeliefert, der erzeugte Code nicht

Zwei verschiedene Code-Teile sind im Spiel, und sie zu verwechseln ist der Beginn der Verwirrung.

Der erzeugte Code — der Rumpf der DynamicMethod, der Typ des TypeBuilder, der Delegat des kompilierten Ausdrucks — wird zur Laufzeit im Speicher erstellt und nie in Ihre Assembly geschrieben, also sieht der Obfuskator ihn nicht: er wird weder geschützt noch beschädigt. (Wäre sein Schutz wichtig, ginge es ohnehin nicht; er ist flüchtig.)

Der Generator — die Methode voller il.Emit(...)-Aufrufe oder der ausdrucksbildende Code — liegt hingegen in Ihrer Assembly. Er ist gewöhnliches IL: er wird umbenannt, sein Kontrollfluss abgeflacht und seine Zeichenketten verschlüsselt wie alles andere. Das ist in Ordnung, mit einer Einschränkung: der Generator referenziert in der Regel Ihre Typen und Mitglieder, und wie er sie referenziert, entscheidet darüber, ob die Obfuskierung ihn bricht.

The generator is obfuscated; the generated code is runtime-only In Ihrer Assembly (obfuskiert): Generator-MethodeILGenerator.Emit / Ausdrucksbaum emittiert zur Laufzeit Nur zur Laufzeit (nie auf Platte): DynamicMethod-Rumpfbeim Prozessende verworfen Das Risiko liegt nur in den Pfeilen, mit denen der Generator IHRE Mitglieder benennt: Zeichenketten-Suche → bricht beim Umbenennen · typeof/MethodInfo-Token → überlebt (wird mit umgeschrieben).

Wo es bricht: die zeichenkettenbasierte Mitgliedssuche

Der Generator, der bricht, ist jener, der die Mitglieder, zu denen er Aufrufe emittiert, über den Namen (Zeichenkette) auflöst. Betrachten wir eine DynamicMethod, die einen über den Namen gefundenen Getter aufruft:

MethodInfo getter = typeof(Order).GetMethod("get_Total")!; // string lookup
var dm = new DynamicMethod("ReadTotal", typeof(decimal), new[] { typeof(Order) });
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, getter);   // emits a call to whatever `getter` resolved to
il.Emit(OpCodes.Ret);

Nachdem die Obfuskierung Order.Total (und seinen Accessor get_Total) umbenannt hat, passt die Zeichenkette "get_Total" zu nichts mehr, also gibt GetMethod null zurück und die nächste Zeile wirft NullReferenceException / ArgumentNullException — zur Laufzeit, ohne jeden Hinweis zur Compile-Zeit. Dasselbe Versagen bei TypeBuilder-Code, der Überschreibungen über den Namen definiert, und bei Expression.Property(p, "Total").

Die Lösung ist eines von zwei Dingen. Den Namen behalten der konkreten Mitglieder, die der Generator über die Zeichenkette sucht (eine gezielte Keep-Regel, keine pauschale Ausnahme). Oder, besser, keine Zeichenketten mehr verwenden: erfassen Sie das Mitglied über etwas, das der Obfuskator umschreiben kann.

Was überlebt: Referenzen, die der Obfuskator sehen kann

Wenn der Generator ein Mitglied als Metadaten-Referenz statt als Zeichenkette erfasst, schreibt der Obfuskator diese Referenz im Zuge der Umbenennung um, und alles funktioniert weiter:

// Survives renaming — MethodInfo captured from a delegate, not a string.
MethodInfo getter = ((Func<Order, decimal>)(o => o.Total)).Method; // won't help: that's a lambda's Method
// Better: read the MemberInfo out of an expression tree
Expression<Func<Order, decimal>> e = o => o.Total;
var prop = (PropertyInfo)((MemberExpression)e.Body).Member;          // real PropertyInfo, token-based

Kompilierte Ausdrucksbäume sind der freundliche Fall: o => o.Total trägt eine PropertyInfo-Referenz, die der Obfuskator zusammen mit der Eigenschaft umbenennt, sodass Expression.Compile() danach an das richtige Mitglied bindet. Die Faustregel: Ausdrucksbäume und typeof(T).GetMethod(aus-MethodInfo-abgeleitete-Referenz) sind umschreibsicher; jeder Mitgliedsname als "Zeichenkette" in einem Generator ist es nicht.

Eine praktische Checkliste

  • Prüfen Sie Ihre Laufzeit-Codegenerierungsstellen auf Zeichenketten-Literale, die Ihre eigenen Mitglieder benennen; das sind die Bruchstellen.
  • Bevorzugen Sie Ausdrucksbäume oder die Erfassung über Delegat/MethodInfo gegenüber GetMethod("Name"), wo es geht.
  • Fügen Sie für unvermeidliche Zeichenketten-Suchen gezielte Keep-Regeln für genau diese Mitglieder hinzu — und denken Sie daran, dass ihre Rümpfe weiterhin obfuskiert werden; nur der Name überlebt.
  • Testen Sie die Codegenerierungspfade des obfuskierten Builds ausdrücklich, denn weder der Compiler noch ein Smoke-Test, der den generierten Pfad überspringt, erkennt eine kaputte Emission.

Mit Nebula umsetzen

Nebula benennt die Zeichenketten Ihrer Generatoren um, flacht sie ab und verschlüsselt sie wie bei jedem anderen Code, und sein Keep-Regel-Modell ist präzise genug, um genau die Mitglieder zu bewahren, die Ihr Reflection.Emit- oder ausdrucksbildender Code über den Namen auflöst, während ihre Rümpfe obfuskiert bleiben. Kombiniert mit den Hinweisen zu Reflexion und Serialisierung können Sie obfuskierten Code ausliefern, der Code erzeugt — ohne eine einzige Laufzeitüberraschung.

Beginnen Sie mit der kostenlosen Edition oder sehen Sie sich die Tarife an.

Nebula.NET testen

Härten Sie Ihren .NET-Code in wenigen Minuten — starten Sie mit der kostenlosen Edition.