Für bestimmte Zwischenberechnungen könntest Du auch eine eigene VM erstellen. Hört sich komplizierter an, als es ist:
Ich würde da einfach eine Stackmachine nehmen:
http://www.ece.cmu.edu/~koopman/stac...rs/sec3_2.html
d.h der arbeitet nicht mit Registern, sondern holt sich alle Operanden vom Stack und schreibt da auch das Ergebnis hin. Da braucht man also nur IP=InstructionPointer und Equal/Signed Flags sowie ein Code-Array, Stack-Array/Stackimplementierung und ein Datenarray.
Dann definierst Du einen grundsätzlichen Befehlssatz:
Code:
Opcode Operand
0001: ADD (holt sich 2 Werte vom Stack und addiert diese)
0002: Sub (genauso, nur mit Subtraktion)
.... weitere Rechenarten
0010 0000: Load Var (als Var kann man eine Ziffer angeben und intern einfach mit dataarray[var] arbeiten)
0011 0000: store Var
0012: cmp (holt sich die 2 variablen vom Stack und vergleicht diese, stetzt dann bei Übereinstimmung den Equal Flag, bei Var1<Var2 den Signed Flag)
0013 0000: JE Addr jmp if equal zu Adresse (sprich, wenn Equal Flag gesetzt ist)
0014 0000: JS Addr jmp if smaller zu Adresse (wenn Signed Flag gesetzt ist)
0015 0000: jg Addr jmp if greater zu Adresse (wenn weder Equal noch Signed gesetzt sind)
0016 0000: call Addr
Ein Interpreter dafür ist relativ schnell geschrieben. Wenn man diesen auch noch obfuscated, wird die "Zurückentwicklung" kein Zuckerschlecken

Mit Macros bzw ein bisschen mehr Aufwand kann man auch die Opcodes für jede Anwendung zufällig generieren. Und natürlich muss man sich nicht an die strenge Nummerierung oder "1 Byte für Operator, 1 Byte für Operand" halten
Der Code kann z.B irgendwelche Werte mit der Serial/HardwareID berechnen, die im "richtigen" Code weiterbenutzt werden können. Bei falschen Werten klappt dann z.B das Speichern oder Laden nicht mehr.
Du kannst sogar mit etwas Überlegung Opcodes in den Key einbauen und z.B bei der Initialisierung einen "Verbund" aus Key und HardwareID mit ausführen. Denn Deine VM kann auch ruhig auf "richtige" Speicherwerte in der Anwendung zugreifen und diese verändern.
Schlägt die Initialisierung fehl, kann man mittels
try X
catch print "Registrierungscode stimmt net!"
die Fehlermeldung ausgeben.
Dadurch wird der Key wirklich gut im Programm eingebunden und man wird ohne Key nicht wirklich weiter kommen können. Aber auch mit Key und richtiger HardwareID dürfte es ein ziemlicher Aufwand sein
Der Nachteil ist natürlich, dass Du den Code dafür in der definierten Assembly schreiben musst und damit nicht wirklich komplexe Berechnungen anstellen kannst.