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”.
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 ;-)
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:
We can easily retrieve it from the package, but it is obviously encrypted (not a DEX).
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
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 😏.
In Dexcalibur, we add a custom hook for
coinknife. To do so, click on
coinknife, and then on the “Probe” button.
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.
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!