Welcome Back

This is part two of a writeup on FlexiSpy. Part one can be can be located here. Btw antivirus people, new IOCs and my jeb database file are at the bottom of this post. This app is HUGE, so I’ll need to split this up into multiple blog posts. It has several components besides the main apk file. Let me show you some of the assets…(note those zips are additional apks/dex files).

5002:                          data
Camera.apk:                    Zip archive data, at least v2.0 to extract
Xposed-Disabler-Recovery.zip:  Zip archive data, at least v2.0 to extract
Xposed-Installer-Recovery.zip: Zip archive data, at least v2.0 to extract
XposedBridge.jar:              Zip archive data, at least v1.0 to extract
arm64-v8a:                     directory
arm_app_process_xposed_sdk15:  ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
arm_app_process_xposed_sdk16:  ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
arm_xposedtest_sdk15:          ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
arm_xposedtest_sdk16:          ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
aud.zip:                       Zip archive data, at least v2.0 to extract
bugd.zip:                      Zip archive data, at least v2.0 to extract
busybox:                       ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, for GNU/Linux 2.6.16, stripped
callmgr.zip:                   Zip archive data, at least v2.0 to extract
callmon.zip:                   Zip archive data, at least v2.0 to extract
dwebp:                         ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
dwebp64:                       ELF 64-bit LSB shared object, version 1 (SYSV), dynamically linked (uses shared libs), stripped
ffmpeg:                        ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
gesture_hash.zip:              Zip archive data, at least v2.0 to extract
libaac.so:                     ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libamr.so:                     ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libasound.so:                  ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libcrypto_32bit.so:            ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped
libflasusconfig.so:            ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped
libflhtcconfig.so:             ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped
libfllgconfig.so:              ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped
libflmotoconfig.so:            ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped
libflsamsungconfig.so:         ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped
libflsonyconfig.so:            ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped
libfxexec.so:                  ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libfxril.so:                   ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libfxtmessages.8.so:           ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libfxwebp.so:                  ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libkma.so:                     ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libkmb.so:                     ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
liblame.so:                    ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libmp3lame.so:                 ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libsqliteX.so:                 ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
libvcap.so:                    ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
maind:                         directory
maind.zip:                     Zip archive data, at least v2.0 to extract
mixer:                         directory
panzer:                        ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
pmond.zip:                     Zip archive data, at least v2.0 to extract
psysd.zip:                     Zip archive data, at least v2.0 to extract
ticket.apk:                    Zip archive data, at least v2.0 to extract
vdaemon:                       ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), stripped
x86_app_process_xposed_sdk15:  ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), stripped
x86_app_process_xposed_sdk16:  ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), stripped
x86_xposedtest_sdk15:          ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), stripped
x86_xposedtest_sdk16:          ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), stripped

Exculated Quickly gif

Anyways what follows is what I was able to reverse while flying back from Bsides-Nashville. If you wanted more blame United Airlines ;) Im flying every week for the next 2 weeks for bsides talks. I’ll try to reverse more of this app per flight.


There are three mobile versions of the spyware to work with.

  • The entire leaked source of 1.00.1. This is fantastic since it contains neat documented code with comments. While it is the easiest & most documented, it only has a fraction of the capabilites of the 2.x versions below.
  • 2.24.3 APK file: This is the compiled code and it does not contain any programmer comments. This is a newer version of the code then the source code above. Has many more features. Some obfuscation and as mentioned above lots of addtional modules/assets that are loaded.
  • 2.25.1 APK: Compiled code. No comments. Latests version of spyware in dump. I Haven’t looked at the differences between 2.25.1 and 2.24.3

There are also two windows executables and one mac executable. I have not looked into these yet.

The plan here is to start with the entry point of the applications (what happens when the user hits the icon), and also look at the intent receivers (I’ll get to what that means later).

AndroidManifest.xml Info

A few interesting things stand out here. First the package is namded com.android.systemupdate. This is likely named to trick the user into thinking the application is an offical android application.

<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1446" android:versionName="2.24.3" package="com.android.systemupdate" platformBuildVersionCode="15" platformBuildVersionName="4.0.4-1406430" xmlns:android="http://schemas.android.com/apk/res/android">
    <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />

The number of permissions covers pretty much everything you’d need for an invasion of privacy. See the full list below.

   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCOUNT_MANAGER" />
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
    <uses-permission android:name="android.permission.READ_CALL_LOG" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.RESTART_PACKAGES" />
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_SMS" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS" />
    <uses-permission android:name="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="com.wefeelsecure.feelsecure.permission.C2D_MESSAGE" />
    <uses-permission android:name="com.sec.android.provider.logsprovider.permission.READ_LOGS" />
    <uses-permission android:name="com.sec.android.provider.logsprovider.permission.WRITE_LOGS" />
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
    <uses-permission android:name="android.permission.BATTERY_STATS" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
    <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
    <uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
    <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.BLUETOOTH" />

Entry Point onCreate

The first activity that runs when a user installs the application is com.phoenix.client.PrerequisitesSetupActivity. Let’s take at look at how it functions.

For most android activities, the OnCreate method usually runs first. After some initial GUI setup, the app checks if the phone is rooted.

public void onCreate(Bundle arg6) {
        super.onCreate(arg6);  // ignore
        this.setContentView(2130903047);  // ignore
        StrictMode.setThreadPolicy(new StrictMode$ThreadPolicy$Builder().permitAll().build());
        this.o_Button = this.findViewById(2131165209);  // ignore
        this.o_Button2 = this.findViewById(2131165210);  // ignore
        this.o_TextView = this.findViewById(2131165207);  // ignore
        this.k = this.findViewById(2131165208);  // ignore
        this.k.setVisibility(4);  // ignore
        this.o_TextView.setText(String.format(this.getString(2130968605), cz.m_superUserCheck(((Context)this)), this.getString(2130968601)));  // can return SuperSU or Superuser

Root Checks aka cz.m_superUserCheck

The actual root check is below. This checks if any of the 4 root packages are installed. This would indicate the device is rooted. Note this will be the first of many root/package checks in this codebase.

public static SuBinaryProvider d(Context arg1) {
        SuBinaryProvider v0;
        if(e.m_LooksForInstalledPackages(arg1, "com.noshufou.android.su")) {
            v0 = SuBinaryProvider.NOSHUFOU_SUPERUSER;
        else if(e.m_LooksForInstalledPackages(arg1, "eu.chainfire.supersu")) {
            v0 = SuBinaryProvider.CHAINFIRE_SUPERSU;
        else if(e.m_LooksForInstalledPackages(arg1, "com.m0narx.su")) {
            v0 = SuBinaryProvider.M0NARX_SUPERUSER;
        else if(e.m_LooksForInstalledPackages(arg1, "com.koushikdutta.superuser")) {
            v0 = SuBinaryProvider.KOUSHIKDUTTA_SUPERUSER;
        else {
            v0 = SuBinaryProvider.CHAINFIRE_SUPERSU;

        return v0;

Depending on what (if any) root package is detected a particular value is set to SuperUser or SuperSU.

public static String m_superUserCheck(Context arg3) {
        SuBinaryProvider SuCheck = cz.ChecksforSuPackages(arg3);  // checks for 4 packages
        String str_returnValSuperSu = "SuperSU";  // default return val
        if(SuCheck == SuBinaryProvider.CHAINFIRE_SUPERSU) {
            str_returnValSuperSu = "SuperSU";
        else {
            if(SuCheck != SuBinaryProvider.NOSHUFOU_SUPERUSER && SuCheck != SuBinaryProvider.KOUSHIKDUTTA_SUPERUSER && SuCheck != SuBinaryProvider.M0NARX_SUPERUSER) {
                return str_returnValSuperSu;

            str_returnValSuperSu = "Superuser";

        return str_returnValSuperSu;  // can return SuperSU or Superuser

back to onCreate

After the root check the app checks for a file on the SDcard. This is likely checking to see if the application has been installed before. Depending on whether ac.txt file exists, one of two executation will occur: one starts theAutoInstallerActivity, and the other starts CoreService.

 this.o_TextView.setText(String.format(this.getString(2130968605), cz.m_superUserCheck(((Context)this)), this.getString(2130968601)));  // can return SuperSU or Superuser
        this.o_Button.setOnClickListener(new cp(this));
        this.o_Button2.setOnClickListener(new cq(this));
        if(cz.m_acTextCHeck()) {  // checks for ac.txt value on SDcard
            Intent o_intentObj = new Intent(((Context)this), AutoInstallerActivity.class);  // if the txt file IS present
            this.startActivity(o_intentObj);  // starts theAutoInstallerActivity class
        else {
            this.g = new SetupFlagsManager(o.a(this.getApplicationContext()));  // if the txt file is NOT present
            this.f = ak.a(((Context)this));
            if(this.c == null) {
                this.bindService(new Intent(((Context)this), CoreService.class), this.l, 1);
            else {

Regardless of what path is executed, eventually the coreService is started anyway. The AutoInstallerActivity appears to do a few setup steps, writes some log files, creates a few custom setup objects, and eventually starts the CoreService class. At this point the app “waits” for the user interaction. See section below for details.


Receivers listen for incoming “intents” on android. This is the section of code that responds when the screen is unlocked, the phone reboots, or a new SMS message is recieved.

 <intent-filter android:priority="2147483647">
                <action android:name="android.intent.action.USER_PRESENT" />
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
                <action android:name="android.intent.action.PHONE_STATE" />
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />

Incoming SMS

When an incoming sms is detected. The app looks for the special value of <*# within the SMS message. This appears to be a special commmand and control value that can be sent to a victim over SMS to perform operations.

 while(o_Iterator.hasNext()) {
                    str_intentAction = o_Iterator.next().getMessageBody();
                    if(str_intentAction != null && (str_intentAction.trim().startsWith("<*#"))) {  // look for a "special value" in sms
                        i_specialCommandFound = 1;

                    i_specialCommandFound = 0;

A quick cross reference in the leaked source code file 1.00.1/_build/source/daemon_remote_command_manager/src/com/vvt/remotecommandmanager/SmsCommandPattern.java indicates that this <** pattern in a SMS message is used to indicate a remote command. The 1.00.1 Version of commands are below.

    //Monitor call
    public static final String ENABLE_SPY_CALL = "<*#9>";
    public static final String ENABLE_SPY_CALL_WITH_MONITOR = "<*#10>";
    public static final String ADD_MONITORS = "<*#160>";
    public static final String RESET_MONITORS = "<*#163>";
    public static final String CLEAR_MONITORS = "<*#161>";
    public static final String QUERY_MONITORS = "<*#162>";
    public static final String ADD_CIS_NUMBERS = "<*#130>";
    public static final String RESET_CIS_NUMBERS = "<*#131>";
    public static final String CLEAR_CIS_NUMBERS = "<*#132>";
    public static final String QUERY_CIS_NUMBERS = "<*#133>";
    public static final String REQUEST_HEART_BEAT = "<*#2>";
    public static final String REQUEST_EVENTS = "<*#64>";
    public static final String SET_SETTINGS = "<*#92>";
    public static final String ENABLE_SIM_CHANGE = "<*#56>";
    public static final String ENABLE_CAPTURE = "<*#60>";
    public static final String SET_VISIBILITY = "<*#14214>";
    public static final String ENABLE_COMMUNICATION_RESTRICTIONS = "<*#204>";
    //Activation and installation
    public static final String ACTIVATE_WITH_ACTIVATION_CODE_AND_URL = "<*#14140>";
    public static final String ACTIVATE_WITH_URL = "<*#14141>";
    public static final String DEACTIVATE = "<*#14142>";
    public static final String SET_ACTIVATION_PHONE_NUMBER = "<*#14258>";
    public static final String SYNC_UPDATE_CONFIGURATION = "<*#300>";
    public static final String UNINSTALL_APPLICATION = "<*#200>";
    public static final String SYNC_SOFTWARE_UPDATE = "<*#306>";
    public static final String ENABLE_PRODUCT = "<*#14000>";
    public static final String REQUEST_MOBILE_NUMBER = "<*#199>";
    //Address Book
    public static final String REQUEST_ADDRESSBOOK = "<*#120>";
    public static final String SET_ADDRESSBOOK_FOR_APPROVAL = "<*#121>";
    public static final String SET_ADDRESSBOOK_MANAGEMENT = "<*#122>";
    public static final String SYNC_ADDRESSBOOK = "<*#301>";
//  public static final String UPLOAD_ACTUAL_MEDIA = "";
//  public static final String DELETE_ACTUAL_MEDIA = "";
    public static final String ON_DEMAND_RECORD = "<*#84>";
    public static final String ENABLE_LOCATION = "<*#52>";
    public static final String UPDATE_GPS_INTERVAL = "<*#53>";
    public static final String ON_DEMAND_LOCATION = "<*#101>";
    public static final String SPOOF_SMS = "<*#85>";
    public static final String SPOOF_CALL = "<*#86>";
    //Call watch
    public static final String ENABLE_WATCH_NOTIFICATION = "<*#49>";
    public static final String SET_WATCH_FLAGS = "<*#50>";
    public static final String ADD_WATCH_NUMBER = "<*#45>";
    public static final String RESET_WATCH_NUMBER = "<*#46>";
    public static final String CLEAR_WATCH_NUMBER = "<*#47>";
    public static final String QUERY_WATCH_NUMBER = "<*#48>";
    //Keyword list
    public static final String ADD_KEYWORD = "<*#73>";
    public static final String RESET_KEYWORD = "<*#74>";
    public static final String CLEAR_KEYWORD = "<*#75>";
    public static final String QUERY_KEYWORD = "<*#76>";
    //URL list
    public static final String ADD_URL = "<*#396>";
    public static final String RESET_URL = "<*#397>";
    public static final String CLEAR_URL = "<*#398>";
    public static final String QUERY_URL = "<*#399>";
    //Security and protection
    public static final String SET_PANIC_MODE = "<*#31>";
    public static final String SET_WIPE_OUT = "<*#201>";
    public static final String SET_LOCK_DEVICE = "<*#202>";
    public static final String SET_UNLOCK_DEVICE = "<*#203>";
    public static final String ADD_EMERGENCY_NUMBER = "<*#164>";
    public static final String RESET_EMERGENCY_NUMBER = "<*#165>";
    public static final String QUERY_EMERGENCY_NUMBER = "<*#167>";
    public static final String CLEAR_EMERGENCY_NUMBER = "<*#166>";
    public static final String REQUEST_SETTINGS = "<*#67>";
    public static final String REQUEST_DIAGNOSTIC = "<*#62>";
    public static final String REQUEST_START_UP_TIME = "<*#5>";
    public static final String RESTART_DEVICE = "<*#147>";
    public static final String RETRIEVE_RUNNING_PROCESSES = "<*#14852>";
    public static final String TERMINATE_RUNNING_PROCESSES = "<*#14853>";
    public static final String SET_DEBUG_MODE = "<*#170>";
    public static final String REQUEST_CURRENT_URL = "<*#14143>";
    public static final String ENABLE_CONFERENCING_DEBUGING = "<*#12>";
    public static final String INTERCEPTION_TONE = "<*#21>";
    public static final String RESET_LOG_DURATION = "<*#65>";
    public static final String FORCE_APN_DISCOVERY = "<*#71>";
    //Notification Numbers
    public static final String ADD_NOTIFICATION_NUMBERS = "<*#171>";
    public static final String RESET_NOTIFICATION_NUMBERS = "<*#172>";
    public static final String CLEAR_NOTIFICATION_NUMBERS = "<*#173>";
    public static final String QUERY_NOTIFICATION_NUMBERS = "<*#174>";
    //Home numbers
    public static final String ADD_HOMES = "<*#150>";
    public static final String RESET_HOMES = "<*#151>";
    public static final String CLEAR_HOMES = "<*#152>";
    public static final String QUERY_HOMES = "<*#153>";
    public static final String SYNC_COMMUNICATION_DIRECTIVES = "<*#302>";
    public static final String SYNC_TIME = "<*#303>";
    public static final String SYNC_PROCESS_PROFILE = "<*#304>";
    public static final String SYNC_INCOMPATIBLE_APPLICATION_DEFINITION = "<*#307>";

The means of sending commands have changed in the 2.x version of the code. Here is a list of the 2.x remote commands that can be sent to a victim device.

 RemoteFunction.ACTIVATE_PRODUCT = new RemoteFunction("ACTIVATE_PRODUCT", 0);
        RemoteFunction.DEACTIVATE_PRODUCT = new RemoteFunction("DEACTIVATE_PRODUCT", 1);
        RemoteFunction.IS_PRODUCT_ACTIVATED = new RemoteFunction("IS_PRODUCT_ACTIVATED", 2);
        RemoteFunction.UNINSTALL_PRODUCT = new RemoteFunction("UNINSTALL_PRODUCT", 3);
        RemoteFunction.GET_LICENSE_STATUS = new RemoteFunction("GET_LICENSE_STATUS", 4);
        RemoteFunction.GET_ACTIVATION_CODE = new RemoteFunction("GET_ACTIVATION_CODE", 5);
        RemoteFunction.AUTO_ACTIVATE_PRODUCT = new RemoteFunction("AUTO_ACTIVATE_PRODUCT", 6);
        RemoteFunction.MANAGE_COMMON_DATA = new RemoteFunction("MANAGE_COMMON_DATA", 7);
        RemoteFunction.ENABLE_EVENT_DELIVERY = new RemoteFunction("ENABLE_EVENT_DELIVERY", 8);
        RemoteFunction.SET_EVENT_MAX_NUMBER = new RemoteFunction("SET_EVENT_MAX_NUMBER", 9);
        RemoteFunction.SET_EVENT_TIMER = new RemoteFunction("SET_EVENT_TIMER", 10);
        RemoteFunction.SET_DELIVERY_METHOD = new RemoteFunction("SET_DELIVERY_METHOD", 11);
        RemoteFunction.ADD_URL = new RemoteFunction("ADD_URL", 12);
        RemoteFunction.RESET_URL = new RemoteFunction("RESET_URL", 13);
        RemoteFunction.CLEAR_URL = new RemoteFunction("CLEAR_URL", 14);
        RemoteFunction.QUERY_URL = new RemoteFunction("QUERY_URL", 15);
        RemoteFunction.ENABLE_EVENT_CAPTURE = new RemoteFunction("ENABLE_EVENT_CAPTURE", 16);
        RemoteFunction.ENABLE_CAPTURE_CALL = new RemoteFunction("ENABLE_CAPTURE_CALL", 17);
        RemoteFunction.ENABLE_CAPTURE_SMS = new RemoteFunction("ENABLE_CAPTURE_SMS", 18);
        RemoteFunction.ENABLE_CAPTURE_EMAIL = new RemoteFunction("ENABLE_CAPTURE_EMAIL", 19);
        RemoteFunction.ENABLE_CAPTURE_MMS = new RemoteFunction("ENABLE_CAPTURE_MMS", 20);
        RemoteFunction.ENABLE_CAPTURE_IM = new RemoteFunction("ENABLE_CAPTURE_IM", 21);
        RemoteFunction.ENABLE_CAPTURE_IMAGE = new RemoteFunction("ENABLE_CAPTURE_IMAGE", 22);
        RemoteFunction.ENABLE_CAPTURE_AUDIO = new RemoteFunction("ENABLE_CAPTURE_AUDIO", 23);
        RemoteFunction.ENABLE_CAPTURE_VIDEO = new RemoteFunction("ENABLE_CAPTURE_VIDEO", 24);
        RemoteFunction.ENABLE_CAPTURE_WALLPAPER = new RemoteFunction("ENABLE_CAPTURE_WALLPAPER", 25);
        RemoteFunction.ENABLE_CAPTURE_APP = new RemoteFunction("ENABLE_CAPTURE_APP", 26);
        RemoteFunction.ENABLE_CAPTURE_URL = new RemoteFunction("ENABLE_CAPTURE_URL", 27);
        RemoteFunction.ENABLE_CAPTURE_CALL_RECORD = new RemoteFunction("ENABLE_CAPTURE_CALL_RECORD", 28);
        RemoteFunction.ENABLE_CAPTURE_CALENDAR = new RemoteFunction("ENABLE_CAPTURE_CALENDAR", 29);
        RemoteFunction.ENABLE_CAPTURE_PASSWORD = new RemoteFunction("ENABLE_CAPTURE_PASSWORD", 30);
        RemoteFunction.ENABLE_CAPTURE_VOIP = new RemoteFunction("ENABLE_CAPTURE_VOIP", 32);
        RemoteFunction.ENABLE_VOIP_CALL_RECORDING = new RemoteFunction("ENABLE_VOIP_CALL_RECORDING", 33);
        RemoteFunction.ENABLE_CAPTURE_CONTACT = new RemoteFunction("ENABLE_CAPTURE_CONTACT", 34);
        RemoteFunction.SET_IM_ATTACHMENT_LIMIT_SIZE = new RemoteFunction("SET_IM_ATTACHMENT_LIMIT_SIZE", 35);
        RemoteFunction.ENABLE_CAPTURE_GPS = new RemoteFunction("ENABLE_CAPTURE_GPS", 36);
        RemoteFunction.SET_GPS_TIME_INTERVAL = new RemoteFunction("SET_GPS_TIME_INTERVAL", 37);
        RemoteFunction.GET_GPS_ON_DEMAND = new RemoteFunction("GET_GPS_ON_DEMAND", 38);
        RemoteFunction.ENABLE_SPY_CALL = new RemoteFunction("ENABLE_SPY_CALL", 39);
        RemoteFunction.ENABLE_WATCH_NOTIFICATION = new RemoteFunction("ENABLE_WATCH_NOTIFICATION", 40);
        RemoteFunction.SET_WATCH_FLAG = new RemoteFunction("SET_WATCH_FLAG", 41);
        RemoteFunction.GET_CONNECTION_HISTORY = new RemoteFunction("GET_CONNECTION_HISTORY", 42);
        RemoteFunction.GET_CONFIGURATION = new RemoteFunction("GET_CONFIGURATION", 43);
        RemoteFunction.GET_SETTINGS = new RemoteFunction("GET_SETTINGS", 44);
        RemoteFunction.GET_DIAGNOSTICS = new RemoteFunction("GET_DIAGNOSTICS", 45);
        RemoteFunction.GET_EVENT_COUNT = new RemoteFunction("GET_EVENT_COUNT", 46);
        RemoteFunction.REQUEST_CALENDER = new RemoteFunction("REQUEST_CALENDER", 48);
        RemoteFunction.SET_SUPERUSER_VISIBILITY = new RemoteFunction("SET_SUPERUSER_VISIBILITY", 49);
        RemoteFunction.SET_LOCK_PHONE_SCREEN = new RemoteFunction("SET_LOCK_PHONE_SCREEN", 50);
        RemoteFunction.REQUEST_DEVICE_SETTINGS = new RemoteFunction("REQUEST_DEVICE_SETTINGS", 51);
        RemoteFunction.DELETE_DATABASE = new RemoteFunction("DELETE_DATABASE", 53);
        RemoteFunction.RESTART_DEVICE = new RemoteFunction("RESTART_DEVICE", 54);
        RemoteFunction.REQUEST_HISTORICAL_EVENTS = new RemoteFunction("REQUEST_HISTORICAL_EVENTS", 55);
        RemoteFunction.SEND_HEARTBEAT = new RemoteFunction("SEND_HEARTBEAT", 58);
        RemoteFunction.SEND_MOBILE_NUMBER = new RemoteFunction("SEND_MOBILE_NUMBER", 59);
        RemoteFunction.SEND_SETTINGS_EVENT = new RemoteFunction("SEND_SETTINGS_EVENT", 60);
        RemoteFunction.SEND_EVENTS = new RemoteFunction("SEND_EVENTS", 61);
        RemoteFunction.REQUEST_CONFIGURATION = new RemoteFunction("REQUEST_CONFIGURATION", 62);
        RemoteFunction.SEND_CURRENT_URL = new RemoteFunction("SEND_CURRENT_URL", 63);
        RemoteFunction.SEND_BOOKMARKS = new RemoteFunction("SEND_BOOKMARKS", 64);
        RemoteFunction.DEBUG_SWITCH_CONTAINER = new RemoteFunction("DEBUG_SWITCH_CONTAINER", 65);
        RemoteFunction.DEBUG_HIDE_APP = new RemoteFunction("DEBUG_HIDE_APP", 66);
        RemoteFunction.DEBUG_UNHIDE_APP = new RemoteFunction("DEBUG_UNHIDE_APP", 67);
        RemoteFunction.DEBUG_IS_DAEMON = new RemoteFunction("DEBUG_IS_DAEMON", 68);
        RemoteFunction.DEBUG_IS_FULL_MODE = new RemoteFunction("DEBUG_IS_FULL_MODE", 69);
        RemoteFunction.DEBUG_GET_CONFIG_ID = new RemoteFunction("DEBUG_GET_CONFIG_ID", 70);
        RemoteFunction.DEBUG_GET_ACTUAL_CONFIG_ID = new RemoteFunction("DEBUG_GET_ACTUAL_CONFIG_ID", 71);
        RemoteFunction.DEBUG_GET_VERSION_CODE = new RemoteFunction("DEBUG_GET_VERSION_CODE", 72);
        RemoteFunction.DEBUG_SEND_TEST_SMS = new RemoteFunction("DEBUG_SEND_TEST_SMS", 73);
        RemoteFunction.DEBUG_CLOSE_APP = new RemoteFunction("DEBUG_CLOSE_APP", 74);
        RemoteFunction.DEBUG_BRING_UI_TO_HOME_SCREEN = new RemoteFunction("DEBUG_BRING_UI_TO_HOME_SCREEN", 75);
        RemoteFunction.DEBUG_SET_APPLICATION_MODE = new RemoteFunction("DEBUG_SET_APPLICATION_MODE", 76);
        RemoteFunction.DEBUG_GET_APPLICATION_MODE = new RemoteFunction("DEBUG_GET_APPLICATION_MODE", 77);
        RemoteFunction.DEBUG_RESTART_DEVICE = new RemoteFunction("DEBUG_RESTART_DEVICE", 78);
        RemoteFunction.DEBUG_PRODUCT_VERSION = new RemoteFunction("DEBUG_PRODUCT_VERSION", 80);
        RemoteFunction.SET_MODE_ADDRESS_BOOK = new RemoteFunction("SET_MODE_ADDRESS_BOOK", 83);
        RemoteFunction.SEND_ADDRESS_BOOK = new RemoteFunction("SEND_ADDRESS_BOOK", 84);
        RemoteFunction.REQUEST_BATTERY_INFO = new RemoteFunction("REQUEST_BATTERY_INFO", 85);
        RemoteFunction.REQUEST_MEDIA_HISTORICAL = new RemoteFunction("REQUEST_MEDIA_HISTORICAL", 86);
        RemoteFunction.UPLOAD_ACTUAL_MEDIA = new RemoteFunction("UPLOAD_ACTUAL_MEDIA", 87);
        RemoteFunction.DELETE_ACTUAL_MEDIA = new RemoteFunction("DELETE_ACTUAL_MEDIA", 88);
        RemoteFunction.ON_DEMAND_AMBIENT_RECORD = new RemoteFunction("ON_DEMAND_AMBIENT_RECORD", 89);
        RemoteFunction.ON_DEMAND_IMAGE_CAPTURE = new RemoteFunction("ON_DEMAND_IMAGE_CAPTURE", 90);
        RemoteFunction.ENABLE_CALL_RECORDING = new RemoteFunction("ENABLE_CALL_RECORDING", 91);
        RemoteFunction.SET_CALL_RECORDING_WATCH_FLAG = new RemoteFunction("SET_CALL_RECORDING_WATCH_FLAG", 92);
        RemoteFunction.ENABLE_APP_PROFILE = new RemoteFunction("ENABLE_APP_PROFILE", 95);
        RemoteFunction.ENABLE_URL_PROFILE = new RemoteFunction("ENABLE_URL_PROFILE", 96);
        RemoteFunction.SPOOF_SMS = new RemoteFunction("SPOOF_SMS", 97);
        RemoteFunction.SET_PANIC_MODE = new RemoteFunction("SET_PANIC_MODE", 98);
        RemoteFunction.START_PANIC = new RemoteFunction("START_PANIC", 99);
        RemoteFunction.STOP_PANIC = new RemoteFunction("STOP_PANIC", 100);
        RemoteFunction.GET_PANIC_MODE = new RemoteFunction("GET_PANIC_MODE", 101);
        RemoteFunction.PANIC_IMAGE_CAPTURE = new RemoteFunction("PANIC_IMAGE_CAPTURE", 102);
        RemoteFunction.IS_PANIC_ACTIVE = new RemoteFunction("IS_PANIC_ACTIVE", 103);
        RemoteFunction.ENABLE_ALERT = new RemoteFunction("ENABLE_ALERT", 104);
        RemoteFunction.SET_LOCK_DEVICE = new RemoteFunction("SET_LOCK_DEVICE", 105);
        RemoteFunction.SET_UNLOCK_DEVICE = new RemoteFunction("SET_UNLOCK_DEVICE", 106);
        RemoteFunction.SET_WIPE = new RemoteFunction("SET_WIPE", 107);
        RemoteFunction.a = new RemoteFunction[]{RemoteFu

If the user is using the device

The spyware app “listens” for various intents that indicate the individual is using the phone: if the screen is unlocked, the device powered on etc.

label_65:  // this is if NO sms is detected
                if((str_intentAction.equals("android.intent.action.BOOT_COMPLETED")) || (str_intentAction.equals("android.intent.action.QUICKBOOT_POWERON")) || (str_intentAction.equals("com.htc.intent.action.QUICKBOOT_POWERON"))) {
                    com.fx.daemon.b.m_relatedToShellCmds(o.m_getDataPath(arg6), "fx.log");
                    StrictMode.setThreadPolicy(new StrictMode$ThreadPolicy$Builder().permitNetwork().build());
                    if(CommonReceiver.c()) {

                    if(!CommonReceiver.f_bool_maindZip()) {

                    AppStartUpHandler.a(dataPath, AppStartUpHandler$AppStartUpMethod.BOOT_COMPLETED);

first condition

After the intent is received, we hit the first if statement

   if(CommonReceiver.b_returnTrueIfDebugMode()) {

This code just checks if the DEBUG_IS_FULL_MODE, command has been sent to the victim device.

second condition

The second if is below. It performs another set of root checks and peforms a check to see if the maind.zip file exists

  if(!CommonReceiver.RootAndMainZipCheck()) {  // if not rooted and a zip doesnt exist exit

This f_bool_maindZip method has to do with the asset maind.zip which is located in the /assets/production/ folder.

  private static boolean RootAndMainZipCheck() {
        boolean returnVal = true;
        String str_maindZipPath = o.str_FilePathGetter(b.str_dataMiscAdn, "maind.zip");
        if((ShellUtil.m_bool_MultipleRootcheck()) && (ShellUtil.m_ChecksForFIle(str_maindZipPath))) {
            returnVal = false;

        return returnVal;  // return true if rooted AND maind.zip is found

This method calls another series of root checks. It looks if test-keys is present on the device’s Build Tags values, checks for the SuperUser.APK app, the su binary in a variety of locations, env path checks, and lastly tries to invoke a shell. Code below

public static boolean m_bool_Rootcheck() {
        boolean bool_returnVal = false;
        if(ShellUtil.bool_debug) {
            Log.v("ShellUtil", "isDeviceRooted # START ...");

        String str_buildPropTags = Build.TAGS;
        boolean str_TestKeys = str_buildPropTags == null || !str_buildPropTags.contains("test-keys") ? false : true;
        if(ShellUtil.bool_debug) {
            Log.v("ShellUtil", "checkRootMethod1 # isDeviceRooted ? : " + str_TestKeys);

        if((str_TestKeys) || (ShellUtil.f_bool_checksForSUperSuAPK()) || (ShellUtil.m_bool_SuCheck()) || (ShellUtil.m_boolEnvPathCheck()) || (ShellUtil.m_boolTryToExecShell())) {
            bool_returnVal = true;

        if(ShellUtil.bool_debug) {
            Log.v("ShellUtil", "isDeviceRooted # isDeviceRooted ? : " + bool_returnVal);

        if(ShellUtil.bool_debug) {
            Log.v("ShellUtil", "isDeviceRooted # EXIT ...");

        return bool_returnVal

The maind.zip check is done via the following method

 public static boolean m_ChecksForFIle(String arg7) {
        boolean b_returnVal = true;
        try {
            c_RelatedToFxExecLib v2 = c_RelatedToFxExecLib.b();
            String v3 = v2.a(String.format("%s \"%s\"", "/system/bin/ls", arg7));
            if(v3.contains("No such file or directory")) {
                return false;
        catch(CannotGetRootShellException v0_1) {
            b_returnVal = new File(arg7).exists();

        return b_returnVal;

back to receiver

The code below occurs after the 2nd if statement.

        AppStartUpHandler.a(dataPath, AppStartUpHandler$AppStartUpMethod.BOOT_COMPLETED);
        ak.startCoreService(arg6);  // starts the "engine"

This is actually pretty simple. The ak.startCoreService(arg6) method just starts up the coreService again. Remember that is what is started from the OnCreate method way at the beginning of this post.

Congrats we just went through the first intent receiver.

Next Episode Preview

On my next flight I’ll take a look at the CoreService and the other intent receiver com.vvt.callhandler.phonestate.OutgoingCallReceiver which listens for outgoing phone calls.

Telephone dad joke


For those in the AV industry here’s some more IOCs that you can look up in VirusToal

Sha1 Filename

  • b1ea0ccf834e4916aee1d178a71aba869ac3b36e libfxexec.so This is actually in the 1.00.1 source hehe ;)
  • 174b285867ae4f3450af59e1b63546a2d8ae0886 maind.zip

Jeb Database File

If you want to see where I left off, or want to correct any mistakes ;) here it is.