The Old Way To Decrypt The Malicious Code of CamScanner

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: An advertising dropper in Google Play

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.zip
DECIMAL       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.

image

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.

image

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

image

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 Duration``fs0c131y@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.