When we talk about traditional malware, we’re usually referring to compiled malware. This means that the malware’s source code is translated into machine language.
Compilation happens when the computer takes human-readable code and converts it into instructions that the processor can understand, creating static files. For instance, it compiles C or C++ code into executable files like .exe or .dll for Windows.
The first compiled malware, called Brain, was created way back in 1986. That’s ancient history in the world of information technology! Over the years, we’ve become very good at detecting this type of threat.
YARA rules and Signatures can be incredibly effective at spotting malicious code in executables.
This doesn’t mean that compiled malware has become obsolete. Mirai (written in C) and FormBook (written in C++) are 3 examples of modern threats that fall into this bucket. But it has led attackers to invent something new.
Enter malicious scripts and script-based malware.
What are malicious scripts?
Scripts have become increasingly popular in recent years, largely because they effectively evade traditional endpoint detection and are easy to obfuscate.
Adversaries generally use them in one of two ways:
- Add a script component that executes an attack step in compiled malware to execute a command or download a payload.
- Create malware using a scripting language directly. An example is Lu0bot, which is written in Node.js.
Later in this article, we’ll look at case studies that analyze each type of threat, so keep reading!
The table below shows examples of malware that’s written entirely using a scripting language:
Example | Type | Language | Noteworthy for |
---|---|---|---|
WSHRAT | RAT | JS | A complex structure that uses numerous JS calls. |
Lu0Bot | Botnet | Node.js | Bundling with a NodeJS interpreter. |
STRRAT | RAT | JS | Dropping a compiled Java-based malware, which unpacks during execution |
Jsoutprox | Backdoor | JS | Being a script-based backdoor. |
(Read our in-depth analysis of Lu0Bot — a malware that theoretically can do almost anything).
Scripts are written in interpreted programming languages, which means that the code is executed by an interpreter at runtime rather than being compiled into an executable file.
Some of the most commonly used languages for script-based malware are JavaScript and PowerShell.
Key characteristics of malicious scripts include:
- Fileless execution: Scripts can run in memory without leaving a significant trace on the infected system’s hard drive. For example, a malicious PowerShell script could use the Invoke-Expression cmdlet to download and execute a payload directly in memory, making it difficult for security software to detect it.
- Living off the land: Scripts often use utilities already present in the OS. Think, JavaScript using the WMI (Windows Management Instrumentation) to query system data.
Types of scripts used by script-based malware
Scripts differ by their runtime environment, and this determines how attackers weaponize them. The table below breaks down the most common ones used in malware.
Scripting Language | Execution Environment |
---|---|
JavaScript | Web browsers (e.g., Chrome, Firefox), Node.js runtime |
JScript | Windows OS, Internet Explorer |
VBScript | Windows OS, Microsoft Office applications (e.g., Word, Excel) |
PowerShell | Windows OS, built-in Windows tool |
Batch Scripts | Windows OS, command-line interface |
Shell Scripts (Bash) | Unix/Linux OS, command-line interface |
Python Scripts | Cross-platform (Windows, macOS, Linux) |
Read our deep dive into UAC bypasses in Windows 11, which includes exploiting COM interfaces, or a breakdown of VBScript and how it’s used in macros (with real-world case-study).
Not all scripts work right away on every machine. Some will run on a bare machine, while others need extra software.
PowerShell, batch scripts, VBScript, and JScript are natively supported on Windows, and shell scripts (Bash) are natively supported on Linux.
Hackers can run them from the command prompt (or terminal in the case of Linux), from the PowerShell IDE, or by tricking the user into running the script by double-clicking the script files.
Scripts that require a special runtime or additional software are JavaScript and Python. Browser-based JavaScript requires a web browser such as Chrome, Firefox, or Edge. Node.js programs require the Node.js server.
Python scripts are cross-platform but require the Python interpreter to be installed on Windows.
How to analyze malicious scripts and script-based malware
When analyzing scripts, you have two main options:
- Review the source code statically to understand how the script works. This involves examining the codebase line by line without executing it.
- Execute the script dynamically. You can use debuggers (x64dbg, IDA Pro) and script tracers to step through the execution, inspect variables, or run the script in a sandbox (ANY.RUN) to see what it does within the system.
Sandboxing and scripting tend to be more effective than static code review — both for script-based malware and compiled malware that uses scripts somewhere in the attack chain.
That’s because reverse-engineering an obfuscated codebase is incredibly time-consuming (and we’ll see why in the next chapter).
Obfuscation and how to get around it
When it comes to scripting languages, their interpretive nature allows malware authors to obfuscate the script directly at the source code level, instead of working at low-level assembly or machine code level, which can be quite challenging.
To understand how powerful this anti-analysis tactic is, let’s take a dummy Python function and obfuscate it.
Here’s a function that pretends to steal a browser session:
def steal_cookies(website):
cookies = get_cookies(website)
send_to_hacker(cookies)
First, we’ll rename variables and functions, and then we’ll encode strings by replacing the string literal with its hexadecimal representation:
def sc(w):
c = gc('\x77\x65\x62\x73\x69\x74\x65')
sth(c)
We’ll add some dummy code and pointless comments. It will make the code look like a bad intern wrote it.
def sc(w):
# TODO: Implement error handling
x = 42
c = gc('\x77\x65\x62\x73\x69\x74\x65')
y = [1, 2, 3]
sth(c)
# FIXME: Optimize performance
Let’s add another layer of complexity by introducing encoding and encryption. We can use a combination of base64 encoding and ROT13 encryption to obscure the original string.
In this example, the base64-encoded string is decoded, and the result is passed through a ROT13 cipher before being sent to the sth function:
def sc(w):
x = 42
c = gc(b64d('d2Vic2l0ZQ==').decode('utf-8'))
y = [1, 2, 3]
sth(rot13(c))
And, as a final sprinkle, let’s cram everything into one line:
def sc(w):x=42;c=gc(b64d('d2Vic2l0ZQ==').decode('utf-8'));y=[1,2,3];sth(rot13(c))
Now, imagine that the source code has 1,000 lines, or more. Reverse engineering it would be like untangling a ball of spaghetti!
It’s not impossible, but In situations like this, it’s more practical to focus on following the execution flow and observing what the program does, instead of getting bogged down in how the source code is written line by line.
This is done with the help of script tracers, like the one that is built into ANY.RUN. ?
Analyzing Malicious Scripts in ANY.RUN
We’ll look at two examples: a case study of a script-enhanced attack chain, and malware that is entirely script-based. We’ll see that Script Tracer is equally useful in both cases.
So. let’s get started.
Example №1. WSHRAT
WSHRAT is written in JS, and it’s an excellent example to illustrate how the script tracker works. Let’s analyze this sample in ANY.RUN.
The screenshot above shows that WSHRAT initiates an HTTP POST request to hxxp://harold.2waky[.]com:3609/is-ready, where it transmits information about the infected operating system via the User-Agent header.
It adds itself to the Start Menu for persistence — this way it will remain active after a system reboot. All of this information is gathered using our script tracer.
(Learn more about WSHRAT malware in ANY.RUN Script Tracer)
Example #2. FormBook which downloads an executable with PowerShell
FormBook, a compiled malware, uses a PowerShell for one of the stages of the attack in this example.
PowerShell scripts can, essentially, write and execute new code on the fly, without ever saving it to disk.
Script tracer deobfuscates these hidden interactions by running the code in a controlled system and recording every API call, function parameter and variable value.
We’ll analyze this FormBook sample together to see how it works.
When you open the recording of the analysis session in ANY.RUN, to access the script tracer, first click on the process you want to examine, and then on the More info button.
The Advanced details of a process window will open, and if the process contains a script, you’ll see a Script Tracer tab on the right. In our case, a PowerShell script was traced, so let’s click on that.
The script tracer above shows a sequential recording of the call stack, and you can start looking from top to bottom to reconstruct every system action that took place while the script was running.
At the very top, note the DownloadDataFromLinks function — it used a System object to download something from the internet.
We see that the DownloadData(System.String) method took a single string parameter. This is a method of the System.Net.WebClient class in .NET, which takes a URI and downloads data as a byte array.
The image above shows what happened next.
- The decoded binary data was passed to System.Text.UnicodeEncoding.GetString, which converted the binary data into a Unicode string—this is the PowerShell command.
- The extracted data was decoded from base64 using the FromBase64String method, converting the Base64-formatted string back into an array of bytes.
- The BASE64_START and BASE64_END flags were used to extract the payload from the downloaded image.
Notice the MZ signature in the tracer. It clearly identifies the decoded file as an executable.
Now, let’s look at the parent process with PID 32:
Here we can find the actual PowerShell command:
Click on the Info button to open it in the Static discovery window and see the whole command.
Wrapping up
Let’s recap the main points.
- Malicious scripts and script-based malware are written in interpreted programming languages and executed by an interpreter at runtime rather than being compiled. This allows fileless execution in memory and makes it harder to detect compared to traditional compiled malware.
- Scripting languages make it easy to obfuscate malicious code by working at the source code level, rather than at the assembly or machine code level as compiled malware does.
- The two main approaches to analyze malicious scripts are reviewing the source code or executing it dynamically and seeing what it does. Dynamic analysis with script tracers tends to be more effective than manually deobfuscating the code.
- ANY.RUN has powerful tools bult-in for analyzing both script-based malware and compiled malware that leverages scripts. The built-in script tracer records API calls, function parameters, and variable values to reconstruct the malware’s behavior.
About ANY.RUN
ANY.RUN helps more than 400,000 cybersecurity professionals worldwide. Our interactive sandbox simplifies malware analysis of threats that target both Windows and Linux systems. Our threat intelligence products, TI Lookup, Yara Search, and Feeds, help you find IOCs or files to learn more about the threats and respond to incidents faster.
Advantages of ANY.RUN
ANY.RUN helps you analyze threats faster while improving detection rates. The platform detects common malware families with YARA and Suricata rules and identifies malware behavior with signatures when detection by family is not possible.
With ANY.RUN you can:
- Detect malware in under 40s.
- Interact with samples in real time.
- Save time and money on sandbox setup and maintenance
- Record and study all aspects of malware behavior.
- Collaborate with your team
- Scale as you need.
Let us give you an interactive presentation of ANY.RUN and show you how it can help your security team.
0 comments