Reversing “V-Alert COVID-19” Android/BankBot

@cryptax
5 min readMay 12, 2020

--

On May 1, 2020, a new version of Android BankBot (aka Anubis, Nautilus Bot) was spotted. The malware poses as an COVID-19 alert application.

We will try to reverse the sample (sha256: 9c7b234d0d46169dcefb9f5b22c5df134b1a120b67666c071feaf97a6078d1a1). Previous versions of BankBot have been disassembled here and here, but that was 2 years ago, and BankBot has changed (and improved) quite a lot.

Load the sample in your favorite Android disassembler tool (I personally use JEB — the tool is not free but very good, and there is a free demo). In the Android manifest, we quickly notice:

1. Names are obfuscated. That’s frequent with malware.
2. The main is jrxrpdcxd.ltnihmedlhocbq.ryqsmeytremjrdbpxl.ncec.myvbo
3. That path is not in the DEX. This typically indicates the sample is packed, and the manifest references names of an unpacked DEX that we need to recover.

How are we going to find it? There is loads of junk code everywhere 😠
I fire up my droidlysis tool: python3 droidlysis3.py --input 9c7b234d0d46169dcefb9f5b22c5df134b1a120b67666c071feaf97a6078d1a1 -- output .. It highlights something particularly relevant to packing/unpacking: the use of DexClassLoader, an Android class used to load DEX executables, and, in the case of unpacking, to load the unpacked DEX.

DroidLysis’ output shows the malware uses DexClassLoader

In droidlysis output directory ./9c7b234d0d46169dcefb9f5b22c5df134b1a120b67666c071feaf97a6078d1a1–9c7b234d0d46169dcefb9f5b22c5df134b1a120b67666c071feaf97a6078d1a1, the file autoanalysis.md tells us DexClassLoader is used within the class Ncoffeetop in the malware:

## DexClassLoader
- file=…gsxtysyue/rqjgllnxahaafqsyplz/lcoguawmyxbdzriqeiczstw/Ncoffeetop.smali no=3864 line=b’ invoke-virtual/range {v1 .. v6}, Lgohcthplmgmyrcnhcgsxtysyue/rqjgllnxahaafqsyplz/lcoguawmyxbdzriqeiczstw/Ncoffeetop;->squeezedefy(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/reflect/Field;Ljava/lang/ref/WeakReference;)Ldalvik/system/DexClassLoader;\n’
- file=…gsxtysyue/rqjgllnxahaafqsyplz/lcoguawmyxbdzriqeiczstw/Ncoffeetop.smali no=5445 line=b’.method public squeezedefy(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/reflect/Field;Ljava/lang/ref/WeakReference;)Ldalvik/system/DexClassLoader;\n’
- file=…gsxtysyue/rqjgllnxahaafqsyplz/lcoguawmyxbdzriqeiczstw/Ncoffeetop.smali no=5474 line=b’ const-class v0, Ldalvik/system/DexClassLoader;\n’
- file=…gsxtysyue/rqjgllnxahaafqsyplz/lcoguawmyxbdzriqeiczstw/Ncoffeetop.smali no=5535 line=b’ check-cast p1, Ldalvik/system/DexClassLoader;\n’

We decompile Ncoffeetop.

As malware’s namespace are long it is particularly helpful to use the search function in the Bytecode/Hierarchy panel

Then, we search for DexClassLoader within Ncoffeetop, and stop on a method named squeezedefy() (remember: all names are obfuscated and have no particular meaning). This is where the unpacked DEX is loaded. So, we need to find the location or the contents of that unpacked DEX.

Obfuscated squeezedefy() method uses DexClassLoader

From documentation, we know that the first argument of DexClassLoader is the path of the DEX to load. So, we can rename arg9 to something more meaningful like dexpath.Then, we work our way back up to the caller of squeezedefy(), the caller of the caller etc. Always keeping track of dexpath (with JEB, press X to find cross-references for a method). We end up in a big method called attachBaseContext, which apparently creates dexpath here:

String dexpath = this.dreamdrill(v12);

We follow dreamdrill(). The method is small: notice the first line creates a variable which is not used: this is junk code, to make reversing complicated. Just skip it and focus on the lines with real code.

Do not pay attention to junk code, and focus online on the last line, calling slowunusual()

Same, slowunusual()had junk code and the only important line is the return statement at the end return this.hockeyrecipe(arg4).getAbsolutePath(). We decompile hockeyrecipe and, again skipping junk, the only important statement is return new File(arg4, this.Uobligeparrot) where Uobligeparrot is an object field initialized earlier in the class by this (horrible) piece of code:

this.Uobligeparrot = String.valueOf(Oconductgaze.pioneerwhat(Integer.numberOfTrailingZeros(Math.round(((float)Color.alpha(Integer.reverse(Math.getExponent(((float)Integer.signum(Color.green(Integer.parseInt("255"))))))))))).trim().intern().toString()).trim().intern();

The File constructor takes 2 strings as argument: parent and child. Typically, we’ll have the path as parent, and the filename as child. So, Uobligeparrot is probably going to be a filename. It isn’t likely at all a filename would require math computations: this is very probably only junk code. In that case, it probably means that Oconductgaze.pioneerwhat takes an input argument it does not use… We decompile pioneerwhat… and confirm: the argument is never used!

Filename decoding function. Lots of junk code.

Actually, this decoding function has lots of junk code. The important parts are that the while loop decodes a byte array with an XOR: v5_1 is the array index, v0 is the encrypted content, v3 is the XOR key.

We copy/paste the code in an editor and simplify it:

pioneerwhat() method, without junk code and explicit variable names

We compile the program and run it: Decoded filename: Pa.json. Hurray! We’ve got the filename for the unpacked DEX. We search in the APK and quickly spot a Pa.json among the assets. Unfortunately, it is not a DEX, nor a ZIP, but encrypted content.

So, at some point the asset Pa.json is read, then decrypted, then loaded with DexClassLoader. To recover the decrypted content, we can continue the reverse engineering of the code, as we did for the filename. Alternatively, we can write a Frida hook for squeezedefy() and print the name of the file with the decrypted unpacked content. Or we can try to be lucky 😏

The decrypted unpacked content will probably be located in the application’s directory, /data/data/gohcthplmgmyrcnhcgsxtysyue.rqjgllnxahaafqsyplz.lcoguawmyxbdzriqeiczstw. We install the malware in an emulator (don’t install on a real phone, remember this is malware!!!), we run it and then head for a shell (adb shell) and go to the app’s dir. That’s the contents:

# ls
app_DynamicLib app_DynamicOptDex cache code_cache shared_prefs

Browse the directories until you spot in app_DynamicOptDex a file name Pa.json. Retrieve that file (adb pull) and you’ll find out it is the unpacked, decrypted DEX 😃. Load that DEX in your favorite disassembler, and now you can inspect the main (jrxrpdcxd.ltnihmedlhocbq.ryqsmeytremjrdbpxl.ncec.myvbo) or any relevant code.

Main of the malware, from the unpacked, decrypted DEX.

If I have time, I’ll write “Part 2” for this article, explaining how to reverse the unpacked DEX, but honestly, the most difficult part is over 😃

Detection name: Android/BankBot.AH!tr (several other names like Anubis, Cerberus…)
sha256: 9c7b234d0d46169dcefb9f5b22c5df134b1a120b67666c071feaf97a6078d1a1

--

--

@cryptax

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