In diesem Post werde ich auf die grundlegenden Funktionsweisen von Obfuscatoren (Plural?) und auf die entsprechenden Gegenmaßnahmen gegen die Codeverschleierer eingehen. Dabei werden zum Großteil Tools benutzt, welche allerdings vorher erklärt werden
Uns dürfte inzwischen bekannt sein dass man durch den MSIL-Code in einer .NET Assembly den originalen Code weitgehend wiederherstellen kann. Wenn man nun sein geistiges Eigentum vor bösen Kopierern schützen möchte, verwendet man einen sog. Obfuscator. Das Prinzip ist dabei immer gleich: Mache es dem “Cracker” so schwer wie möglich und verstecke was nur geht.
In der folgenden Auflistung werden Features von Obfuscatoren genannt. Jeder Obfuscator funktioniert anders, aber die Grundprinzipien sind immer gleich:
String Verschlüsselung:
Nehmen wir an, ein File “secret.sec” wird zur Laufzeit entschlüsselt, um die darin enthaltenen Informationen auszulesen. Damit der böse Cracker nicht einfach den Key zum entschlüsseln auslesen kann, wird dieser zur Laufzeit aus den Ressourcen geladen und kurz vor der Verwendung entschlüsselt.
Bringen tut das ganze nichts, der String ist im Klartext im Speicher und kann, wie beim knacken des PSC-Managers, einfach durch einen IL-Patch ausgelesen werden. Wem das zu viel Arbeit ist kann DeObfsucatoren wie De4Dot oder auch den SimpleAssemblyExplorer verwenden, diese Entschlüsselungsfunktionen zu statischen Strings zu verwandeln.
Code Flow Obfuscation:
Niemand freut es dieses Wort zu hören, vor allem wenn man sich intensiver mit der Software beschäftigen will. Durch die Code Flow Obfuscation (Codefluss Verschleierung) wird im Code willkürlich hin und her gesprungen. Dadurch ist es fast unmöglich den Code einen Sinn abzugewinnen und die Abläufe zu verstehen.
Gegenmaßnahmen gibt es nicht wirklich, der schon erwähnte De4Dot Deobfuscator scheint wohl das ganze ein wenig zu entwirren. Respekt!
Disassembler Crasher:
Auch hier gilt: Mehr Schein als Sein. Sobald etwas nicht 100% Regelkonform ist, verweigert der Redgate .NET Reflector seinen Dienst. Vorallem bei der IL->C# Umwandlung ist der Reflector leicht zu “crashen”, indem z.B. am Anfang folgende MSIL-Befehle verwendet werden:
L_0000: br L_0007 // Funktionsbeginn, es wird auf 0007 gesprungen
L_0005: pop // dieses Crashed den Reflector
L_0006: ldc.i4.0 // ein Int32 mit 0 wird gepushed (dieser Code wird auch nie erreicht)
L_0007: ldarg.0 // Ab hier ist die eigenliche Funktion
L_0008: ldarg.1
L_0009: stfld object wLi4HZ52yOdC082JDDw.JPBLCe51Perll5dUTub::H5vj8Bi4l
L_000e: ret |
Der Reflector versucht diese POP-Anweisung in C# umzuwandeln, dabei ist der Stack doch leer. Dieser Code wird nie erreicht, sonst würde er natürlich einen Fehler werfen. Doch so läuft das Programm einwandfrei und der Reflector meckert. Weitere solcher Reflector-Crasher sind hier aufgelistet!
Eine tolle Programmieraufgabe um sich mit der Mono.Cecil-Libary anzufreunden: Ein Tool sollte alle Methoden durchgehen. Wenn Methode[i] an ILCode[5] ein POP-Opcode hat, sollten die ersten 6 Bytes des MSIL-Codes gelöscht werden. So kann sie ohne Probleme im Reflector angezeigt werden
Alternativen wie der IL-Spy haben übrigens keine Probleme mit solchen Spielereien, aber leider braucht man manchmal den Reflector um mit Reflexil arbeiten zu können.
Namen unleserlich machen:
Schon mal probiert das ASCII-Zeichen 0x9B normal darzustellen? Im besten Fall bekommt man ein Quadrat als Zeichenersetzung, manchmal aber auch einfach nichts. Obfuscatoren ersetzen alle Methoden, Typen, Felder und sonstige Namen des #String-Streams durch nicht-darstellbare Zeichen. Dies erschwert die Unterscheidung von Klassen/Namespaces und verwirrt natürlich.
Als Gegenmittel gibts den .NET Deobfuscator. Dieses Tool ersetzt (mal wieder) durch die Mono.Cecil-Libary die Namen dieser Typen und versucht eine Sinnvolle Zuordnung. So heißt eine Form dann z.B. Type001_Form, was die Analyse ungemein erleichtert.
MSIL-Encryption und JITHooks:
Das wohl härteste aber auch schwächste Gegenmittel gegen böse Cracker. Die Methoden-Bodies sind hierbei leer und werden erst kurz vor dem umwandeln in nativen Code (über den Just-In-Time Compiler) entschlüsselt. So bekommt man von dem MSIL-Code nichts mit.
Die Theorie zum brechen dieses Schutzes ist in .NET Internals and Code Injection zur genüge beschrieben. Wer sich ernsthaft mit .NET Reversing beschäftigen möchte, sollte dies wie die Bibel vor dem einschlafen lesen
Der Clou ist den JIT-Compiler selbst zu hooken bevor es der Obfuscator macht. Denn in der CompileMethod-Funktion wird der MSIL-Code übergeben, welchen man hier abgreifen kann. Mithilfe der Mono.Cecil-Libary wird dann eine neue Assembly erstellt, natürlich mit gefüllten Method-Bodies. Die wohl effektivste Implementierung dieses JITDumps ist der Jitdumper3. Das Teil ist komplett in .NET geschrieben, also kann sehr schön untersucht werden. Wenn ich die Zeit finde wird auch mal eine Analyse drüber gepostet, mal schaun…
Sonstige Tools:
Zum Abschluss noch ein paar Tools, welche die Arbeit gegen Obfuscatoren ungemein erleichtern. Zum einen wäre das der .NET Dumper, um Assemblys aus dem Speicher zu dumpen wenn sie schon unpacked sind. Weiter ist der Universal Fixer ganz nützlich, welcher die Assembly wieder in Ordnung bringt und ein paar Sachen, die sonst per Hand gemacht werden, für euch erledigt.
Wie bei allen Tools gilt: Was bringt euch ein SQL-Injection Tool, welches alles für euch macht? Lerneffekt = 0! Lernt wie die Tools arbeiten!!11elf! wollt ihr die Tools beherrschen oder beherrschen die Tools euch?
Greez Easy
5 people like this post.