In the world of cybersecurity, attackers are constantly evolving their techniques to evade detection. One of the most effective strategies is “Living off the Land” (LotL), where attackers uses legitimate, trusted tools already present on a system to carry out malicious activities. This approach helps them blend in with normal administrative traffic and bypass security products that look for known bad files. Today, I’m going to dissect a classic LotL technique: using PowerShell to execute an inline JavaScript payload via mshta.exe. All without writing a single file to disk. We’ll build the command, obfuscate it, and then explore how attackers deliver it in the real world.

What is mshta.exe and Why Do Attackers Abuse It?

mshta.exe is the Microsoft HTML Application host. It executes HTML Applications (HTA) and can interpret HTML, VBScript, and JScript (JavaScript) passed either from a local .hta file or via a javascript: URI. Because mshta.exe is a signed, legitimate binary present on Windows systems, attackers commonly abuse it (a LOLBin) to execute script payloads in-memory without dropping additional executable. Which is a classic LotL technique.

Key points:

  • HTA Files (.hta): These are file-backed HTML + script combos. You can run them directly with mshta.exe <file.hta>.
  • Inline Execution: mshta.exe can also interpret scripts passed via a URI scheme like javascript:, allowing code to run entirely in memory without writing anything to disk.
  • Why It’s Attractive for Attackers: As a legitimate, whitelisted Microsoft binary (often found in C:\Windows\System32\mshta.exe), it blends into normal system activity. This makes it a classic “LOLBin” (Living off the Land Binary), a trusted tool abused for malicious purposes.

With mshta.exe you can:

  • Run inline JavaScript or VBScript (mshta “javascript:…”;).
  • Execute DOM code and create windows / dialogs.
  • Use ActiveX objects (COM) inside scripts to call Win32/COM APIs.
  • Launch other processes or perform network actions if the script issues such commands.

In LotL scenarios, attackers feed JavaScript or VBScript directly to mshta.exe via command-line arguments, enabling fileless execution. No droppers or executables hit the disk, reducing forensic footprints.

Malicious use of mshta.exe: A Practical Example

LotL technique abuses mshta.exe, a native Windows application designed to run Microsoft HTML Applications (.HTA files). Because mshta.exe is a signed, legitimate Microsoft program, it’s often whitelisted by security software. Crucially, it can execute JavaScript and VBScript with the full permissions of the user, outside the confines of a browser’s sandbox.

While it can run .hta files, its real power for attackers comes from its ability to execute code directly from the command line using a javascript: URI.

Step 1: The Basic PowerShell Command

Basic PowerShell Command

Here’s the plain PowerShell command to do this:

Start-Process -FilePath 'mshta.exe' -ArgumentList 'javascript:alert("mshta used as a LOLBin for LotL");close();' -WindowStyle Hidden

When you run the command:
mshta.exe is launched and immediately executes the inline javascript: URI. The result is a modal alert dialog (a message box) that displays the text “mshta used as a LOLBin for LotL”. Let’s break down how this simple line works.

How it Works:

  1. PowerShell starts the process

    • Start-Process -FilePath ‘mshta.exe’ launches the mshta.exe binary.

  2. The JavaScript URI is passed as a single argument

    • javascript:alert("mshta used as a LOLBin for LotL");close();
    • -ArgumentList ‘javascript:alert(“mshta used as a LOLBin for LotL”);close();’ hands the whole javascript: URI to mshta.exe as its argument.

  3. The -WindowStyle Hidden part

    • Hides the PowerShell launch console so you don’t get a flashing PowerShell window.

    • Note: mshta.exe itself can still create UI (the alert dialog) even when the parent PowerShell window is hidden.

  4. mshta.exe interprets the javascript: scheme

    • mshta.exe recognizes the javascript: URI and executes the following JavaScript code:
      alert(“mshta used as a LOLBin for LotL”); close();

  5. JavaScript runs in the mshta host

    • alert(“mshta used as a LOLBin for LotL”) opens a modal message box showing mshta used as a LOLBin for LotL.

    • close() tells the MSHTA host to exit — the mshta.exe process will terminate after the alert is dismissed.

  6. Result
    • The mshta.exe process starts, executes the inline JavaScript, a modal alert with the message appears, and when the user clicks OK the mshta.exe process exits.

The Quoting Quirk:

Notice the quotes in the command. The outer string in PowerShell uses single quotes (‘…’), which treats everything inside it literally. The inner JavaScript string uses double quotes (“…”). This nesting is crucial to avoid “unterminated string” errors and ensure PowerShell passes the entire JavaScript URI as a single, correct argument to mshta.exe.

To include the string ‘hello inline js’ mshta used as a LOLBin for LotL’ in a PowerShell single-quoted string, you write ”hello inline js”, where each ‘ is escaped as ”. When PowerShell processes alert(‘ ‘hello inline js’ ‘), it sends alert(‘hello inline js’) to mshta.exe, which JavaScript then interprets correctly.

Template 1: For JavaScript Code That Uses a Text String

Start-Process -FilePath 'mshta.exe' -ArgumentList 'javascript:YOUR_FUNCTION("Your Text Message Here");close();' -WindowStyle Hidden

Use this template when your JavaScript function needs to handle a piece of text (a “string”), like displaying a message. The quotes are part of the JavaScript syntax itself. YOUR_FUNCTION: Replace this with a function like alert, confirm, or prompt.

Template 2: For JavaScript Code That Does Not Use a Text String

Start-Process -FilePath 'mshta.exe' -ArgumentList 'javascript:YOUR_FUNCTION();close();' -WindowStyle Hidden

Use this template when your JavaScript function performs an action that doesn’t require a text message, like a command with no arguments or one that uses numbers.

YOUR_FUNCTION(): Replace this with a function that doesn’t need a string, like window.print() or history.back().

Note:

The rule of thumb is simple: The quotes belong to the JavaScript, not the PowerShell template.

Write your JavaScript code correctly first, and then place that complete, correct snippet inside the PowerShell -ArgumentList’s single quotes.

Writing the Command in Another Way

For better readability, especially with longer scripts, you can define the argument in a variable first and then pass that variable to the Start-Process command. This is a cleaner way to write it.

# Step 1: Define your JavaScript code in a variable.
$js_code = 'javascript:alert("This is a cleaner way to write the command.");close();'

# Step 2: Use that variable in the Start-Process command.
Start-Process -FilePath 'mshta.exe' -ArgumentList $js_code -WindowStyle Hidden

Step 2: Obfuscation with Base64

Leaving a readable command like that is risky. Security tools can easily detect it based on keywords like mshta.exe and javascript:. To make this even stealthier, attackers often encode the PowerShell command in Base64 (UTF-16LE format) and run it via powershell.exe -EncodedCommand. This obscures the payload in logs and makes it easier to embed in other delivery mechanisms. This also makes the payload unreadable to simple signature-based scanners.

Create the Base64 (UTF-16LE) String

PowerShell needs the command to be encoded in a specific format: UTF-16LE. Here’s how to convert our command into the required Base64 string.

The safest way is to use a multi-line “here-string” (@’…’@) to avoid any quoting issues.

# Define the script to be encoded
$script = @'
Start-Process -FilePath 'mshta.exe' -ArgumentList 'javascript:alert("mshta used as a LOLBin for LotL");close();' -WindowStyle Hidden
'@

# Convert the script string to bytes using Unicode (UTF-16LE) encoding
$bytes = [System.Text.Encoding]::Unicode.GetBytes($script)

# Convert the bytes to a Base64 string
$b64   = [Convert]::ToBase64String($bytes)

# Display the result
$b64

The Encoded Payload

Running the script above produces the following Base64 string. This is the payload we’ll use in our final command.

UwB0AGEAcgB0AC0AUAByAG8AYwBlAHMAcwAgAC0ARgBpAGwAZQBQAGEAdABoACAAJwBtAHMAaAB0AGEALgBlAHgAZQAnACAALQBBAHIAZwB1AG0AZQBuAHQATABpAHMAdAAgACcAagBhAHYAYQBzAGMAcgBpAHAAdAA6AGEAbABlAHIAdAAoACIAbQBzAGgAdABhACAAdQBzAGUAZAAgAGEAcwAgAGEAIABMAE8ATABCAGkAbgAgAGYAbwByACAATABvAHQATAAiACkAOwBjAGwAbwBzAGUAKAApADsAJwAgAC0AVwBpAG4AZABvAHcAUwB0AHkAbABlACAASABpAGQAZABlAG4A

Step 3: The Final Stealthy Command

Now we can assemble the full, stealthy command that executes our hidden payload.

powershell.exe -NoProfile -WindowStyle Hidden -EncodedCommand <Your_Long_Base64_String_Here>

powershell.exe -NoProfile -WindowStyle Hidden -EncodedCommand UwB0AGEAcgB0AC0AUAByAG8AYwBlAHMAcwAgAC0ARgBpAGwAZQBQAGEAdABoACAAJwBtAHMAaAB0AGEALgBlAHgAZQAnACAALQBBAHIAZwB1AG0AZQBuAHQATABpAHMAdAAgACcAagBhAHYAYQBzAGMAcgBpAHAAdAA6AGEAbABlAHIAdAAoACIAbQBzAGgAdABhACAAdQBzAGUAZAAgAGEAcwAgAGEAIABMAE8ATABCAGkAbgAgAGYAbwByACAATABvAHQATAAiACkAOwBjAGwAbwBzAGUAKAApADsAJwAgAC0AVwBpAG4AZABvAHcAUwB0AHkAbABlACAASABpAGQAZABlAG4A

Output:

If the command runs on an interactive desktop session with UI available, the final visible artifact is a JavaScript alert dialog that says:

After running the Final Command you will see an output like this.

mshta inline js output

Example of mshta.exe-triggered JavaScript alert popup in Windows

Real-World Delivery: Common Attack Vectors

No one hands a victim a raw PowerShell command. Instead, attackers embed it in carriers that look innocent:

  • Phishing Attachments: LNK shortcuts in ZIPs/ISOs/VHDs with the encoded command in the target field. Or HTA/JS/VBS files that spawn PowerShell.
  • Office Documents: Macros in Word/Excel, XLL add-ins, or OneNote pages that execute the payload.
  • HTML Smuggling: Malicious web pages drop and run scripts that invoke PowerShell.
  • Installer Abuse: MSI files or setup executables with custom actions running the command.
  • LotL Chains: Other LOLBins like regsvr32.exe, rundll32.exe, or wmic.exe to trigger PowerShell.
  • Post-Compromise: After initial access (e.g., via RDP or exploits), use WMI, scheduled tasks (schtasks), GPO, or PsExec to deploy across networks.
  • Persistence: Registry Run keys, WMI event subscriptions, or service hijacks that fire the command on boot/logon.

The encoded payload is tucked inside these vectors, making the attack chain harder to spot.

What mshta.exe Can Really Do (Malicious Potential)

While our examples are benign popups, attackers swap in harmful JavaScript:

  • Download-and-Execute: Use XMLHTTP or ADODB.Stream to fetch and run payloads in memory.
  • Command Execution: Spawn cmd.exe or PowerShell for further actions.
  • Persistence: Create scheduled tasks, registry keys, or startup items via COM objects.
  • Lateral Movement: Invoke WMI or SMB to target other machines.
  • C2 Communication: HTTP requests or WebSockets for beaconing.
  • Recon and Theft: Enumerate system info, read files, or harvest credentials.
  • Fileless Loaders: Build and reflectively load shellcode or .NET assemblies.

mshta.exe supports ActiveX/COM, giving it access to Win32 APIs—perfect for advanced in-memory ops.

How to Detect and Hunt for mshta.exe Abuse

Spotting mshta.exe abuse requires monitoring beyond basic AV:

  • Process Monitoring (e.g., Process Explorer): Look for mshta.exe as a child of unusual parents like powershell.exe. Check command lines for javascript: URIs or suspicious objects like XMLHTTP.
  • Sysmon/Event Logs: Event ID 1 (Process Create) with:
    • Image: C:\Windows\System32\mshta.exe.
    • CommandLine: Contains javascript: or long JS strings.
    • ParentImage: powershell.exe or unexpected.
    • IntegrityLevel: Elevated processes are higher risk.
  • Behavioral Indicators: Inline scripts, network calls from mshta.exe, or child processes like cmd.exe.
  • Lab Testing: Run in a VM with tools like Sysmon enabled. Decode Base64 payloads before execution.

Conclusion

mshta.exe shows how attackers turn common Windows tools into weapons using living off the land techniques. Understanding how it works, from inline URIs to encoded payloads, helps you defend against or reproduce these attacks. Always test only in isolated environments and remember that the most effective defense is visibility into process behavior and command lines.