Unpacking an Android malware with Dexcalibur and JEB

A few days ago, @H_Miser retweeted about a smishing attempt where malware authors were sending a link to a malicious app faking the official French Android app “Tous Anti Covid”.

Image for post
Image for post
This is the Android malware we are going to analyze. A phishing SMS is known as “smishing”. Dec 4, 2020

I downloaded the malicious sample (c1dd9c26671fddc83c9923493236d210d7461b29dd066f743bd4794c1d647549) and we are going to unpack it using JEB and Dexcalibur. There are many other ways to do this: pure static analysis (with JEB or another decompiler), with Frida etc.

The sample is packed

Quickly, we notice the sample is packed. Indeed, the package’s namespace is tuna.obvious.trust, but the Android manifest references numerous activities and services with namespace dad.calm.invest, which does not exist in the sample.

<application android:allowBackup=”true”  android:icon=”@mipmap/ic_launcher” android:label=”TousAntiCovid”  android:name=”tuna.obvious.trust.NRuUxBnCsMhKmHjAbPxLqMdBpSmOaQzLaXpNqHwHrMhAbRzKiXfTz”  android:roundIcon=”@mipmap/ic_launcher_round”  android:supportsRtl=”true”  android:theme=”@android:style/Theme.Translucent.NoTitleBar”  android:usesCleartextTraffic=”true”><activity android:name="dad.calm.invest.KSfDnRiNpEoOsTdLcMhOyFtJdPzPhJbGhIuFiGxGpJlDsJr" android:screenOrientation="1"/>
<service android:exported="false" android:label="hgqkxlqj" android:name="dad.calm.invest.ffajxodmncsk" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
...

That’s because that namespace exists in another DEX the sample loads. This is the typical behaviour of packers : a first DEX decrypts and loads another DEX. The second DEX contains the malicious payload.

Where is the second DEX?

The first DEX needs to load the second DEX using DexClassLoader. So, we search for code using DexClassLoader in the first DEX. To quickly search, you can use DroidLysis, or Dexcalibur, or simply grep on smali ;-)

Image for post
Image for post
In Dexcalibur, we search for code using DexClassLoader using “calleed.name:”. We spot 2 references in the first DEX’s namespace. Both are in tuna.obvious.trust.XOiFqQlOuCjFxOyLkSn.coinknife.

So, tuna.obvious.trust.XOiFqQlOuCjFxOyLkSn.coinknife is the place where the second DEX gets loaded. The first argument of DexClassLoader is the path for the DEX, so we are going to walk up the call graph to find the value of that argument. Using JEB, we use x to find who calls coinknife, and then again, x to find who calls the caller of coinknife etc. We end up in a method called attachBaseContext. This is where the dexpath variable gets a value : it is filled using ecologyshoulder. Follow the calls in there. You will notice names are strongly obfuscated and lots of junk code. In the end, we get the name of the DEX file: wJDeTjC.json.

Live reverse engineering of the packed sample using JEB Decompiler

We can easily retrieve it from the package, but it is obviously encrypted (not a DEX).

Image for post
Image for post
the DEX is contained in wJDeTjC.json. Unfortunately, it is encrypted.

Unpacking with Dexcalibur

We need to decrypt the encrypted wJDeTjC.json file. At this point, we could go on with JEB and find the decryption routine. Or write a Frida hook on coinknife, because when coinknife calls DexClassLoader, the data has to be decrypted at that moment. We could print the data in the hook, and consequently have the malware decrypt the file for us 😏.

Writing Frida hooks (JavaScript) takes a little time if you are not used to it. Personally, I just hate JavaScript 😝 (but I like Frida). So, there is even a cooler solution: use Dexcalibur, which prepares all the Frida hooks we need automatically!

In Dexcalibur, we add a custom hook for coinknife. To do so, click on coinknife, and then on the “Probe” button.

Image for post
Image for post
Click on the blue “Probe” button to add a hook for coinknife method.

In Dexcalibur’s Hook Manager, we can disable hooks we are not interested in (if we don’t want to be spammed with too much information), or leave them (if we don’t mind scrolling). Then, we launch the application (“Run (spawn)”) and sit and watch for the various hooks to display info.

Image for post
Image for post
Our custom hook (white) got called. Interesting, we see it was called with a file named /data/user/0/tuna.obvious.trust/app_DynamicOptDex/wJDeTjC.json

The hooks show that a wJDeTjC.json file is present in /data/user/0/tuna.obvious.trust/app_DynamicOptDex. As this exact file is given to our coinknife hook, we suppose it contains the decrypted version of a DEX.

We pull the file using adb (NB. you’ll need to be root to access this path). Quickly with the command file, we confirm it is a DEX (note: the file command does not work well inside the Android shell, use your host’s file command). We can now decompile the second (unencrypted) DEX 😃 Unpacking is complete!

Image for post
Image for post
We have successfully unpacked the malware. Here, JEB is decompiling the “main” activity of the second DEX.

— cryptax

Mobile and IoT malware researcher. The postings on this account are solely my own opinion and do not represent my employer.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store