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.
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
.
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.
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.
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!
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:
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.
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