When managed is not enough - Confuser broken

Nach einem Monat der mehr oder weniger intensivsten Arbeit erscheint hier der nächste große Artikel zum Confuser. Es wird (wiedermal) um das Auflösen des Proxy-Systems gehen. Wem das Proxy-System nicht geläufig ist, kann im vorherigen Artikel sein Wissen nochmal auffrischen.

Doch zunächst ein wenig Vorgeschichte: Ein deutschsprachiger Reverser “li0nsar3c00l” war so nett mir eine Binary zu builden, welche es zu cracken galt. Dieses “CrackMe” wurde mit Confuser 1.9 Aggressive erstellt. Aggressive enthält das Proxy-System (Methoden und Konstuktoren), String-Encryption, Code-Flow-Obfuscation, Anti-Debug und natürlich Type-Renaming. Man muss im Hinterkopf behalten dass es nicht möglich ist eine lauffähige Binary zu erstellen, wenn nicht alle dieser Systeme umgangen sind. Denn jedes System benutzt entweder Signaturen von Methoden, welche sich ändern sobald die Binary neu erstellt wird, oder aber Verrechnungen mit dem MethodToken aus dem Callstack. Das hört sich alles kompliziert an, bedeutet aber einfach dass wir keine neue Binary erstellen können ohne zuvor alles aufgelöst zu haben.

Bevor wir intensiv ans Reversing gehen können, gilt es das Anti-Debug-Modul auszuhebeln. Dazu noppen wir einfach die ganze Methode, unschön aber effektiv. Den Call zu noppen hätte auch gereicht, allerdings wusste ich nicht ob die Anti-Debug Funktion nochmal (dynamisch?) aufgerufen wird. Sicher ist sicher.

Nun kam die statische Analyse. Aus den vorherigen Confuser-Versionen war bekannt, dass alle Methoden und Konstruktoren (.ctor) erst zur Laufzeit mit einem Delegate verknüpft werden. Dafür wurde aus dem #BLOB-Stream jeweils die Signatur für ein Feld ausgelesen und aus dieser Signatur ein “aufgelöstes” Token errechnet. Die Rechnung sieht also in etwa so aus:

byte[] array = fieldFromHandle.Module.ResolveSignature(fieldFromHandle.MetadataToken);
uint num = (uint)((int)array[array.Length - 6] | (int)array[array.Length - 5] << 8 | (int)array[array.Length - 3] << 16 | (int)array[array.Length - 2] << 24);
methodInfo = (fieldFromHandle.Module.ResolveMethod((int)((num ^ 1746562777u) | (uint)((uint)array[array.Length - 7] << 24))) as MethodInfo);

Sieht überschaubar aus, oder? :P Nach ein paar Bitshifting und OR Operationen erhalten wir eine Zahl, welche sich eben aus der Signatur errechnet. Diese Zahl wird nochnmal geXORed und damit bekommen wir unser echtes MethodToken mit der “echten” Funktion. Soweit sogut, aber es klappte einfach nicht. Ich kann leider nicht mehr nachvollziehen woran es lag, aber ich bekam immer falsche Werte. Daher wurde auf eine Analyse zur Laufzeit übergegangen…

ILSpy bietet, wie schon in vorherigen Posts erwähnt, einen guten Debugger. Dieser funktioniert aber erst richtig, nachdem die Main-Funktion (Entrypoint) aufgerufen wurde. Das ganze Auflösen von Methoden&Co läuft im <Module>.cctor(), das Kontruktor des ganzen Programms. Also habe ich mich in das so ziemlich mächtigste Debuggingwerkzeug das es gibt eingearbeitet: WinDbg!

WinDbg ist ein eigentlich nativer Debugger, welcher allerdings über Erweiterungen auch .NET Unterstützung hat. Ich bin mehr der Freund von GUIs, gerade wenn es um die Darstellung vieler Informationen geht. Doch WinDbg bietet nur eine “Kommandozeile”. Man gewöhnt sich allerdings schnell dran. Die ganzen WinDbg Funktionen zu erklären würde hier den Rahmen sprengen, vielleicht entsteht wann anders mal ein Artikel drüber. Hauptsache ist, dass WinDbg Breakpoints auf Funktionen des .NET Frameworks setzen kann. Und ja, aufmerksame Leser wissen schon was kommt: Ein Breakpoint auf System.Reflection.Module.ResolveMethod mit dem aufgelösten Token im Parameter!

0:000> !bpmd mscorlib.dll System.Reflection.Module.ResolveMethod
Found 2 methods in module 6ba21000...
MethodDesc = 6baa9a38
MethodDesc = 6baa9a44
Setting breakpoint: bp 6C47AFB9 [System.Reflection.Module.ResolveMethod(Int32, System.Type[], System.Type[])]
Setting breakpoint: bp 6C47AF93 [System.Reflection.Module.ResolveMethod(Int32)]
Adding pending breakpoints...

Zwei Breakpoints wurden also gesetzt, mit einem “g” feuern wir das Programm los. Und landen prompt bei Breakpoint 1. Ein !clrstack -p (-p = Parameter) zeigt uns was Sache ist:

0:000> !clrstack -p
OS Thread Id: 0x10f4 (0)
Child SP       IP Call Site
0020e9b8 6c47af93 System.Reflection.Module.ResolveMethod(Int32)
    PARAMETERS:
        this () = 0x01c75a74
        metadataToken () = 0x0a000188.蹦 噈䫫᭶淴箝(System.RuntimeFieldHandle)
    PARAMETERS:
        f (0x0020eb00) = 0x01cb7944

Yeah! Ein valides MethodToken. Doch wie kommt es dazu? Um dieses zu erforschen müssen wir eine Erweiterung von WinDbg nutzen, SosEx.dll! SosEx ist eine Erweiterung, welche uns eine genauere Objektinspektion über den Speicher erlaubt und sogar IL-Code und nativen ASM-Code gegenüberstellt. Doch langsam, erst zu den Objekten. Die erhalten wir über ein !mdv (method display variables):

0:000> !mdv
Frame 0x0: (.蹦 噈䫫᭶淴箝(System.RuntimeFieldHandle)):
[A0]:f:VALTYPE (MT=6bdce33c, ADDR=0020eb00) (System.RuntimeFieldHandle)
[L0]:null (System.Reflection.Emit.ILGenerator)
[L1]:null (System.Reflection.MethodInfo)
[L2]:0x0 (System.Int32)
[L3]:null (System.Type[])
[L4]:01cb7a08 (System.Reflection.FieldInfo)
[L5]:0x681a6f51 (System.UInt32)
[L6]:null (System.Reflection.Emit.DynamicMethod)
[L7]:01cb7a34 (System.Byte[])
[L8]:0x0 (System.Int32)
[L9]:null (System.Reflection.ParameterInfo[])
[L10]:0x13 (System.Int32)

Durch ein Klick auf das Byte-Array erhalten wir die Signature, L5 zeigt das (fast vollständig) aufgelöste MethodenToken an. Durch ein XOR auf L5 erhalten wir also:

0x681a6f51 ^ 0x681A6ED9 = 0x188

Durch ein OR erhalten wir noch am Anfang ein 0A und haben so unser Token:

0x0A000188!

Diesen .ctor-Resolver im Deobfuscator zu implementieren war nicht das Problem. Allerdings hat die zweite Resolver-Funktion für normale (und statische) Methoden ein komplizierteren Weg der Rechnung. Dieser sieht in C# so aus:

uint num = (uint)((int)array[array.Length - 6] | (int)array[array.Length - 5] << 8 | (int)array[array.Length - 3] << 16 | (int)array[array.Length - 2] << -1578062949 + (-225617010 + 531481603 ^ (-1145791625 - (418024953 ^ 1550462051) ^ 1939839452 - (1940476579 - 990997240))));

Dieser Code kann so nicht in C# übernommen werden! Ein Überlauffehler lässt sich einfach nicht eliminieren, das Ausrechnen der einzelnen Klammern über den Taschenrechner war auch nicht erfolgreich. Zuletzt wurde die Klasse Stack<Int32> genutzt, um den IL-Code genaustens zu reproduzieren.

stackTemp.Push(-1145791625); // IL_01de: ldc.i4 -1145791625
stackTemp.Push(418024953); // IL_01e3: ldc.i4 418024953
stackTemp.Push(1550462051); // IL_01e8: ldc.i4 1550462051
stackTemp.Push(stackTemp.Pop() ^ stackTemp.Pop()); // xor
stackTemp.Push(stackTemp.Pop() - stackTemp.Pop()); // sub

Fehlanzeige! Aber irgendwann muss diese Bitshifting-Operation im Speicher laden und dort ausgeführt werden! Also wurde in natives Debugging übergangen. Nach mehreren Stunden des Verfluchens von Confuser war sie endlich da!

eax=001d7934 ebx=6508e64c ecx=00000018 edx=0000005a esi=123b1f91 edi=001cd45c
eip=002b248c esp=001cd3c4 ebp=001cd478 iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000217
002b248c d3e2            shl     edx,cl

shl edx,cl (shift left) verschiebt die Bits in EDX um den Betrag in CL. CL sind die unteren 16 Bits in ECX, und dieses sind wohl 0x18h oder 24d! Ist ja auch irgendwie logisch wenn zuvor überall 8, 16 und 24 verwendet worden sind. Egal, die Freude im Nachhinein war es auf jeden Fall wert :D

So, damit haben wir wieder alle Tokens die wir brauchen um die Assembly zu “entschlüsseln”. Nach ein paar Optimierungen der Ersetzungsmethoden (Getter/Setter sind schwul und das Werfen einer Exception ist kein call!) wurden alle Funktion aufgelöst, alle Delegates gelöscht. Und siehe da, es bleibt nur eine Form und 10 Helperklassen übrig. Nun noch Strings decrypten und gut ist. Aber das folgt wann anders.

Nun noch zum Abschluss ein vorher/nachher Vergleich der Main-Funktion:

So Far
Easy

9 people like this post.
  1. noch nichma fertig gelesen und schon kann ich sagen: awesome
    bin stolz, dass du mich erwähnt hast ;)

  1. Noch keine TrackBacks.


× eins = 7