Comment j'ai failli attraper un virus en essayant de vendre des bottes



Je fais partie de ces gens qui, avec le début de l'automne, essaient de passer moins de temps dans la rue. À Moscou, ce n'est pas difficile: vous êtes limité à la route du domicile au bureau et retour. Cependant, le temps humide peut causer de l'inconfort dans la pièce, surtout si votre lieu de travail, comme le mien, est à la fenêtre, et qu'un collègue sur deux, se plaignant de congestion, demande à aérer le bureau. Afin de ne pas tomber dans la rate, cet automne j'ai mis à jour la garde-robe.

En pensant au sort des choses inutiles, je me suis demandé quoi en faire: les jeter, les couper en lambeaux, les donner à mon petit frère pour les transporter? Mais pour une chose, aucune de ces méthodes ne convenait: il s'agissait de bottes en cuir 44 d'une taille décente, mais elles me dérangeaient dans l'ordre. J'ai décidé de les vendre sur Avito. J'ai téléchargé des photos, indiqué un faux nom (la sécurité de l'information est la même), j'ai mis mes bottes, quelques autres choses et je me suis endormi. Comment savais-je que cela entraînerait une longue analyse de l'application des menaces cachées?



Agréable surprise


Le lendemain de quelques appels douteux, j'ai reçu un message SMS intéressant avec le contenu suivant:



Après quelques jours, j'ai reçu un autre message similaire:



Surpris que quelqu'un ait pu me transférer de l'argent sur Internet (apparemment, je suis le seul vieux - j'utilise toujours des livres d'épargne papier), j'ai cliqué sur le lien dans SMS.

Après cela, on m'a proposé de télécharger une application Android (fichier apk). Heureusement en téléchargeant le fichier, j'ai vu ce qui suit:



C'est crédible! Je voulais ardemment tout installer rapidement et y mettre fin.
Mais ici, comme d'habitude, le système d'exploitation Android ennuyeux pour une raison quelconque ne m'a pas permis d'exécuter le fichier. "Donnez-moi déjà l'argent!" J'étais indigné. J'ai dû aller dans les paramètres et activer une option "Sources inconnues", le téléphone est-il vraiment si stupide en 2018? Au fait, mon téléphone est Xiaomi Remdi avec Andoid 6.0.1 (note pour les techniciens).



Cela a été suivi d'une chaîne d'événements étranges. Le téléphone a continué de signaler des sources non fiables. Mais Avito est une source fiable ! J'ai dû le rechercher sur Google, trouver un moyen de contourner ce problème, puis le désactiver dans les paramètres. Bientôt un certain antivirus est apparu que je n'ai pas installé.



Enfin, j'ai vu la fenêtre d'installation standard tant convoitée - cela n'a aucun sens de regarder les autorisations de nos jours , maintenant même un ordinateur portable ne démarre pas tant que vous ne lui donnez pas un accès complet au téléphone. Moment joyeux de la fin de l'installation de l'application et COMMENCEZ! J'attendais avec impatience l'argent promis. En outre, l'application a demandé des privilèges d'administrateur, avec lesquels j'ai joyeusement accepté. Malheureusement, l'application s'est comportée étrangement et ne voulait pas payer, et bientôt elle a complètement disparu de la liste des applications sur l'écran commun.





Spoiler
Plus tard, j'ai vérifié sur un autre téléphone - Lenovo avec Android 4.4.2 à bord. La liste des autorisations lors de l'installation s'est avérée beaucoup plus longue. Et aucune protection contre le jeu et anti-virus n'interfèrent, vous devez uniquement autoriser l'installation à partir de sources non fiables.





Altruisme



Alors, où en sommes-nous:

  • L'installation a duré 20 minutes.
  • Je n'ai pas reçu d'argent.

Je pensais que le problème était une erreur de code, comme c'est souvent le cas avec les programmeurs. J'ai décidé d'identifier les erreurs qui se produisent lorsque l'application s'exécute sur mon téléphone et d'envoyer un rapport à ce sujet au développeur.

Spoiler
Maintenant, il reste si peu de gens désintéressés, l'un d'eux est moi.

Il est clair que ce type d'application devrait fonctionner lorsque Internet est connecté, donc pour commencer j'ai essayé d'analyser le trafic entre le téléphone et le serveur d'application.

Guide technique de configuration du proxy
Vous pouvez écouter le trafic Internet à tout moment entre le téléphone et le serveur, qu'il s'agisse d'un routeur domestique, d'un fournisseur Internet local, d'un réseau fédérateur Internet ou d'un serveur d'applications.

Les problèmes:

  • L'accès à cet équipement est obligatoire.
  • Il est nécessaire de séparer le trafic de l'application souhaitée du reste.
  • Dans le cas du cryptage (et en 2018, tout est déjà crypté) - la connaissance de la clé est nécessaire.

J'ai décidé d'aller de manière plus classique et de configurer des proxy sur le téléphone avec un serveur proxy sur mon propre ordinateur portable. Afin d'atténuer le problème de cryptage, j'ai décidé d'importer mon certificat et je n'ai pas démarré d'autres applications pour séparer le trafic des applications.
J'ai choisi burp suite comme programme de serveur proxy. J'ai configuré un serveur proxy et exporté un certificat sur mon téléphone.





Après avoir installé un logiciel spécialisé, j'ai pu voir quelles demandes l'application envoie au serveur:



Comme vous pouvez le constater, les données ne sont pas envoyées (la valeur d'hôte inconnue est indiquée dans la colonne IP), de plus, elles ne peuvent pas être analysées en raison de la présence d'un chiffrement supplémentaire au niveau de l'application. A en juger par erreur, le téléphone n'a pas pu déterminer l'adresse IP du serveur et de ses sous-domaines https: //*.sky-sync.pw par son nom de domaine.

Cela ne peut signifier que les options suivantes:

  • Le nom de domaine a cessé d'exister
    • Il a été bloqué par le propriétaire lui-même.
    • Il a été bloqué par le greffier sur la plainte.
  • Problème avec le serveur DNS
    • Le serveur DNS ne connaît pas l'adresse, car le développeur en production a déployé l'adresse DNS locale.
    • Le serveur DNS a spécifiquement bloqué cette demande, ce qui n'est pas surprenant à l'ère de la censure sur Internet.

Pour vérifier l'hypothèse d'un problème avec le serveur DNS, j'ai essayé de faire des demandes à partir de différents grands serveurs DNS: Google, Yandex, OpenDNS (généralement le DNS local est censuré):



On peut voir qu'aucun d'eux ne sait rien de ce nom. Ensuite, j'ai regardé les informations whois sur l'enregistrement de domaine:



Curieusement: le domaine est enregistré, c'est-à-dire qu'il n'est probablement pas local, mais comme le domaine ne se résout pas, il peut avoir été bloqué par le registraire pour abus. Mais pour quoi? Quel mal a-t-il fait?

Pour découvrir ce qu'est cette application tout de même et comment je peux prendre mon argent, j'ai décidé d'utiliser la magie du reverse engineering.

Abyss


Si vous êtes un humaniste et avez lu jusqu'à cet endroit, alors c'est bien - pour le développement de la suite, vous méritez un prix posthume .

Boîte à outils


Pour savoir ce qui est «sous le capot» de l'application, nous devons télécharger des outils spécialisés. Vous pouvez les télécharger individuellement:

  • Déballer le conteneur apk
    • Classique - ApkTool .
    • Vous pouvez le décompresser avec un archiveur classique, mais toutes les ressources binaires, y compris les applications et le fichier manifeste, seront alors illisibles.
  • Décompilateur de code Smali
    • La norme est Dex2Jar , mais apprenez que ce programme fonctionne souvent de manière tordue.
    • Vous devez aborder la question très attentivement, car le décompilateur au décompilateur est différent, nous y réfléchirons plus tard.
  • Un programme pour visualiser le code décompilé , je recommanderais jd-gui

Ou vous pouvez utiliser le produit, généralement payé, où il y a tout à la fois. Je préfère JebDecompiler : il peut simplement soumettre une application apk à son entrée, et il organisera soigneusement tout dans les onglets, en plus il est pratique de basculer entre smali et code Java décompilé.

Séparément, je veux noter:

  • S'il y avait des bibliothèques natives (le dossier / libs dans la structure de l'application), un désassembleur serait nécessaire.
  • Vous aurez peut-être besoin d'un ensemble d'utilitaires pour travailler avec le code smali (dex à smali, smali à dex).
  • Syntaxe en surbrillance dans Notepad ++ afin de ne pas casser les yeux.

Seuls les vieillards entrent dans la bataille


Revue


Lorsque vous ouvrez le code décompilé, il devient immédiatement clair qu'il est obscurci.



Comment est-ce que je comprends cela?

  • Noms de classe lisibles par l'homme
    isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq.wukovicq;
  • Code inaccessible
     if(0 != 0) {</li> String v1 = "flnwznvh";</li> if(v1.length() != 661 && v1.charAt(0) == 104) {</li> v1.length();</li> } 
  • Cryptage des chaînes
     vcgrnfjx.execSQL(nvhdzjfo.xipswfqb(new String[]{"f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6","378f40211b6e32a5406cd97e85bcf9ad","6378a459b1c20edf", "gexnfwok", "meazfhdp", "bsmotaxn"}) 

Il est peu probable que le programmeur ait été assez fou pour développer initialement le code. Très probablement, il a utilisé l'un des obscurcisseurs accessibles au public. Cette étape est assez courante pour compliquer l'analyse de code afin de protéger la propriété intellectuelle, par exemple, mais quel genre de «twist» pour le chercheur.

Prenons attention à la fonction de cryptage principale:



La fonction de cryptage elle-même accepte 3 lignes d'entrée (si plus, les autres n'ont pas de sens):

  1. texte chiffré
  2. la clé
  3. vecteur d'initialisation pour CBC - AES

Cette fonction est référencée dans le programme au moins 213 fois:



Je note qu'il s'agit d'une clé importante pour l'analyse de code normale. Ensuite, vous devez penser que nous avons les façons suivantes d'analyser le programme:

  1. Restaurer la logique de la fonction, collecter tous les appels dans l'analyse statique, décrypter les lignes. Cela peut être difficile et long, mais cela donnera un résultat à 100%.
  2. Apportez des modifications au code smali de l'application, compilez à nouveau, exécutez l'application et capturez les lignes déchiffrées dans les journaux. C'est facile à faire, mais le comportement de l'application à un lancement particulier est inconnu, et vous risquez de ne pas voir l'image dans son intégralité (pas d'appels à toutes les fonctions). De plus, il peut y avoir des problèmes avec l'autocontrôle par l'application du certificat et (ou) l'intégrité.
  3. S'il est difficile de restaurer la logique de la fonction, vous pouvez collecter tous les appels de fonction et extraire ces fonctions elles-mêmes avec les paramètres nécessaires directement dans la dynamique (en utilisant, par exemple, le logiciel Frida .

Nous choisirons la méthode numéro 1 comme la méthode la plus fiable.

Désobfuscation


Faites immédiatement une réservation, la désobfuscation est souvent un processus long et fastidieux, vous devez donc évaluer correctement votre calendrier. Pour notre analyse, il suffit de décrypter toutes les lignes au moins d'une certaine manière et de le faire en un minimum de temps, même de manière béquille, que de jouer pendant un mois dans le but d'une vague option idéale.

La désobfuscation qualitative est logique dans le cas d'une ingénierie inverse complète, par exemple, les voleurs de propriété intellectuelle doivent le faire lorsqu'ils essaient de copier la solution d'un concurrent, ou si vous devez souvent analyser des programmes traités par un obscurcisseur, mais ce n'est pas notre cas.

Code source après le décompilateur JEB Decompiler v.1.4

Spoiler
 public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; String v7 = args[1]; String v0 = args[2]; if(v10 == null) { goto label_9; } if(v10.length() != 0) { goto label_11; } goto label_9; label_11: IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); goto label_15; } catch(NoSuchPaddingException v3) { } catch(NoSuchAlgorithmException v3_1) { } String v11 = ""; goto label_10; label_15: SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); if(v2.length <= 0) { goto label_48; } v4 = 0; v6 = v2.length - 1; label_29: if(v6 < 0) { goto label_38; } if(v2[v6] != 0) { goto label_33; } } catch(Exception v3_2) { goto label_51; } ++v4; label_33: --v6; goto label_29; label_38: if(v4 <= 0) { goto label_48; } try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch(Exception v3_2) { label_51: v11 = ""; goto label_10; } label_48: v11 = new String(v2); goto label_10; label_9: v11 = ""; label_10: return v11; } } 

Note du décompilateur
Soit dit en passant, dex2jar plante souvent. Ainsi, dans la figure ci-dessous, on peut voir que la version 2.0 dex2jar ne pouvait pas faire face et vient de publier un code smali.



Sa dernière version, compilée à partir des sources, a produit un code décompilé pour cette fonction, mais n'a pas pu en décompiler beaucoup d'autres (c'est l'astuce).





Conclusion: réfléchissez soigneusement au choix d'un décompilateur - cela vous fera gagner beaucoup de temps et sera plus facile que d'analyser le code smali.


Donc, si nous collons simplement ce code dans l'IDE maintenant, cela ne fonctionnera pas en raison d'erreurs.

Il est important de se souvenir: le décompilateur n'est pas obligé de produire du code valide écrit par le développeur. Il n'est qu'une bouée de sauvetage dans l'analyse et fait des hypothèses sur la façon dont le code pourrait être écrit. Dans la plupart des cas, après optimisation par le compilateur, la tâche de restauration du code d'origine cesse complètement d'être triviale.

Exemple de décompilation incorrecte:

 if(v10 == null) { goto label_9; } if(v10.length() != 0) { goto label_11; } goto label_9; … label_9: v11 = ""; return v11; 

On voit que ça s'est mal passé et inopérant. Nous réécrivons:

 if ((v10 == null) || (v10.length() == 0)) { return ""; } 

Maintenant, c'est beaucoup plus clair, voici la vérification d'entrée habituelle. Dans ce cas, nous avons besoin de:

  • Remplacez tous les "goto" par d'autres constructions de langage, comme "Goto" est depuis longtemps un opérateur invalide.
  • Remplacez les appels de bibliothèque Android par des appels de bibliothèque Java (si nous essayons d'exécuter du code dans l'IDE Java).
  • Insérez des classes dépendantes référencées par notre code.
  • Pensez par vous-même à ce qui ne va pas.

En conséquence, nous obtenons:

 package com.company; //package isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq; import java.util.Base64; //import android.util.Base64; //import bnxvhlyg.nkhoirul.zfxogwqi.mdpqejcw.srnepbly.pcbvwxrs.vixdqclm.wnuqvrhp.bnvceayd.bwdoclkr; //   import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public abstract class Main { public Main() { super(); } //hex to ascii public static byte[] xkvasepi(String str) { byte[] v0 = null; if(str != null && str.length() >= 2) { int v2 = str.length() / 2; v0 = new byte[v2]; int v1; for(v1 = 0; v1 < v2; ++v1) { v0[v1] = ((byte)Integer.parseInt(str.substring(v1 * 2, v1 * 2 + 2), 16)); } } return v0; } public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; //text String v7 = args[1]; //key String v0 = args[2]; //IV //check if ((v10 == null) || (v10.length() == 0)) { return ""; } IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); } catch(NoSuchPaddingException v3) { return ""; } catch(NoSuchAlgorithmException v3_1) { return ""; } SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); //v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); v2=v1.doFinal(xkvasepi(v10)); //check if(v2.length <= 0) { return new String(v2); } } catch(Exception v3_2) { return ""; } v4=0; for (v6=v2.length-1;v6>=0;v6--){ if (v2[v6]==0) ++v4; } if(v4 > 0) { try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch (Exception v3_2) { return ""; } } v2 = Base64.getDecoder().decode(v2); return new String(v2); } public static void main(String[] args) { // write your code here System.out.println(podxiwkt(new String[] { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu" })); } } { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu"})); package com.company; //package isqpwcmx.isfdztgb.adscjobz.nxscomkr.jypbdxnt.utagwpym.wprtdznb.swldgrhm.yrbjpktq; import java.util.Base64; //import android.util.Base64; //import bnxvhlyg.nkhoirul.zfxogwqi.mdpqejcw.srnepbly.pcbvwxrs.vixdqclm.wnuqvrhp.bnvceayd.bwdoclkr; //   import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public abstract class Main { public Main() { super(); } //hex to ascii public static byte[] xkvasepi(String str) { byte[] v0 = null; if(str != null && str.length() >= 2) { int v2 = str.length() / 2; v0 = new byte[v2]; int v1; for(v1 = 0; v1 < v2; ++v1) { v0[v1] = ((byte)Integer.parseInt(str.substring(v1 * 2, v1 * 2 + 2), 16)); } } return v0; } public static String podxiwkt(String[] args) { int v6; int v4; byte[] v2; Cipher v1; String v10 = args[0]; //text String v7 = args[1]; //key String v0 = args[2]; //IV //check if ((v10 == null) || (v10.length() == 0)) { return ""; } IvParameterSpec v5 = new IvParameterSpec(v0.getBytes()); try { v1 = Cipher.getInstance("AES/CBC/NoPadding"); } catch(NoSuchPaddingException v3) { return ""; } catch(NoSuchAlgorithmException v3_1) { return ""; } SecretKeySpec v9 = new SecretKeySpec(v7.getBytes(), "AES"); int v11_1 = 2; try { v1.init(v11_1, ((Key)v9), ((AlgorithmParameterSpec)v5)); //v2 = Base64.decode(v1.doFinal(bwdoclkr.xkvasepi(v10)), 0); v2=v1.doFinal(xkvasepi(v10)); //check if(v2.length <= 0) { return new String(v2); } } catch(Exception v3_2) { return ""; } v4=0; for (v6=v2.length-1;v6>=0;v6--){ if (v2[v6]==0) ++v4; } if(v4 > 0) { try { byte[] v8 = new byte[v2.length - v4]; System.arraycopy(v2, 0, v8, 0, v2.length - v4); v2 = v8; } catch (Exception v3_2) { return ""; } } v2 = Base64.getDecoder().decode(v2); return new String(v2); } public static void main(String[] args) { // write your code here System.out.println(podxiwkt(new String[] { "b1acd584a6eae4ca6321b1f7cdf9ba9617112b4fb39e76c8def876346e3032fbd32b2d188a09715f27124c1bf9facfdc", "637904cd08aeb2d3f6a21b5c7e84f519", "8f4c796d5a3120eb", "zcmwgvdn", "mkngbsyr", "rwcdaieu" })); } } 

Ce code remplit avec succès. Une fois que son travail est devenu clair, il peut être simplifié et simplifié, conduisant au genre prétendument laconique écrit par le programmeur (à moins, bien sûr, que ses mains n'étaient pas tordues au départ).

Remarque
Soit dit en passant, dans ce cas, le déchiffrement des chaînes peut être démontré en utilisant un tas de ressources en ligne. Un exemple d'appel d'une chaîne cryptée dans un programme:



Ici, le vecteur d'initialisation doit d'abord être converti au format Hex:



Remplacez toutes les valeurs:



Et à la fin, décodez à partir de base64:



En conséquence, nous obtenons la chaîne habituelle et l'appel prend un aspect significatif.

Ainsi, vous devez parcourir tout le code et collecter toutes les chaînes cryptées, maintenant nous pouvons les décrypter indépendamment. Le point important ici est qu'au stade de «modifier et commenter le code», nous pouvons travailler à la fois au niveau smali et au niveau Java (smali décompilé).

Avantages de la modificationMods
SmaliVous pouvez apporter des modifications, recompiler en dex et décompiler avec de nouvelles lignesIl n'est pas toujours facile de travailler avec le code smali. Si la modification est incorrecte, l'application ne compilera pas
JavaIl est souvent beaucoup plus facile d'extraire des données d'opérations de niveau supérieur.La plupart des visualiseurs de code Java ne peuvent pas modifier.

Un autre exemple de ligne

 vcgrnfjx.execSQL(nvhdzjfo.xipswfqb(new String[]{"f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6","378f40211b6e32a5406cd97e85bcf9ad","6378a459b1c20edf", "gexnfwok", "meazfhdp", "bsmotaxn"}) 

De là, il est très facile de récupérer des paramètres à l'aide d'une expression régulière, que d'écrire un régulier sur le code suivant:

Exemple de code Smali 1

 00000280 new-instance v13, Ljava/lang/StringBuilder; 00000284 invoke-direct {v13}, Ljava/lang/StringBuilder;-><init>()V 0000028A const/4 v14, 0x6 0000028C new-array v14, v14, [Ljava/lang/String; 00000290 const/4 v15, 0x0 00000292 const-string v16, "f741f04a4991fc2f0a0029f610bbd1c250dfe115fb7770b892f75d8718b822d273251013991b4407e224fa3f9d4e92f6" 00000296 aput-object v16, v14, v15 0000029A const/4 v15, 0x1 0000029C const-string v16, "378f40211b6e32a5406cd97e85bcf9ad" 000002A0 aput-object v16, v14, v15 000002A4 const/4 v15, 0x2 000002A6 const-string v16, "6378a459b1c20edf" 000002AA aput-object v16, v14, v15 000002AE const/4 v15, 0x3 000002B0 const-string v16, "gexnfwok" 000002B4 aput-object v16, v14, v15 000002B8 const/4 v15, 0x4 000002BA const-string v16, "meazfhdp" 000002BE aput-object v16, v14, v15 000002C2 const/4 v15, 0x5 000002C4 const-string v16, "bsmotaxn" 000002C8 aput-object v16, v14, v15 


Exemple de code Smali 2

 0000008E new-array v0, v0, [Ljava/lang/String; 00000092 move-object/from16 v89, v0 00000096 const/16 v90, 0x0 0000009A const-string v91, "4500b5e2e2ad26b7545eb54d70ab360ae28c9d031e2afcc3f6a2b2ac488ea440" 0000009E aput-object v91, v89, v90 000000A2 const/16 v90, 0x1 000000A6 const-string v91, "da96f678922d4b07350b3a184ecc1f5e" 000000AA aput-object v91, v89, v90 000000AE const/16 v90, 0x2 000000B2 const-string v91, "0cf69e3d2745a1b8" 000000B6 aput-object v91, v89, v90 000000BA const/16 v90, 0x3 000000BE const-string v91, "jhiqsaoe" 000000C2 aput-object v91, v89, v90 000000C6 const/16 v90, 0x4 000000CA const-string v91, "khbqxurn" 000000CE aput-object v91, v89, v90 


Exemple de code Smali 3

 00000D3E new-array v0, v0, [Ljava/lang/String; 00000D42 move-object/16 v298, v0 00000D48 const/4 v0, 0x0 00000D4A move/16 v299, v0 00000D50 const-string v0, "b286945744e085f4d5c19916fd261481" 00000D54 move-object/16 v300, v0 00000D5A move-object/from16 v0, v300 00000D5E move-object/from16 v1, v298 00000D62 move/from16 v2, v299 00000D66 aput-object v0, v1, v2 00000D6A const/4 v0, 0x1 00000D6C move/16 v299, v0 00000D72 const-string v0, "df6883742b2911ac5ac7b4dee065390f" 00000D76 move-object/16 v300, v0 00000D7C move-object/from16 v0, v300 00000D80 move-object/from16 v1, v298 00000D84 move/from16 v2, v299 00000D88 aput-object v0, v1, v2 00000D8C const/4 v0, 0x2 00000D8E move/16 v299, v0 00000D94 const-string v0, "90a463ce2df17b58" 00000D98 move-object/16 v300, v0 00000D9E move-object/from16 v0, v300 00000DA2 move-object/from16 v1, v298 00000DA6 move/from16 v2, v299 00000DAA aput-object v0, v1, v2 00000DAE const/4 v0, 0x3 00000DB0 move/16 v299, v0 00000DB6 const-string v0, "cupyzsgq" 00000DBA move-object/16 v300, v0 00000DC0 move-object/from16 v0, v300 00000DC4 move-object/from16 v1, v298 00000DC8 move/from16 v2, v299 00000DCC aput-object v0, v1, v2 


Comme nous pouvons le voir, les variables internes changent, la séquence des commandes varie, le nombre d'arguments varie également, en plus, dans le programme, la fonction de déchiffrement est appelée non pas directement, mais via les fonctions de la couche. Essayez d'écrire vous-même une règle pour rechercher cette construction, évitez les erreurs de capture de chaînes à partir d'autres fonctions et faites tout cela rapidement ( bonne chance ).

Plan de piège:

  1. Nous allons extraire toutes les valeurs du code décompilé.
  2. Déchiffrez.
  3. Remplacez le texte chiffré par celui ouvert dans le code smali. Nous substituons, par exemple, au lieu du premier opérateur. (Il serait plus professionnel de couper tout l'appel de fonction et de laisser la chaîne déchiffrée retournée, mais là encore, il y a un gros risque de rupture du programme).
  4. Collectons le code smali dans un fichier dex.
  5. Il sera commode d'approfondir l'analyse de code, où nous avons commencé.

Si vous collectez tout le code décompilé dans un seul fichier, vous obtenez environ 20000 lignes, ce qui pour l'analyse manuelle nécessite beaucoup de temps, ce qui coûte clairement plus cher que les bottes que je mets en vente. Tout d'abord, rassemblez toutes les lignes avec une expression régulière.



On voit 593 matches, plus une dizaine qui ne tombent pas sous cette règle, la famille a son mouton noir . Un exemple:



Trier, filtrer, 422 lignes uniques au total:



Nous passons par la fonction de décryptage que nous avons restaurée plus tôt. Résultat:



Remplacez le texte chiffré par celui ouvert en code smali en utilisant Python:

 import os words_replace=dict() words_replace["0018aacad3d146266317d8d8c51785fd"]="imei" words_replace["016d15e4d0a72667c61428e736a6f3b8"]="WakeLock" words_replace["032c534efb6c9990cd845a08c5a08b95"]="inbox" #…  .. # smali- #      def change(path): print("file="+path) file_handle = open(path, 'r') context_full = file_handle.read() file_handle.close() for i in words_replace: context_full=context_full.replace(i, words_replace[i]) #print (i+""+words_replace[i]) file_handle = open(path, 'w') context_full = file_handle.write(context_full) file_handle.close() #      smali- for top, dirs, files in os.walk('C:\\work\\test'): for nm in files: path=os.path.join(top, nm) print (path) change(path) 

Nous collectons les fichiers smali en dex:



Maintenant, cela peut être en quelque sorte analysé (en lisant le premier argument de l'ensemble de la construction):



Analyse


Donc, nous avons 20 000 lignes de code plus ou moins lisibles, nous n'avons pas besoin de faire une analyse complète. Il est nécessaire de comprendre la fonctionnalité dans son ensemble. Ici, en fait, seule la possibilité de lire le code source Java est requise. Parcourez le code, examinez les références croisées, renommez les variables et les fonctions.

Quelle est la meilleure façon d'analyser une application Android, en particulier une grande?

Option 1: vous pouvez vous déplacer à partir du fichier manifeste

Par exemple, séquentiellement à partir de LAUNCHER, essayez de dérouler toute la chaîne d'appels. Soit dit en passant, n'oubliez pas qu'il existe toujours «Receiver» et «Service» qui peuvent changer l'exécution linéaire du programme.



Fichier manifeste complet
 <?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft"> <uses-permission android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.permission.C2D_MESSAGE"/> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.QUICKBOOT_POWERON"/> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <permission android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.permission.C2D_MESSAGE" android:protectionLevel="signature"/> <application android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/tgiwmpqy" android:noHistory="true"> <activity android:configChanges="orientation" android:excludeFromRecents="true" android:label="@string/tgiwmpqy" android:launchMode="singleTop" android:name="zemquyog.csrtmnak.xrkfygen.wkahrnjd.acnfunjh.rgipxbuf.lruiwxeg.blqndche.dcjihbou" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:configChanges="orientation" android:launchMode="singleTop" android:name="xbfrscou.hxrvwnoi.djvpcqri.enlnrfio.aoegxbiu.heywzmnb.znfnxcht.nazcxobq" android:screenOrientation="portrait"/> <activity android:configChanges="orientation" android:launchMode="singleTop" android:name="hcfkagds.timkagsd.oetvghzr.fcioynvl.psynofdj.slcghdjz.tapnwsdk.gzvwnban.htenafdb.qwebhzgy" android:noHistory="true" android:screenOrientation="portrait"/> <activity android:configChanges="orientation" android:excludeFromRecents="true" android:launchMode="singleTop" android:name="njfbwmre.voefarqx.ftuxvngl.wrmshxqj.zdenywgn.eiwyunlg.jysgkbam.yrijthab.vstqxpuo.iplamgxf" android:priority="2147483647" android:screenOrientation="portrait"/> <receiver android:name="gfbaznoc.asyoqtnm.kbetoqca.mqysobzu.gqwfibrv.dorxijuk.wgzkmiep.ywnnurzv.csfpqhrn" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="@string/pkzrlscm" android:resource="@xml/ynqukvnb"/> <intent-filter android:priority="2147483646"> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> </intent-filter> </receiver> <receiver android:name="ykwbodxc.gymjhibn.kgmdfqor.hbasvmfz.yegkmaif.ortzknvm.quplincn.cuxytvhs.fqonzuts.cyuoxgqi.znumwyct" android:permission="com.google.android.c2dm.permission.SEND"> <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE"/> <action android:name="com.google.android.c2dm.intent.REGISTRATION"/> <action android:name="com.google.android.c2dm.intent.UNREGISTRATION"/> <category android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft"/> </intent-filter> </receiver> <receiver android:enabled="true" android:exported="true" android:name="kqwihjot.nvkqjloc.grjnyknm.owydvckh.mugknwdx.enhcyvja.mhvbpcue.ztbwjhfo"> <intent-filter android:priority="2147483646"> <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/> <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/> <action android:name="android.intent.action.QUICKBOOT_POWERON"/> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.USER_PRESENT"/> <action android:name="android.intent.action.BATTERY_OKAY"/> <action android:name="android.intent.action.BATTERY_LOW"/> <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/> <action android:name="android.intent.action.APP_ERROR"/> <action android:name="android.intent.action.HEADSET_PLUG"/> <action android:name="android.intent.action.PHONE_STATE"/> <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> <action android:name="android.intent.action.TIME_TICK"/> <action android:name="android.intent.action.SCREEN_ON"/> <action android:name="android.intent.action.SCREEN_OFF"/> <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> <action android:name="android.net.wifi.WIFI_STATE_CHANGED"/> <action android:name="android.intent.action.DREAMING_STOPPED"/> <category android:name="android.intent.category.HOME"/> </intent-filter> </receiver> <receiver android:name="btnsxnuz.wmjizbky.lynvjxqz.zinomjuv.yizlgcnf.qwoikgnc.wnrskjea.wfqgmeny.lcgvqrms.ocwkgblp"> <intent-filter android:priority="2147483646"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver> <service android:name="ltvsrezg.ehxndrat.twnnyxrf.nqynefws.dhbalcnr.ynjkuxod.nhoxmsbq.nackoyhn.voycgfek.znhwkqba.taxvnfyn"/> <service android:name="rbnakfzo.qsreiubk.pwvlnngs.twoxnhfv.mftarcnd.pfioxcub.xjlaftqr.nxrqvlwh"/> <service android:enabled="true" android:name="xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft.ugshpjvo"/> </application> </manifest> 


Option 2: vous pouvez vous déplacer à partir de lignes intéressantes



Partie de chaînes déchiffrées
 system_update.apk () () , error = , unregistered = ,  .permission.C2D_MESSAGE //sky-sync.pw/ //sms/inbox /system_update.apk ALLCONTACTS ALLMSG AUTHENTICATION_FAILED Acquiring wakelock Application BLOCKER_BANKING_START BLOCKER_EXTORTIONIST_START BLOCKER_STOP BLOCKER_UPDATE_START Banking CHANGE_GCM_ID CONTACTS CONTACTS_PRO CREATE TABLE IF NOT EXISTS END Error|No process list|No access Extortionist Foreground GCM returned invalid number of GCMBaseIntentService GCMBroadcastReceiver GCMIntentService- GCMRegistrar GCM_LIB GET MESSAGE Mobile Network NEWMSG Not retrying failed operation ONLINE PAGE POST Process finished with exit code 0 RESTART Received deleted messages Registering receiver Releasing wakelock SERVICE_NOT_AVAILABLE SSL START STOP Saving regId on app version Scheduling registration retry, backoff = Setting registeredOnServer status as Stop System UNBLOCK UPDATE UPDATE_PATTERNS URL UTF-8 Update WakeLock Wakelock reference is null Wi-Fi WiMax _success add_msg_ok address android.intent.action.QUICKBOOT_POWERON answer_text answer_to api_url app appVersion application application/vnd.android.package-archive apps_list ask backoff_ms blocker blocker_banking blocker_banking_autolock blocker_banking_forced_access blocker_banking_success blocker_extortionist blocker_extortionist_autolock blocker_extortionist_forced_access blocker_extortionist_success blocker_update blocker_update_forced_access blocker_update_success body build callback cardSuccess check com.android.settings com.google.android.c2dm.intent.RECEIVE com.google.android.c2dm.intent.REGISTER com.google.android.c2dm.intent.REGISTRATION com.google.android.c2dm.intent.UNREGISTER com.google.android.gcm com.google.android.gcm.intent.RETRY com.google.android.gsf com.htc.intent.action.QUICKBOOT_POWERON command command_receive contactslist country data date delete deleted_messages device_block disableDataConnectivity enableDataConnectivity error failure file deleted. first_start force-locked gafzpjxb.cix gcm gcm_id gcm_register gcm_register_ok getITelephony get_message_list id integer primary key autoincrement, id=? imei immunity inbox init_bootable init_imei is_admin is_awake_display is_imunnity is_locked is_network_type is_top_activity job job_date job_id komgejif.hqr locked message message_delivered message_type method model msg msg_id msglist name not nypjtinq.nvp ok onServer onServerExpirationTime onServerLifeSpan operator org.android.sys.admin.disabled org.android.sys.admin.enabled org.android.sys.admin.request org.android.sys.command.receive org.android.sys.launch.first org.android.sys.sms.pro.sent org.android.sys.sms.push org.android.sys.sms.sent outbox page params pattern patterns personal phone phone_list privet process_list protocol qwertyuiopasdfghjklzxcvbnm receive regId regex register register_ok registrationId = registration_id repeat resetting backoff for ru save_contacts_list save_message_history sender sent sent_status sid ss status stop_blocker text text, text/html time token total_deleted type unknown unregistered until url useragent utf-8 value version xpls yes   !     ...                   !  ?                ! 

Option 3: vous pouvez vous déplacer à partir de ressources intéressantes (actifs, bibliothèques)

Dans ce cas, l'option 3 était préférable. Il y a trois fichiers html intéressants dans le dossier / assets (conteneur apk). Voici leur vue dans le navigateur:





Cela semble douteux pour le programme officiel de transfert de paiement Avito, ne vous semble-t-il pas? Voyons ce qui se passe lorsque vous appuyez sur la touche pour envoyer des données bancaires sur la page avec le logo Sberbank. JavaScript appelle la fonction sendCardData() :



Et puis il est transféré en code Java via l'appel ok.performClick() :



En code Java, le traitement est effectué:



De plus, tout cela est crypté dans la classe mcrypt :



À l'intérieur de la fonction, les données sont cryptées de la même manière que précédemment considéré:



Mais pour le reste, les touches sont câblées:



Nous essayons de décrypter via la ressource en ligne:



Et convertissez de base64. Succès! Nous pouvons décrypter toutes les données d'application: testé sur le trafic capté plus tôt.

L'application signale au serveur tous les événements
 { "sid":15, "imei":"861117030537111", "phone":"System", "message":"     22.10.2018 23:30:47", "time":"1540240247", "msg_id":1, "status":"unknown", "type":"inbox", "method":"message" } 

Il transfère également périodiquement toutes les applications en cours d'exécution
 { "sid": 15, "imei": "861117030537111", "country": "ru", "operator": "MTS RUS", "phone": "", "model": "Xiaomi Redmi 3X", "version": "6.0.1", "application": "", "build": "30.0.2", "process_list": [ "Background|com.android.bluetooth|com.android.bluetooth.hid.HidService", "Background|com.android.settings:remote|com.android.settings.wifi.MiuiWifiService", "Background|com.android.phone|org.codeaurora.ims.ImsService", "Background|system|com.qualcomm.location.LocationService", ..., "Background|xfmpuwon.mtnbupnc.ihqdgjal.ndgmqawx.bjunzerq.cznfpnoq.fzevcuym.jmpdiqft|ltvsrezg.ehxndrat.twnnyxrf.nqynefws.dhbalcnr.ynjkuxod.nhoxmsbq.nackoyhn.voycgfek.znhwkqba.taxvnfyn" ], "apps_list": [ "com.introspy.config", "com.google.android.youtube", "com.google.android.googlequicksearchbox", "org.telegram.messenger", ..., "com.google.android.inputmethod.latin", "jakhar.aseem.diva" ], "method": "register" } 

Si j'avais une fenêtre de saisie de données bancaires dans la dynamique, alors les données seraient dans le trafic. Ainsi, nous pouvons conclure qu'il s'agit d'une application de "phishing".

Ceux qui ont fait attention ont remarqué que le fichier Manifest dispose de quelques autorisations et que l'application a des fonctionnalités plus riches. Nous effectuerons une analyse approfondie de la fonctionnalité dans un autre article. En attendant, succès!

Conclusions


Je suis déçu de ne pas avoir vendu de bottes. Et les conclusions sont les suivantes:

  • Ne vendez pas de bottes sur Avito
  • Ne cliquez pas sur des liens obscurs (même s'ils proviennent d'amis et même si "vous devez emprunter d'urgence 100 roubles - une question de vie ou de mort")
  • Ne téléchargez pas d'applications autres que depuis Google Play ou l'AppStore
    • Déconnectez l'installation des "sources non fiables" si vous ne comprenez vraiment pas quoi.
    • Ne déconnectez pas la «protection contre le jeu».
    • N'oubliez pas qu'il peut y avoir des logiciels malveillants sur Google Play
  • Installez un antivirus sur le téléphone (cela fonctionne vraiment).
  • Si vous êtes développeur, n'obscurcissez pas le code, laissez les gens s'assurer de vos bonnes intentions ( je plaisante )
  • Si vous êtes chercheur, ne travaillez pas pour la nourriture, analysez les applications pendant votre temps libre et publiez des rapports. Ensemble, nous ferons du monde un meilleur endroit.

PS J'ai essayé d'écrire légèrement l'article dans un format humoristique et de le soumettre aussi simple que possible, car même je ne voudrais probablement pas lire vendredi un long long trajet intitulé "Reverse Engineering of an Obfuscated Malicious Android Application".

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


All Articles