The Old Way To Decrypt The Malicious Code of CamScanner

Aug 29 · 4 min read
© Andrew Champ

On August 27, 2019, Kaspersky published an article about an Android application called CamScanner. They detected that this very popular app, more than 100 million downloads, contained a malicious dropper component.

I am often curious about the technical details in order to know the latest techniques used by bad actors. So I decided to give it a look.

In their article, Kaspersky gave the list of IOCs. I downloaded c69a2d2b0bf67265590c9be65cd4286b on Koodous

According to the article:

When the app is run, dropper decrypts and executes the malicious code contained in the mutter.zip file in the app resources.

fs0c131y@Elliots-MacBook-Pro:~/CamScanner$ apktool --no-src d 7aed7771b15d795bcd5ee119885613a3d5124f46d72c501fce3429f6c3c78ba4 -o apktool.out
[...]
fs0c131y@Elliots-MacBook-Pro:~/CamScanner$ find apktool.out -name mutter.zip
apktool.out/assets/mutter.zip
fs0c131y@Elliots-MacBook-Pro:~CamScanner$ file apktool.out/assets/mutter.zip
apktool.out/assets/mutter.zip: data
fs0c131y@Elliots-MacBook-Pro:~/CamScanner$ binwalk apktool.out/assets/mutter.zipDECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------

Thanks to Apktool we managed to obtain the file called mutter.zip. As expected, due to the encryption, this file is not a zip file for the moment. We need to understand how CamScanner decrypt it.

So I opened Jeb and search “mutter” in the bytecode and as said in the article, the decryption is done in a class called Duration. Now we have 2 choices:
- We can use Frida in order to intercept the output of the “decrypt method” at the runtime. The only constraint is that we need to be here at the correct moment.
- We can decrypt the zip file outside of the app context by using a good old Java class.

Let’s look at the 2nd option. I created a new folder, moved the mutter.zip in it and created a new file called Duration.java.

In this Duration.java I created a main function and copy paste the code of the decryption code from CamScanner.

import android.content.Context;
import android.os.Process;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;

If I check the imports, I see 2 Android specific imports. In order to compile our Java class we need to get rid of these two imports.

These 2 imports are used to get the zip file from the assets and give a specific name to the decrypted file.

Simplification done, easy right?

I also remove the unused methods and add generic try catch everywhere and we have our final decryptor.

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.ArrayList;
public class Duration { public static void main(String[] args) {
fireman("mutter.zip", "ugi");
}
private static void climate(InputStream arg15, OutputStream arg16, int arg17) {
try {
byte[] v1 = new byte[4];
arg15.read(v1);
int v1_1 = arg17 ^ ((v1[0] & 0xFF) << 24) + ((v1[1] & 0xFF) << 16) + ((v1[2] & 0xFF) << 8) + ((v1[3] & 0xFF) << 0);
arg15.read(new byte[(arg15.read() ^ v1_1) & 0xFF]);
ArrayList v3 = new ArrayList();
int v4;
for(v4 = 0; v4 < 0x100; ++v4) {
v3.add(Integer.valueOf(v4));
}
int[] v4_1 = new int[0x100];
long v7 = (long)v1_1;
int v6;
for(v6 = 0; v6 < v4_1.length; ++v6) {
v7 = (long)(((int)((v7 * 0x3EAABF9EL + 0xACL & 0xFFFFFFFFL) >>> 16)));
v4_1[((Integer)v3.remove(((int)(Math.abs(v7) % (((long)v3.size())))))).intValue()] = v6;
}
byte[] v3_1 = new byte[0x400];
while(true) {
int v5 = arg15.read(v3_1);
if(v5 <= 0) {
return;
}
int v6_1;
for(v6_1 = 0; v6_1 < v5; ++v6_1) {
v3_1[v6_1] = (byte)v4_1[(v3_1[v6_1] ^ v1_1) & 0xFF];
}
arg16.write(v3_1, 0, v5);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static File fireman(String arg5, String arg6) {
try {
File v4 = new File("~/CamScanner/mutter.zip");
InputStream v0 = new FileInputStream(v4);
File v4_1 = new File("~/CamScanner/", "decrypted.zip");
FileOutputStream v5_1 = new FileOutputStream(v4_1);
int v6 = arg6.hashCode() > 0 ? arg6.hashCode() : -arg6.hashCode();
Duration.climate(v0, v5_1, v6 % 0x100);
return v4_1;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

We are ready to compile it and decrypt our zip file.

fs0c131y@Elliots-MacBook-Pro:~/CamScanner/decryptor$ javac Duration.java
Note: Duration.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
fs0c131y@Elliots-MacBook-Pro:~/CamScanner/decryptor$ java Durationfs0c131y@Elliots-MacBook-Pro:~/CamScanner/decryptor$ file decrypted.zip
decrypted.zip: Zip archive data, at least v2.0 to extract
fs0c131y@Elliots-MacBook-Pro:~/CamScanner/decryptor$ unzip decrypted
Archive: decrypted.zip
inflating: classes.dex

And voila! We decrypted mutter.zip and have a new DEX file to analyse.

I wanted to share this story for one reason: don’t get lost with all the fancy tools available on the Internet, sometimes a gold old Java class is enough to do what you want to do.

Elliot Alderson

Written by

French security researcher. Worst nightmare of Oneplus, Wiko, UIDAI, Kimbho, Donald Daters and others. Not completely schizophrenic. Not related to USANetwork.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade