Confuser String Decrypter über mDbg
Der Confuser ist nach wie vor ein aktuelles Thema. Und dabei ist die String-Decryption soweit ich weiß nicht geknackt! Confuser stellt für fast alle Strings eine eigene Decrypterfunktion mit eigener Ressource sowie eigenen Verschlüsselungsparametern bereit. Der reine Horror für jeden, der einen statischen Unpacker schreiben will.
Dieses mal ging es um einen anderen, dynamischen Ansatz: Warum nicht die Strings auslesen, nachdem sie sich entschlüsselt im Speicher befinden? Und da ich bei dem Proxy System von Confuser Erfolg mit WinDbg hatte, ist auch hier zum Debugger gegriffen worden. Microsoft stellt uns eine .NET Debugging API bereit, auch wenn sich kaum etwas darüber im Internet finden lässt. Und genau mit dieser API in Kombination mit ein wenig Confuserwissen, Speicherlayout und .NET ist es möglich, einen Debugger und Unpacker für die Strings zu schreiben. Dabei laufen folgende Schritte ab:
- Debugger Instanz erstellen + die Executable laden
- Nach dem Laden vom “mscorlib.dll”-Modul ein Breakpoint auf System.Reflection.Module.ResolveMethod setzen (wird von .cctor gecalled, siehe Proxy-System)
- Sobald dieser Breakpoint erreicht ist, werden Breakpoints auf alle String-Decrypter Funktionen gesetzt. Der ResolveMethod BP wird entfernt.
- Wenn ein Decrypt-BP erreicht ist, so wird aus der Funktion raus gesprungen und der Wert in EAX ausgelesen. Dabei wird der Parameter (Token) von dem Decrypt-Call zusammen mit dem entschlüsselten String abgespeichert.
- Alle Decrypt-Funkionen, die ein Token (+ entschlüsselten String) haben, werden mit dem echten String ersetzt.
Kingt nach viel Arbeit und Source? Nicht wirklich. Fangen wir bei Punkt 1 an:
Engine = new MDbgEngine(); Proc = Engine.CreateProcess(@"C:\Users\AL\Downloads\li0nsar3c00l enhanceviews.net bot_patched.exe", "", DebugModeFlag.Debug, null); Proc.PostDebugEvent += Proc_PostDebugEvent; // Jedes Event wird hierhin weitergereicht Engine.Processes.Active.CorProcess.OnBreakpoint += CorProcess_OnBreakpoint; // ein BP wird als Event gecalled Proc.Go().WaitOne(); // Wir lassen laufen, bis ein BP erreicht ist. Console.ReadLine(); |
In dem Post-Debug Event suchen wir uns nun den “OnAssemblyLoad”-Callback raus und setzen dann den BP:
case ManagedCallbackType.OnAssemblyLoad: Console.WriteLine("Assembly load:" + e.CallbackArgs.ToString()); Type Temp = e.CallbackArgs.GetType(); // Type der geladen wurde String Name = ((CorAssemblyEventArgs)e.CallbackArgs).Assembly.Name; CorAssembly ASM = ((CorAssemblyEventArgs)e.CallbackArgs).Assembly; if (Name.EndsWith("mscorlib.dll")) // Ist es mscorelib? { MDbgBreakpoint BP = Proc.Breakpoints.CreateBreakpoint("mscorlib.dll", "System.Reflection.Module", "ResolveMethod", 0); // Breakpoint gesetzt Console.WriteLine(BP.ToString()); } |
Weiter geht es bei dem Erreichen des ResolveMethod-Breakpoints. Dabei ist zu erwähnen, dass sich in diesem Fall die Method-Tokens der Decrypterfunktionen ihren Anfang bei 0×06000185 hatten und 0x0A an der Zahl sind.
static void CorProcess_OnBreakpoint(object sender, CorBreakpointEventArgs e) { String Name = Proc.Threads.Active.CurrentFrame.Function.FullName; MDbgFrame CurrentFrame = Proc.Threads.Active.CurrentFrame; switch (Name) { case "System.Reflection.Module.ResolveMethod": // nächstes Frame (1 hoch) um in richtiger Assembly zu landen MDbgFrame Next = CurrentFrame.NextUp;// Nächster Frame (Eine Funktion "hoch") e.Breakpoint.Activate(false); // Wir brauchen ResolveMethod nicht mehr... Proc.Breakpoints.DeleteAll(); // Vorsichtshalber alle löschen for (int i = 0; i < 0x0a; i++) { MDbgFunction TempFunc = Next.Function.Module.GetFunction(0x06000185 + i); Proc.Breakpoints.CreateBreakpoint(TempFunc, 0); // Ein kleiner Hack um über das MethodToken an die Funktion zu kommen } Proc.Go(); // Ab gehts! break; |
Zu beachten ist, dass über CurrentFrame.NextUp wir uns eine Ebene hoch in der Callstack-Hirarchie bewegen können. So landen wir in dem Modul, was es zu Unpacken gilt. Und dieses Modul löst uns dann auch das Method-Token erfolgreich auf.
Nachdem jetzt die Breakpoints gesetzt sind, geht es in eine decrypter Funktion. Zwar ist mDbg ein .NET Debugger, dieser besitzt aber keine Funktionen zum prüfen oder anzeigen des Rückgabewertes der Funktion. Also können wir uns nicht die Werte auf dem Stack, auf dem sich auch unser String befindet, anzeigen oder auslesen. Aber Reverser sind kluge Menschen und nicht an .NET gebunden Zunächst einmal wissen wir, dass .NET Code in normalen ASM-Code zur Laufzeit “umgewandelt” wird. Und normalerweiße wird ein Rückgabewert einer nativen Funktion im EAX-Register zurückgegeben, so auch in .NET. Wir sagen dem Debugger also, dass er aus der aktuellen Funktion rausspringen sollte und breaken da erneut, um uns den Wert in EAX anzuschauen. Und dort erwartet uns ein Zeiger auf eine ominöse Struktur, welche allerdings leicht zu durchschauen ist.
- [4 Byte] String Token
- [4 Byte] String Länge
- [LENGTH * 2] String in UNICODE
Daraus können wir unseren String auslesen! Und das passiert alles in folgendem Source. Ich gebe zu er ist nicht aufgeräumt, aber ich denke das Prinzip kommt rüber
// in einem Breakpoint MDbgValue[] Params = Proc.Threads.Active.CurrentFrame.Function.GetArguments(Proc.Threads.Active.CurrentFrame); // Parameter des aktuellen Frames auslesen // != 2 Argumente -> Weiter gehts, keine Decrypt Funktion if (Params.Length != 2) { Proc.Go().WaitOne(); continue; } // Keine UInts als Parameter? Weiter gehts! if (!Params[0].TypeName.Contains("Uint") && !Params[1].TypeName.Contains("UInt")) { Proc.Go().WaitOne(); continue; } // Wir sind wohl in einer Decrypter-Funktion, also eine Ebene nach oben Proc.StepOut().WaitOne(); // EAX auslesen ulong eax = Proc.Threads.Active.CorThread.RegisterSet.GetRegister(CorCorDebugRegister.Eax); byte[] Out = new byte[8]; // EAX ist 0x00 ? Weiter gehts! if (eax == 0x00) { Proc.Go().WaitOne(); continue; } // Wir lesen die ersten 8 Bytes (Header) des Strings aus Proc.CorProcess.ReadMemory((long)eax, Out); // Uns interessiert nur die Länge des Strings (Offset = 4) Int32 Length = BitConverter.ToInt32(Out, 4); // Alle Daten mit Länge auslesne byte[] Data = new byte[8 + Length * 2 + 1]; Proc.CorProcess.ReadMemory((long)eax, Data); // In Struktur Parsen StringFromAddress TemPSTR = new StringFromAddress(Data); // Parameter der Decrypter-Funktion sichern zur String zuordnung Object Param1 = Params[0].GetStringValue(false); try { // String + Token in eine Liste schreiben lstCollection.Add(new StringUIntCollection(TemPSTR.Value, Convert.ToUInt32(Param1))); Console.WriteLine("String decrypted: " + TemPSTR.Value); System.IO.File.AppendAllText("Out_NEW_2.txt", String.Format("{0}|||{1}{2}", TemPSTR.Value, Param1, Environment.NewLine)); } catch { } finally { Proc.Go().WaitOne(); } |
Dieser doch etwas komplexerer Source ermöglicht es uns, die Strings auszulesen und abzuspeichern.
Doch ich gebe zu, diese Methode hat große Nachteile:
- Es werden nicht alle Strings entschlüsselt, sondern nur die zur Laufzeit entschlüsselten
- Es ist laaaaangsam, ca. 3 Strings pro Sekunde. Das kommt vorallem durch das Raussteppen aus der Decrypterfunktion
In einem Unpacker kann man nun alle Decrypter-Calls mit den echten Strings ersetzten und hat einen weitaus besser lesbaren Source
Das sich im Download befindliche Projekt arbeitet zwar mit diesem Source, allerdings nicht Threadsyncron. So wird gewartet, bis alles geladen ist und dann die Enter-Taste gedrückt wurde. Erst dann beginnt der Debugger zu laufen und das bis in alle Ewigkeit. Diese Lösung ist unsauber, funktioniert aber. Und da ich die beiden Threads einfach nicht synchron hin bekommen habe, war das die einfachste (und nervensparensde) Lösung.
Download:
Confuser String Decrypter (249)
Greez Easy
Schonmal an die TPL gedacht bzgl Threadsynchronisation ?
Würde das ganze erheblich verschnellern parallel damit zu arbeiten.
Ansonsten feine Sache, aber bitte nicht noch mehr Threads zum Confuser.
Man soll sich doch zumindest gegen skids damit behelfen können.
Gruß
Naja, wie schon gesagt: Ich war froh dass es überhaupt lief ^^ Und ich denke der Flaschenhals sind nicht die Threads, sondern vielmehr das breaken im Debugger ansich.
Es gibt schon so viele Step by Step Anweisen zum Unpacken von Confuser, dass wenigstens ich etwas Hintergrundwissen vermitteln will
Aber ich denke das wars jetzt erstmal zum Confuser…