K-Pro

Comment j'ai découvert un œuf de Pâques dans la sécurité d'Android sans avoir décroché un emploi chez Google

jeudi 4 avril 2019

https://www.getursoft.site/2019/04/comment-jai-decouvert-un-uf-de-paques.htmlGoogle aime les oeufs de Pâques. Il les aime tellement, en fait, que vous pourriez les trouver dans pratiquement tous leurs produits. La tradition des œufs de Pâques sous Android a commencé dans les toutes premières versions de l'OS (je pense que tout le monde sait ce qui se passe lorsque vous entrez dans les paramètres généraux et que vous tapez plusieurs fois sur le numéro de version). 

Mais parfois, vous pouvez trouver un œuf de Pâques dans les endroits les plus improbables. Il existe même une légende urbaine selon laquelle un programmeur googled «mutex lock» un jour, mais au lieu des résultats de recherche atterrit sur foo.bar, a résolu toutes les tâches et obtenu un emploi chez Google.

Reconstruction

La même chose (sauf sans la fin heureuse) m'est arrivée. Messages cachés où il ne pouvait absolument pas y en avoir, inversant le code Java et ses bibliothèques natives, une VM secrète, une interview de Google - tout cela se trouve ci-dessous.

DroidGuard


Une nuit ennuyeuse, j’ai réinitialisé mon téléphone en usine et je l’ai réinstallé. Tout d’abord, une nouvelle installation d’Android m’a demandé de me connecter au compte Google. Et je me suis demandé: comment fonctionne le processus de connexion à Android? Et la nuit devint soudainement moins ennuyeuse. 

J'utilise la suite Burp de PortSwigger pour intercepter et analyser le trafic réseau. La version communautaire gratuite suffit à nos besoins. Pour voir les requêtes https, nous devons d'abord installer le certificat de PortSwigger sur le périphérique. En tant que dispositif d’essai, j’ai choisi un Samsung Galaxy S de 8 ans avec Android 4.4. N'importe quoi de plus récent que cela et vous pourriez avoir des problèmes avec l'épinglage de certificats et d'autres choses.

En toute honnêteté, il n'y a rien de particulièrement spécial avec les requêtes Google API. L'appareil envoie des informations sur lui-même et reçoit des jetons en réponse… La seule étape curieuse est une demande POST adressée au service anti-abus. 



Une fois la requête effectuée, parmi de nombreux paramètres très normaux, apparaît un paramètre intéressant, nommé droidguard_result . C'est une très longue chaîne Base64: 



DroidGuard est le mécanisme de Google pour détecter les robots et les émulateurs parmi de vrais appareils. SafetyNet, par exemple, utilise également les données de DroidGuard. Botguard est également similaire à Google. 

Mais quelles sont ces données? Découvrons-le.

Tampons de protocole


Qu'est - ce que génère le lien ( www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI ) et ce dans Android fait cette demande? Après une brève enquête, il est apparu que le lien, sous cette forme exacte, se trouvait dans l'une des classes masquées de Google Play Services:

public bdd(Context var1, bdh var2) {
  this(var1, "https://www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI", var2);
}

Comme nous l'avons déjà vu dans Burp, les requêtes POST sur ce lien ont Content-Type - application / x-protobuf (tampons de protocole Google, protocole de Google pour la sérialisation binaire). Ce n'est pas JSON, cependant - il est difficile de découvrir ce qui est envoyé exactement. 

Les tampons de protocole fonctionnent comme ceci:

  • Nous décrivons d’abord la structure du message dans un format spécial, puis l’enregistrons dans un fichier .proto;
  • Ensuite, nous compilons les fichiers .proto, et le compilateur protocole génère le code source dans un langage choisi (dans le cas d'Android, il s'agit de Java);
  • Enfin, nous utilisons les classes générées dans notre projet.

Nous avons deux façons de décoder les messages protobuf. La première consiste à utiliser un analyseur de prototypes et à essayer de recréer la description originale des fichiers .proto. La seconde consiste à extraire les classes générées par le protocole des services Google Play, ce que j'ai décidé de faire.

Nous prenons le fichier .apk des services Google Play de la même version que celle installée sur l'appareil (ou, si l'appareil est rooté, prenons le fichier directement à partir de là). En utilisant dex2jar, nous reconvertissons le fichier .dex en .jar et nous l’ouvrons dans un décompilateur de choix. Personnellement, j'aime Fernflower de JetBrains. Cela fonctionne comme un plugin pour IntelliJ IDEA (ou Android Studio), nous lançons donc simplement Android Studio et ouvrons le fichier avec le lien que nous essayons d'analyser. Si proguard n'essayait pas trop, le code Java décompilé permettant de créer des messages protobuf pourrait simplement être copié-collé dans votre projet. 

En regardant le code décompilé, nous voyons que les constantes Build. * Sont envoyées dans le message protobuf. (ok, ce n'était pas trop difficile à deviner).

...
var3.a("4.0.33 (910055-30)");
a(var3, "BOARD", Build.BOARD);
a(var3, "BOOTLOADER", Build.BOOTLOADER);
a(var3, "BRAND", Build.BRAND);
a(var3, "CPU_ABI", Build.CPU_ABI);
a(var3, "CPU_ABI2", Build.CPU_ABI2);
a(var3, "DEVICE", Build.DEVICE);
...

Mais malheureusement, dans la réponse du serveur, tous les champs de protobuf se sont transformés en soupe de l’alphabet après l’obfuscation. Mais nous pouvons découvrir ce qu'il y a dedans en utilisant un gestionnaire d'erreurs. Voici comment les données provenant du serveur sont vérifiées:

if (!var7.d()) {
    throw new bdf("byteCode");
}
if (!var7.f()) {
    throw new bdf("vmUrl");
}
if (!var7.h()) {
    throw new bdf("vmChecksum");
}
if (!var7.j()) {
 throw new bdf("expiryTimeSecs");
}

Apparemment, c'est comme ça que les champs étaient appelés avant l'obscurcissement: byteCode , vmUrl , vmChecksum et expiryTimeSecs . Ce schéma de nommage nous donne déjà quelques idées. 

Nous combinons toutes les classes décompilées des services Google Play dans un projet test, les renommons, générons des commandes de test Build. * Et lançons (en imitant le périphérique de votre choix). Si quelqu'un veut le faire lui-même, voici le lien vers mon GitHub . 

Si la requête est correcte, le serveur renvoie ceci:
00: 06: 26,761 [principale] INFO daresponse.AntiabuseResponse - Taille byteCode: 34446 
00: 06: 26,761 [principal] INFO daresponse.AntiabuseResponse - vmChecksum: C15E93CCFD9EF178293A2334A1C9F9B08F115993 
00: 06: 26,761 [principal] INFO daresponse.AntiabuseResponse - vmUrl: www. gstatic.com/droidguard/C15E93CCFD9EF178293A2334A1C9F9B08F115993
00: 06: 26.761 [main] INFO daresponse.AntiabuseResponse - expiryTimeSecs: 10

Étape 1 terminée. Voyons maintenant ce qui est caché derrière le lien vmUrl .

Secret APK


Le lien nous mène directement à un fichier .apk, nommé d'après son propre hachage SHA-1. C'est plutôt petit - seulement 150Ko. Et c'est tout à fait justifié: s'il est téléchargé par chacun des 2 milliards d'appareils Android, cela représente 270 To de trafic sur les services de Google. 



DroidGuardServicefaisant partie des services Google Play, class télécharge le fichier sur le périphérique, le décompresse, extrait .dex et utilise la com.google.ccc.abuse.droidguard.DroidGuardclasse par réflexion. En cas d'erreur, DroidGuardServicevous revenez de DroidGuard à Droidguasso. Mais c'est une autre histoire entièrement. 

En gros, DroidGuardclass est un simple wrapper JNI autour de la bibliothèque native .so. L’ABI de la bibliothèque native correspond à ce que nous avons envoyé sur le CPU_ABIterrain dans la requête protobuf: nous pouvons demander armeabi, x86 ou même MIPS. 

leDroidGuardServicele service lui-même ne contient aucune logique intéressante pour travailler avec la DroidGuardclasse. Il crée simplement une nouvelle instance de DroidGuard, lui envoie le byteCode à partir du message protobuf, appelle une méthode publique, qui renvoie un tableau d'octets. Ce tableau est ensuite envoyé au serveur dans le paramètre droidguard_result . 

Pour avoir une idée approximative de ce qui se passe à l'intérieur, DroidGuardnous pouvons répéter la logique de DroidGuardService(mais sans télécharger le fichier .apk, car nous avons déjà la bibliothèque native). Nous pouvons prendre un fichier .dex de l'APK secret, le convertir en .jar, puis l'utiliser dans notre projet. Le seul problème est de savoir comment la DroidGuardclasse charge la bibliothèque native. Le bloc d'initialisation statique appelle la loadDroidGuardLibrary()méthode:

static
  {
    try
    {
      loadDroidGuardLibrary();
    }
    catch (Exception ex)
    {
      throw new RuntimeException(ex);
    }
  }

Ensuite, la loadDroidGuardLibrary()méthode lit le fichier library.txt (situé à la racine du fichier .apk) et charge la bibliothèque avec ce nom lors de l' System.load(String filename)appel. Ce n'est pas très pratique pour nous, car nous aurions besoin de construire le .apk de manière très spécifique pour mettre library.txt et le fichier .so à la racine. Il serait beaucoup plus pratique de conserver le fichier .so dans le dossier lib et de le charger System.loadLibrary(String libname)

Ce n'est pas difficile à faire. Nous utiliserons smali / baksmali - assembleur / désassembleur pour les fichiers .dex. Après l'avoir utilisé, classes.dex se transforme en un tas de fichiers .smali. La com.google.ccc.abuse.droidguard.DroidGuardclasse doit être modifiée pour que le bloc d'initialisation statique appelle la System.loadLibrary("droidguard")méthode à la place de loadDroidGuardLibrary()La syntaxe de Smali est assez simple, le nouveau bloc d'initialisation ressemble à ceci:

.method static constructor ()V
    .locals 1
    const-string v0, "droidguard"
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
    return-void
.end method

Ensuite, nous utilisons backsmali pour tout reconstituer en .dex, puis nous le convertissons en .jar. À la fin, nous obtenons un fichier .jar que nous pouvons utiliser dans notre projet - le voici , en passant. 

La section entière relative à DroidGuard est composée de quelques chaînes. La partie la plus importante est de télécharger le tableau d'octets obtenu à l'étape précédente après avoir adressé le service anti-abus et de le transmettre au DroidGuardconstructeur:

private fun runDroidguard() {
        var byteCode: ByteArray? = loadBytecode("bytecode.base64");
        byteCode?.let {
            val droidguard = DroidGuard(applicationContext, "addAccount", it)
            val params = mapOf("dg_email" to "test@gmail.com", "dg_gmsCoreVersion" to "910055-30",
                "dg_package" to "com.google.android.gms", "dg_androidId" to UUID.randomUUID().toString())
            droidguard.init()
            val result = droidguard.ss(params)
            droidguard.close()
        }
    }

Nous pouvons maintenant utiliser le profileur d'Android Studio et voir ce qui se passe pendant le travail de DroidGuard: 



La méthode native initNative () collecte des données sur le périphérique et appelle des méthodes Java hasSystemFeature(), getMemoryInfo(), getPackageInfo(). C'est quelque chose, mais je ne vois toujours pas de logique solide. Eh bien, tout ce qui reste à faire est de désassembler le fichier .so.

libdroidguard.so


Heureusement, analyser la bibliothèque native n’est pas plus difficile que de le faire avec des fichiers .dex et .jar. Nous aurions besoin d'une application similaire à Hex-Rays IDA et d'une certaine connaissance du code assembleur x86 ou ARM. J'ai choisi ARM, car j'avais un périphérique enraciné pour déboguer. Si vous n'en avez pas, vous pouvez prendre une bibliothèque x86 et déboguer à l'aide d'un émulateur. 

Une application similaire à Hex-Rays IDA décompile le binaire en quelque chose qui ressemble au code C. Si nous ouvrons la Java_com_google_ccc_abuse_droidguard_DroidGuard_ssNativeméthode, nous verrons quelque chose comme ceci:

__int64 __fastcall Java_com_google_ccc_abuse_droidguard_DroidGuard_initNative(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9)  
...
  v14 = (*(_DWORD *)v9 + 684))(v9, a5);  
  v15 = (*(_DWORD *)v9 + 736))(v9, a5, 0);
...

Ne semble pas trop prometteur. Premièrement, nous devons faire quelques étapes préliminaires pour transformer cela en quelque chose de plus utile. Le décompilateur ne sait rien de JNI, nous avons donc installé Android NDK et importé le fichier jni.h. Comme nous le savons, les deux premiers paramètres d’une méthode JNI sont JNIEnv*et jobject (this)Nous pouvons trouver les types d'autres paramètres à partir du code Java de DroidGuard. Après avoir affecté les types appropriés, les décalages sans signification se transforment en appels de méthode JNI:

__int64 __fastcall Java_com_google_ccc_abuse_droidguard_DroidGuard_initNative(_JNIEnv *env, jobject thiz, jobject context, jstring flow, jbyteArray byteCode, jobject runtimeApi, jobject extras, jint loggingFd, int runningInAppSide)
{
...
  programLength = _env->functions->GetArrayLength)(_env, byteCode);  
  programBytes = (jbyte *)_env->functions->GetByteArrayElements)(_env, byteCode, 0);
...

Si nous avons assez de patience pour retrouver le tableau d'octets reçu du serveur anti-abus, nous serons… déçus. Malheureusement, il n'y a pas de réponse simple à «qu'est-ce qui se passe ici? Il s'agit d'un code d'octets distillé pur et la bibliothèque native est une machine virtuelle. Une partie du cryptage AES est saupoudrée sur le dessus, puis la VM lit le code d'octet, octet par octet, et exécute les commandes. Chaque octet est une commande suivie d'opérandes. Il n'y a pas beaucoup de commandes, seulement environ 70: read int, read octet, read string, appelle la méthode Java, multiplie deux nombres, if-goto etc.

Réveille-toi, Neo


J'ai décidé d'aller encore plus loin et de comprendre la structure du code d'octet pour cette machine virtuelle. Les appels posent un autre problème: parfois (une fois toutes les deux semaines), il existe une nouvelle version de la bibliothèque native dans laquelle les paires d'octets-commandes sont brouillées. Cela ne m'a pas empêché et j'ai décidé de recréer la VM en utilisant Java. 

Le code d'octet fait tout le travail de routine pour collecter des informations sur le périphérique. Par exemple, il charge une chaîne avec le nom d'une méthode, obtient son adresse via dlsym et s'exécute. Dans ma version Java de la machine virtuelle, je n'ai recréé que 5 méthodes environ et ai appris à interpréter les 25 premières commandes du code octet du service anti-abus. À la 26ème commande, la VM a lu une autre chaîne cryptée à partir du code d'octet. Il s'est soudainement avéré que ce n'était pas le nom d'une autre méthode. Loin de là.
Commande de machine virtuelle n ° 26 
Invocation de méthode vm-> vm_method_table [2 * 0x77] 
Méthode vmMethod_readString 
index est 0x9d 
longueur de chaîne est 0x0066 
(une nouvelle clé est générée) 
octets de chaîne codés sont EB 4E E6 DC 34 13 35 4A DD 55 B3 91 33 05 61 04 C0 54 FD 95 2F 18 72 04 C1 55 E1 92 28 11 66 04 DD 4F B3 94 33 04 35 0A C1 4E B2 DB 12 17 79 4F 92 55 FC DB 33 05 35 45 C6 01 F7 89 29 1F 71 43 C7 40 E1 9F 6B 1E 70 48 DE 4E B8 CD 75 44 23 14 85 14 A7 C2 7F 40 26 42 84 17 A2 BB 21 19 7A 43 DE 44 BD 98 29 1B
les octets de chaîne décodés sont 59 6F 75 27 72 65 20 6E 6F 74 20 6A 75 73 74 20 72 75 6E 69 6E 67 20 73 74 72 69 6E 67 73 20 6F 6E 20 6F 75 72 20 2E 73 6F 21 20 54 61 6C 6B 20 74 6F 20 75 73 20 61 74 20 64 72 6F 69 64 67 75 61 72 64 2D 68 68 6 6C 6F 2B 36 33 32 36 30 35 35 39 39 36 36 66 36 36 36 40 40 6 6 6 67 6C 65 2E 63 6F La 
valeur de chaîne décodée est 6D vous ne faites pas que courir des chaînes sur notre .so! Contactez-nous à l'adresse droidguard@google.com )
C'est étrange. Une machine virtuelle ne m'avait jamais parlé auparavant. Je pensais que si vous commencez à voir des messages secrets qui vous sont adressés, vous devenez fou. Juste pour m'assurer que j'étais toujours sain d'esprit, j'ai traité plusieurs centaines de réponses différentes du service anti-abus via mon ordinateur virtuel. Littéralement toutes les 25-30 commandes, un message était caché dans le code d'octet. Ils ont souvent répété, mais ci-dessous sont uniques. J'ai toutefois modifié les adresses électroniques: chaque message avait une adresse différente, quelque chose comme «droidguard+tag@google.com», le tag étant unique pour chacun.
droidguard@google.com: Ne soyez pas étranger! 
Vous êtes entré! Parlez-nous à droidguard@google.com 
Salutations de droidguard@google.com voyageur intrépide! Dis salut! 
Était-ce facile à trouver? droidguard@google.com aimerait savoir 
Les personnes de droidguard@google.com aimeraient avoir de vos nouvelles! 
C'est quoi tout ce gobbledygook? Demandez à droidguard@google.com… ils le sauraient! 
Hey! Envie de vous voir ici. Avez-vous déjà parlé à droidguard@google.com? 
Vous ne faites pas que courir des ficelles sur notre .so! Parlez-nous à droidguard@google.com
Suis-je l'élu? Je pensais qu'il était temps d'arrêter de jouer avec DroidGuard et de parler à Google, car ils me l'avaient demandé.

Votre appel est très important pour nous


J'ai raconté mes découvertes dans le courriel que j'ai trouvé. Pour rendre les résultats un peu plus impressionnants, j'ai automatisé un peu le processus d'analyse. Le fait est que les chaînes et les tableaux d'octets sont stockés dans le code d'octet crypté. La VM les décode à l'aide de constantes insérées par le compilateur. En utilisant une application similaire à Hex-Rays IDA, vous pouvez les extraire assez facilement. Mais à chaque nouvelle version, les constantes changent et il est assez gênant de toujours les extraire manuellement. 

Mais l’analyse par Java de la bibliothèque native s’est révélée étonnamment simple. En utilisant jelf (une bibliothèque pour analyser des fichiers ELF), nous trouvons le décalage de la Java_com_google_ccc_abuse_droidguard_DroidGuard_initNativeméthode dans le binaire, puis en utilisant Capstone(un framework de désassemblage avec des liaisons pour différents langages, y compris Java), nous obtenons le code assembleur et le recherchons pour le chargement des constantes dans les registres. 

En fin de compte, j’ai eu une application qui émulait l’ensemble du processus DroidGuard: fait une demande au service anti-abus, télécharge le fichier .apk, le décompresse, analyse la bibliothèque native, extrait les constantes requises, sélectionne le mappage des commandes de machine virtuelle et interprète le code d'octet. J'ai tout compilé et envoyé à Google. Pendant que j'y travaillais, j'ai commencé à préparer un déménagement et j'ai cherché Glassdoor pour un salaire moyen chez Google. J'ai décidé de ne pas accepter moins de six chiffres. 

La réponse ne prit pas longtemps. Un e-mail d'un membre de l'équipe DroidGuard se lit simplement: "Pourquoi faites-vous cela?"



«Parce que je peux» - ai-je répondu. Un employé de Google m'a expliqué que DroidGuard est censé protéger Android des pirates informatiques (vous ne dites pas!) Et qu'il serait sage de garder le code source de ma machine virtuelle DroidGuard pour moi. Notre conversation s'est terminée là.

Entrevue


Un mois plus tard, j'ai reçu un autre email. L’équipe DroidGuard à Zurich avait besoin d’un nouvel employé. Étais-je intéressé à devenir membre? Bien sûr! 

Il n'y a pas de raccourci pour entrer dans Google. Tout ce que je pouvais faire était de transmettre mon CV au département des ressources humaines. Après cela, j'ai dû passer par le tracas bureaucratique habituel et une série d'interviews. 

Il y a beaucoup d'histoires sur les interviews Google. Les algorithmes, les tâches Olympiad et la programmation Google Docs ne sont pas mon truc, alors j'ai commencé mes préparatifs. J'ai lu le cours «Algorithmes» de Coursera des dizaines de fois, résolu des centaines de tâches sur Hackerrank et appris à contourner un graphique dans les deux dimensions, les yeux fermés.

Deux mois passèrent. Dire que je me sentais préparé serait un euphémisme. Google Docs est devenu mon IDE préféré. J'avais l'impression de tout savoir sur les algorithmes. Bien sûr, je connaissais mes faiblesses et me suis rendu compte que je ne réussirais probablement pas la série de 5 entretiens à Zurich, mais aller gratuitement chez le programmeur Disneyland était une récompense en soi. La première étape a été une interview téléphonique visant à éliminer les candidats les plus faibles et à ne pas perdre de temps aux développeurs zurichois lors de réunions en personne. Le jour était réglé, le téléphone sonna…



… Et j'ai immédiatement échoué à mon premier test. J'ai eu de la chance - ils ont posé une question que j'ai déjà vue sur Internet et que j'ai déjà résolue. Il s'agissait de sérialiser un tableau de chaînes. J'ai proposé de coder des chaînes en Base64 et de les enregistrer via un séparateur. L'intervieweur m'a demandé de développer un algorithme Base64. Après cela, l'interview s'est transformée en une sorte de monologue, où l'interviewé m'a expliqué le fonctionnement de Base64 et j'ai essayé de me souvenir des opérations de bits en Java.

Si quelqu'un chez Google lit ceci
Les gars, vous êtes des génies sanglants si vous êtes là! Sérieusement. Je ne peux pas imaginer comment on peut effacer tous les obstacles qu’ils vous posent.

Trois jours après l'appel, j'ai reçu un email m'informant qu'ils ne voulaient plus m'interviewer. Et voilà comment ma communication avec Google s'est terminée. 

Pourquoi DroidGuard contient des messages demandant à discuter, je n’en ai toujours aucune idée. Probablement juste pour les statistiques. Le gars à qui j'ai écrit en premier lieu m'a dit que les gens y écrivaient, mais la fréquence varie: parfois, ils reçoivent 3 réponses par semaine, parfois une par an. 

Je pense qu'il existe des moyens plus faciles d'obtenir une interview sur Google. Après tout, vous pouvez demander à n'importe lequel des 100 000 employés (bien que tous ne soient pas des développeurs, certes). Mais c'était quand même une expérience amusante.
Partager :
:)
:(
hihi
:-)
:D
=D
:-d
;(
;-(
@-)
:P
:o
-_-
(o)
[-(
:-?
(p)
:-s
(m)
8-)
:-t
:-b
b-(
:-#
=p~
$-)
(y)
(f)
x-)
(k)
(h)
(c)
cheer
(li)
(pl)