1/27 Security Lab March 29, 2021 Zloader email campaign using MHTML to download and decrypt XLS hornetsecurity.com/en/threat-research/zloader-email-campaign-using-mhtml-to-download-and-decrypt-xls/ Summary Zloader malware (associated with the kev configuration tag) is spreading via malspam using MIME encapsulation of aggregate HTML documents (MHTML) attachments. These MHTML files contain a Word document with VBA macros. The VBA macro code downloads and decrypts a password-protected XLS file, and after that, the XLS file decodes and executes the Zloader malware embedded within it. Background In February 2020, campaigns distributing Zloader ramped up usage of XLM (also known as Excel 4.0) macros. Detection of this old spreadsheet-based by design self-modifiable macro code format by anti-virus software is far lower than detection of regular sequential not by design self-modifiable plain-text VBA macro source code. We already highlighted the abuse of XLM macros in previous reports, e.g., XLM macros used to spread QakBot or BazarLoader . However, as detection for XLM macro code has picked up with even Microsoft adding XLM macro support to AMSI , threat actors continue to evolve. Starting in January 2021, Hornetsecurity took notice of a new Zloader campaign using MHTML attachments. MIME encapsulation of aggregate HTML documents (MHTML) is a web page archive format used to combine multiple files into one. It used base64 encoding and MIME-boundaries similar to multipart MIME encoding in emails. Microsoft Word can open documents stored inside MHTML files. Technical Analysis The chain of infection of the Zloader MHTML campaign is as follows: 1 5 2 3 4 5 https://www.hornetsecurity.com/en/threat-research/zloader-email-campaign-using-mhtml-to-download-and-decrypt-xls/ https://www.hornetsecurity.com/en/threat-research/qakbot-distributed-by-xlsb-files/ https://www.hornetsecurity.com/en/threat-research/bazarloaders-elaborate-flower-shop-lure/ 2/27 We will now outline each step of the attack chain. Emails The attack starts with emails. Januar (first wave) The first emails were designed and built like purchase invoices. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-chain_en_white.png 3/27 The wording and pretext changed between emails of this campaign. However, the general “invoicing” theme remained constant. Initial email attacks were of low volume, the emails templates above have not been used much. February In February, contract pretexts were added to the mix of invoice pretexts. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-email_0101.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-email_0102.png 4/27 At one point, the numero sign ( № ) was used instead of the number sign ( # ). After that, with recent changes to the email template, the campaign’s volume started to increase sharply. Targets The campaign targets international, Canadian, US, and British companies, mainly English- speaking users. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-email_0201.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-email_0203.png 5/27 However, the time histogram shows that on 2021-02-15, the majority of Zloader MHTML emails were destined for Canadian recipients. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-bin_owner_country_en_white.png 6/27 The estimated distribution of recipients by industries would suggest a bias towards the professional services industry, i.e., consultancies, freelancers, funeral homes, law firms, etc. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-time_owner_country_en_white.png 7/27 MHTML documents The MHTML document’s extension was set to .doc, so Microsoft Word will open the documents directly. The smaller January campaign and later February campaign MHTML document’s main difference is the image instructing the user to “enable content” and “enable editing”, i.e., activating macro execution. The January campaign lure image looks as follows: https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-bin_owner_industry_en_white.png 8/27 The February campaign lure image looks as follows: https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-doc_0101.png 9/27 The MHTML document is an ASCII document featuring multiple MIME-parts. One MIME-part contains the lure image. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-doc_0201.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-mime_head.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-mime.png 10/27 The other parts contain an application/vnd.ms-officetheme and an application/x- mso file. Which (in addition to the text/xml files) are used by Microsoft Word to load the embedded Word document. The document will automatically execute the macro code on closing the document: The VBA code uses UserForm objects for obfuscation. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-vba_01.png 11/27 Within the ComboBox objects’ initialization code in the UserForm objects and various other mechanisms, a download URL and a password are assembled and used within a call to the VBA function CallByName . This calls the Workbook object’s open function with the fileName parameter set to the download URL, the Password parameter set to the assembled password, and the other optional parameters left empty. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-vba_02.png 12/27 The call will cause Word to open Excel and download the encrypted XLS file from the URL https://findinglala[.]com/down/doc.xls?ekyh_vD91041.z4730435.doc . Excel will use the provided password to decrypt the document. The XLS document will use XLM macros to decode and use rundll32.exe to execute an embedded Zloader payload. Zloader Zloader is a fork of the famous Zeus banking Trojan. It is a loader that allows its operator to load additional malware onto infected devices. Coarse dynamic analysis The via rundll32.exe started Zloader process from the XLS document will spawn a suspended msiexec.exe process and inject code into it. First, the original DLL running in rundll32.exe starts a msiexec.exe process. Then, WriteProcessMemory is used to write code into it. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-vba_03.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-processtree.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-msiexec01.png 13/27 Eventually, the Zloader code running in rundll32.exe resumes the thread in the msiexec.exe process via NtResumeThread and the injected code starts running. Zloader will then generate a lot of random directories in %APPDATA%\Roaming . https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-msiexec02.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-msiexec03.png 14/27 It then copies the original DLL into one of the folders. To this end, it first reads the original DLL into memory. In the next step, data from the DLL is being written back into a new file. The other folders remain empty but can be used at later points in time to hold additional data. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-msiexec04.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-msiexec05.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-msiexec06.png 15/27 The original DLL is deleted. Eventually, C2 communication initializes. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-msiexec08a.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-msiexec07.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-msiexec08.png 16/27 Unpacking The Zloader DLL injected into msiexec.exe can be extracted either automatically via the open-source CAPE sandbox, manually dumped by breaking on NtResumeThread in the original rundll32.exe process, then dumping the msiexec.exe process, or semi- automatically by using a tool such as hollows_hunter (or PE-Sieve). The following analysis was performed on a Zloader DLL dumped from the msiexec.exe process. Obfuscation The Zloader malware is obfuscated. It makes extensive use of junk code, i.e., adding program instructions that do not contribute to the program logic with the sole purpose of complicating analysis. Further, it often calls complicated functions to perform trivial calculations, making the code appear very complex. For example, the following is a function that performs the binary AND operation on two parameters. The code is littered with such junk code. 5 https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-msiexec09.png 17/27 Dynamic library, function and string resolution Functions are dynamically resolved at runtime via a hash lookup. Instead of calling a function directly, a proxy function returning a pointer to the desired function is called. The following example shows the function with which Zloader deletes its original file. The function we named zl_get_func received two parameters, the first is a library ID ( 0 is ntdll) and the second is a hash of the function name that should be called. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-zloader_ghidra_junkcode.png 18/27 This is standard practice in modern malware, so no suspicious imports are present in the binary. It also makes a static analysis more complicated. Obviously, the hash calculation also uses the previous mentioned junk code obfuscation. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-zloader_ghidra_delete.png 19/27 However, it can be reimplemented in Python as follows. def zl_hash(func_name): func_name = func_name.lower() hash = 0 for c in func_name: a = 0 - (hash << 4) hash = 0 - (a - ord(c)) b = 0x647400ac ^ 0x947400ac b = hash & (~(b ^ hash)) if b != 0: d = 0x647400ac ^ 0x6b8bff53 hash = (b >> 0x18) ^ (d & hash) return hash With this function calls can be de-obfuscated. Strings are XOR encoded with a static repeating ASCII keystream. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-zloader_ghidra_hash.png 20/27 Configuration The Zloader configuration is RC4 encrypted with a key using only ASCII as keyspace. The data at the location we labeled zl_encrypted_config can be decoded with the ASCII string handed as the second parameter to the function we named zl_decrypt_config . Consequently, the configuration of the Zloader sample will be revealed. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-zloader_ghidra_strings.png https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-zloader_ghidra_rc4.png 21/27 The configuration contains a botnet name the particular sample is associated with, a campaign ID (presumably for the threat actors to keep track of infections per campaign), an RC4 key (used to encrypt and decrypt updated configuration stored in the registry) and last but not least a list of command and control URLs the malware should connect to for commands and updates. We provide an update to the DC3-MWCP script included with the open-source CAPE sandbox that handles configuration extraction for the analyzed Zloader sample in the appendix. It helps automating the configuration extraction. Malware objectives The configurations of downloaded pieces of Zloader malware associates them with the kev botnet. The kev ID has been publicly observed since December 2020. Zloader has been identified as an access vector for Ryuk and Egregor ransomware deployments. Whether the installments associated with the kev configuration tag are part of this or a different ransomware operation is currently unknown. However, by the direction the current threat landscape is moving, it is highly likely the malware is also used to deploy ransomware. Conclusion and Countermeasures Spreading the attack into multiple encoded stages (document in HMTL; payload URL in UserForm s; download of password-protected XLS; decoding of Zloader payload from decrypted XLS) shows that much effort was put into evading detection. Even after the campaign ran for several weeks, the initial MHTML documents still only got 7 out of 61 detections when first scanned on VirusTotal. https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-zloader_ghidra_config.png 22/27 The unusual MHTML encoding of the initial Word document can pose problems for security software unfamiliar with this format. Its initial layer must be parsed differently from OLE/CDF/OpenXML-based Office documents and being ASCII plain-text may completely bypass some detections. For network-based protection software, it is impossible to investigate the intermediary downloaded XLS document with the Zloader payload – because it is encrypted. Another struggle is the low level of maliciousness of the initial Word document. While downloads from documents should always be deemed at least suspicious, in this case, only another Excel document was download. Some business workflows may require Word documents to download resources from web. Consequently, the observed behaviour may fly under the radar. Hence, spreading the malicious components (download; dropper; Zloader malware) over multiple stages can bypass detection for some security solutions. Hornetsecurity’s Spam Filtering Solutions and Malware Protection detects and quarantines the outlined threat. Hornetsecurity’s Advanced Threat Protection extends this protection by also detecting yet unknown threats. References Appendix DC3-MWCP / CAPE configuration parser The following DC3-MWCP configuration parser is an update to CAPE’s Zloader parser and can be used as a drop in replacement (additionally we opened a pull request with the upstream project): https://www.hornetsecurity.com/wp-content/uploads/2021/03/20210218-mhtml-vt.png https://www.hornetsecurity.com/en/services/spam-filter/ https://www.hornetsecurity.com/en/services/advanced-threat-protection/ https://github.com/kevoreilly/CAPEv2/blob/master/modules/processing/parsers/mwcp/Zloader.py 23/27 # Copyright (C) 2020 Kevin O'Reilly (kevoreilly@gmail.com) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from mwcp.parser import Parser import struct import string import pefile import yara import re from Crypto.Cipher import ARC4 import logging log = logging.getLogger(__name__) rule_source = ''' rule Zloader { meta: author = "kevoreilly" description = "Zloader Payload" cape_type = "Zloader Payload" strings: $rc4_init = {31 [1-3] 66 C7 8? 00 01 00 00 00 00 90 90 [0-5] 8? [5-90] 00 01 00 00 [0-15] (74|75)} $decrypt_conf = {e8 ?? ?? ?? ?? e8 ?? ?? ?? ?? e8 ?? ?? ?? ?? e8 ?? ?? ?? ?? 68 ?? ?? ?? ?? 68 ?? ?? ?? ?? e8 ?? ?? ?? ?? 83 c4 08 e8 ?? ?? ?? ??} condition: uint16(0) == 0x5A4D and any of them } ''' MAX_STRING_SIZE = 32 yara_rules = yara.compile(source=rule_source) def decrypt_rc4(key, data): cipher = ARC4.new(key) return cipher.decrypt(data) def string_from_offset(data, offset): string = data[offset : offset + MAX_STRING_SIZE].split(b"\0")[0] return string class Zloader(Parser): 24/27 DESCRIPTION = 'Zloader configuration parser' AUTHOR = 'kevoreilly' def run(self): filebuf = self.file_object.file_data pe = pefile.PE(data=filebuf, fast_load=False) image_base = pe.OPTIONAL_HEADER.ImageBase matches = yara_rules.match(data=filebuf) if not matches: return for match in matches: if match.rule != "Zloader": continue for item in match.strings: if '$decrypt_conf' in item[1]: decrypt_conf = int(item[0])+21 va = struct.unpack("I",filebuf[decrypt_conf:decrypt_conf+4])[0] key = string_from_offset(filebuf, pe.get_offset_from_rva(va-image_base)) data_offset = pe.get_offset_from_rva(struct.unpack("I",filebuf[decrypt_conf+5:decrypt_conf+9])[0]- image_base) enc_data = filebuf[data_offset:].split(b"\0\0")[0] raw = decrypt_rc4(key, enc_data) items = list(filter(None, raw.split(b'\x00\x00'))) self.reporter.add_metadata("other", {"Botnet name": items[1].lstrip(b'\x00')}) self.reporter.add_metadata("other", {"Campaign ID": items[2]}) for item in items: item = item.lstrip(b'\x00') if item.startswith(b'http'): self.reporter.add_metadata("address", item) elif len(item) == 16: self.reporter.add_metadata("other", {"RC4 key": item}) return Indicators of Compromise (IOCs) Email Subjects Agreement information#?[0-9]+ Agreement info#?[0-9]+ Contract info#?[0-9]+ Contract information#?[0-9]+ Payment data#?[0-9]+ Invoicing data#?[0-9]+ Contract data#?[0-9]+ Invoicing information#?[0-9]+ Invoice data#?[0-9]+ 25/27 Invoicing details#?[0-9]+ Payment info#?[0-9]+ Invoicing info#?[0-9]+ Agreement data#?[0-9]+ Contract details#?[0-9]+ Invoice information#?[0-9]+ Invoice details#?[0-9]+ Payment information#?[0-9]+ Invoice info#?[0-9]+ Agreement details#?[0-9]+ Payment details#?[0-9]+ Important agreement #?[0-9]+ update Essential contract No. #?[0-9]+ update Essential agreement No. #?[0-9]+ documentation Important contract No. #?[0-9]+ update Important contract №#?[0-9]+ update Essential agreement Number #?[0-9]+ update Necessary contract №#?[0-9]+ update Important contract № #?[0-9]+ update Important agreement No. #?[0-9]+ documentation Important agreement #?[0-9]+ documentation Necessary agreement Number #?[0-9]+ documentation Important contract № #?[0-9]+ documentation Important contract #?[0-9]+ documentation Important agreement № #?[0-9]+ documentation Essential contract #?[0-9]+ update Essential agreement No. #?[0-9]+ update Necessary agreement Number #?[0-9]+ update Necessary contract № #?[0-9]+ update Important agreement Number #?[0-9]+ update Essential contract No. #?[0-9]+ documentation Important contract #?[0-9]+ update Essential agreement #?[0-9]+ update Necessary agreement № #?[0-9]+ update Important agreement No. #?[0-9]+ update Necessary contract Number #?[0-9]+ documentation Necessary contract No. #?[0-9]+ update Necessary agreement #?[0-9]+ update Essential contract Number #?[0-9]+ documentation Essential contract № #?[0-9]+ documentation Essential agreement №#?[0-9]+ update Necessary agreement №#?[0-9]+ update 26/27 Essential contract Number #?[0-9]+ update Necessary contract No. #?[0-9]+ documentation Important contract №#?[0-9]+ documentation Important agreement № #?[0-9]+ update Essential contract №#?[0-9]+ documentation Essential contract #?[0-9]+ documentation Necessary contract №#?[0-9]+ documentation Necessary agreement #?[0-9]+ documentation Important contract Number #?[0-9]+ documentation Necessary contract #?[0-9]+ update Important agreement №#?[0-9]+ update Essential contract №#?[0-9]+ update Essential agreement №#?[0-9]+ documentation Necessary contract #?[0-9]+ documentation Important agreement Number #?[0-9]+ documentation Essential agreement № #?[0-9]+ documentation Necessary agreement № #?[0-9]+ documentation Essential agreement Number #?[0-9]+ documentation Essential contract № #?[0-9]+ update Necessary contract Number #?[0-9]+ update Necessary agreement No. #?[0-9]+ update Essential agreement № #?[0-9]+ update Essential agreement #?[0-9]+ documentation Important contract No. #?[0-9]+ documentation Necessary agreement No. #?[0-9]+ documentation Important contract Number #?[0-9]+ update Necessary contract № #?[0-9]+ documentation Necessary agreement №#?[0-9]+ documentation Important agreement №#?[0-9]+ documentation Attachments The following regular expressions describe the attachment names used in the campaigns: ([a-z]{4,8}_){1,2}[a-z]{4,8}[0-9]+.doc [A-z0-9][A-z][0-9]+.doc Hashes Hashes of publicly available files: MD5 Filename Description 6743ca84f7e9929c2179238e20934f57 nG772044.doc Zloader MHTML document 27/27 MD5 Filename Description 7a888f899a4850f02bad194bf01daaa7 eU107462.doc Zloader MHTML document 35ee0681eb3076674e01efec565f663b L1978883.doc Zloader MHTML document 25e2cffc5621cab99bd0a36d234c234f QG915014.doc Zloader MHTML document 222cb61e1041f3e4dbdc3493572388e6 dY433632.doc Zloader MHTML document URLs https://findinglala[.]com/down/doc.xls?ekyh_vD91041.z4730435.doc DNS findinglala[.]com funkstarnews[.]com heavenlygem[.]com 2tut[.]com khalilmouna[.]com