RĂ©daction sur Flare-On 2019



-0x01 - Intro


Cet article est consacré à l'analyse de toutes les tâches de Flare-On 2019 - le concours annuel de rétro-ingénierie de FireEye. Dans ces compétitions, je participe pour la deuxième fois. L'année précédente, j'ai réussi à atteindre la 11e place en termes de temps de réalisation, après avoir résolu tous les problèmes en 13 jours environ. Cette année, l'ensemble des tâches a été plus facile, et je me suis retrouvé en 54 heures, prenant en même temps 3 place en terme de livraison.


Dans cet article, j'ai essayé de décrire ces moments qui ont suscité mon plus grand intérêt, par conséquent, l'analyse ne décrira pas la routine de travail dans l'IDA, la compréhension des algorithmes de chaque fonction et d'autres points peu intéressants. J'espère qu'après avoir lu ceci, vous trouverez quelque chose de nouveau et d'utile pour vous. Vous pouvez trouver l'analyse des problèmes des auteurs, ainsi que des statistiques et des prix pour les gagnants ici .


Si vous êtes intéressé, alors bienvenue au chat!


0x00 - Contenu


  1. 0x01 - Memecat Battlestation [Édition de démonstration Shareware]
  2. 0x02 - Overlong
  3. 0x03 - Flarebear
  4. 0x04 - Dnschess
  5. 0x05 - démo
  6. 0x06 - bmphide
  7. 0x07 - wopr
  8. 0x08 - serpent
  9. 0x09 - rechargé
  10. 0x0A - Mugatu
  11. 0x0B - vv_max
  12. 0x0C - aide
  13. 0x0D - Résumé


0x01 - Memecat Battlestation [Édition de démonstration Shareware]


Bienvenue au sixième défi Flare-On!

Ceci est un jeu simple. Effectuer une ingénierie inverse pour déterminer les "codes d'armes" que vous devez entrer pour vaincre chacun des deux ennemis et l'écran de victoire révélera le drapeau. Entrez le drapeau ici sur ce site pour marquer et passer au niveau suivant.

* Ce défi est écrit en .NET. Si vous n'avez pas encore d'outil de rétro-ingénierie .NET préféré, je recommande dnSpy

** Si vous avez déjà résolu la version complète de ce jeu sur notre stand à BlackHat ou la version ultérieure sur Twitter, félicitations, entrez le drapeau de l'écran de victoire maintenant pour contourner ce niveau.

Cette tâche a été définie à l'avance dans le cadre de Black Hat USA 2019, à peu près au moment où je l'ai décidé. Je ne me souviens pas comment il l'a résolu La tâche est assez simple, nous n'envisagerons donc pas sa solution.



0x02 - Overlong


Le secret de ce prochain défi est intelligemment caché. Cependant, avec la bonne approche, trouver la solution ne prendra pas trop de temps.

Étant donné le fichier x86 .exe. Lorsque vous essayez de démarrer, un message s'affiche avec le contenu suivant:



Lors de l'analyse de l'application, vous pouvez constater que le message est stocké dans un certain codage avec une longueur de caractère variable (de 1 à 4 octets). Lorsque la fonction de décodage est appelée, elle reçoit la longueur du résultat attendu, qui est plus courte que le message lui-même, c'est pourquoi l'indicateur n'est pas visible. Vous pouvez corriger la valeur de longueur passée à la fonction en mode débogage et obtenir le message complet avec l'indicateur:



Vous pouvez également réécrire l'algorithme de décodage en Python et obtenir un indicateur:


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


Chez Flare, nous avons créé notre propre animal de compagnie Tamagotchi, le flarebear. Il est très pointilleux. Gardez-le vivant et heureux et il vous donnera le drapeau.

Dans cette tâche, un fichier apk pour Android . Envisagez une méthode de solution sans lancer l'application elle-même.


La première étape consiste à obtenir le code source de l'application. Pour ce faire, à l'aide de l'utilitaire dex2jar convertissez apk en jar puis récupérez le code source Java à l'aide du décompilateur, que je préfère utiliser cfr .


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

En analysant le code source de l'application, vous pouvez trouver une méthode .danceWithFlag() intéressante, qui se trouve dans le fichier FlareBearActivity.java . Dans .danceWithFlag() , les ressources raw de l'application sont déchiffrées à l'aide de la .decrypt(String, byte[]) , dont le premier argument est la chaîne obtenue à l'aide de la méthode .getPassword() . Le drapeau est sûrement dans les ressources cryptées, alors essayons de les décrypter. Pour ce faire, j'ai décidé de réécrire un peu le code décompilé, en supprimant Android dépendances Android et en ne laissant que les méthodes nécessaires au déchiffrement, afin que le code résultant puisse être compilé. De plus, au cours de l'analyse, il a été constaté que la méthode .getPassword() dépend de trois valeurs d'état entier. Chaque valeur se situe dans un petit intervalle de 0 à N , vous pouvez donc parcourir toutes les valeurs possibles à la recherche du mot de passe souhaité.


Le résultat est le code suivant:


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); } } 

Nous allons extraire les ressources chiffrées, compiler et exécuter le fichier résultant:


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

Heureusement, une seule de toutes les options de mot de passe que vous sélectionnez est appropriée. En conséquence, nous obtenons deux images avec un drapeau:


 ~/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


Un trafic réseau suspect nous a conduits à ce programme d'échecs non autorisé exécuté sur un bureau Ubuntu. Cela semble être le travail des pirates informatiques du cyberespace. Vous devrez prendre les bonnes mesures pour résoudre celui-ci. Bonne chance!

Cette tâche contient un vidage du trafic, un fichier exécutable ELF ChessUI et une bibliothèque ChessAI.so . En exécutant le fichier exécutable, vous pouvez voir l'échiquier.



Commençons l'analyse par un vidage du trafic.



Tout le trafic consiste en des requêtes vers un serveur DNS type A Les requêtes elles-mêmes consistent en les noms des pièces, la description du coup dans le jeu d'échecs et la partie constante de .game-of-thrones.flare-on.com , par exemple rook-c3-c6.game-of-thrones.flare-on.com . Par la partie constante, vous pouvez facilement trouver le bon endroit dans la bibliothèque ChessAI.so :


 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; } 

Le code montre que, sur la base des adresses ip reçues, une certaine chaîne d'octets est déchiffrée, qui est stockée dans une autre zone de mémoire, que j'ai appelée flag .


Pour résoudre la tâche, la première chose à faire est d'obtenir toutes les adresses ip du vidage de trafic. Vous pouvez le faire avec la commande suivante:


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

ips enregistré toutes les adresses ip dans le fichier ips , ips pouvez utiliser le code Python suivant pour obtenir l'indicateur:


 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 - démo


Un membre de l'équipe de Flare a essayé de nous impressionner par ses compétences en matière de démoscène. Cela semble vide. Voyez si vous pouvez le découvrir ou peut-être que nous devrons les renvoyer. Pas de pression.

Étant donné le fichier exécutable 4k.exe , qui utilise DirectX . Une fois lancé, le logo FlareOn rotatif s'affiche dans la fenêtre principale.



L'analyse statique du programme révèle une seule fonction, qui est le point d'entrée. Dans le contenu, la fonction ressemble à l'implémentation du décryptage de code. Nous ne perdrons pas de temps à analyser l'algorithme de cette fonction, il suffit de mettre un point d'arrêt sur l'instruction ret et de voir où le contrôle est transféré. Après retour, nous nous retrouvons à l'adresse 0x00420000 , le code auquel est démonté comme quelque chose de suffisant:



Il a ensuite été décidé de transférer ce code du mode débogage vers la base de données IDA à l'aide de l' API et de poursuivre l'analyse statique.


Le nouveau code au début importe les fonctions nécessaires à partir de diverses bibliothèques. Le tableau de ces fonctions peut également être restauré en dynamique. Le résultat est l'ensemble de fonctions suivant:



Le "vrai" point d'entrée du programme sera:



Notez la création de DeviceInterface type IDirect3DDevice9 ** . A l'avenir, cette interface est activement utilisée, et pour simplifier l'inverse, il est nécessaire de définir un tableau de ses méthodes. Il a été possible de trouver la définition de l'interface assez rapidement, par exemple ici . J'ai analysé ce tableau et l'ai converti en une structure pour l' IDA . L'application du type résultant à DeviceInterface peut considérablement simplifier l'analyse du code. Les captures d'écran suivantes montrent le résultat du décompilateur pour la fonction principale du cycle de rendu de scène avant et après l'application du type.




Après une analyse plus approfondie, il a été constaté que deux maillages polygonaux (maillage, maillage polygonal) sont créés dans le programme, bien que lorsque le programme est en cours d'exécution, nous ne voyons qu'un seul objet. De plus, lors de la construction de grilles, leurs sommets sont chiffrés à l'aide de XOR , ce qui soulève également des soupçons. Décryptons et visualisons les sommets. La deuxième grille est la plus intéressante, car il a beaucoup plus de sommets. matplotlib tous les sommets, j'ai trouvé que la coordonnée Z de chacun d'eux est 0, donc pour la visualisation, il a été décidé de dessiner des graphiques bidimensionnels en utilisant matplotlib . Le code et le résultat suivants avec un indicateur se sont avérés:


 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 a parcouru le mont. Elbert (la plus haute montagne du Colorado) à 2h du matin pour capturer cette photo au moment idéal. Ne sautez jamais le jour de la jambe. Nous avons trouvé cette photo et cet exécutable sur une clé USB qu'il a laissée au début du sentier. Peut-on lui faire confiance?

Dans la tâche, le fichier exécutable bmphide.exe et l'image image.bmp sont image.bmp . On peut supposer que certains messages sont cachés dans l'image à l'aide de méthodes de stéganographie.


Le binaire est écrit en C# , j'ai donc utilisé l'utilitaire dnSpy pour l'analyse. Vous pouvez immédiatement remarquer que la plupart des noms des méthodes sont obscurcis. Si vous regardez la méthode Program.Main , vous pouvez comprendre la logique du programme et faire des hypothèses sur le but de certains d'entre eux:


 // 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); } 

  • L'application est initialisĂ©e Ă  l'aide de la mĂ©thode Program.Init()
  • Lire le fichier de donnĂ©es et le fichier image
  • En utilisant la mĂ©thode byte [] Program.h(byte []) , certaines conversions de donnĂ©es sont effectuĂ©es
  • En utilisant la mĂ©thode Program.i(Bitmap, byte[]) , les donnĂ©es converties sont insĂ©rĂ©es dans l'image
  • L'image rĂ©sultante est enregistrĂ©e avec un nouveau nom.

Lorsque l'application est initialisée, différentes méthodes de classe A appelées. Une analyse superficielle de la classe a montré la similitude de certaines de ses méthodes avec les méthodes de l'obfuscateur ConfuserEx (fichier AntiTamper.JIT.cs ). L'application est vraiment protégée contre le débogage. Dans le même temps, il n'a pas été possible de retirer les mécanismes de protection à l'aide de l'utilitaire de4dot et de ses fourches, il a donc été décidé de poursuivre l'analyse.


Considérez la méthode Program.i , qui est utilisée pour insérer des données dans une image.


 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); } } } 

Cependant, très similaire au LSB classique, dans les endroits où des constantes sont attendues, la méthode int Program.j(byte) est utilisée. Le résultat de son travail dépend des différentes valeurs globales obtenues, y compris lors de l'initialisation dans la méthode Program.Init() . Il a été décidé de ne pas inverser son travail, mais d'obtenir toutes les valeurs possibles lors de l'exécution. dnSpy permet de modifier le code d'application décompilé et d'enregistrer les modules modifiés. Nous en profitons et Program.Main méthode Program.Main comme suit:


 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))); } } 

Au démarrage, nous obtenons les valeurs suivantes:


 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 ... 

Remplacez les appels à Program.j dans la méthode Program.j par les constantes résultantes:


 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; } } } 

Maintenant, il devient clair comment insérer chaque octet du message dans l'image:


  • les bits 0 Ă  2 sont placĂ©s dans les 3 LSB du canal rouge du point
  • les bits 3 Ă  5 sont placĂ©s dans les 3 LSB du canal vert du point
  • les bits 6 Ă  7 sont placĂ©s dans les 2 LSB du canal bleu du point

Ensuite, j'ai essayé de répéter l'algorithme de la méthode de conversion des données, mais le résultat du calcul ne correspondait pas à la sortie du programme. Il s'est avéré que la classe A également des fonctionnalités pour remplacer les méthodes (dans A.VerifySignature(MethodInfo m1, MethodInfo m2) ) et modifier le code d'octet IL des méthodes (dans A.IncrementMaxStack ).


Pour sélectionner les méthodes qui doivent être remplacées dans Program , dans Program.Init , l' IL bytecode IL méthodes est haché et comparé aux valeurs pré-calculées. Au total, deux méthodes sont remplacées. Pour savoir lesquels, exécutez l'application sous le débogueur en définissant des points d'arrêt sur les appels A.VerifySignature et vous devez ignorer l'appel A.CalculateStack() dans Program.Init , car Il empêche le débogage.



Par conséquent, vous pouvez voir que la méthode Program.a est remplacée par Program.b et Program.c est remplacée par Program.d .


Vous devez maintenant gérer la modification du bytecode:


 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); } 

Il est clair que les méthodes avec des valeurs MetadataToken spécifiques, à savoir 0x6000015 et 0x6000014 , seront modifiées. Ces jetons correspondent aux méthodes Program.h et Program.g . dnSpy dispose d'un éditeur hex intégré dans lequel, lors du survol, les données de la méthode sont mises en évidence: leur titre (surligné en violet) et leur code d'octet (surligné en rouge), comme indiqué dans la capture d'écran. Vous pouvez accéder à la méthode souhaitée dans l'éditeur hex en cliquant sur l'adresse correspondante dans le commentaire avant la méthode décompilée (par exemple, File Offset: 0x00002924 ).



Essayons d'appliquer toutes les modifications décrites: nous créerons une copie du fichier, dans n'importe quel éditeur hexadécimal, nous changerons les valeurs aux décalages nécessaires, que nous avons appris de dnSpy et nous remplacerons les méthodes a a -> b et c -> d dans Program.h . Nous Program.Init également de Program.Init tous les appels au module A Si tout est fait correctement, lorsque nous essayons d'insérer un message dans l'image à l'aide de l'application modifiée, nous obtenons le même résultat que lorsque l'application d'origine fonctionnait. Les captures d'écran ci-dessous montrent le code décompilé des méthodes des applications originales et modifiées.




Il reste à créer un algorithme de transformation inverse. C'est assez simple, donc je ne donnerai que le script Python résultant:


 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) 

En exécutant ce script, nous obtenons une autre image bmp sans indicateur. En répétant la procédure dessus, nous obtenons l'image finale avec le drapeau.




0x07 - wopr


Nous avons utilisé nos propres compétences en piratage informatique pour "trouver" cette IA sur un supercalculateur militaire. Il ressemble fortement au film classique WarGames de 1983. Peut-être que la vie imite l'art? Si vous pouvez trouver les codes de lancement pour nous, nous vous laisserons passer au prochain défi. Nous nous engageons à ne pas déclencher une guerre thermonucléaire.

En tâche, l'application console worp.exe . Apparemment, pour le résoudre, vous devez récupérer du code.



L'analyse du point d'entrée montre qu'il s'agit d'une archive auto-extractible. Au démarrage, la variable d'environnement _MEIPASS2 . Si cette variable n'existe pas, un répertoire temporaire est créé dans lequel le contenu de l'archive est décompressé, et l'application redémarre avec la variable d'environnement donnée _MEIPASS2 . Contenu de l'archive:


 . ├── 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 

A en juger par le contenu, nous avons affaire à une application Python conditionnée en exe . À l'appui de cela, dans le binaire principal, vous pouvez trouver l'importation dynamique des fonctions correspondantes de la bibliothèque Python : PyMarshal_ReadObjectFromString , PyEval_EvalCode et autres. Pour une analyse plus approfondie, vous devez extraire le bytecode Python . Pour ce faire, enregistrez le contenu de l'archive à partir du répertoire temporaire et _MEIPASS2 chemin d'accès dans la variable d'environnement _MEIPASS2 . Exécutez le binaire principal en mode débogage en définissant un point d'arrêt sur la fonction PyMarshal_ReadObjectFromString . Cette fonction prend comme arguments un pointeur vers un tampon avec un code Python sérialisé et sa longueur. Nous vidons le contenu du tampon de longueur connue pour chacun des appels. Je n'ai reçu que 2 appels, alors que dans le second, l'objet sérialisé est beaucoup plus gros, et nous allons l'analyser.


Une manière assez simple d'analyser les données obtenues consiste à les convertir au format de fichiers .pyc (bytecode Python compilé) et à les décompiler en utilisant uncompyle6 . Pour ce faire, il suffit d'ajouter un en-tête de 16 octets aux données reçues. En conséquence, j'ai obtenu le fichier suivant:


 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 

Ensuite, nous décompilons le fichier résultant en utilisant uncompyle6 :


 uncompyle6 task.pyc > task.py 

Si nous essayons d'exécuter le fichier décompilé, nous obtiendrons une exception dans la ligne BOUNCE = pkgutil.get_data('this', 'key') . Ceci est facilement résolu en affectant simplement le contenu du fichier key de l'archive à la variable BOUNCE . En relançant le script, nous ne verrons que l'inscription LOADING... Apparemment, certaines techniques sont utilisées dans la tâche qui empêchent la décompilation. Nous procédons à l'analyse du code Python résultant. À la toute fin, nous voyons le cycle suivant:


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

Vous pouvez comprendre que la fonction d' print est en fait remplacée par exec , et son argument dépend uniquement de __doc__.encode() - le texte au début du fichier.Au début de l'exécution du code, enregistrez la fonction printsous un nom différent et remplacez-la printdans le bloc try-except. Lorsque nous exécutons le script résultant, rien ne nous sera de nouveau affiché. La décompilation __doc__a peut-être été enregistrée incorrectement. Essayons d'extraire la valeur __doc__directement du code sérialisé comme suit:


 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) 

Exécutez à nouveau le script en remplaçant le contenu __doc__. Par conséquent, à une certaine valeur i, le code s'affiche avec succès à l'écran. Nous l'enregistrons dans un nouveau fichier et l'analysons. Dans la fonction, wrongvous pouvez trouver la ligne suivante:


 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


Bonjour

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)


Merci,
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.dll — 192.168.1.243 ;
  • c.dll — RC4 . ;
  • 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 . , , . 6666 — KeePass .


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/fr469393/


All Articles