Flare-On 2019 Zuschreibung



-0x01 - Intro


Dieser Artikel widmet sich der Analyse aller Aufgaben von Flare-On 2019 - dem jährlichen Wettbewerb für Reverse Engineering von FireEye. An diesen Wettbewerben nehme ich zum zweiten Mal teil. Im Vorjahr habe ich es geschafft, den 11. Platz in Bezug auf die Fertigstellungszeit zu erreichen, nachdem ich alle Probleme in ungefähr 13 Tagen gelöst hatte. In diesem Jahr waren die Aufgaben einfacher, und ich traf mich in 54 Stunden und belegte gleichzeitig 3 Plätze in Bezug auf die Lieferung.


In diesem Artikel habe ich versucht, jene Momente zu beschreiben, die mein größtes Interesse geweckt haben. Daher beschreibt die Analyse nicht die Routine der Arbeit in der IDA, das Verständnis der Algorithmen jeder Funktion und anderer nicht sehr interessanter Punkte. Ich hoffe, dass Sie nach dem Lesen etwas Neues und Nützliches für sich finden. Hier finden Sie die Analyse der Probleme der Autoren sowie einige Statistiken und Preise für die Gewinner.


Wenn Sie interessiert sind, dann willkommen bei cat!


0x00 - Inhalt


  1. 0x01 - Memecat Battlestation [Shareware Demo Edition]
  2. 0x02 - Überlang
  3. 0x03 - Flarebear
  4. 0x04 - Dnschess
  5. 0x05 - Demo
  6. 0x06 - bmphide
  7. 0x07 - wopr
  8. 0x08 - Schlange
  9. 0x09 - neu geladen
  10. 0x0A - Mugatu
  11. 0x0B - vv_max
  12. 0x0C - Hilfe
  13. 0x0D - Zusammenfassung


0x01 - Memecat Battlestation [Shareware Demo Edition]


Willkommen zur sechsten Flare-On Challenge!

Dies ist ein einfaches Spiel. Reverse Engineering, um herauszufinden, welche "Waffencodes" Sie eingeben müssen, um jeden der beiden Feinde zu besiegen. Auf dem Siegesbildschirm wird die Flagge angezeigt. Geben Sie hier auf dieser Site die Flagge ein, um zu punkten und zum nächsten Level zu gelangen.

* Diese Herausforderung ist in .NET geschrieben. Wenn Sie noch kein bevorzugtes .NET-Reverse-Engineering-Tool haben, empfehle ich dnSpy

** Wenn Sie die Vollversion dieses Spiels bereits an unserem Stand auf der BlackHat oder der nachfolgenden Veröffentlichung auf Twitter gelöst haben, gratulieren Sie, geben Sie jetzt die Flagge auf dem Siegesbildschirm ein, um dieses Level zu umgehen.

Diese Aufgabe wurde im Voraus im Rahmen von Black Hat USA 2019 festgelegt, ungefähr zu der Zeit, als ich mich entschied. Ich kann mich nicht erinnern, wie er es gelöst hat Die Aufgabe ist recht einfach, daher werden wir ihre Lösung nicht in Betracht ziehen.



0x02 - Überlang


Das Geheimnis dieser nächsten Herausforderung ist geschickt verborgen. Mit dem richtigen Ansatz dauert das Finden der Lösung jedoch nicht lange.

Gegebene x86 .exe-Datei. Wenn Sie versuchen zu starten, wird eine Meldung mit folgendem Inhalt angezeigt:



Bei der Analyse der Anwendung stellen Sie möglicherweise fest, dass die Nachricht in einer Codierung mit variabler Zeichenlänge (von 1 bis 4 Byte) gespeichert ist. Wenn die Decodierungsfunktion aufgerufen wird, erhält sie die Länge des erwarteten Ergebnisses, die kürzer als die Nachricht selbst ist, weshalb das Flag nicht sichtbar ist. Sie können den an die Funktion im Debug-Modus übergebenen Längenwert korrigieren und die vollständige Nachricht mit dem Flag abrufen:



Sie können den Dekodierungsalgorithmus auch in Python neu schreiben und ein Flag erhalten:


msg = [ ... ] #      output = [] i = 0 while i < len(msg): if (msg[i] >> 3) == 0x1e: out_char = ( ((msg[i + 3] & 0x3F) << 0 ) | ((msg[i + 2] & 0x3F) << 6 ) | ((msg[i + 1] & 0x3F) << 12) | ((msg[i + 0] & 7) << 18) ) output.append(out_char) i += 4 elif (msg[i] >> 4) == 0x0e: out_char = ( ((msg[i + 2] & 0x3F) << 0 ) | ((msg[i + 1] & 0x3F) << 6 ) | ((msg[i + 0] & 0xF) << 12) ) output.append(out_char) i += 3 elif (msg[i] >> 5) == 6: out_char = ( ((msg[i + 1] & 0x3F) << 0 ) | ((msg[i + 0] & 0xF) << 6 ) ) output.append(out_char) i += 2 else: output.append(msg[i]) i += 1 print(bytes([i for i in output])) # b'I never broke the encoding: I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com' 


0x03 - Flarebear


Wir bei Flare haben unser eigenes Tamagotchi-Haustier kreiert, den Flarebear. Er ist sehr pingelig. Halte ihn am Leben und glücklich und er wird dir die Flagge geben.

In dieser Aufgabe wird eine apk Datei für Android . Betrachten Sie eine Lösungsmethode, ohne die Anwendung selbst zu starten.


Der erste Schritt besteht darin, den Quellcode der Anwendung abzurufen. Konvertieren Sie dazu mit dem Dienstprogramm dex2jar apk in jar und dex2jar dann den Java Quellcode mit dem Dekompiler ab, den ich lieber cfr .


 ~/retools/d2j/d2j-dex2jar.sh flarebear.apk java -jar ~/retools/cfr/cfr-0.146.jar --outputdir src flarebear-dex2jar.jar 

Durch Analysieren des Quellcodes der Anwendung finden Sie eine interessante .danceWithFlag() -Methode, die sich in der Datei FlareBearActivity.java . In .danceWithFlag() werden die .decrypt(String, byte[]) der Anwendung mit der .decrypt(String, byte[]) entschlüsselt, deren erstes Argument die mit der Methode .getPassword() erhaltene Zeichenfolge ist. Sicherlich befindet sich das Flag in verschlüsselten Ressourcen. Versuchen wir also, sie zu entschlüsseln. Zu diesem Zweck habe ich beschlossen, den dekompilierten Code ein wenig umzuschreiben, Android Abhängigkeiten zu Android und nur die für die Entschlüsselung erforderlichen Methoden zu belassen, damit der resultierende Code kompiliert werden kann. Ferner wurde während der Analyse festgestellt, dass die .getPassword() -Methode von drei ganzzahligen .getPassword() abhängt. Jeder Wert liegt in einem kleinen Intervall von 0 bis N , sodass Sie alle möglichen Werte auf der Suche nach dem gewünschten Passwort durchgehen können.


Das Ergebnis ist der folgende Code:


Main.java
 import java.io.InputStream; import java.nio.charset.Charset; import java.security.Key; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.KeySpec; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; import java.util.Collections; import java.io.*; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; public final class Main { public static void main (String args []) throws Exception { Main a = new Main(); InputStream inputStream = new FileInputStream("ecstatic"); long fileSize = new File("ecstatic").length(); byte[] file1 = new byte[(int) fileSize]; inputStream.read(file1); inputStream = new FileInputStream("ecstatic2"); fileSize = new File("ecstatic2").length(); byte[] file2 = new byte[(int) fileSize]; inputStream.read(file2); for(int i = 0; i < 9; i++) { for(int j = 0; j < 7; j++) { for(int k = 1; k < 16; k++) { String pass = a.getPassword(i, j, k); try { byte[] out1 = a.decrypt(pass, file1); byte[] out2 = a.decrypt(pass, file2); OutputStream outputStream = new FileOutputStream("out1"); outputStream.write(out1); outputStream = new FileOutputStream("out2"); outputStream.write(out2); System.out.println("yep!"); } catch (javax.crypto.BadPaddingException ex) { } } } } } public final byte[] decrypt(Object object, byte[] arrby) throws Exception { Object object2 = Charset.forName("UTF-8"); object2 = "pawsitive_vibes!".getBytes((Charset)object2); object2 = new IvParameterSpec((byte[])object2); object = ((String)object).toCharArray(); Object object3 = Charset.forName("UTF-8"); object3 = "NaClNaClNaCl".getBytes((Charset)object3); object = new PBEKeySpec((char[])object, (byte[])object3, 1234, 256); object = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret((KeySpec)object); object3 = new SecretKeySpec(((SecretKey)object).getEncoded(), "AES"); object = Cipher.getInstance("AES/CBC/PKCS5Padding"); ((Cipher)object).init(2, (Key)object3, (AlgorithmParameterSpec)object2); object = ((Cipher)object).doFinal(arrby); return (byte [])object; } public final String getPassword(int n, int n2, int n3) { String string2 = "*"; String string3 = "*"; switch (n % 9) { case 8: { string2 = "*"; break; } case 7: { string2 = "&"; break; } case 6: { string2 = "@"; break; } case 5: { string2 = "#"; break; } case 4: { string2 = "!"; break; } case 3: { string2 = "+"; break; } case 2: { string2 = "$"; break; } case 1: { string2 = "-"; break; } case 0: { string2 = "_"; } } switch (n3 % 7) { case 6: { string3 = "@"; break; } case 4: { string3 = "&"; break; } case 3: { string3 = "#"; break; } case 2: { string3 = "+"; break; } case 1: { string3 = "_"; break; } case 0: { string3 = "$"; } case 5: } String string4 = String.join("", Collections.nCopies(n / n3, "flare")); String string5 = String.join("", Collections.nCopies(n2 * 2, this.rotN("bear", n * n2))); String string6 = String.join("", Collections.nCopies(n3, "yeah")); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(string4); stringBuilder.append(string2); stringBuilder.append(string5); stringBuilder.append(string3); stringBuilder.append(string6); return stringBuilder.toString(); } public final String rotN(String charSequence, int n) { Collection<String> collection = new ArrayList(charSequence.length()); for (int i = 0; i < charSequence.length(); ++i) { char c; char c2 = c = charSequence.charAt(i); if (Character.isLowerCase(c)) { char c3; c2 = c3 = (char)(c + n); if (c3 > 'z') { c2 = c3 = (char)(c3 - n * 2); } } collection.add(Character.valueOf(c2).toString()); } return collection.stream().collect(Collectors.joining()); // return ArraysKt.joinToString$default(CollectionsKt.toCharArray((List)collection), (CharSequence)FLARE_BEAR_NAME, null, null, 0, null, null, 62, null); } } 

Wir werden die verschlüsselten Ressourcen extrahieren, die resultierende Datei kompilieren und ausführen:


 $ ~/retools/apktool/apktool d flarebear.apk $ cp flarebear/res/raw/* . $ javac Main.java $ java Main 

Glücklicherweise ist nur eine der von Ihnen ausgewählten Passwortoptionen geeignet. Als Ergebnis erhalten wir zwei Bilder mit einer Flagge:


 ~/flareon2019/3 - Flarebear$ file out* out1: PNG image data, 2100 x 2310, 8-bit/color RGB, non-interlaced out2: PNG image data, 2100 x 2310, 8-bit/color RGB, non-interlaced 




0x04 - Dnschess


Verdächtiger Netzwerkverkehr führte uns zu diesem nicht autorisierten Schachprogramm, das auf einem Ubuntu-Desktop ausgeführt wurde. Dies scheint die Arbeit von Cyberspace-Computerhackern zu sein. Sie müssen die richtigen Schritte unternehmen, um dieses Problem zu lösen. Viel Glück!

Diese Aufgabe enthält einen Verkehrsauszug, eine ausführbare ELF Datei ChessUI und eine Bibliothek ChessAI.so . Durch Ausführen der ausführbaren Datei können Sie das Schachbrett sehen.



Beginnen wir die Analyse mit einem Verkehrsdump.



Der gesamte Datenverkehr besteht aus Abfragen an einen DNS Server vom Typ A Die Abfragen selbst bestehen aus den Namen der Figuren, der Beschreibung des Zuges im Schachspiel und dem konstanten Teil von .game-of-thrones.flare-on.com , zum Beispiel rook-c3-c6.game-of-thrones.flare-on.com . Durch den konstanten Teil können Sie leicht den richtigen Platz in der ChessAI.so Bibliothek finden:


 signed __int64 __fastcall getNextMove(int idx, const char *chess_name, unsigned int pos_from, unsigned int pos_to, \__int64 a5) { struct hostent *v9; // [rsp+20h] [rbp-60h] char *ip_addr; // [rsp+28h] [rbp-58h] char dns_name; // [rsp+30h] [rbp-50h] unsigned __int64 v12; // [rsp+78h] [rbp-8h] v12 = __readfsqword(0x28u); strcpy(&dns_name, chess_name); pos_to_str(&dns_name, pos_from); pos_to_str(&dns_name, pos_to); strcat(&dns_name, ".game-of-thrones.flare-on.com"); v9 = gethostbyname(&dns_name); if ( !v9 ) return 2LL; ip_addr = *v9->h_addr_list; if ( *ip_addr != 127 || ip_addr[3] & 1 || idx != (ip_addr[2] & 0xF) ) return 2LL; sleep(1u); flag[2 * idx] = ip_addr[1] ^ key[2 * idx]; flag[2 * idx + 1] = ip_addr[1] ^ key[2 * idx + 1]; *(_DWORD *)a5 = (unsigned __int8)ip_addr[2] >> 4; *(_DWORD *)(a5 + 4) = (unsigned __int8)ip_addr[3] >> 1; strcpy((char *)(a5 + 8), off_4120[idx]); return (unsigned __int8)ip_addr[3] >> 7; } 

Aus dem Code ist ersichtlich, dass basierend auf den empfangenen ip Adressen eine bestimmte Byte-Zeichenfolge entschlüsselt wird, die in einem anderen Speicherbereich gespeichert ist, den ich als flag .


Um die Aufgabe zu lösen, müssen Sie zunächst alle ip Adressen aus dem Verkehrsdump abrufen. Sie können dies mit dem folgenden Befehl tun:


 tshark -r capture.pcap | grep -P -o '127.(\d+).(\d+).(\d+)' | grep -v '127.0.0.1' 

ips alle ip Adressen in der ip Datei gespeichert haben, können ips den folgenden Python Code verwenden, um das Flag abzurufen:


 with open('ips') as f: ips = f.read().split() flag = bytearray(64) key = b'yZ\xb8\xbc\xec\xd3\xdf\xdd\x99\xa5\xb6\xac\x156\x85\x8d\t\x08wRMqT}\xa7\xa7\x08\x16\xfd\xd7' for ip in ips: a, b, c, d = map(int, ip.split('.')) if d & 1: continue idx = c & 0xf if idx > 14: continue flag[2*idx] = b ^ key[2*idx] flag[2*idx + 1] = b ^ key[2*idx + 1] print(flag.decode() + '@flare-on.com') # LooksLikeYouLockedUpTheLookupZ@flare-on.com 


0x05 - Demo


Jemand aus dem Flare-Team hat versucht, uns mit seinen Demoszenenfähigkeiten zu beeindrucken. Es scheint leer zu sein. Sehen Sie, ob Sie es herausfinden können oder ob wir sie feuern müssen. Kein Druck.

Angesichts der ausführbaren Datei 4k.exe , die DirectX . Beim Start wird das rotierende FlareOn Logo im Hauptfenster angezeigt.



Die statische Analyse des Programms zeigt eine einzelne Funktion, die der Einstiegspunkt ist. Inhaltlich ähnelt die Funktion der Implementierung der Code-Entschlüsselung. Wir werden keine Zeit damit verschwenden, den Algorithmus dieser Funktion zu analysieren. Setzen Sie einfach einen Haltepunkt auf den ret Befehl und sehen Sie, wohin die Steuerung übertragen wird. Nach der Rückkehr befinden wir 0x00420000 unter der Adresse 0x00420000 , deren Code als etwas 0x00420000 zerlegt wird:



Anschließend wurde beschlossen, diesen Code mithilfe der API aus dem Debug-Modus in die IDA Datenbank zu übertragen und die statische Analyse fortzusetzen.


Der neue Code am Anfang importiert die notwendigen Funktionen aus verschiedenen Bibliotheken. Die Tabelle dieser Funktionen kann auch dynamisch wiederhergestellt werden. Das Ergebnis sind die folgenden Funktionen:



Der "echte" Einstiegspunkt in das Programm ist:



Beachten Sie die Erstellung von DeviceInterface Typ IDirect3DDevice9 ** . In Zukunft wird diese Schnittstelle aktiv genutzt, und um das Gegenteil zu vereinfachen, muss eine Tabelle ihrer Methoden definiert werden. So konnte beispielsweise hier schnell die Definition der Schnittstelle gefunden werden . Ich habe diese Tabelle analysiert und in eine Struktur für die IDA konvertiert. Das Anwenden des resultierenden Typs auf DeviceInterface kann die weitere Codeanalyse erheblich vereinfachen. Die folgenden Screenshots zeigen das Ergebnis des Dekompilierers für die Hauptfunktion des Szenenwiedergabezyklus vor und nach dem Anwenden des Typs.




Bei weiterer Analyse wurde festgestellt, dass zwei polygonale Netze (Netz, Polygonnetz) im Programm erstellt werden, obwohl beim Ausführen des Programms nur ein Objekt angezeigt wird. Außerdem werden beim Erstellen von Gittern deren Scheitelpunkte mit XOR verschlüsselt, was ebenfalls Verdacht erregt. Lassen Sie uns die Eckpunkte entschlüsseln und visualisieren. Das zweite Gitter ist seitdem von größtem Interesse es hat deutlich mehr Eckpunkte. matplotlib alle Scheitelpunkte matplotlib , stellte ich fest, dass die Z Koordinate von jedem von ihnen 0 ist. Zur Visualisierung wurde daher beschlossen, zweidimensionale Graphen mit matplotlib zu zeichnen. Der folgende Code und das folgende Ergebnis mit einem Flag haben sich herausgestellt:


 import struct import matplotlib.pyplot as plt with open('vertexes', 'rb') as f: data = f.read() n = len(data) // 4 data = list(struct.unpack('{}I'.format(n), data)) key = [0xCB343C8, 0x867B81F0, 0x84AF72C3] data = [data[i] ^ key[i % 3] for i in range(len(data))] data = struct.pack('{}I'.format(n), *data) data = list(struct.unpack('{}f'.format(n), data)) x = data[0::3] y = data[1::3] z = data[2::3] print(z) plt.plot(x, y) plt.show() 



0x06 - bmphide


Tyler Dean wanderte den Mt. Elbert (Colorados höchster Berg) um 2 Uhr morgens, um dieses Bild zur perfekten Zeit aufzunehmen. Überspringen Sie niemals den Beintag. Wir fanden dieses Bild und die ausführbare Datei auf einem USB-Stick, den er am Trail Head hinterlassen hatte. Kann man ihm vertrauen?

In Task werden die ausführbare Datei bmphide.exe und das Bild image.bmp . Es kann davon ausgegangen werden, dass eine Nachricht mithilfe von Steganografiemethoden im Bild verborgen ist.


Die Binärdatei ist in C# , daher habe ich das Dienstprogramm dnSpy für die Analyse verwendet. Sie können sofort feststellen, dass die meisten Namen der Methoden verschleiert sind. Wenn Sie sich die Program.Main Methode ansehen, können Sie die Logik des Programms verstehen und Annahmen über den Zweck einiger von ihnen treffen:


 // BMPHIDE.Program // Token: 0x06000018 RID: 24 RVA: 0x00002C18 File Offset: 0x00002C18 private static void Main(string[] args) { Program.Init(); Program.yy += 18; string filename = args[2]; string fullPath = Path.GetFullPath(args[0]); string fullPath2 = Path.GetFullPath(args[1]); byte[] data = File.ReadAllBytes(fullPath2); Bitmap bitmap = new Bitmap(fullPath); byte[] data2 = Program.h(data); Program.i(bitmap, data2); bitmap.Save(filename); } 

  • Die Anwendung wird mit der Program.Init() -Methode initialisiert
  • Datendatei und Bilddatei lesen
  • Mit der Methode byte [] Program.h(byte []) wird eine Datenkonvertierung durchgeführt
  • Mit der Program.i(Bitmap, byte[]) Methode Program.i(Bitmap, byte[]) werden die konvertierten Daten in das Bild eingefügt
  • Das resultierende Bild wird unter einem neuen Namen gespeichert.

Während der Anwendungsinitialisierung werden verschiedene Methoden der Klasse A aufgerufen. Eine oberflächliche Analyse der Klasse zeigte die Ähnlichkeit einiger ihrer Methoden mit den Methoden des Obfuscators ConfuserEx (Datei AntiTamper.JIT.cs ). Die Anwendung ist wirklich vor dem Debuggen geschützt. Gleichzeitig war es nicht möglich, die Schutzmechanismen mit dem Dienstprogramm de4dot und seinen Gabeln zu entfernen, sodass beschlossen wurde, die Analyse fortzusetzen.


Betrachten Sie die Program.i Methode, mit der Daten in ein Bild Program.i werden.


 public static void i(Bitmap bm, byte[] data) { int num = Program.j(103); for (int i = Program.j(103); i < bm.Width; i++) { for (int j = Program.j(103); j < bm.Height; j++) { bool flag = num > data.Length - Program.j(231); if (flag) { break; } Color pixel = bm.GetPixel(i, j); int red = ((int)pixel.R & Program.j(27)) | ((int)data[num] & Program.j(228)); int green = ((int)pixel.G & Program.j(27)) | (data[num] >> Program.j(230) & Program.j(228)); int blue = ((int)pixel.B & Program.j(25)) | (data[num] >> Program.j(100) & Program.j(230)); Color color = Color.FromArgb(Program.j(103), red, green, blue); bm.SetPixel(i, j, color); num += Program.j(231); } } } 

Sehr ähnlich dem klassischen LSB wird jedoch an Stellen, an denen Konstanten erwartet werden, die Methode int Program.j(byte) verwendet. Das Ergebnis seiner Arbeit hängt von verschiedenen globalen Werten ab, die während der Initialisierung in der Program.Init() -Methode erhalten wurden. Es wurde beschlossen, seine Arbeit nicht umzukehren, sondern zur Laufzeit alle möglichen Werte zu erhalten. dnSpy können dnSpy dekompilierten Anwendungscode bearbeiten und geänderte Module speichern. Wir nutzen dies und schreiben die Program.Main Methode wie folgt um:


 private static void Main(string[] args) { Program.Init(); Program.yy += 18; for (int i = 0; i < 256; i++) { Console.WriteLine(string.Format("j({0}) = {1}", i, Program.j((byte)i))); } } 

Beim Start erhalten wir folgende Werte:


 E:\>bmphide_j.exe j(0) = 206 j(1) = 204 j(2) = 202 j(3) = 200 j(4) = 198 j(5) = 196 j(6) = 194 j(7) = 192 j(8) = 222 j(9) = 220 j(10) = 218 j(11) = 216 j(12) = 214 j(13) = 212 j(14) = 210 j(15) = 208 j(16) = 238 j(17) = 236 j(18) = 234 j(19) = 232 j(20) = 230 ... 

Ersetzen Sie die Aufrufe von Program.j in der Program.j Methode durch die resultierenden Konstanten:


 public static void i(Bitmap bm, byte[] data) { int num = 0; for (int i = 0; i < bm.Width; i++) { for (int j = 0; j < bm.Height; j++) { bool flag = num > data.Length - 1; if (flag) { break; } Color pixel = bm.GetPixel(i, j); int red = ((int)pixel.R & 0xf8) | ((int)data[num] & 0x7); int green = ((int)pixel.G & 0xf8) | (data[num] >> 3 & 0x7); int blue = ((int)pixel.B & 0xfc) | (data[num] >> 6 & 0x3); Color color = Color.FromArgb(0, red, green, blue); bm.SetPixel(i, j, color); num += 1; } } } 

Jetzt wird klar, wie jedes Byte der Nachricht in das Bild eingefügt wird:


  • Die Bits 0 bis 2 werden in die 3 LSBs des roten Kanals des Punktes gelegt
  • Die Bits 3 bis 5 werden in die 3 LSBs des grünen Kanals des Punkts gelegt
  • Die Bits 6 bis 7 werden in die 2 LSBs des blauen Kanals des Punktes gelegt

Als nächstes habe ich versucht, den Algorithmus der Datenkonvertierungsmethode zu wiederholen, aber das Berechnungsergebnis stimmte nicht mit der Ausgabe des Programms überein. Wie sich herausstellte, verfügt Klasse A auch über Funktionen zum Ersetzen von Methoden (in A.VerifySignature(MethodInfo m1, MethodInfo m2) ) und zum Ändern der IL des Methodenbytecodes (in A.IncrementMaxStack ).


Um die Methoden auszuwählen, die in Program in Program.Init , wird der Bytecode IL Methoden gehasht und mit vorberechneten Werten verglichen. Insgesamt werden zwei Methoden ersetzt. Um herauszufinden, welche, führen Sie die Anwendung unter dem Debugger aus, indem Sie Haltepunkte für A.VerifySignature Aufrufe A.VerifySignature , und Sie müssen den Aufruf A.CalculateStack() in A.CalculateStack() überspringen, da Es verhindert das Debuggen.



Infolgedessen können Sie sehen, dass die Program.a Methode durch Program.b und Program.c durch Program.d ersetzt wird.


Jetzt müssen Sie sich mit der Änderung des Bytecodes befassen:


 private unsafe static uint IncrementMaxStack(IntPtr self, A.ICorJitInfo* comp, A.CORINFO_METHOD_INFO* info, uint flags, byte** nativeEntry, uint* nativeSizeOfCode) { bool flag = info != null; if (flag) { MethodBase methodBase = Ac(info->ftn); bool flag2 = methodBase != null; if (flag2) { bool flag3 = methodBase.MetadataToken == 100663317; if (flag3) { uint flNewProtect; A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, 4u, out flNewProtect); Marshal.WriteByte((IntPtr)((void*)info->ILCode), 23, 20); Marshal.WriteByte((IntPtr)((void*)info->ILCode), 62, 20); A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, flNewProtect, out flNewProtect); } else { bool flag4 = methodBase.MetadataToken == 100663316; if (flag4) { uint flNewProtect2; A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, 4u, out flNewProtect2); Marshal.WriteInt32((IntPtr)((void*)info->ILCode), 6, 309030853); Marshal.WriteInt32((IntPtr)((void*)info->ILCode), 18, 209897853); A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, flNewProtect2, out flNewProtect2); } } } } return A.originalDelegate(self, comp, info, flags, nativeEntry, nativeSizeOfCode); } 

Es ist klar, dass Methoden mit bestimmten MetadataToken Werten, nämlich 0x6000015 und 0x6000014 , geändert werden. Diese Token entsprechen den Methoden Program.h und Program.g . dnSpy verfügt über einen integrierten hex Editor, in dem beim dnSpy die Methodendaten hervorgehoben werden: Titel (lila hervorgehoben) und Bytecode (rot hervorgehoben), wie im Screenshot gezeigt. Sie können im hex Editor zur gewünschten Methode hex indem Sie vor der dekompilierten Methode auf die entsprechende Adresse im Kommentar klicken (z. B. File Offset: 0x00002924 ).



Versuchen wir, alle beschriebenen Änderungen anzuwenden: Wir erstellen eine Kopie der Datei, ändern in jedem Hex-Editor die Werte an den erforderlichen Offsets, die wir von dnSpy gelernt dnSpy und ersetzen die Methoden a -> b und c -> d in Program.h . Wir entfernen auch aus Program.Init alle Aufrufe von Modul A Wenn alles richtig gemacht wurde, erhalten wir beim Versuch, mit der geänderten Anwendung eine Nachricht in das Bild einzufügen, das gleiche Ergebnis wie bei der Arbeit der ursprünglichen Anwendung. Die folgenden Screenshots zeigen den dekompilierten Code der Methoden der ursprünglichen und geänderten Anwendungen.




Es bleibt ein inverser Transformationsalgorithmus zu erstellen. Es ist ganz einfach, daher werde ich nur das resultierende Python Skript Python :


 from PIL import Image # Rotate left: 0b1001 --> 0b0011 rol = lambda val, r_bits, max_bits: \ (val << r_bits%max_bits) & (2**max_bits-1) | \ ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits))) # Rotate right: 0b1001 --> 0b1100 ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) rol8 = lambda a, b: rol(a, b, 8) ror8 = lambda a, b: ror(a, b, 8) def extract(fname): img = Image.open(fname) w, h = img.size result = bytearray() for i in range(w): for j in range(h): r, g, b = img.getpixel((i, j)) # print('{:02x} {:02x} {:02x}'.format(r, g, b)) byte = (r & 0b111) | ((g & 0b111) << 3) | ((b & 0b11) << 6) result.append(byte) return result enc = extract('image.bmp') n = len(enc) dec = bytearray() def g(idx): b = ((idx + 1) * 309030853) & 0xff k = ((idx + 2) * 209897853) & 0xff return b ^ k j = 0 for i in range(n): x = enc[i] x = rol8(x, 3) x ^= g(2*i + 1) x = ror8(x, 7) x ^= g(2*i + 0) dec.append(x) with open('output', 'wb') as f: f.write(dec) 

Durch Ausführen dieses Skripts erhalten wir ein weiteres bmp Bild ohne Flag. Wenn Sie den Vorgang wiederholen, erhalten Sie das endgültige Bild mit der Flagge.




0x07 - wopr


Wir haben unsere eigenen Computer-Hacking-Fähigkeiten eingesetzt, um diese KI auf einem militärischen Supercomputer zu "finden". Es ähnelt stark dem Filmklassiker von 1983, WarGames. Vielleicht ahmt das Leben die Kunst nach? Wenn Sie die Startcodes für uns finden, lassen wir Sie zur nächsten Herausforderung übergehen. Wir versprechen, keinen thermonuklearen Krieg zu beginnen.

In Task wird die Konsolenanwendung worp.exe . Um es zu lösen, müssen Sie anscheinend Code abholen.



Die Analyse des Einstiegspunkts zeigt, dass dies ein selbstextrahierendes Archiv ist. Beim Start wird die Umgebungsvariable _MEIPASS2 . Wenn diese Variable nicht vorhanden ist, wird ein temporäres Verzeichnis erstellt, in das der Inhalt des Archivs entpackt wird, und die Anwendung startet erneut mit der angegebenen Umgebungsvariablen _MEIPASS2 . Archivinhalt:


 . ├── api-ms-win-core-console-l1-1-0.dll ├── ... ├── ... ├── api-ms-win-crt-utility-l1-1-0.dll ├── base_library.zip ├── _bz2.pyd ├── _ctypes.pyd ├── _hashlib.pyd ├── libcrypto-1_1.dll ├── libssl-1_1.dll ├── _lzma.pyd ├── pyexpat.pyd ├── python37.dll ├── select.pyd ├── _socket.pyd ├── _ssl.pyd ├── this │  ├── __init__.py │  └── key ├── ucrtbase.dll ├── unicodedata.pyd ├── VCRUNTIME140.dll └── wopr.exe.manifest 1 directory, 56 files 

Dem Inhalt nach zu urteilen, haben wir es mit einer Python Anwendung zu tun, die in exe . Um dies zu unterstützen, finden Sie in der PyMarshal_ReadObjectFromString einen dynamischen Import der entsprechenden Funktionen der Python Bibliothek: PyMarshal_ReadObjectFromString , PyEval_EvalCode und andere. Für die weitere Analyse müssen Sie den Python Bytecode extrahieren. Speichern Sie dazu den Inhalt des Archivs aus dem temporären Verzeichnis und _MEIPASS2 Pfad in die Umgebungsvariable _MEIPASS2 . Führen Sie die PyMarshal_ReadObjectFromString im Debug-Modus aus, indem Sie einen Haltepunkt für die Funktion PyMarshal_ReadObjectFromString . Diese Funktion verwendet als Argumente einen Zeiger auf einen Puffer mit einem serialisierten Python Code und dessen Länge. Wir geben den Inhalt des Puffers bekannter Länge für jeden Aufruf aus. Ich habe nur 2 Anrufe erhalten, während im zweiten das serialisierte Objekt viel größer ist und wir es analysieren werden.


Eine ziemlich einfache Möglichkeit, die empfangenen Daten zu analysieren, besteht darin, sie in das Format von .pyc Dateien (kompilierter Python Bytecode) zu konvertieren und mit uncompyle6 dekompilieren. Dazu reicht es aus, den empfangenen Daten einen 16-Byte-Header hinzuzufügen. Als Ergebnis habe ich die folgende Datei erhalten:


 00000000: 42 0d 0d 0a 00 00 00 00 de cd 57 5d 00 00 00 00 B.........W].... 00000010: e3 00 00 00 00 00 00 00 00 00 00 00 00 09 00 00 ................ 00000020: 00 40 00 00 00 73 3c 01 00 00 64 00 5a 00 64 01 .@...s<...dZd 00000030: 64 02 6c 01 5a 01 64 01 64 02 6c 02 5a 02 64 01 dlZddlZd 

Als nächstes dekompilieren wir die resultierende Datei mit uncompyle6 :


 uncompyle6 task.pyc > task.py 

Wenn wir versuchen, die dekompilierte Datei auszuführen, erhalten wir eine Ausnahme in der Zeile BOUNCE = pkgutil.get_data('this', 'key') . Dies lässt sich leicht beheben, indem einfach der Inhalt der key aus dem Archiv der Variablen BOUNCE wird. LOADING... das Skript erneut ausführen, wird nur die Inschrift LOADING... Anscheinend werden in der Aufgabe einige Techniken verwendet, die eine Dekompilierung verhindern. Wir analysieren den resultierenden Python Code. Ganz am Ende sehen wir den folgenden Zyklus:


 for i in range(256): try: print(lzma.decompress(fire(eye(__doc__.encode()), bytes([i]) + BOUNCE))) except Exception: pass 

Sie können verstehen, dass die print tatsächlich als exec überschrieben wird und ihr Argument nur von __doc__.encode() abhängt __doc__.encode() - der Text am Anfang der Datei. print print try-except . . , __doc__ . __doc__ :


 import marshal with open('pycode1', 'rb') as inp: data = inp.read() code = marshal.loads(data) doc = code.co_consts[0] with open('doc.txt', 'w') as outp: outp.write(doc) 

, __doc__ . , i , . . wrong :


 trust = windll.kernel32.GetModuleHandleW(None) 

, . 0x100000 wrong , . , .


. z3 :


 from z3 import * from stage2 import wrong xor = [212, 162, 242, 218, 101, 109, 50, 31, 125, 112, 249, 83, 55, 187, 131, 206] h = list(wrong()) h = [h[i] ^ xor[i] for i in range(16)] b = 16 * [None] x = [] for i in range(16): x.append(BitVec('x' + str(i), 32)) b[0] = x[2] ^ x[3] ^ x[4] ^ x[8] ^ x[11] ^ x[14] b[1] = x[0] ^ x[1] ^ x[8] ^ x[11] ^ x[13] ^ x[14] b[2] = x[0] ^ x[1] ^ x[2] ^ x[4] ^ x[5] ^ x[8] ^ x[9] ^ x[10] ^ x[13] ^ x[14] ^ x[15] b[3] = x[5] ^ x[6] ^ x[8] ^ x[9] ^ x[10] ^ x[12] ^ x[15] b[4] = x[1] ^ x[6] ^ x[7] ^ x[8] ^ x[12] ^ x[13] ^ x[14] ^ x[15] b[5] = x[0] ^ x[4] ^ x[7] ^ x[8] ^ x[9] ^ x[10] ^ x[12] ^ x[13] ^ x[14] ^ x[15] b[6] = x[1] ^ x[3] ^ x[7] ^ x[9] ^ x[10] ^ x[11] ^ x[12] ^ x[13] ^ x[15] b[7] = x[0] ^ x[1] ^ x[2] ^ x[3] ^ x[4] ^ x[8] ^ x[10] ^ x[11] ^ x[14] b[8] = x[1] ^ x[2] ^ x[3] ^ x[5] ^ x[9] ^ x[10] ^ x[11] ^ x[12] b[9] = x[6] ^ x[7] ^ x[8] ^ x[10] ^ x[11] ^ x[12] ^ x[15] b[10] = x[0] ^ x[3] ^ x[4] ^ x[7] ^ x[8] ^ x[10] ^ x[11] ^ x[12] ^ x[13] ^ x[14] ^ x[15] b[11] = x[0] ^ x[2] ^ x[4] ^ x[6] ^ x[13] b[12] = x[0] ^ x[3] ^ x[6] ^ x[7] ^ x[10] ^ x[12] ^ x[15] b[13] = x[2] ^ x[3] ^ x[4] ^ x[5] ^ x[6] ^ x[7] ^ x[11] ^ x[12] ^ x[13] ^ x[14] b[14] = x[1] ^ x[2] ^ x[3] ^ x[5] ^ x[7] ^ x[11] ^ x[13] ^ x[14] ^ x[15] b[15] = x[1] ^ x[3] ^ x[5] ^ x[9] ^ x[10] ^ x[11] ^ x[13] ^ x[15] solver = Solver() for i in range(16): solver.add(x[i] < 128) for i in range(16): solver.add(b[i] == h[i]) if solver.check() == sat: m = solver.model() print(bytes([m[i].as_long() for i in x])) else: print('unsat') 

, : 5C0G7TY2LWI2YXMB




0x08 — snake


The Flare team is attempting to pivot to full-time twitch streaming video games instead of reverse engineering computer software all day. We wrote our own classic NES game to stream content that nobody else has seen and watch those subscribers flow in. It turned out to be too hard for us to beat so we gave up. See if you can beat it and capture the internet points that we failed to collect.

NES - . FCEUX , .. . , .



, , 0x25 . , . NES - IDA . inesldr . 0x25 . C82A , . 0x33 .



, — 0x32 0x25 . , . , FCEUX . .




0x09 — reloadered


This is a simple challenge, enter the password, receive the key. I hear that it caused problems when trying to analyze it with ghidra. Remember that valid flare-on flags will always end with @flare-on.com

reloaderd.exe , . , , . , , XOR , @FLAG.com , .



, NOP . , , . . , . , , . , , NOP , .


, , XOR , . @flare-on.com , . :


 flag = bytearray(b'D)6\n)\x0f\x05\x1be&\x10\x04+h0/\x003/\x05\x1a\x1f\x0f8\x02\x18B\x023\x1a(\x04*G?\x04&dfM\x107>(>w\x1c?~64*\x00') for i in range(0x539): for j in range(0x34): if (i % 3) == 0 or (i % 7) == 0: flag[j] ^= (i & 0xff) end = b'@flare-on.com' def xor(a, b): return bytes([i^j for i, j in zip(a, b)]) for i in range(len(flag)): print(i, xor(end, flag[i:])) print(xor(flag, b'3HeadedMonkey'*4)) 



0x0A — Mugatu


Hallo

I'm working an incident response case for Derek Zoolander. He clicked a link and was infected with MugatuWare! As a result, his new headshot compilation GIF was encrypted.

To secure an upcoming runway show, Derek needs this GIF decrypted; however, he refuses to pay the ransom.

We received an additional encrypted GIF from an anonymous informant. The informant told us the GIF should help in our decryption efforts, but we were unable to figure it out.

We're reaching out to you, our best malware analyst, in hopes that you can reverse engineer this malware and decrypt Derek's GIF.

I've included a directory full of files containing:
  • MugatuWare malware
  • Ransom note (GIFtToDerek.txt)
  • Encrypted headshot GIF (best.gif.Mugatu)
  • Encrypted informant GIF (the_key_to_success_0000.gif.Mugatu)


Thanks,
Roy

:


  • best.gif.Mugatu
  • GIFtToDerek.txt
  • Mugatuware.exe
  • the_key_to_success_0000.gif.Mugatu

, , GIF -. , .Mugatu . Mugatuware.exe . , — . , , .



IDA , :


 import ida_segment import ida_name import ida_bytes import ida_typeinf idata = ida_segment.get_segm_by_name('.idata') type_map = {} for addr in range(idata.start_ea, idata.end_ea, 4): name = ida_name.get_name(addr) if name: tp = ida_typeinf.idc_get_type(addr) if tp: type_map[name] = tp for addr in range(idata.start_ea, idata.end_ea, 4): imp = ida_bytes.get_dword(addr) if imp != 0: imp_name = ida_name.get_name(imp) name_part = imp_name.split('_')[-1] ida_name.set_name(addr, name_part + '_imp') if name_part in type_map: tp = type_map[name_part] ida_typeinf.apply_decl(addr, tp.replace('(', 'func(') + ';') 

:



, , in-memory PE -. , CrazyPills!!! . , . Sleep , http -. , , , . , , , . .



- :


  • ;
  • ;
  • Mailslots ;
  • really, really, really, ridiculously good looking gifs ;
  • .gif . .Mugatu . GIFtToDerek.txt .

, — 8 . XOR CrazyPills!!! , . , :



XTEA , — BYTE , DWORD . . Python :


 def crypt(a, b, key): i = 0 for _ in range(32): t = (i + key[i & 3]) & 0xffffffff a = (a + (t ^ (b + ((b >> 5) ^ (b << 4))))) & 0xffffffff i = (0x100000000 + i - 0x61C88647) & 0xffffffff t = (i + key[(i >> 11) & 3]) & 0xffffffff b = (b + (t ^ (a + ((a >> 5) ^ (a << 4))))) & 0xffffffff return a, b def decrypt(a, b, key): i = 0xc6ef3720 for _ in range(32): t = (i + key[(i >> 11) & 3]) & 0xffffffff b = (0x100000000 + b - (t ^ (a + ((a >> 5) ^ (a << 4))))) & 0xffffffff i = (i + 0x61C88647) & 0xffffffff t = (i + key[i & 3]) & 0xffffffff a = (0x100000000 + a - (t ^ (b + ((b >> 5) ^ (b << 4))))) & 0xffffffff return a, b 

, the_key_to_success_0000.gif.Mugatu . , . :



, , . C . GIF -.


 #include <stdio.h> #include <unistd.h> void decrypt(unsigned int * inp, unsigned int * outp, unsigned char * key) { unsigned int i = 0xc6ef3720; unsigned int a = inp[0]; unsigned int b = inp[1]; unsigned int t; for(int j = 0; j < 32; j++) { t = i + key[(i >> 11) & 3]; b -= t ^ (a + ((a >> 5) ^ (a << 4))); i += 0x61C88647; t = i + key[i & 3]; a -= t ^ (b + ((b >> 5) ^ (b << 4))); } outp[0] = a; outp[1] = b; } int main() { int fd = open("best.gif.Mugatu", 0); unsigned int inp[2]; unsigned int outp[2]; unsigned int key = 0; read(fd, inp, 8); close(fd); for(unsigned long long key = 0; key < 0x100000000; key++) { if((key & 0xffffff) == 0) { printf("%lf\n", ((double)key) / ((double)0x100000000) * 100.0); } decrypt(inp, outp, &key); if( ((char *)outp)[0] == 'G' && ((char *)outp)[1] == 'I' && ((char *)outp)[2] == 'F' && ((char *)outp)[5] == 'a') { printf("%#llx\n", key); } } } 

0xb1357331 :




0x0B — vv_max


Hey, at least its not subleq.

vv_max.exe , . 256- . AVX2 , vpermd , vpslld . - :


 0000 clear_regs 0001 r0 = 393130324552414c46 0023 r1 = 3030303030303030303030303030303030303030303030303030303030303030 0045 r3 = 1a1b1b1b1a13111111111111111111151a1b1b1b1a1311111111111111111115 0067 r4 = 1010101010101010080408040201101010101010101010100804080402011010 0089 r5 = b9b9bfbf041310000000000000000000b9b9bfbf04131000 00ab r6 = 2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f 00cd r10 = 140014001400140014001400140014001400140014001400140014001400140 00ef r11 = 1100000011000000110000001100000011000000110000001100000011000 0111 r12 = ffffffff0c0d0e08090a040506000102ffffffff0c0d0e08090a040506000102 0133 r13 = ffffffffffffffff000000060000000500000004000000020000000100000000 0155 r16 = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0177 r17 = 6a09e667bb67ae853c6ef372a54ff53a510e527f9b05688c1f83d9ab5be0cd19 0199 r18 = 428a2f9871374491b5c0fbcfe9b5dba53956c25b59f111f1923f82a4ab1c5ed5 01bb r19 = 300000002000000010000000000000007000000060000000500000004 01dd r20 = 0 01ff r21 = 100000001000000010000000100000001000000010000000100000001 0221 r22 = 200000002000000020000000200000002000000020000000200000002 0243 r23 = 300000003000000030000000300000003000000030000000300000003 0265 r24 = 400000004000000040000000400000004000000040000000400000004 0287 r25 = 500000005000000050000000500000005000000050000000500000005 02a9 r26 = 600000006000000060000000600000006000000060000000600000006 02cb r27 = 700000007000000070000000700000007000000070000000700000007 02ed r20 = vpermd(r0, r20) 02f1 r21 = vpermd(r0, r21) 02f5 r22 = vpermd(r0, r22) 02f9 r23 = vpermd(r0, r23) 02fd r24 = vpermd(r0, r24) 0301 r25 = vpermd(r0, r25) 0305 r26 = vpermd(r0, r26) 0309 r27 = vpermd(r0, r27) 030d r7 = vpsrld(r1, 4) 0311 r28 = r20 ^ r21 0315 r28 = r28 ^ r22 0319 r28 = r28 ^ r23 031d r28 = r28 ^ r24 0321 r28 = r28 ^ r25 0325 r28 = r28 ^ r26 0329 r28 = r28 ^ r27 032d r7 = r7 & r6 0331 r29 = vpslld(r17, 7) 0335 r30 = vpsrld(r17, 25) 0339 r15 = r29 | r30 033d r8 = vpcmpeqb(r1, r6) 0341 r29 = vpslld(r17, 21) 0345 r30 = vpsrld(r17, 11) 0349 r29 = r29 | r30 034d r15 = r15 ^ r29 0351 r8 = vpcmpeqb(r1, r6) 0355 r29 = vpslld(r17, 26) 0359 r30 = vpsrld(r17, 6) 035d r29 = r29 | r30 0361 r15 = r15 ^ r29 0365 r29 = r20 ^ r16 0369 r30 = r20 & r18 036d r29 = r29 ^ r30 0371 r15 = add_d(r29, r15) 0375 r20 = add_d(r15, r0) 0379 r7 = add_b(r8, r7) 037d r29 = r20 ^ r28 0381 r17 = vpermd(r29, r19) 0385 r7 = vpshufb(r5, r7) 0389 r29 = vpslld(r17, 7) 038d r30 = vpsrld(r17, 25) 0391 r15 = r29 | r30 0395 r29 = vpslld(r17, 21) 0399 r30 = vpsrld(r17, 11) 039d r29 = r29 | r30 03a1 r15 = r15 ^ r29 03a5 r29 = vpslld(r17, 26) 03a9 r30 = vpsrld(r17, 6) 03ad r29 = r29 | r30 03b1 r15 = r15 ^ r29 03b5 r2 = add_b(r1, r7) 03b9 r29 = r21 ^ r16 03bd r30 = r21 & r18 03c1 r29 = r29 ^ r30 03c5 r15 = add_d(r29, r15) 03c9 r21 = add_d(r15, r0) 03cd r29 = r21 ^ r28 03d1 r17 = vpermd(r29, r19) 03d5 r20 = r20 ^ r21 03d9 r29 = vpslld(r17, 7) 03dd r30 = vpsrld(r17, 25) 03e1 r15 = r29 | r30 03e5 r29 = vpslld(r17, 21) 03e9 r30 = vpsrld(r17, 11) 03ed r29 = r29 | r30 03f1 r15 = r15 ^ r29 03f5 r29 = vpslld(r17, 26) 03f9 r30 = vpsrld(r17, 6) 03fd r29 = r29 | r30 0401 r15 = r15 ^ r29 0405 r7 = vpmaddubsw(r2, r10) 0409 r29 = r22 ^ r16 040d r30 = r22 & r18 0411 r29 = r29 ^ r30 0415 r15 = add_d(r29, r15) 0419 r22 = add_d(r15, r0) 041d r29 = r22 ^ r28 0421 r17 = vpermd(r29, r19) 0425 r20 = r20 ^ r22 0429 r29 = vpslld(r17, 7) 042d r30 = vpsrld(r17, 25) 0431 r15 = r29 | r30 0435 r29 = vpslld(r17, 21) 0439 r30 = vpsrld(r17, 11) 043d r29 = r29 | r30 0441 r15 = r15 ^ r29 0445 r29 = vpslld(r17, 26) 0449 r30 = vpsrld(r17, 6) 044d r29 = r29 | r30 0451 r15 = r15 ^ r29 0455 r2 = vpmaddwd(r7, r11) 0459 r29 = r23 ^ r16 045d r30 = r23 & r18 0461 r29 = r29 ^ r30 0465 r15 = add_d(r29, r15) 0469 r23 = add_d(r15, r0) 046d r29 = r23 ^ r28 0471 r17 = vpermd(r29, r19) 0475 r20 = r20 ^ r23 0479 r29 = vpslld(r17, 7) 047d r30 = vpsrld(r17, 25) 0481 r15 = r29 | r30 0485 r29 = vpslld(r17, 21) 0489 r30 = vpsrld(r17, 11) 048d r29 = r29 | r30 0491 r15 = r15 ^ r29 0495 r29 = vpslld(r17, 26) 0499 r30 = vpsrld(r17, 6) 049d r29 = r29 | r30 04a1 r15 = r15 ^ r29 04a5 r29 = r24 ^ r16 04a9 r30 = r24 & r18 04ad r29 = r29 ^ r30 04b1 r15 = add_d(r29, r15) 04b5 r24 = add_d(r15, r0) 04b9 r29 = r24 ^ r28 04bd r17 = vpermd(r29, r19) 04c1 r20 = r20 ^ r24 04c5 r29 = vpslld(r17, 7) 04c9 r30 = vpsrld(r17, 25) 04cd r15 = r29 | r30 04d1 r29 = vpslld(r17, 21) 04d5 r30 = vpsrld(r17, 11) 04d9 r29 = r29 | r30 04dd r15 = r15 ^ r29 04e1 r29 = vpslld(r17, 26) 04e5 r30 = vpsrld(r17, 6) 04e9 r29 = r29 | r30 04ed r15 = r15 ^ r29 04f1 r29 = r25 ^ r16 04f5 r30 = r25 & r18 04f9 r29 = r29 ^ r30 04fd r15 = add_d(r29, r15) 0501 r25 = add_d(r15, r0) 0505 r29 = r25 ^ r28 0509 r17 = vpermd(r29, r19) 050d r20 = r20 ^ r25 0511 r2 = vpshufb(r2, r12) 0515 r29 = vpslld(r17, 7) 0519 r30 = vpsrld(r17, 25) 051d r15 = r29 | r30 0521 r29 = vpslld(r17, 21) 0525 r30 = vpsrld(r17, 11) 0529 r29 = r29 | r30 052d r15 = r15 ^ r29 0531 r29 = vpslld(r17, 26) 0535 r30 = vpsrld(r17, 6) 0539 r29 = r29 | r30 053d r15 = r15 ^ r29 0541 r29 = r26 ^ r16 0545 r30 = r26 & r18 0549 r29 = r29 ^ r30 054d r15 = add_d(r29, r15) 0551 r26 = add_d(r15, r0) 0555 r29 = r26 ^ r28 0559 r17 = vpermd(r29, r19) 055d r20 = r20 ^ r26 0561 r29 = vpslld(r17, 7) 0565 r30 = vpsrld(r17, 25) 0569 r15 = r29 | r30 056d r29 = vpslld(r17, 21) 0571 r30 = vpsrld(r17, 11) 0575 r29 = r29 | r30 0579 r15 = r15 ^ r29 057d r29 = vpslld(r17, 26) 0581 r30 = vpsrld(r17, 6) 0585 r29 = r29 | r30 0589 r15 = r15 ^ r29 058d r2 = vpermd(r2, r13) 0591 r29 = r27 ^ r16 0595 r30 = r27 & r18 0599 r29 = r29 ^ r30 059d r15 = add_d(r29, r15) 05a1 r27 = add_d(r15, r0) 05a5 r29 = r27 ^ r28 05a9 r17 = vpermd(r29, r19) 05ad r20 = r20 ^ r27 05b1 r19 = ffffffffffffffffffffffffffffffffffffffffffffffff 05d3 r20 = r20 & r19 05d7 r31 = 2176620c3a5c0f290b583618734f07102e332623780e59150c05172d4b1b1e22 

FLARE2019 . , , . , FLARE2019 . r2 r20 . , r20 . r2 — 6 r2 . , 6 . Frida :


 # vvmax.py from __future__ import print_function import frida import string import hexdump def check(val): global gdata with open('vvmax.js', 'r') as f: script_src = f.read() pid = frida.spawn(['vv_max.exe', 'FLARE2019', val.ljust(32, 'a')]) session = frida.attach(pid) script = session.create_script(script_src) def handler(message, data): handler.data = data script.on('message', handler) script.load() frida.resume(pid) while not hasattr(handler, 'data'): pass session.detach() return handler.data alph = string.printable def to_bits(x): return ''.join(bin(ord(i))[2:].zfill(8) for i in x) target = to_bits('pp\xb2\xac\x01\xd2^a\n\xa7*\xa8\x08\x1c\x86\x1a\xe8E\xc8)\xb2\xf3\xa1\x1e\x00\x00\x00\x00\x00\x00\x00\x00') password = '' while len(password) != 32: for c in alph: data = to_bits(check(password + c)) i = 6*len(password + c) if data[:i] == target[:i]: password += c i += 1 break print() print('----->', `password`) print() 

 // vvmax.js var modules = Process.enumerateModules(); var base = modules[0].base; Interceptor.attach(base.add(0x1665), function() { var p = this.context.rdx.add(0x840); var res = p.readByteArray(32); send(null, res); }); 

:




0x0C — help


You're my only hope FLARE-On player! One of our developers was hacked and we're not sure what they took. We managed to set up a packet capture on the network once we found out but they were definitely already on the system. I think whatever they installed must be buggy — it looks like they crashed our developer box. We saved off the dump file but I can't make heads or tails of it — PLEASE HELP!!!!!!

. RAM - . 4444 , 6666 , 7777 8888 . , , , RAM -. volatility . volatility Win10x64_15063 , , Win7SP1x64 , .


volatility :


 $ volatility --profile Win7SP1x64 -f help.dmp modules Volatility Foundation Volatility Framework 2.6 Offset(V) Name Base Size File ------------------ -------------------- ------------------ ------------------ ---- 0xfffffa800183e890 ntoskrnl.exe 0xfffff80002a49000 0x5e7000 \SystemRoot\system32\ntoskrnl.exe ... 0xfffffa800428ff30 man.sys 0xfffff880033bc000 0xf000 \??\C:\Users\FLARE ON 2019\Desktop\man.sys 

:


 $ volatility --profile Win7SP1x64 -f help.dmp moddump --base 0xfffff880033bc000 -D drivers Volatility Foundation Volatility Framework 2.6 Module Base Module Name Result ------------------ -------------------- ------ 0xfffff880033bc000 man.sys Error: e_magic 0000 is not a valid DOS signature. 

, . volshell .


 $ volatility --profile Win7SP1x64 -f help.dmp volshell In [1]: db(0xfffff880033bc000) 0xfffff880033bc000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ In [2]: db(0xfffff880033bc000 + 0x1100) 0xfffff880033bd100 01 48 8b 4c 24 20 48 8b 44 24 28 48 89 41 08 48 .HL$.HD$(HAH 0xfffff880033bd110 83 c4 18 c3 cc cc cc cc cc cc cc cc cc cc cc cc ................ 0xfffff880033bd120 48 89 4c 24 08 48 83 ec 38 48 8b 44 24 40 0f be HL$.H..8H.D$@.. 0xfffff880033bd130 48 43 48 8b 44 24 40 0f be 40 42 83 c0 01 3b c8 HCH.D$@..@B...;. 0xfffff880033bd140 7e 27 45 33 c9 41 b8 15 5b 00 00 48 8d 15 de 44 ~'E3.A..[..H...D 0xfffff880033bd150 00 00 48 8d 0d 07 45 00 00 ff 15 71 4f 00 00 c7 ..H...E....qO... 0xfffff880033bd160 44 24 20 00 00 00 00 eb 08 c7 44 24 20 01 00 00 D$........D$.... 0xfffff880033bd170 00 48 8b 44 24 40 48 8b 80 b8 00 00 00 48 83 c4 .HD$@H......H.. In [4]: man = addrspace().read(0xfffff880033bc000, 0xf000) In [5]: with open('man_writeup.sys', 'wb') as f: ...: f.write(man) ...: 

, , moddump . . . - , , .


RC4 . , .


user-space . DLL - . DLL - ( m.dll ), . , . :


  • ( +0x8 )
  • _EPROCESS ( +0x68 )
  • ( +0x48 )
  • ( +0x58 )

DLL - RC4 , 0x2c - , 0x48 .


volatility volshell :


 import struct from Crypto.Cipher import ARC4 head = 0xfffff880033c8158 krnl = addrspace() def u64(x): return struct.unpack('Q', x)[0] fd = u64(krnl.read(head, 8)) while True: proc_addr = u64(krnl.read(fd + 0x68, 8)) base = u64(krnl.read(fd + 0x48, 8)) key = krnl.read(fd + 0x48, 0x2c) sz = u64(krnl.read(fd + 0x58, 8)) fd = u64(krnl.read(fd, 8)) p = obj.Object('_EPROCESS', proc_addr, krnl) print p.ImageFileName.v(), hex(proc_addr), hex(base), hex(sz) proc_space = p.get_process_address_space() dump = proc_space.read(base, sz) if dump[:0x100] == '\x00' * 0x100: dump = ARC4.new(key).decrypt(dump) with open('proc_{:016x}'.format(base), 'wb') as f: f.write(dump) if fd == head: break 

, , RC4 . IDA , , :


IDA
 from __future__ import print_function import sys import re from idaapi import get_func, decompile, get_name_ea, auto_wait, BADADDR from idaapi import cot_call, cot_obj, init_hexrays_plugin, qexit import ida_typeinf import ida_lines def rc4(key, data): S = list(range(256)) j = 0 for i in list(range(256)): j = (j + S[i] + ord(key[i % len(key)])) % 256 S[i], S[j] = S[j], S[i] j = 0 y = 0 out = [] for char in data: j = (j + 1) % 256 y = (y + S[j]) % 256 S[j], S[y] = S[y], S[j] out.append(chr(ord(char) ^ S[(S[j] + S[y]) % 256])) return ''.join(out) def decrypt_stack_str_args(ea): func = get_func(ea) if func is None: return try: c_func = decompile(func) c_func.pseudocode except Exception as ex: return for citem in c_func.treeitems: citem = citem.to_specific_type if citem.is_expr() and\ citem.op == cot_call and\ citem.ea == ea: args = [] key = citem.a[0] key_len = citem.a[1] s = citem.a[2] s_len = citem.a[3] def get_var_idx(obj): while obj.opname != 'var': if obj.opname in ('ref', 'cast'): obj = obj.x else: raise Exception('can\'t find type') return obj.v.idx if key_len.opname != 'num' or s_len.opname != 'num': print('[!] can\'t get length: 0x{:08x}'.format(ea)) else: try: key_len_val = key_len.n._value s_len_val = s_len.n._value print('0x{:08x}'.format(ea), 'key_len =', key_len_val, ', s_len =', s_len_val) hx_view = idaapi.open_pseudocode(ea, -1) key_var_stkoff = hx_view.cfunc.get_lvars()[get_var_idx(key)].location.stkoff() s_var_stkoff = hx_view.cfunc.get_lvars()[get_var_idx(s)].location.stkoff() key_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == key_var_stkoff][0] tif = ida_typeinf.tinfo_t() ida_typeinf.parse_decl(tif, None, 'unsigned __int8 [{}];'.format(key_len_val), 0) hx_view.set_lvar_type(key_var, tif) s_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == s_var_stkoff][0] tif = ida_typeinf.tinfo_t() ida_typeinf.parse_decl(tif, None, 'unsigned __int8 [{}];'.format(s_len_val + 1), 0) hx_view.set_lvar_type(s_var, tif) key_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == key_var_stkoff][0] s_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == s_var_stkoff][0] key_regex = re.compile('{}\[(.+)\] = (.+);'.format(key_var.name)) s_regex = re.compile('{}\[(.+)\] = (.+);'.format(s_var.name)) key = bytearray(key_len_val) s = bytearray(s_len_val + 1) src = '\n'.join([ida_lines.tag_remove(i.line) for i in hx_view.cfunc.pseudocode]) for i, j in s_regex.findall(src): s[int(i)] = (0x100 + int(j)) & 0xff for i, j in key_regex.findall(src): key[int(i)] = (0x100 + int(j)) & 0xff key = ''.join(chr(i) for i in key) s = ''.join(chr(i) for i in s) result = rc4(key, s[:-1]) # unicode to ascii if set(ord(i) for i in result[1::2]) == {0}: result = 'wide_' + ''.join(result[0::2]) hx_view.rename_lvar(s_var, 's_' + result, True) except Exception as ex: print('[!] error: {}'.format(ex)) print('#### decryption helper script ####') xref_to = get_name_ea(BADADDR, 'decrypt_stack_str') xref_from = get_first_cref_to(xref_to) while xref_from != BADADDR: print('### 0x{:08x}'.format(xref_from)) decrypt_stack_str_args(xref_from) xref_from = get_next_cref_to(xref_to, xref_from) 

:



. :


  • m.dll — , . 4444 . — ;
  • n.dll192.168.1.243 ;
  • c.dllRC4 . ;
  • k.dll — (keylogger);
  • s.dll — ;
  • f.dll — .

, XOR 8. 4444 , .. . : , — . , - .


( 4444 ) . , - . , . :


  • keys.kdb
  • C:\
  • C:\keypass\keys.kdb

, f.dll : keys.kdb , .


6666 . LZNT1 RC4 XOR . , XOR - , .. . RC4 , RAM -: FLARE ON 2019 . , GetUserNameA , , - , RC4 . LZNT1 :


 from ctypes import * nt = windll.ntdll for fname in ['input']: with open(fname, 'rb') as f: buf = f.read() dec_data = create_string_buffer(0x10000) final_size = c_ulong(0) status = nt.RtlDecompressBuffer( 0x102, # COMPRESSION_FORMAT_LZNT1 dec_data, # UncompressedBuffer 0x10000, # UncompressedBufferSize c_char_p(buf), # CompressedBuffer 0xFFFFFF, # CompressedBufferSize byref(final_size) # FinalUncompressedSize ) with open(fname + '.uncompressed', 'wb') as f: f.write(dec_data.raw[:final_size.value]) 

6666 . :


 00000000: CC 69 94 FA 6A 37 18 29 CB 8D 87 EF 11 63 8E 73 .i..j7.).....cs 00000010: FE AB 43 3B B3 94 28 4B 4D 19 00 00 00 4F DB C7 ..C;..(KM....O.. 00000020: F3 1E E4 13 15 34 8F 51 A9 2B C2 D7 C1 96 78 F7 .....4.Q.+....x. 00000030: 91 98 

, :


 00000000: 19 00 00 00 4F DB C7 F3 1E E4 13 15 34 8F 51 A9 ....O.......4.Q. 00000010: 2B C2 D7 C1 96 78 F7 91 98 +....x... 

4 — , 25. :


 00000000: 12 B0 00 43 3A 5C 6B 65 79 70 61 04 73 73 01 70 ...C:\keypa.ss.p 00000010: 73 2E 6B 64 62 s.kdb 

C:\keypass\keys.kdb . , , . 6666KeePass .


7777 BMP . XOR , , , .. . , , KeePass .




8888 k.dll — .


 C:\Windows\system32\cmd.exe nslookup googlecom ping 1722173110 nslookup soeblogcom nslookup fiosquatumgatefiosrouterhome C:\Windows\system32\cmd.exe Start Start menu Start menu chrome www.flare-on.com - Google Chrome tis encrypting something twice better than once Is encrypting something twice better than once? - Google Search - Google Chrome Start Start menu Start menu keeKeePass <DYN_TITLE> th1sisth33nd111 KeePass keys.kdb - KeePass Is encrypting something twice better than once? - Google Search - Google Chrome Start Start menu Start menu KeePass <DYN_TITLE> th1sisth33nd111 Open Database - keys.kdb KeePass Start Start menu Start menu KeePass Start menu Start menu Start menu KeePass <DYN_TITLE> th1sisth33nd111 

th1sisth33nd111 , . , . , keylogger . , , ping . hashcat KeePass , . :


 $ strings help.dmp | grep -i '3nd!' !s_iS_th3_3Nd!!! 

Th .



. , -.



0x0D —


, . , , . , volatility , . ( UTC+3:00):


Source: https://habr.com/ru/post/de469393/


All Articles