Mobile Malware Analysis Part 3 – Pegasus

Application Detail

Name: Media Sync

Package: seC.dujmehn.qdtheyt

SHA-256 Hash: bd8cda80aaee3e4a17e9967a1c062ac5c8e4aefd7eaa3362f54044c2c94db52a

Introduction

Welcome back, malware enthusiasts, to the third chapter of our Mobile Malware Analysis saga! Today, we’re diving headfirst into the world of a Pegasus/Chryasor variant that’s about as unpredictable as a rollercoaster ride. Throughout this analysis, we will be uncovering sneaky obfuscation techniques, and embarking on a thrilling journey through a horde of malicious binaries.

So, without further ado, let’s get started!

Analysis

Let’s begin analyzing the sample using JADX to get an idea of what the Android malware is doing.

Android Manifest.xml

Permissions

				
					[..REDACTED..]
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCOUNT_MANAGER"/>
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
<uses-permission android:name="android.permission.BATTERY_STATS"/>
<uses-permission android:name="android.permission.BIND_APPWIDGET"/>
<uses-permission android:name="android.permission.BIND_DEVICE_ADMIN"/>
<uses-permission android:name="android.permission.BIND_INPUT_METHOD"/>
<uses-permission android:name="android.permission.BIND_REMOTEVIEWS"/>
<uses-permission android:name="android.permission.BIND_WALLPAPER"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BRICK"/>
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_REMOVED"/>
<uses-permission android:name="android.permission.BROADCAST_SMS"/>
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"/>
[..REDACTED..]
				
			

We could see that the application is requesting for tons of permissions including dangerous permissions like android.permission.BRICK , android.permission.MOUNT_FORMAT_FILESYSTEMS , android.permission.DIAGNOSTIC and much more.

Components

				
					//AndroidManifest.xml

<activity android:label=“@string/abc_fade_in” android:name=“seC.dujmehn.qdtheyt.Dujmehnpqyd”>
<intent-filter>
<action android:name=“android.intent.action.MAIN”/>
</intent-filter>
</activity>
<activity android:theme=“@android:style/Theme.Black.NoTitleBar” android:name=“.heeCJqf.IxemTuinjef” class=“com.network.android.ShowDesktop”/>
<activity android:theme=“@android:style/Theme.Black.NoTitleBar” android:name=“.heeCJqf.RBqsnIshuud” class=“com.network.android.BlackScreen”/>
<receiver android:name=“seC.dujmehn.qdtheyt.ICiHusuyluh” android:enabled=“true”>
<intent-filter android:priority=“100”>
<action android:name=“android.intent.action.DATA_SMS_RECEIVED”/>
<data android:scheme=“sms”/>
<data android:host=“localhost”/>
<data android:port=“0”/>
</intent-filter>
</receiver>
<receiver android:name=“seC.dujmehn.qdtheyt.qwudj.DujmehnHusuyluh” android:enabled=“true”>
<intent-filter>
[…REDACTED…]
</intent-filter>
</receiver>
<receiver android:name=“.heeCJqf.QkjeQdimuhHusuyluh” android:enabled=“true”>
<intent-filter android:priority=“100”>
<action android:name=“android.intent.action.PHONE_STATE”/>
</intent-filter>
</receiver>
<receiver android:name=“seC.dujmehn.qdtheyt.QdtheytSqBBTyhusjMqjsxuh” android:enabled=“true”>
<intent-filter android:priority=“100”>
<action android:name=“android.intent.action.PHONE_STATE”/>
</intent-filter>
</receiver>
<receiver android:name=“seC.dujmehn.qdtheyt.ReejHusuyluh” android:enabled=“true”>
<intent-filter>
<action android:name=“android.intent.action.BOOT_COMPLETED”/>
</intent-filter>
</receiver>
<receiver android:name=“seC.dujmehn.Cutyq.SehuHusuyluh”/>
<receiver android:name=“seC.dujmehn.Besqjyed.FydwHusuyluh”/>
<receiver android:name=“seC.dujmehn.Besqjyed.EdQBqhCHusuyluh”/>
<service android:name=“seC.dujmehn.qdtheyt.qdtheyt.Cedyjeh.QffIuhlysu”/>
<service android:name=“seC.dujmehn.qdtheyt.qdtheyt.Cedyjeh.QffIuhlysuFydwuh”/>
<service android:name=“seC.dujmehn.qdtheyt.qdtheyt.Cedyjeh.DujmehnpqdqwuhIuhlysu” android:enabled=“true”/>
<service android:name=“.heeCJqf.putyqFBqOuhXqdtBuhIuhlysu” android:enabled=“true”/>
<service android:name=“seC.dujmehn.kiit.STKIITIuhlysu” android:enabled=“true” android:exported=“true”>
<intent-filter>
<action android:name=“com.android.ussd.IExtendedNetworkService”/>
</intent-filter>
</service>







There are so many components whose names are obfuscated (probably) ,but the most important thing to note is 90 % of these components are not present in the disk/apk .

Now, what does this statement Components not present in the apk mean?

Basically in this APK file, these component’s Smali / Java code arent included and are possibly loaded at Runtime using DexClassLoader, InMemoryDexClassLoader .

Components named EdQBqhCHusuyluh , FydwHusuyluh and SehuHusuyluh are the ones present in the apk.

If we also looked into the Resources section we could see the directory org/eclipse/paho/client/mqttv3 , popularly known as Paho Android Service which is famous for MQTT Client Library.

MQTT is the standard protocol used for communicating with an IoT/C2C Server via TCP/IP connection.

Also if we decompile the apk and look inside res/raw we could see binaries named addk , take_screen_shot , libk which we will be covering in the later sections.





Source Code Analysis





Let’s start our analysis from the Broadcast Receiver EdQBqhCHusuyluh

Tip: The first function that gets executed when calling a Broadcast Receiver is the OnReceive(Context, Intent) function.








//Path -> seC/dujmehn/Besqjyed/EdQBqhCHusuyluh.java
@Override
public void onReceive(Context context, Intent intent) {
BuBlJJJJJXDFKYirSooj(ZQjvHTeBlVUCiutw(QVozqddybqHRWaDm(QmdafFtUsXvVcahq(mprchPoltGTBsyTr(new StringBuilder(onReceive9584()), mRvgkfrWJgjwKERW(new Date())), onReceive9585()), igMiBMAFlnpIGcji(intent))));
try {
jCtyZivgTauTIKZr(utdMSQrIClmTpHFk(context), new ComponentName(context, EdQBqhCHusuyluh.class), 1, 1);
} catch (Throwable unused) {
IVOkEjCYymmGsred(yeaXZTigXaYYobJf(wBYoNCNZXpMFYvJJJJJF(new StringBuilder(onReceive9586()), DXEOUgKPIcuXDPPe(r0))), r0);
}
try {
pJgvOKzMdYWbWRjG(context, new Intent(context, QffIuhlysu.class));
} catch (Throwable unused2) {
PbNrMrmkNhtwhwfM(SldrPqhEjvpDJzsX(gwnDiWdGqIIaSogW(new StringBuilder(onReceive9587()), HhkJJJJJBuHgJZBJSeUv(r0))), r0);
}
}







Lets start analyzing the line,

BuBlJJJJJXDFKYirSooj(ZQjvHTeBlVUCiutw(QVozqddybqHRWaDm(QmdafFtUsXvVcahq(mprchPoltGTBsyTr(new StringBuilder(onReceive9584()), mRvgkfrWJgjwKERW(new Date())), onReceive9585()), igMiBMAFlnpIGcji(intent))));

If we check function BuBlJJJJJXDFKYirSooj() we could see a Reflective Function call.








//Path -> seC/dujmehn/Besqjyed/EdQBqhCHusuyluh.java

public static void BuBlJJJJJXDFKYirSooj(String str) {
seC.dujmehn.qdtheyt.s.q.q.class.getMethod(BuBlJJJJJXDFKYirSooj5955(), String.class).invoke(null, str);
}







Now a question might arise. What is meant by a Reflective function call?

Consider it as an alternative way to call a function of a particular class/package. To call a Java function through reflection we need to use 2 functions namely getMethod() and invoke().

getMethod() → get the specified method of this class with the specified parameter type and return a Method object for the specified method. It has 2 parameters

  • methodName which is the Method to get.

  • parameterType which is the array of parameter types for the specified method.

invoke() → invokes the method using the Method object.

If we check BuBlJJJJJXDFKYirSooj5955() (its simply a Base64 + XOR encryption function) which returns the function name that is getting invoked.








//BuBlJJJJJXDFKYirSooj5955()
import java.util.Base64;

public class MyClass {
	public static void main(String[] args) {
	int x = 10;
	int y = 25;
	int z = x + y;
	// Decode the Base64 encoded string
	byte[] wjxzqy = Base64.getDecoder().decode("BQ==");
	// Convert the byte array to a string
	String decodedString = new String(wjxzqy);
	// Create a StringBuilder object
	StringBuilder decryptedString = new StringBuilder();
	// Decrypt the string using a XOR cipher
	for (int i = 0; i &lt; decodedString.length(); i++) {
	decryptedString.append((char) (decodedString.charAt(i) ^ "d8c7862163c34762ac7a51ee3af50058".charAt(i % "d8c7862163c34762ac7a51ee3af50058".length())));
	}
	// Get the decrypted string
	String w = decryptedString.toString();
	// Print the decrypted string
	System.out.println("Function Name = " + w);
}

}







  • It decodes a Base64-encoded string BQ== into a byte array then gets converted into a string

  • It decrypts the string using a simple XOR cipher. It iterates through each character of the decoded string and XORs it with a corresponding character from the key d8c7862163c34762ac7a51ee3af50058 in a repeating manner.

  • It stores the decrypted string in the variable w.

On running the above code, a is printed as output. Thus we are invoking function a of package seC.dujmehn.qdtheyt.s.q.q. The issue is, that class q is also not present in the apk.

Here is the objective of the other obfuscated reflective functions

ZQjvHTeBlVUCiutw() → calls toString method of String class

QVozqddybqHRWaDm() , QmdafFtUsXvVcahq() and mprchPoltGTBsyTr() → call append method of StringBuilder class.

onReceive9584() [doesnt use Reflection] → returns OnAlarmReceiver onReceive:

mRvgkfrWJgjwKERW() → returns the GMT Time

onReceive9585() → returns the string action:

igMiBMAFlnpIGcji() → returns the action of the intent used to call this Broadcast.

If we combine all of these together , the string could look like

"OnAlarmReceiver onReceive: "+ GMT time+“action:” + Intent action

that is being to function a()

Lets now focus on the code within the first try clause.








jCtyZivgTauTIKZr(utdMSQrIClmTpHFk(context), new ComponentName(context, EdQBqhCHusuyluh.class), 1, 1);







The function jCtyZivgTauTIKZr() uses reflection to call setComponentEnabledSetting() of PackageManager class. If we look for this function in the official Android Documentation for Package Manager , we can see that this function is used to set the enabled setting for a package component. In this instance, they are enabling the EdQBqhCHusuyluh component.

If we look at the code of the last try clause,








pJgvOKzMdYWbWRjG(context, new Intent(context, QffIuhlysu.class));







pJgvOKzMdYWbWRjG() is a function that uses reflection to call startService() method. In this code they are calling QffIuhlysu service (which is also not there in the disk).

Let’s move to the next available component.





FydwHusuyluh.java





Like the previous component, let’s start our analysis from the OnReceive() function








//Path -> seC/dujmehn/Besqjyed/FydwHusuyluh.java

@Override // android.content.BroadcastReceiver
public void onReceive(Context context, Intent intent) {
try {
String zoSDMlVDuwCIxpyV = zoSDMlVDuwCIxpyV(intent);
mhVReMZccjAUGetY(jzxTDWGVbDeOjxsz(bGFBgWEDBqkoKoqb(new StringBuilder(onReceive7320()), zoSDMlVDuwCIxpyV)));
JiQdkRDOwqunNJck(onReceive7321());
ctWtShGoSwHSSxxr(CeWpeHaKgNFpdtUu(context), new ComponentName(context, FydwHusuyluh.class), 1, 1);
String CPsvElsJJJJJdEWxvPak = CPsvElsJJJJJdEWxvPak(intent, onReceive7322());
int UgtRmbrTtaChMXod = UgtRmbrTtaChMXod(intent, onReceive7323(), 1);
YrXmfFPhiJJJJJVbIkvE(hBgTzNTeetEdKWyu(ivzmJYjJJJJJtYdaNrmk(new StringBuilder(onReceive7324()), CPsvElsJJJJJdEWxvPak)));
nwXpEeIBSquoScUy(xwnXHzXYlBXFhbbE(CmTufwtRBPtdCAAK(new StringBuilder(onReceive7325()), UgtRmbrTtaChMXod)));
Intent intent2 = new Intent(context, QffIuhlysuFydwuh.class);
woYYyPIDYwvgDBiD(intent2, zoSDMlVDuwCIxpyV);
CDdhpIDdKrIqKIRG(intent2, onReceive7326(), CPsvElsJJJJJdEWxvPak);
PmhcIDdlIVkAssEa(intent2, onReceive7327(), UgtRmbrTtaChMXod);
doFNUlGakzJJJJJTxsmD(context, intent2);
} catch (Throwable unused) {
wtyjhQukAxKGueQp(JsOyKHvPGlltfrdO(kqMxGArxaRFdleCJJJJJ(new StringBuilder(onReceive7329()), QbdEweZZIWkmOBuP(r0))), r0);
}
}







Let’s analyze the try-block line by line.

String zoSDMlVDuwCIxpyV = zoSDMlVDuwCIxpyV(intent);zoSDMlVDuwCIxpyV uses reflection to return the action of the intent object

mhVReMZccjAUGetY(jzxTDWGVbDeOjxsz(bGFBgWEDBqkoKoqb(new StringBuilder(onReceive7320()), zoSDMlVDuwCIxpyV))); → Passes a string which looks like

PingReceiver onReceive action: <intent_action> to function a of package seC.dujmehn.qdtheyt.s.q.q

After this next 4-5 lines passes different strings to this mysterious function a()

They are also enabling this receiver using the function setComponentEnabledSetting()

After which they are creating an intent and pass various extras to it and then start a new service i.e QffIuhlysuFydwuh

Let’s move to the next receiver i.e SehuHusuyluh 





SehuHusuyluh.java





If we start analyzing from the OnReceive() function,








//Path -> seC/dujmehn/Cutyq/SehuHusuyluh.java

@Override // android.content.BroadcastReceiver
public void onReceive(Context context, Intent intent) {
try {
String kfIJkjFmWhBsjKBb = kfIJkjFmWhBsjKBb(intent);
JwmywxXEFXZRWoTm(jJJJJJfgySwEmZtctJJJJJZv(wgcFPzkzJvQwpQNR(NQVJqQmdtXCzMZwr(lBlRxthHkMBxrmAh(gqogknrztmhAqnbU(fESOpFhfggYrqIbU(MxoKecvKTxNPvSih(new StringBuilder(onReceive3935()), kfIJkjFmWhBsjKBb), ", Agent Version: "), seC.dujmehn.r.r.d), ", Date: "), RCvwiRQWhNVPiVwM(new Date())), onReceive3936())));
RkuTHMzDzKwBxnYG(f39v, new RunnableC0259u(this, context, intent, kfIJkjFmWhBsjKBb));
} catch (Throwable unused) {
ARXNnuprJJJJJYzvtkOB(yKzdwDVMxrvTAScq(mYynmeuKZgangzlk(new StringBuilder(onReceive3937()), YPSHXSTQPliIBVkJJJJJ(r0))), r0);
}
}







The first 2 lines basically get the action part of the intent and pass a string to function a() which we don’t have to go into much detail.

The last line basically starts a new Handler, which at first thought would be dangerous but nothing much is happening

Now that we have completed the Java source code analysis, a question to ponder upon.

Why they have used Reflection to invoke functions instead of calling them directly?

Actually, its a very good strategy to use Reflection for 2 important reasons

  • Evade static code analyzer checks and get low scores to get marked as a normal app (Check this article for more info)

  • Make the process of reversing a sample much more tougher like this.





ELF Binaries





Now we turn our focus toward the binaries that we found in the res/raw directory.

The binaries include addk , cmdshell , libk , sucopier and take_screen_shot .

take_screen_shot

If we load the binary in a decompiler we could see,









In the main function, we could see that we are passing 2 arguments, one of them being a destination string. Along with that, we could see that we are passing a string that seems like a binary name.

Now what is the relevance of the /dev/graphics/fb0 binary?

Basically /dev/graphics/fb0 represents a Framebuffer. Framebuffers are essential for rendering UI on the screen. If we look at the Android Source Code repository for Framebuffer [link1, link2], the code execution depends on the existence of this binary which explains its importance and features (Do check the links to the source code).

We could even use fb0 to get screenshots using the commands,








adb pull /dev/graphics/fb0 fb0
ffmpeg -vframes 1 -vcodec rawvideo -f rawvideo -pix_fmt rgb32 -s 320x480 -i fb0 -f image2 -vcodec png image.png













libk









If we begin analyzing this ELF binary from init(), we could see

















We could see strings like /system/lib/libbinder.so and this weird-looking string _ZN7android14IPCThreadState8transactEijRKNS_6ParcelEPS1_j which actually turned to be a function defined in libbinder.so. After looking around a bunch of functions we found some interesting piece of code.

















If we actually turned our focus to the hardcoded string com.android.internal.view.IInputContext and research about it a bit, we could see that it is an Android Internal Framework that inputs contexts and handles various input-related operations.

Here is the link to the Android Source Code Repository to learn more about functions being implemented. This gets more interesting with the next set of code.

















Here we could see that they are creating some temp files named ulmntdd.tmp and data is being written to it. If we check for references for this function call we find,












__android_log_print(3,“Jigglypuff_KS”,
“call_func_from_parcel. function code: %d firstInt: %d secondInt %d dataPo sition: %d”
,param_1,uVar2,uVar3,uVar4);
android::Parcel::readString16();
__android_log_print(3,“Jigglypuff_KS”,“call_func_from_parcel. string16: (x): %x , char16: %s”
,*local_20,local_20);
iVar1 = strlen16(local_20);
mw_FUN_0001551c(iVar1,(byte *)local_20);
android::String16::~String16((String16 *)&local_20);











On connecting all of these this elf binary could be possibly writing all of the keyboard logging to the temp file which is later getting exfiltrated.

addk

This ELF binary is used for performing process injection and possibly injecting shellcode to provide a backdoor for the attackers.

If we look at the main function,

















We could see that the first argument we pass is the process pid which is being passed to inject_process function. If we check that function,

















We could see some pretty important strings like /system/bin/linker which are generally used for loading the ELF executable into memory (To understand more refer to this blog), use of mmap (used for creates a new mapping in the virtual address space of the calling process.) based on the above code.

Conclusion

In conclusion, our exploration of this Pegasus/Chryasor variant has expanded our knowledge significantly. We’ve grasped the crucial concept of Reflection and witnessed the versatility it offers to malware creators. While examining ELF binaries like “addk,” “libk,” and “take_screen_shot,” we gained valuable insights into the world of malicious coding.

Our discovery of MQTT as a communication method highlighted the intricate ways in which malware maintains connections and communicates with its operators. Although this sample didn’t follow the common malware patterns, it reinforced the importance of adaptability in the field of cybersecurity.





The post Mobile Malware Analysis Part 3 – Pegasus first appeared on 8kSec.

Article Link: https://8ksec.io/mobile-malware-analysis-part-3-pegasus/?utm_source=rss&utm_medium=rss&utm_campaign=mobile-malware-analysis-part-3-pegasus