ConfuserEx – Follow the x86 Predicate

In diesem Artikel geht es darum Wege zu erkunden. Zwar keine Wege im eigentlichen oder fantasievollen Sinne, sondern der Weg von Instruktionen und Code. Denn genau das macht die sogenannte „Control-Flow-Obfuscation“ von ConfuserEx . Dabei wird der sonst so linear ablaufende Code durch Codefragmente und scheinbar unvorhersehbare Sprünge ersetzt. Jedes Codefragment liefert nach der Ausführung eine Zahl, die genutzt wird um den Sprung zum nächsten Codefragment zu berechnen.

Die Idee dahinter ist nicht neu und wird bereits in vielen Obfuscatoren eingesetzt. Auch kann Code-Flow-Obfuscation bereits hinreichend gut von dem Universaldeobfuscator de4dot entfernt werden. Doch was verleitet mich dennoch dazu diesem Schutzmechanismus einen eigenen Artikel zu widmen ? Es geht um die spezielle Art wie diese Code-Flow-Obfuscation aufgebaut ist und wie man sie im rückgängig machen kann.

An dieser Stelle muss ich direkt anmerken dass es in diesem Artikel nicht darum geht die Code-Flow-Obfuscation zu entfernen mit dem Ziel wieder einen linearen Code zu erhalten. Das können andere Tools inzwischen sehr gut. Es geht vielmehr darum Obfuscation in eine abgewandelte, ja sogar hübschere Form zu überführen die von weiteren Tools bearbeitet werden kann. Wessen Interesse trotzdem geweckt ist darf gerne weiter lesen und sich in die Welt der x86 Predicates begeben.

.NET Code landet früher oder später als nativer x86 Code im Speicher und wird dort ausgeführt. Allerdings gibt es auch wilde .NET Konstrukte wie C++/CLI welches die C++-Sprache mit .NET Interfaces nutzt und dabei sowohl den .NET MSIL-Code als auch nativen Code beinhaltet. Solche sogenannte „Mixed-Mode-Assemblies“ können nicht von reinem .NET geladen werden.

Fast alle öffentlich verfügbaren Tools die durch ConfuserEx bearbeitete Programm lesbar machen führen eine dynamische Entschlüsselung durch. Es ist einfacher die Funktion „DecryptRessources()aufzurufen und die entschlüsselten Ressourcen aus dem Speicher zu lesen als die gesamte Funktion „DecryptRessources()“ nachzubauen . Zudem machen kleine Änderungen in der Entschlüsselungsfunktion statische Tools unbrauchbar. Im konkreten Fall hatte ich eine per ConfuserEx unleserlich gemacht Assembly welche Mixed-Mode war. Und genau diese dynamischen Tools funktionieren bei Mixed-Mode Assemblies nicht, denn diese enthalten ja nicht nur .NET Code. Daher konnte keines der tollen Tools zum Entschlüsseln der Strings oder zum Entfernen der nervigen Proxy-Calls verwendet werden.

Zurück zu den Predicates, denn diese Reihen sich bisher nicht in das Problem mit den Mixed-Mode Assemblies und der Control-Flow-Obfuscation ein. Predicates sind Funktionen, die aus einer Eingangszahl Y eine Ausgangszahl X berechnen. X gibt an welches Codefragment als nächstes angesprungen wird, Y wird am Ende des vorherigen Codefragments gesetzt. Ein Predicate stellt also eine Verbindung zwischen letztem Codefragment und neuem Codefragment her, indem es Rechenoperationen ausführt. Wenn es keine Predicates gäbe, so wäre es relativ leicht möglich den Fluss des Codes zu bestimmen, Predicates verschleiern aber den Weg den der Code nimmt.
Hier sieht man eine (bisschen aufgehübschte) Code-Flow-Obfuscation. Zu Beginn wird ein inertialer X-Wert mit dem Variablennamen arg_1C_0 gesetzt. Mit diesem Eingangswert wird nun der Y Wert über den Aufruf von „X=PredicateFunc(Y)“ berechnet. Die folgende Switch-Instruction springt nun eins von den vielen Codefragmenten an, je nachdem welchen Wert die Predicate-Funktion zurückgeliefert hat. Am Ende jedes Codefragments wird ein neues X berechnet, welches wiederum zur Berechnung des nächsten Sprungs genutzt wird.

„Ist ja einfach“ könnte man sich nun zurecht denken! Denn kluge Tools gegen die Control-Flow-Obfuscation können all diese Variablen auswerten und den normalen Flow wiederherstellen! Aber diese Tools sind oftmals in .NET geschrieben, die Predicates wiederrum sind nativer X86 Code! Damit ist es nicht einmal möglich das File in ein solches Tool zu laden, denn .NET mag keine Mixed-Mode-Assemblies.

Hier sieht man ein solches x86 Predicate. Eigentlich relativ kurz und schön. Es nimmt ein paar Variablen, multipliziert diese, führt noch ein paar Bit-Operationen durch und addiert das Ergebnis welches in EAX landet.

In dem unten angehängten Paper werden zwei Lösungswege für dieses x86 Predicate Dilemma beschrieben: Man kann entweder den (jeweils relativ einfach aufgebauten) x86 Predicate Code analysieren und durch reinen .NET Code ersetzen oder die Auswertung des x86 Predicates von .NET aus durchführen. Letzteres hilft beachtlich wenn man den eigentlichen Control-Flow wiederherstellen möchte, aber das Predicate nur für bestimmte Variablen „auswerten“ und nicht ersetzen will.

Das obige x86 Predicate sieht in C#-Code viel hübscher aus. Diese Umwandlung nimmt mein Tool vor: Es ersetzt alle x86 Predicate Funktionen mit MSIL, also .NET Funktionen die genau das selbe machen und somit das selbe Predicate-Resultat liefern. Nachdem alle x86 Funktionen ersetzt sind kann man die Datei als normale .NET ohne Mixed-Mode-Einschränkungen abspeichern und laden. Dadurch ist es möglich weitere Tools zu nutzen (Strings und Ressourcen decrypten, Proxy Calls auflösen, …) um eine Analyse des Programms durchzuführen. Intern wird also jede x86 Instruktion analysiert und durch ein oder mehr äquivalente MSIL-Opcodes ersetzt.

Anbei findet ihr mein auf englisch verfasstes Paper und das eigentlich Tool. Das Paper beschriebt nochmal ein paar hier vernachlässigte Details und geht genauer auf die Umwandlung von x86 zu IL Code ein.

Dank geht an Ubbelol für seine Vorarbeit zum Thema x86 Predicates und der x86 Emulation .

Download Paper:

Replacing-and-Calling-ConfuserEx-x86-Predicates.pdf (26 Downloads)

Download Programm + Sourcecode:

EasyPredicateKiller.zip (18 Downloads)

 

3+

So, what do you think ?