Meet The Final Boss— Clearing each level of Multi-Stage Malware

Darrel
CSG @ GovTech
Published in
7 min readJan 18, 2023

Introduction

For a long time, malware authors have built and deployed multi-stage malware to evade detection mechanisms and frustrate malware analysis efforts. When you think you have cleared the final hurdle to find the malware’s core functionality, another level of obfuscation presents itself.

In this post, we will go through a couple of cool, reliable tools and techniques that help us discover the inner workings of one such malware. These will be useful and can be applied across analysis or reverse engineering of other malware.

Link to the malware via VirusTotal can be accessed here: https://www.virustotal.com/gui/file/564cbc7e47b7a758c8590dfe0aec63d8ff8ab465dea054292ab1027fbd70fb20

Delivery

Email remains the most popular way to deliver malware. Archive File Formats such as ZIP and ACE files, ISO image files, and HTML files are the most common avenues used to smuggle nasty payloads when opened by their victims. Phishing emails, for instance, can be cleverly crafted to entice recipients to open these files, executing their contents. This typically leads to the start of the multi-stage execution of the malware. Since an adversary may need to rely on follow-on behaviour by a victim upon receiving the phishing email to achieve execution of their malware, a common method to build a payload for the victim to execute is to package the malware using NSIS into a single, easy-to-click executable.

NSIS Installers

NSIS (Nullsoft Scriptable Install System) is a script-driven open-source system to create Windows installers. Its ubiquity, flexibility and extensive capabilities make it a great pick for malware authors seeking to package their malware into one executable file.

At first glance, this may look like a normal PE Executable. But with the “Detect It Easy” tool, we quickly find that it is an NSIS Installer. We can then use a tool like 7-Zip to unzip and inspect all files packaged, including the main NSI script, which runs when the NSIS Installer is clicked.

Upon opening the NSIS installer with 7-zip, we see a couple of files contained within and a [NSIS].nsi file. The [NSIS].nsi file is the NSI script controlling what happens when the installer is clicked. We can inspect its contents using any text editor software.

Contents of the NSIS Installer

We want to focus on what the malware is doing here. It sets the output path to $INSTDIR, which was set to the $temp environment variable in an earlier part of the script. The “File” command then extracts each file to the output path. After the files are copied to the Windows temp directory, ExecWait executes the “eiwshq.exe” executable with “xnntpm.au3” as its argument. Both these files are found within the NSIS Installer.

NSIS Script — .oInit Function

AutoIt v3

AutoIt is another open-source project with powerful capabilities for automating the Windows GUI and general scripting. Malware authors widely use it in the infection execution chain due to its capabilities.

We quickly identify that “eiwshq.exe” turns out to be the AutoIt Application.

The AutoIt script, “xnntpm.au3”, will be passed to the application for execution. At first glance, we can see that $A30diehlk seems to be an alias for the Chr function. The Chr function returns a character corresponding to its ASCII code, which is what we observe here, albeit another layer of obfuscation using the subtraction of two integers to prevent us from calculating the ascii code easily.

Contents of “xnntpm.au3”

A simple python script will help make the integer calculations, convert the ascii code to its associated character and replace in-place. This should do the trick to make the code human-readable and allow us to understand what the AutoIt Script is programmed to do.

import redef readfilelines(filename):
file1 = open(filename, 'r')
Lines = file1.readlines()
return Lines


def deobfuscate_chr(txt):
if txt == "":
return

while True:
current_char = re.search("Chr\(.*?\)",txt)
if current_char == None:
break
else:
current_char_arithmetic = chr(eval(current_char.group()[4:-1]))
try:
txt = re.sub("Chr\(.*?\)", current_char_arithmetic, txt, 1)
except:
if current_char_arithmetic == "\\":
txt = re.sub("Chr\(.*?\)", "\\\\", txt, 1)
else:
txt = re.sub("Chr\(.*?\)", "<ERROR>", txt, 1)
print(txt.replace(" & ", ""))

def main():
lines = readfilelines("xnntpm.au3")
for line in lines:
#replace all $A30diehlk with Chr
line = line.replace("$A30diehlk","Chr")
deobfuscate_chr(line)

main()

After running the deobfuscation script, we can manually replace the rest of the alias to make the code look more readable.

We can see that the script essentially reads the “yecrxn.ke” file and removes all instances of “448782054140” in the file. It uses Windows API functions such as VirtualAlloc to create a region of memory, and EnumTimeFormatsA to execute the decoded shellcode in “yecrxn.ke”.

Deobfuscated Autoit Script

We can do the same now that we know what the AutoIt script was doing. Remove all instances of “448782054140” in the next stage shellcode file and then use a scripting language like python to read from the file and write its bytes into a binary format file. We can then use IDA or Ghidra to analyse the shellcode.

Shellcode Analysis

Once the shellcode file opens in IDA, we can reverse engineer the disassembly to make sense of what this stage of the payload is doing. Upon execution,we see that the shellcode reads the “zkhlbwzloru.xft” file, which is also contained in the NSIS installer. The filesize is checked, and the address pointer to the data buffer containing the contents of the file is passed to a decryption function using the EDI register.

Disassembly of the next stage shellcode, with comments added during reversing.

We identify a long looping subroutine that decrypts the “zkhlbwzloru.xft” file contents.

Faced with this issue, we realised that there was no way we could decrypt and run the next stage code contained in “zkhlbwzloru.xft” manually. Unicorn to the rescue!

Unicorn

Unicorn is a lightweight multi-platform, multi-architecture CPU emulator framework. In our case, we can use it to emulate the execution of the decryption subroutine and give us the decrypted final payload.

The python script below achieves that by first reading bytes from the “zkhlbwzloru.xft” file and storing it in an emulated memory region. The decryption function is then imported into our python script in its hex form, and will be the opcodes to be run by the emulated CPU.

Just as how it was in the actual shellcode, we will set the address pointer to the memory region containing the encrypted file data through the EDI register. The other registers used with the subroutine will also have to be properly initialised.

Once the decryption function from the malware runs in the emulator, the bytes from the original “zkhlbwzloru.xft” file will be decrypted. We will then use the binascii python module to format the decrypted data and write it into a new file named “zkhlbwzloru-decrypted.bytes”

from unicorn import *
from unicorn.x86_const import *
import binascii
#read bytes from file
with open("zkhlbwzloru.xft", "rb") as f:
X86_ENCRYPTED_SHELLCODE = f.read()
# code to be emulated (Decryption Function)
X86_CODE32 = b"\x8A\x04\x1F\xB1\xB3\x2A\xC3\x34\x5F\xC0\xC0\x02\x2C\x56\x32\xC3\xF6\xD8\xC0\xC0\x02\x34\x5F\xF6\xD0\x2C\x4D\x32\xC3\x34\xB3\xC0\xC0\x03\x2A\xC3\x34\x54\xF6\xD8\x32\xC3\xC0\xC8\x02\x04\x3D\xC0\xC0\x02\x2C\x4E\xD0\xC0\x2A\xC8\xB0\x95\x32\xCB\x80\xE9\x43\x80\xF1\xE2\x02\xCB\xF6\xD1\x32\xCB\xD0\xC9\x2A\xCB\x80\xF1\xB2\x80\xC1\x1C\xF6\xD1\x80\xC1\x49\x80\xF1\x52\xC0\xC1\x02\x2A\xC1\xB1\xA7\x34\x02\x2C\x31\xD0\xC8\xF6\xD0\x2A\xC3\x32\xC3\x2A\xC8\x2A\xCB\x32\xCB\x80\xE9\x40\x32\xCB\xF6\xD1\x80\xE9\x7D\xC0\xC9\x02\xF6\xD9\xC0\xC9\x02\x80\xF1\x03\x80\xE9\x2A\xC0\xC1\x03\x88\x0C\x1F\x43\x3B\xDE\x0F\x82\x66\xFF\xFF\xFF" # Decoding Function
# memory address where emulation starts
ADDRESS = 0x1000000
# memory address where encrypted shellcode starts
ADDRESS_EncryptedShellcodeBuffer = 0x2000000
print("Emulate i386 code")
try:
# Initialize emulator in X86-32bit mode
mu = Uc(UC_ARCH_X86, UC_MODE_32)
# map 2MB memory for this emulation
mu.mem_map(ADDRESS, 2 * 1024 * 1024)
mu.mem_map(ADDRESS_EncryptedShellcodeBuffer, 2 * 1024 * 1024)

# write machine code to be emulated to memory
mu.mem_write(ADDRESS, X86_CODE32)
mu.mem_write(ADDRESS_EncryptedShellcodeBuffer, X86_ENCRYPTED_SHELLCODE)

# initialize machine registers
mu.reg_write(UC_X86_REG_ECX, 0x0)
mu.reg_write(UC_X86_REG_ESI, len(X86_ENCRYPTED_SHELLCODE))
#mu.reg_write(UC_X86_REG_EDX, 0x7890)
mu.reg_write(UC_X86_REG_EDI, ADDRESS_EncryptedShellcodeBuffer)
# emulate code in infinite time & unlimited instructions
mu.emu_start(ADDRESS, ADDRESS + len(X86_CODE32))
# now print out some registers
print("Emulation done. Below is the CPU context")
r_eax = mu.reg_read(UC_X86_REG_EAX)
r_ecx = mu.reg_read(UC_X86_REG_ECX)
r_edx = mu.reg_read(UC_X86_REG_EDX)
r_ebx = mu.reg_read(UC_X86_REG_EBX)
r_edi = mu.reg_read(UC_X86_REG_EDI)
r_esi = mu.reg_read(UC_X86_REG_ESI)

# read the decrypted shellcode into variable
mem_EncryptedShellcodeBuffer = mu.mem_read(r_edi,len(X86_ENCRYPTED_SHELLCODE))
# write the decrypted shellcode into a new file "zkhlbwzloru-decrypted.bytes"
with open('zkhlbwzloru-decrypted.bytes', 'wb') as fout:
for b in mem_EncryptedShellcodeBuffer:

fout.write(
binascii.unhexlify("{:02x}".format(b))
)


except UcError as e:
print("ERROR: %s" % e)

The Final Boss — Formbook Infostealer

After decrypting the final payload, we can continue to run our analysis, or do a simple hash check against OSINT sources such as VirusTotal.

Based on information from VirusTotal, the final payload, “zkhlbwzloru-decrypted.bytes” that we decrypted earlier is a Windows Portable Executable many AV vendors categorise as Formbook Infostealer malware.

Virustotal Automated Dynamic Analysis

Some of the Network IOCs returned by VirusTotal

Link to the Formbook malware via VirusTotal can be accessed here: https://www.virustotal.com/gui/file/076f6d91e7dd2dcbcae3c16aae2438a058eb95458720e2ad3f6bde0f567134d0

Conclusion

Multi-stage malware is challenging to analyse for cyber defence and incident response teams everywhere.

The tactics deployed by the malware author discussed in this post are by no means the only ones. There are countless methods and edge cases where Cyber Adversaries continually push boundaries and increase the sophistication level of malware throughout the infection chain.

The more we understand the evasion tactics and techniques used in malware, the better we can defend against them.

--

--