Triple E

Your package has been delayed, click here to reschedule

NB: This article was originally going to be published as “You have a missed call, caller left you a message” to reflect the original campaign of the Australian Flubot “strain”. In the beginning the scam involved an SMS informing the intended victim that a message was left on their phone and they must click the link to retrieve it. The latest “mutation” is now taking advantage of the issues with the postal system in a largely locked-down Australia. You can read more about this scam campaign and how to protect yourself and others on scamwatch.

Around a month ago a new SMS-based malware hit Australian shores. During this time, the scam has been circulating wide and far, it’s likely you have either received on of these texts or know someone that has. One of the reasons it has likely been spreading in this way is because it is believed to upload the victim’s contacts to the scammers to use when they install the malware. It has been quite literally spreading like a virus, finding new victims to infect from an original infection.

alt text The first text message I received from the scammers. Link redacted to preserve privacy

We can use a sandbox environment to explore how the campaign is operating. What we find is a sophisticated operation using several complex techniques to evade authorities and ensure analysis is as difficult as possible.

A Sophisticated Scam

The first indication that this scam is quite sophisticated was when the link did not work in a desktop sandbox browser. When opening the link on desktop we are presented with a whitescreen and the site appears to be “dead”. Instead we can simulate opening the link on a mobile device by firing up an Android emulator. The link now works.

It is likely the scammers have some code to determine the operating system from the User-Agent and then change the display depending on the user’s OS. If the link is opened on desktop, they can cover their tracks by showing a blank screen and prevent trivial analysis.

alt text The scam site includes an Australian carrier (adding to the legitimacy) along with my real phone number

Redirects, Obfuscation and More

By using the reverse proxy in burpsuite we can capture any network requests on the Android emulator. The proxy captured multiple page requests which appeared to redirect to a new page. Each request returned a 200 indicating that the “redirects” were not HTTP-based where we would expect to see one of the 300 response codes. Rather, the JavaScript embedded in the page performs the page loads; most likely through window.location.href to replace the current page with the new one.

The JavaScript that was embedded in the original link was obfuscated using a JavaScript packer, indicated by the prefix: eval(function(p,a,c,k,e,d). Reading the packed code manually is practically impossible as the majority is encoded in base62. The function(p,a,c,k,e,d) decodes it and the eval then evaluates the unpacked code. We can see a sample of the packed code below, where the encoded data has been redacted for privacy.

<script>eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('3 6=\'v\';3 2=\'h/g/f/b/d/c/a/9+8+7+i+e+k+l+j+n+o\';2=p(2);3 4=\'\';3 1;q(1=0;1<2.r;++1){4+=s.t(2.5(1)^6.5(0))}u.m(4);',32,32,'|FVqAM|vofTs|var|NEyEm|charCodeAt|wDJSc|REDACTED_DATA_ENCODING|atob|for|length|String|fromCharCode|document|D'.split('|'),0,{})) </script>

Unpacking JavaScript

Although we cannot read the packed JavaScript manually, the code is executable and therefore can be interpreted by a JavaScript engine. We can compute the packed function in node and output the JavaScript that would be eval‘ed. By wrapping the packed function in a String object we can compute the original JavaScript as a string.

> const value = eval("String" + "(function(p,a,c,k,e,d){e=...}...)") > value var wDJSc='D'; var vofTs='REDACTED_BASE64_ENCODING'; vofTs=atob(vofTs); var NEyEm=''; var FVqAM; for(FVqAM=0;FVqAM<vofTs.length;++FVqAM) { NEyEm+=String.fromCharCode(vofTs.charCodeAt(FVqAM)^wDJSc.charCodeAt(0)) } document.write(NEyEm);

The unpacked JavaScript appears to include more obfuscation, where the vofTs variable is encoded in base64 and then decoded on the next line using the atob function. The resulting decoded string is subject to more obfuscation by computing the XOR of each character with D. The end result is then written to the document by calling document.write(NeyEm), replacing the contents of the webpage with the result of the decoding. Once again, we can easily compute what will be written to the page by running the code ourselves.

> const script = eval("var wDJSc='D';var vofTs='REDACTED_BASE64_ENCODING';vofTs=atob(vofTs);var NEyEm='';var FVqAM;for(FVqAM=0;FVqAM<vofTs.length;++FVqAM){NEyEm+=String.fromCharCode(vofTs.charCodeAt(FVqAM)^wDJSc.charCodeAt(0))}") > script "<script>eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\\\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c])}}return p}('3 6=\\'l\\';3 2=\\'8/9/a/b/c/d+7=\\';2=f(2);3 4=\\'\\';3 1;e(1=0;1<2.h;++1){4+=i.j(2.5(1)^6.5(0))}k.g(4);',22,22,'|IUKEe|yRkmt|var|WbpDa|charCodeAt|NMoxE|REDACTED_DATA_ENCODING|for|atob|write|length|String|fromCharCode|document|T'.split('|'),0,{}))\n" + '</script>'

Argh! The scammers have not only packed the JavaScript, but repeatedly packed it to slow analysis even further. While it may help them slow manual analysis, we can easily write some of our own JavaScript to recursively unpack it and find the end result in an instant.

const atob = (b) => Buffer.from(b, "base64").toString("utf-8") const removeScript = (str) => { const startScript = "<script>" const endScript = "</script>" if(str.startsWith(startScript)) { const end = str.length - endScript.length return str.slice(startScript.length, end) } return str } const removeDocWrite = (str) => { if(str.indexOf("document.write") !== -1) { return str.slice(0, str.length - (str.length - str.indexOf("document.write"))) } } const hasEval = (str) => str.startsWith("eval") if (process.argv.length != 3) { console.error("Only accepts packed JavaScript as single CLI argument") process.exit(1) } const input = process.argv[2].trim() const unpack = (packedStr) => { if(!hasEval(removeScript(packedStr))) { return packedStr; } const cleanedUpPackedStr = removeScript(packedStr).slice(4) const unpackedStr = eval("String" + cleanedUpPackedStr) const evaled = eval(removeDocWrite(unpackedStr)) return unpack(evaled) } console.log(unpack(input))

The function that recursively unpacks the obfuscated JavaScript can be seen on lines 28-36. The base case is when eval is no longer at the start of the string. It is assumed that if eval is missing the JavaScript is completely unpacked. Line 32 removes any script tags as well as the eval at the beginning of the code block before it is evaluated on the next line. At this point, the code is unpacked but still obfuscated using base64 encoding. We can remove the document write at the end of the obfuscated code and evaluate it as on line 34. Finally, we can call the unpack function recursively on the resulting de-obfuscated code.

Using this script to unpack the JavaScript from the original link, we can see the final JavaScript that is run on the page which sets the window location.

<!-- tn --><script>window.location.href="?REDACTED_ADDRESS"</script>

This effectively loads the referenced address in place of the current page. Again, the actual address has been redacted to preserve privacy. The next two pages use the same recursive packing technique to obfuscate similar code to set the window location. The final page that is loaded (and is what displays in the screenshot earlier) has a large obfuscated payload. To put it into perspective, the first payload had 2,275 characters, while the final payload has 25,246 characters.

Regardless of the size, our recursive unpacker makes quick work of decoding it.

<div id="content"> <p id="header">Optus: You have new voicemail</p> <p> <table id="len"> <tr><td>Your phone number</td><td>REDACTED</td></tr> <tr><td>Message length</td><td>2 minutes and 34 seconds</td></tr> </table> <p>This voicemail is in a high quality format and can only be listened to with our app.</p> <a href="" id="btn">Download voicemail app</a> <p id="info">If a window appears preventing the installation, select "settings" and enable the installation of unknown apps.</p> </p> </div> <script> var oTitle = document.title; var iTitle = 0; setInterval(function() { iTitle = 1-iTitle; document.title = ((iTitle % 2 == 0) ? "(1) " : "") + oTitle; }, 1000); </script> </body> </html>

The JavaScript of the final page evaluates to the HTML above. It is the HTML of the page we saw earlier which had the victim’s phone number along with the download link. As an added bonus, the page includes a small JavaScript script which modifies the page title to flash (1) every second.

Analysing the App

To continue with our analysis we have to download the APK using the link on the site. After downloading, the first thing we can do is upload it to VirusTotal to see if they have any reports on it.

alt text VirusTotal shows that 21 out of 63 vendors detected the malware; source

VirusTotal also executes the APK within its own sandbox environment to perform dynamic analysis of the suspected malware. From this, we can see that there were at least 100 DNS resolutions of obscure domain names.

alt text Just a sample of some of the obscure domains that the app reaches out to; source

VirusTotal In-Depth Analysis

After allowing VirusTotal to execute the malware and analyse it using automated tools, we can inspect the behaviour further. Once the dynamic analysis is complete, the dashboard shows “Crowdsourced IDS rules” which matched to the app’s behaviour. According to VirusTotal, intrusion-detection system rules are used to match patterns of the network traffic the app generated while executing.

Domain Generation Algorithms

One network traffic pattern that VirusTotal detected in its ruleset is the use of a potential domain generation algorithm.

alt text The matching rule mentions the possible match to the use of a domain generation algorithm

A domain generation algorithm (DGA) is used to generate multiple domains (which may range in the hundreds or thousands) from a single starting point, or a seed. Malware that uses a DGA will try to call out to each domain to check for what it should do next. If the scam is still active, one of the domains will have a command and control (C2) server running on it.

To avoid having the malware shutdown almost immediately, hackers utilise a DGA so that their C2 server cannot be easily blocked. If the address was hardcoded into the malware (and hence easily discovered) it could be blocked by ISPs or even taken down by legal authorities, effectively stopping the malware in its tracks.

Static and Dynamic Analysis Evasion

I recently learned about a tool called APKiD. It is used to identify key information from an APK such as how it was compiled, if any obfuscators were used and “other weird stuff” (according to the creators). We can use it to find out more about the voicemail malware.

apkid Voicemail89.apk [+] APKiD 2.1.2 :: from RedNaga :: [*] Voicemail89.apk!classes.dex |-> anti_debug : Debug.isDebuggerConnected() check |-> anti_vm : Build.MANUFACTURER check, network operator name check, possible Build.SERIAL check |-> compiler : dexlib 2.x

We can see that the malware was compiled using dexlib 2.x and uses two techniques to evade dynamic analysis, anti_debug and anti_vm. If the malware believes it is under inspection via these checks, it may disable itself to avoid further analysis. The malware can be executed and interacted with manually to observe behaviour or it could be automated using tools such as Frida.

While dynamic analysis might be off the cards, it is still possible to perform our own static analysis. This involves decompiling the application and manually reading the decompiled code (typically smali or non-compilable Java code). However, it looks like the malware designers also utilised further techniques to evade even static analysis. The Android Manifest claims the package name is com.didiglobal.passenger; the legitimate package name of the DiDi ride sharing app. VirusTotal also provides the same information about the package name and main activity.

alt text VirusTotal shows Android specific details; source

Of course, when we decompile the malware using apktool, we cannot find the elusive com.didiglobal.passenger package anywhere. It seems like this app is working purely on magic!

alt text Decompiled app does not have the com.didiglobal.passenger package

However, we know from installing the app on the emulator that the app runs without any trouble. What could be going on here? As it turns out, the malware designers have utilised another technique to obfuscate their operation. According to VirusBulletin, Android malware makes use of packers to “encrypt an original classes.dex file, use an ELF binary to decrypt the dex file to memory at runtime, and then execute via DexclassLoader”. Put simply, the app that ends up running on the victim’s device is loaded at runtime by decrypting the actual malicious code and loading it in place using reflection. The encrypted, malicious code has the package name of com.didiglobal.passenger which is why the app is able to run successfully despite not existing in the main apk.

The Malware Bazaar

MalwareBazaar is a tool similar to VirusTotal where researchers are able to upload and share malware samples they collect. The three samples we uploaded (both the voicemail and delivery scam) can be viewed here, here and here. After some time, MalwareBazaar was able to positively identify the samples as flubot.

alt text MalwareBazaar dashboard showing the positively identified sample as flubot

One of the samples further confirms a few of the evasion techniques used by the malware, including the use of reflection, loading of a dropped dex/jar file, and that the payload is flubot.

alt text Hatching Triage identified several behaviours we discussed in one of the samples

Where to From Here?

We could continue with our analysis and attempt to decrypt the dropped dex file and reverse engineer the malware further. In doing so, we may be able to discover the domain generation algorithm that is used to generate the hundreds of domains we saw previously. This may make for a possible part two if we are able to successfully decrypt the dropped dex file and decompile it. For now though, I will leave you with a few interesting reads to satisfy your curiosity written by real security researchers.

Appendix: Further Readings