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..]
[..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
[..REDACTED..]
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 < 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 stringIt 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.
Looking to elevate your expertise in iOS Security?
Offensive iOS Internals Training
365 Days of Access | Hands-On Learning | Self-Paced Training
GET IN TOUCH
Visit our training page if you’re interested in learning more about these techniques and developing your abilities further. Additionally, you may look through our Events page and sign up for our upcoming Public trainings.
Check out our Certifications Program and get Certified today.
Please don’t hesitate to reach out to us through out Contact Us page or through the Button below if you have any questions or need assistance with Penetration Testing or any other Security-related Services. We will answer in a timely manner within 1 business day.
We are always looking for talented people to join our team. Visit out Careers page to look at the available roles. We would love to hear from you.