SMS Spy ZertSecurity with decompiled, analyzed Java sources

The implementation of this Android malware is strong and clean. Nothing really innovative though.


Here’s a short summary:

  • Masquerades as a German certificate installer app. The fake login/pin is used to uniquely identify the phone (on top of the phone number and IMEI.)
  • Achieves SMS interception, most likely to break 2-factor authentication. There are 3 modes of operation: do nothing, intercept and proceed, intercept and cancel SMS broadcast.
  • Multiple command-and-control servers. Two hard-coded domains are hxxp://app-smartsystem.(com|net)/sms/d_m009.php
  • The C&C urls can be updated, either by contacting the existing the existing C&C, or via SMS sent by the master. Such SMS contain the string “&Sign28tepXXX”.
  • The communication with the C&C is encrypted using AES-ECB, with the static key “0523850789a8cfed”. The server also base64-encodes its payloads. (The client does not.)
  • The malware will try to start at boot time (BOOT_COMPLETED receiver), and also registers a 15-minute timer to query the server for updated C&C urls.



The APK was run through a name obfuscator. I’m attaching the sources decompiled using JEB 1.1, and with most of the refactoring/renaming/commenting work done. For JEB users, here is the JDB file. Enjoy.

Sample MD5: 1cf41bdc0fdd409774eb755031a6f49d

Dexguard’s assets encryption

Continuing the post on Dexguard’s protection mechanisms, let’s have a look at another feature advertised on Saikoa’s website, assets encryption:

Encrypt assets. Hide important data as well. DexGuard can encrypt your asset files transparently, so hackers won’t just run away with them.”

Let’s have a look at an app containing a protected asset, 1.png. The hexdump below shows the encrypted 1.png. The PNG header is gone, as this is the encrypted version of the file.


The decryption routine is easy to spot: it appears the call was replaced by inline code that uses encrypted strings and reflection to instantiate a Cipher class and decrypt the asset file. We will use the script from the previous post to decode the strings and determine exactly what is happening. The screenshot below shows the decompiled routine in question, after it was marked up and commented using JEB (try-catch have been disabled for improved clarity, as they do not help understand how the routine works):


The references to the AssetManager class, open method, asset filename 1.png as well as cipher type AES-CFB have been encrypted using the string encryption feature. The decryption is achieved using standard Android classes, and the scheme used is AES with a random key and IV (outlined in red in.)

The final call to InputStream.available() is actually what the original method was doing. Therefore, the protection mechanism replaced by the following section of code:


We conclude that manually restoring protected assets is not difficult, although it cannot be easily automated. One way to improve that protection scheme would be to use custom decryption routines instead of SDK-provided classes, and/or generalize the use of reflection to reference the decryption code.

A look inside Dexguard

Dexguard is the commercial alter-ego of Proguard, for Android applications. While Proguard provides name obfuscation and unused code paths removal, Dexguard can also encrypt strings, classes, and assets, add tamper detection code, and use Java reflection facility to deeply obfuscate what a piece of code does.

Let’s have a quick look at one feature of Dexguard, the string encryption mechanism. The app in question is a Dexguard’ed version of Cyanide.

First, access to strings inside a protected class are replaced by calls to a static private decryption routine. That routine accesses a static private array containing all protected strings within the class. That array is created by the class initializer upon loading:


The decryption routine usually takes 3 arguments, although in some simpler cases, it may only take two arguments. The arguments may also be shuffled. (Note: from a Dalvik standpoint, that routine is oddly structured. Although JEB’s decompiled output is structurally fine, a for-loop construct cannot be created, mainly because of the irregular jump inside the first if-cond branch.)

The screenshot below represents that decryption routine. The name was obfuscated by Dexguard. Notice the constant numerals that have been circled in that code snippet: they are the decryption routine characteristics.


The following screenshot shows the same piece of code after marking it up. As you can see, the encryption algorithm is fairly simple: the current output character is derived from the previous character as well as the current byte of the encryptedStrings array. A constant (-8 in this case) is added to yield the final character. The initial length (length) as well as initial character (curChar) are also offset by hard-coded constants. These three constants (0x3E, 0x199, -8) characterize the decryption routine, and are unique for each routine. The curChar parameter acts as the decryption key.


You may customize this Python script to decrypt Dexguard strings:

# decryptor for Dexguard encrypted strings

# customize
strings = [0x4C, 0xE, 2, 9, -7, 0x10, -54, 0x3E, 0x17, -9, -44, 0x4C, 0xA, ...]
c0, c1, c2 = (0x199, 0x3E, -8)

def decrypt(length, curChar, pos):
  length += c0
  curChar += c1
  r = ''
  for i in range(length):
    r += chr(curChar & 0xFF)
    curEncodedChar = strings[pos]
    pos += 1
    curChar = curChar + curEncodedChar + c2
  return r

Combined with reflection (another feature of the protector), the protection can be pretty effective.

Help for the Bluebox challenge

The Bluebox fellows have posted an interesting Android crackme a few days ago.

The APK presents several oddities, including:

  • APK entries marked as password-protected.
  • Reuse of java.lang.String to confuse tools and analysts.
  • Some basic cryptography in the APK.
  • The most interesting part: a native library that replaces the bytecode for the custom String.add() upon loading the main activity class

As Jurriaan Bremer pointed out, the replacement bytecode in question can be found at offset 4004h of, and is E0h bytes long. The original String.add() is only A6h bytes long, and references exception handlers. Replacing the bytecode is not trivial, but annoying.


After reassembly, JEB decompiles the real String.add() to clean code: (marked-up in the screenshot below)


I’m providing the sources for both com.bluebox.lab.poc.Action and the fixed java.lang.String if you want to complete the challenge.


Bad Java decompilation means erroneous statement in research paper

Back in July 9, 2012, Martin Georgiev et al. published a paper entitled “The Most Dangerous Code in the World: Validating SSL Certificates in Non-Browser Software” (download) that points out broken SSL certificates validation in various applications and third-party libraries.

One of the case studied is the Chase mobile banking app for Android. It turns out the version studied by the authors was 2.5 or earlier, released on April 23, 2012. (The APK can be found on Android-Drawer.) In paragraph 10.1, the authors wrote:

“Decompilation and analysis of this app’s code show that it overrides the default X509TrustManager. The replacement code simply returns without checking the server’s certificate. The code below is the result of reverse-engineering, thus variable names and other details may differ from the actual code.”

While the first statement is true (X509Certificate.checkServerTrusted() is overriden), the second is false. The claim was made because of improper decompilation to Java:

public final void checkServerTrusted(X509Certificate[]paramArrayOfX509Certificate,    String paramString)
  if ((paramArrayOfX509Certificate != null)
    && (paramArrayOfX509Certificate.length == 1))
  while (true)
    return;  // makes checkServerTrusted unreachable
    this.a.checkServerTrusted(paramArrayOfX509Certificate, paramString);

The decompiled code is incorrect: the “while(true) { return; …” is a misconstruct that lets the reverse engineer believe that this.a.checkServerTrusted() is never called.

Unfortunately, the authors relied in part on this faulty piece of Java code to claim that:

“Note the unreachable invocation of checkServerTrusted. We conjecture that this was a temporary plug during development that somehow found its way into the production version of the app.

Chase [is] completely insecure against a man-in-the-middle attack.”

Note that the conclusion that Chase is insecure to MiTM attacks is not disputed here. Martin Georgiev confirmed that the app was tested and was vulnerable to such attacks.

The authors may have run out of time and probably skipped the Dalvik bytecode verification step.

In fact, the routine in question is pretty simple, and JEB decompiles it to a clean:1

Now, it looks clear that the original checkServerTrusted() gets called, if and only if the certificates’ array is null or the array does not contain just one certificate.

Decompilation is not a guaranteed process, but one should use professional tools to minimize exposure to bugs. A manual check of the low-level bytecode or assembly is also a requirement before making claims that a particular code path is or is not executed.

Thanks to Juliano Rizzo for pointing out this potential issue.

Japanese Contact Stealer

Let’s have a quick look at a variant of Android.Uracto, an app that steals (and potentially spams) contacts from Android devices. There is nothing particularly interesting about this piece of malware, but it’s the occasion to demonstrate some of JEB less-known and forthcoming abilities.

Upon startup, the app displays the following spinner, that translates to “Reading the articles…”:


The onCreate() method for the main activity displays the above spinner, and also starts a new Thread, that will create and run a Progress object. Here it is:



The run() method will call postMailList(). This method gets the ContentResolver for the app, and enumerates all entries having the “” MIME type. According to the documentation, these entries represent “contacts’ proper names”.


A buffer representing the data1, data2, and data3 fields (respectively, Display Name, Given Name, and Family Name) is dynamically created.

[JEB specific]

Notice the optimizations that allowed the creation of that compact construct:

  • for-loop optimization
  • string concatenation
  • aggressive variable substitution

Some of these optimizations are already present in 1.0.x, others will be included in the 1.1 versions.

[/JEB specific]

The final data (“contact1, contact2, …”) is dumped on the external memory storage, encoded and POST’ed to hxxp://


Find the decompiled activity here: solution.newsandroid.MainviewActivity

Sample MD5: ba73e96caa95999321c1cdd766bdf58b

Korean SMS Interceptor

Let’s inaugurate this blog by looking at a fairly simple piece of Android malware, an SMS interceptor app whose prime targets are South Korean users.

The app impersonates a Starbucks coupon (스타벅스 쿠폰) app.


Let’s check the decompiled Java code.

When the user starts the app, it displays a fake error message, indicating that the server is out-of-service, and terminates. In the meantime, it has registered and started the malicious service com.catchspam.catchservice.


Notice that a preference variable “runYN” is set to “execute” (실행.) More on it later.

The malicious service does two things:
– First, it registers the cellphone number by POST’ing it to hxxp:// (If the number is Korean and starts with the prefix +82, it receives “special treatment”.)
– Then, it registers a BroadcastReceiver meant to process incoming text messages.


The BroadcastReceiver com.catchspam.catchsms2 processes SMS_RECEIVED intents. The last PDU is processed and (supposedly) contains the text message.

In the following screenshot, notice that the author implemented a minimal C&C-like behavior:
– If the text reads “execute”, the runYN preference variable will also be set to “execute”.
– If the text matches the “magic passphrase”, that variable will be set to “pause”. (It roughly translates to: “Back and forth the same versus luck ♥ Lee ♥ ♥ Please call 1588-1588”.)
Later, runYN is checked and the interception procedure will bail if it is not set to “execute”. This allows the author to enable/disable the interception, either globally or for specific phone numbers.


The intent broadcast is cancelled to prevent other apps (and eventually, the user) from processing the SMS. Finally, the message is POST’ed to the author’s server. The data format is “mobile=<number>&revsms=<textmessage>”.

SMS stealers can be simple annoyance or steal personal data. In this case, it might be used to capture 2-factor authentication codes sent by online banking websites.

Download the sample here.