Schutz vor dekompilieren von C#-Anwendungen bzw. von .NET

Hallo Leute,

da ich zur Zeit ein Programm am programmieren bin, welche eine Testverion ist und ich mein Programm bestmöglich schützen will, möchte ich euch einfach fragen. Soweit wie ich verstanden habe, kann man mithilfe des .NET Reflector an den Quellcode des Programms drankommen. Das möchte ich aber verhinden!

Nun, es gibt die Obfuscatoren, welche aber auch nicht den bestmöglichen Schutz bieten. Zudem würde hier erwähnt: [schwer] KeygenMe! , dass das Abfragen wie beisielsweise mit einen
Code:
if (eingegebenerLizenzschlüssel.Text == getLizenzschlüssel() )
// nur Beispiel!
keine sichere Variante ist. Wie soll ich sonst prüfen ob der Serial gültig oder ungültig ist?

Zudem ist es auch möglich im Arbeitsspeicher auf die Adressen der Variablen zuzugreifen und die einzelnen Werte der Variablen auszulesen. Gibt es da auch Schutzmöglichkeiten?

Was muss ich nun alles beachten, damit es für die Cracker so schwierig wie möglich ist? Wie kann ich meinem Programm bestmögliche Sicherheit bieten? Was muss ich alles beachten? Man kann nie sagen, dass etwas wirklich sicher ist. Alles was verschlüsselt ist, kann man auch wieder entschlüsseln ;) . Aber was gibt es für Möglichkeiten?

Daniel ?(
 
CDW hat dazu doch bereits einige Tips gegeben, wie du die Abfrage sicherer machen kannst bzw. wie es allgemein besser geht.

Ansonsten kann ich dir aus eigener Erfahrung noch sagen, dass diese Obfusicator manchmal mehr Probleme bereiten, als sie scheinbar lösen. Habe schon erlebt, dass ein Programm danach nicht mehr lief.
An dieser Stelle halte ich es also eher so, dass ich persönlich nichts zum Schutz des Quelltextes mache, oder ich verwende gleich eine andere Sprache. Nur wie du ja an den c-keygenmes auch siehst ist, dass es ja auch ohne Quelltext zu knacken ist ;) Verschwende also nicht zu sehr deine Energie auf die Schutzmechanismen, denn wenn einer es knacken will (und Ahnung hat), dann schafft er es ziemlich sicher
 
Hallo lightsaver,

wie ich schon sagte: "Alles was verschlüsselt ist kann man auch wieder entschlüsseln". Wenn sich einer dransetzt und das Programm versuchen will zu cracken, jedoch es aber schwieriger ist wäre es möglich das der jeniger aufgibt. Aber Schutz schadet doch nicht, wird jedoch nicht viel bringen, aber immerhin etwas.

Was gibt es noch für Möglichkeiten sich davor zu schüutzen, abgesehen von einem Obfuscator?

Daniel
 
Original von the_uxreal
wie ich schon sagte: "Alles was verschlüsselt ist kann man auch wieder entschlüsseln".

es gibt bei weitem nicht für jede Verschlüsselung auch eine Entschlüsselung...

Viele Verschlüsselungs- oder Hash-Verfahren sind nur über den Umweg der Bruteforce-Attacke zu knacken... und das kann ziemlich lange dauern, wenn es den verschlüsselten Code nicht in irgend einem RainbowTable gibt...

Wenn ich mich nicht täusche, hat CDW dich (oder jmd. anderen mit ähnlicher Frage?) schonmal auf das Thema der asynchronen Verschlüsselungsverfahren hingewiesen...

http://de.wikipedia.org/wiki/Asymmetrisches_Kryptosystem
http://de.wikipedia.org/wiki/Hash-Funktion
http://de.wikipedia.org/wiki/Salted_Hash
 
Kennst Du http://www.s-a-ve.com/faq/Anti-Cracking-Tips-2.htm ?
Das sicherste wäre es, die Testversionen um Funktionen zu beschneiden (praktisch zu einer Demo umfunktionieren) und die Vollversionen nur nach Bezahlung zu verteilen. Der Code, der nicht in der Anwendung vorhanden ist, kann auch schlecht gecrackt werden ;) (allerdings erinnere ich mich daran, dass für irgendein Programm die fehlende(Speicher?)Funktion nachgeschrieben wurde :D)
Verschlüsselungsverfahren werden i.R dazu genutzt, um Teile des Codes zu entschlüsseln - der Kunde braucht also nur die Lizenz einzugeben und muss nichts weiter herunterladen. Da gibt schon tolle Möglichkeiten, wo durch unterschiedliche Lizenzen unterschiedlich viel Code freigeschaltet wird.
Vom Risiko her halte ich aber beide Verfahren für etwa gleichwertig - denn in beiden Fällen würde eine Weitergabe des Keys/Programms den Mechanismus aushebeln.
Von solchen Programmen wird eben gesagt "kann man nur mit Key cracken".
Ein bekannter Shareware HexEditor wäre z.B ein Beispiel.

Was noch interessanter ist und immer mehr aufkommt - CodeVirtualisierung und Code Morphing. Naja, eigentlich eher Buzzwörter weil beides darauf hinausläuft, den vorhandenen Code zu nehmen und entweder in virtuellen umzuwandeln (Befehlssatz einer VirtualMachine) oder aufzublähen (Anweisungen werden durch Serien von gleichwertigen Anweisungen ersetzt - mehr Code - mehr Aufwand). Da wäre man aber wieder fast schon bei Obfuscatoren angekommen.

Zu VMs: z.B Oreans (Themida, CodeVirtualizer, WinLizence) setzt darauf, die haben anscheinend einen dynamischen Generator der jedesmall für eine Intel-Opcode Instruction eine anders codierte VM-Opcode(Folge) generiert, so dass der Cracker erstmal (im Idealfall natürlich ;) ) die VM im alleingang decodieren müsste um hinter die Programmlogik zu kommen.

Nachteile der beiden Methoden - VM Code wird interpretiert, der gemorphte Code einfach nur aufgebhläht - beides kann man für zeitkritische Codeteile nicht gebrauchen.
Und zweitens: es gibt meistens ganz andere Schwachpunkte.
Die Name-Key-Check Routinen sind meistens "perfekt" geschützt. Zwar gibt es ja den Spruch "alles kann geknackt werden" - aber es setzt sich kaum jemand daran, 10 000 Zeilen Code zu analysieren und den Algorithmus daraus zu extrahieren. Die Checkroutine ist nämlich nur die halbe Miete - die meisten Patzer passieren bei den Prüfungen im Programmablauf selbst (ob das Programm registriert ist).
 
Also ich finde den Ansatz, die Serial zu interpretieren sehr interessant. Ein Teilstück der Serial könnte z.B. so aussehen:
Code:
5b4c4b3a5-.....

Nun könnte man zb. sagen, dass alle Buchstaben für Operatoren stehen;
Code:
a = +
b = -
c = *
d = /

Dann erhällt man:
Code:
5-4*4-3+5
... und berechnet das Ergebnis. Dieses Ergebnis verwendet man nun an ein paar Stellen im Code, um damit zu arbeiten.

Natürlich muss man sich noch viel mehr ins Zeug legen, um mit diesem simplen Beispiel etwas brauchbares zu entwerfen, aber so als Grund Idee ist es ganz lustig.

Ich habe mal ein Crackme in dem Stil begonnen zu schreiben, hänge aber momentan noch mit einem Keygen, der valide Serials generiert.

lg
 
Hallo 90nop,

bietet das wirklich den bestmöglichen Schutz vor dem Dekompilieren? Ich hab so eine ähnliche Methode schon ausprobiert. Kleines Beispiel: Wenn beispielsweise der erste Serial länger(bei geraden Zahlen!) als 8 oder gleich dann nehme die Mitte und teile es z.B. durch die Quersumme mal das erste Zeichen und und die kleinste Zahl mal sich selber multiplizieren.

Würden diese kryptischen Verfahren oder auch wie deine was bringen? Jedoch habe ich festgestellt das es länger dauert den Lizenzschlüssel zu berechnen, sogar mehr als 5 Sekunden! Sehen wir einfach mal über die Performance hinüber ;) .

Bringt das wirklich Schutz?

Daniel
 
genauso hatten wir es mal hier:
https://www.buha.info/board/showthread.php?t=52715&highlight=crackme
ist allerdings kein Quellcode oder ähnlches dabei - der Grundgedanke war, die Serial als Anweisungsfolgen zu interpretieren (also als Opcodes) und war selbst für diese Größe schon recht anstrengend zu durchschauen.

Zu der Sicherheit: es kommt auch auf das Gesamtkonzept an ;) . D.h
Code:
...
superkomplexer_check_algo
superkomplexer_check_algo
superkomplexer_check_algo
superkomplexer_check_algo

irgendwo: registered_flag=true;

superkomplexer_check_algo
superkomplexer_check_algo
bringt nichts, wenn sonst später
Code:
 if (registered_flag==true) then erweiterte_funktion_verfügbar
  else "dies ist eine demo"
zum Einsatz kommt.
 
Hallo CDW,

nun bin ich etwas verwirrt. Ich hab alles druchgelesen, jedoch nicht verstanden wie ich mich davor schützen kann ?( .

Soweit wie ich verstanden habe, sollte ich den Code möglichst kompliziert aufbauchen, aber was bringt mir das nun, wenn man das Programm mithilfe eines Obfuscators das Programm wiederherstellen kann? Das man hundertprozentigen Schutz nicht bieten kann ist mir klar, jedoch möchte ich es sicher wie möglich haben, sprich Schutz vor Reverse Engineering hat man nie. Was ist mit den Arbeitsspeicher, dort ist es auch möglich z.B. Strings herauszusuchen.

Wie ist es nun mit einer if-Abfrage? Es muss doch eine Abfrage stattfinden, welche dem Anwender sagt ob der eingegebene Lizenzschlüssel richtig oder falsch ist.

Daniel

P.S.: Man könnte den Thread als Sticky setzen, damit andere es nachlesen können. Es gibt bestimmt einige Andere ;) .
 
aber was bringt mir das nun, wenn man das Programm mithilfe eines Obfuscators das Programm wiederherstellen kann?
Also ein guter Obfuscator lässt sich nicht mehr so ohne weiteres entfernen ;)

Wie ist es nun mit einer if-Abfrage? Es muss doch eine Abfrage stattfinden, welche dem Anwender sagt ob der eingegebene Lizenzschlüssel richtig oder falsch ist.
Bsp:
Code:
byte[] decrypt_key=extract_decrypt_key(licence, Username);
decrypt(erweiterte_funktion1,decrypt_key);
int crc=bilde_crc/sha1(Adresse_erweiterte_funktionen);
if crc==12345
{
   set_flag(licence_flags,homeedition);
}
decrypt(premium_funktion,decrypt_key);
int crc=bilde_crc(adresse_premium_funktion)
if crc==54321
{
  set_flag(licence_flags,premiun_basic_edition)
}
...
if (licence_flags.isSet())
   Msg "der key war richtig!"
else "falsch!!!";
}

Man kann hier als Cracker nichts mit den Werten anfangen, da sie nur CRCs darstellen (sofern man natürlich ein normales Verschlüsselungsverfahren verwendet und nicht XORt;) ).
Wird hier die IF-Abfrage gepatcht, wird zwar der falsche Key erstmal aktzeptiert, das Programm stürtzt aber beim ausführen der vollversion Funktionen ab.
Die große Schwäche ist hier wie immer, dass der Cracker irgendwann mal den Key bekommt. Hier könnte nur eine Keybindung an die Hardware helfen.
 
Hallo CDW,

meinst du also, dass man aus den Lizenzschlüssel einen CRC32-Wert generiert und diesen mit einer If-Abfrage prüft? So ganz hab ich das nicht verstanden.

Daniel
 
Die Premium-Funktionen werden auf Basis des Lizensschlüssels entschlüsselt. Für die entschlüsselten Funktionen wird eine Prüfsumme berechnet. Wenn diese korrekt ist, wird ausgegeben, dass der Lizensschlüssel richtig ist.

Wenn der Cracker nun die Überprüfung der Prüfsumme rauspatcht bringt ihm das nichts, da die Premium-Funktionen immer noch verschlüsselt sind.

Ist das verständlicher? ;)
 
the_uxreal: ich glaube, wir reden an einander vorbei ;)
Es geht darum, die Lizenz oder den Schlüssel in wesentliche Programmfunktionen einzubinden. Dazu braucht man ein Konzept für das gesamte Programm und nicht nur die Überprüfung (ich weiß allerdings nicht, in wieweit sich diese Punkte in NET umsetzen lassen - die "nativ Sprachler" sollten aber keine Probleme haben)

1.die Lizenzabfrage muss also über die gesamte Anwendung verteilt sein (und zwar nicht als Aufruf zu einer Funktion, sondern am besten immer leicht unterschiedliche).

2.die Erfolgs/Fehlermeldungen sollten nicht unmittelbar nach der Prüfung auftauchen, sondern verzögert (durch Timer, Threads). Am besten ist auch die Prüfung von der Eingabe zeitlich getrennt - nicht umsonst melden viele Programme nach der Keyeingabe, dass sie nun neugestartet werden möchten.

3.Die Meldung an den Benutzer sollte zwar gut sichtbar sein, aber möglichst nicht für den Cracker - sprich: statt den String "registrierte Version" auszugeben, bietet es sich an, diesen selbst zu zeichnen, als Bild beizulegen (lohnt sich aber nur, wenn das Programm auch sonst viele Bilddateien nutzt) oder zumindest nicht so offensichtlich zu machen
Bsp:
Code:
zeichne_buchstaben('n',pos(1,1));
dummy_aufruf;
zeichne_buchstaben('i',pos(1,2));
dummy_anweisung;
zeichne_buchstaben('c',pos(1,3));
dummy_aufruf;
zeichne_buchstaben('h',pos(1,4));
dummy_anweisung;
zeichne_buchstaben('t',pos(1,5));
dummy_aufruf;
zeichne_buchstaben('r',pos(1,2));
dummy_anweisung;
zeichne_buchstaben('e',pos(1,2));
dummy_aufruf;
zeichne_buchstaben('g',pos(1,2));
....
Ausgabe: "nicht registrierte Version"
Bei C könnte man das sogar mit Macros erledigen, die das automatisch generieren - oder man schreibt sich vorher ein kleines Programm, welches bei eingabe eines Strings diesen zerlegt.
Der Benutzer bekommt die Meldung ganz normal zu sehen - im Speicher wird man allerdings keine Strings finden - und der Cracker hat damit erstmal viel weniger Hinweise auf wichtige Codestellen.

Liest man den Key nicht aus einer Datei ein, empfielt es sich eine eigene Einleseroutine zu verwenden - man ließt dann Buchstabe für Buchstabe ein und fügt es in das Eingabefeld ein. Irgendein vor der Prüfung gestarter Thread/Timerfunktion holt sich die Eingabe und prüft sie in regelmäßigen Abständen. Vorteil: der böse Cracker wird zwar im Speicher den Eingabestring finden - allerdings wird dann mehrere male pro Sekunde von der internen Windowszeichenfunktion (ja, Eingabefelder werden auch nur gezeichnet ;) )darauf zugegriffen. Das macht das gezielte Platzieren von Hardware Breakpoints (on access) sehr schwer - und ohne diese kommt man nicht so leicht an den Prüfthread. Allerdings weiß ich nicht, in wieweit dieser Tipp sich für NET eignet.

4. genauso verhält es sich mit dem abschalten wichtiger Funktionen nach dem die Trialzeit abgelaufen ist (hier gibt es aber eine andere Problematik, auf die ich gleich zu sprechen komme). Schön verzögern und wiederum unterschiedliche Funktionen an mehreren Programmstellen aufrufen. Sofern man Demos anbietet, gilt der obere Tipp: entweder die Funktion gar nicht ins Programm einbringen oder verschlüsselt - und nur die Richtigkeit der Entschlüsselung testen.

Das Abschalten sollte sich so gestalten, dass man einmal global die Funktion deaktiviert (dieser Schritt muss nicht ungebdingt maskiert werden - auch unmaskiert sind solche Aktionen nur schwer zu finden). Innerhalb der geschützten Funktion sollte nochmal eine Prüfung stattfinden und dabei folgende Logik berücksichtigt werden:
wenn die prüfung positiv war, heißt es, dass die Anwendung gepatcht/manipuliert wurde. Nun könnte man per Zufallsgenerator entscheiden, ob die Anwendung beendet wird oder nicht. Das Beenden natürlich wieder verzögert machen (Timer/Thread).
Solche Aktionen sind in soweit nützlich, als dass sie nicht wirklich reproduziert wedern können.

5.Checks einbauen. Diese können extremst nervig sein, besonders wenn es mehrere gibt :). Mir fällt der Name nicht mehr ein, aber irgendeine Anwendung hatte 4 Stück davon + einen eigenen Check für die restlichen Checkroutinen (Paranoia wäre durchaus zutreffende Bezeichnung :D )
Man startet heimlich einen Thread/Timer, der dann ab und zu gewisste Codestellen auf Manipulationen durchsucht. Natürlich großflächiger angelegt (also nicht nur die wirklcih wichten 4 Bytes checken ;) ). Davon hat man erstmal mehrere und kann auch ruhig einen oder zwei "Checker" von einem anderen überwachen lassen. Gibt es da Ungereihmheiten, entscheidet man wieder per Zufallsgenerator, ob weitergemacht oder beendet wird (ich würde die Rate je nach Häufigkeit der Checks auf 1% oder noch weniger setzen - damit auch z.B schön Zeit vergehen kann, bevor das Programm sich beendet).

6.Programm beenden: immer mehrere Beendefunktionen haben und möglichst nicht über ExitProgramm oder ähnliches. Bei Windows simuliert man das Beenden über Usereingaben, sofern man Windowsnachrichten nutzen kann: SendMessage, this,bla,WM_CLOSE. Das ist deutlich schwerer zurückzuverfolgen. Vor allem wenn nur Zufallsbasierend beendet wird und dann auch mehrere Funktionen dafür gibt.

7.Speichern der Lizenzinformation: niemals im Klartext, sonderund als abgewandelter Wert. Kein geXORe des Strings (fällt direkt auf), sondern als Zahl - eine Möglichkeit wäre es, diese Information zwischen den anderen Einstllungen, die man speichert, zu verstecken oder zu verteilen.
Auf den Usernamen verzichtet man am besten komplett.

Zu dem Trialzeitproblem: hier besteht die große Schwierigkeit, die tatsächlich vergangene Zeit festzustellen. Denn dazu muss man vorgegebene Methoden nutzen. Es gibt zwar paar Tricks (arbeiten mit Dateien - eine wird bei der Installation erstellt, eine andere wird bei jedem Programmstart neu erstellet - nun ließt man die Timestamps der beiden Dateien ein und vergleicht diese), aber größtenteils versucht man ja zu bewirken, dass eine Nutzung über X Tage hinweg nicht möglich ist. D.h die Prüfungen kann man zwar so ähnlich wie bei der Serial verteilen, allerdings sind diese deutlich schwerer zu verstecken.

Insgesamt sieht man, dass es verdammt viel Aufwand sein kann, den man abwägen muss. Insbesondere bei größeren Projekten kommt auch viel Testaufwand dazu - denn nichts ist schlimmer, als wenn bei dem Endkunden, der die Lizenz erworben hat, ein False-Positiv auftritt oder auch in der Trial/Demo das Programm durch den False-Positiv scheinbar "abstürzt".
 
Hallo CDW,

wenn ich nun deine Ratschläge befolge, jedoch man einen .NET Reflector verwendet, würde das dann keinen Sinn ergeben? Sagen wir ich setze setze deine Ratschläge um, dann ist es doch möglich mithilfe des .NET Reflectors den Quellcode des Programms so zu rekonstruieren, dass man nachvollziehen kann wie das Crackme oder die Testversion arbeitet!

Ich habe auch das Gefühl, dass wir, CDW, aneinander vorbeireden. Aber trozdem vielen Dank für die Informationen :) .

Daniel
 
Naja, NET ist ja einge Geschichte für sich. Es stimmt schon , dass die meisten Schutzschritte darauf setzen, dass Hochsprachen pro Zeile Code bis zu paar hundert Zeilen Assembly produzieren oder aufrufen - das entfällt bei NET durch Reflector.

Abgesehen von den kommerziellen Protectoren bleibt wohl wirklich nur der Obfuscator-weg. Es sollten auch einfache, konstenlose verfübgar sein (z.B Scater)
Angenommen, man wendet irgendeinen einfachen Obfuscator an - der nichts weiter macht, als alle Funktionen/Variablen/Klassen und was sonst noch sichbtar ist umzubenennen (func0001, func0002 usw). Dann zeigt Reflector auch nur diese Sachen an.
Bei einem größeren Projekt >5000 Codezeilen sollte man diese Wirkung nicht unterschätzen. Weiterhin gibt es Obfuscatoren, die gewisse Schutzmassnahmen ergreifen - z.B Salamander. Es wird also erstmal erschwert, überhaupt mit dem Reflector an den Code zu kommen. Dann können wohl manche auch noch die Klassen/Methoden aufsplitten. D.h der Cracker wird im Reflector bei einem größeren Projekt mit tausenden Klassen/Methoden a la "Class_001,func_0001(), func_0002 ...Class_xxx, func_XXX" konfrontiert. Er braucht also erstmal überhaupt einen Ansatzpunkt. Versucht er es mit Debugging, greifen ein paar Tipps von oben wieder. Ist also schon eine gewisse Hürde.
Klar, bei einem kleinen Crackme stört ein Obfuscator nicht wirklich, da es trotzdem nur sehr wenige Funktionen gibt.
 
Angenommen, man wendet irgendeinen einfachen Obfuscator an - der nichts weiter macht, als alle Funktionen/Variablen/Klassen und was sonst noch sichbtar ist umzubenennen (func0001, func0002 usw). Dann zeigt Reflector auch nur diese Sachen an.
Das ist nicht nötig, imho, da diese Informationen beid der Umwandlung in den bytecode sowieso verlohrem gehen.

Obfuscatoren können natürlich noch mehr, als nur die Namen von Variabeln und Funktionen ändern. Das geht dann vom einfügen von Trash-Code bis hin zum umbauen von ganzen Konstrukten. Variabelzuweisungen usw. werden durch komplexe Elemente ersetzt, damit man am Ende nichts mehr mit dem wiederhergestellten Source anfangen kann.

Aber auch hier ist es oft ein Katz und Mausspiel - deobfuscatoren gibts ja einige. Da ist man am besten beraten, einen einfachen obfsc. selber zu schreiben. Der ist dann verglichen mit anderen evtl. einfacher zu knacken, aber es gibt keine Vorgefertigten Deobfuscatoren, die dem Cracker die Arbeit abnehmen.

Achja; Wenn du auf die NET Konstrukte verzichtest, kannst du dein Progrtram auch als win32 assembly erstellen lassen. Mit C++ funktioniert das jedenfalls, C# ist dann vielleicht etwas ungünstiger, ist ja immerhin "die" NET Sprache. Dann wären Reverser gleichweit wie mit normalen C++ Programmen.
 
Original von 90nop
Angenommen, man wendet irgendeinen einfachen Obfuscator an - der nichts weiter macht, als alle Funktionen/Variablen/Klassen und was sonst noch sichbtar ist umzubenennen (func0001, func0002 usw). Dann zeigt Reflector auch nur diese Sachen an.
Das ist nicht nötig, imho, da diese Informationen beid der Umwandlung in den bytecode sowieso verlohrem gehen.

Nicht bei .net. Untersuche mit dem Reflector einfach mal eine beliebige(nicht obfuscated natürlich) .net-Anwendung.
 
Zurück
Oben