├── .gitignore ├── .vscode ├── c_cpp_properties.json └── settings.json ├── LICENSE ├── README.md ├── binding.gyp ├── index.d.ts ├── index.js ├── injector.cpp ├── package.json ├── testdll.dll └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | #node-gyp 61 | build 62 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "/usr/include", 7 | "/usr/local/include", 8 | "${workspaceFolder}" 9 | ], 10 | "defines": [], 11 | "intelliSenseMode": "clang-x64", 12 | "browse": { 13 | "path": [ 14 | "/usr/include", 15 | "/usr/local/include", 16 | "${workspaceFolder}" 17 | ], 18 | "limitSymbolsToIncludedHeaders": true, 19 | "databaseFilename": "" 20 | }, 21 | "macFrameworkPath": [ 22 | "/System/Library/Frameworks", 23 | "/Library/Frameworks" 24 | ] 25 | }, 26 | { 27 | "name": "Linux", 28 | "includePath": [ 29 | "/usr/include", 30 | "/usr/local/include", 31 | "${workspaceFolder}" 32 | ], 33 | "defines": [], 34 | "intelliSenseMode": "clang-x64", 35 | "browse": { 36 | "path": [ 37 | "/usr/include", 38 | "/usr/local/include", 39 | "${workspaceFolder}" 40 | ], 41 | "limitSymbolsToIncludedHeaders": true, 42 | "databaseFilename": "" 43 | } 44 | }, 45 | { 46 | "name": "Win32", 47 | "includePath": [ 48 | "${workspaceFolder}", 49 | "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.13.26128/include/*", 50 | "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.13.26128/atlmfc/include/*", 51 | "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/um", 52 | "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/ucrt", 53 | "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/shared", 54 | "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/winrt", 55 | "${workspaceFolder}/node_modules/nan" 56 | ], 57 | "defines": [ 58 | "_DEBUG", 59 | "UNICODE", 60 | "_UNICODE" 61 | ], 62 | "intelliSenseMode": "msvc-x64", 63 | "browse": { 64 | "path": [ 65 | "${workspaceFolder}", 66 | "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.13.26128/include/*", 67 | "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.13.26128/atlmfc/include/*", 68 | "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/um", 69 | "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/ucrt", 70 | "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/shared", 71 | "C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/winrt" 72 | ], 73 | "limitSymbolsToIncludedHeaders": true, 74 | "databaseFilename": "" 75 | }, 76 | "cStandard": "c11", 77 | "cppStandard": "c++17" 78 | } 79 | ], 80 | "version": 4 81 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.pylintEnabled": false, 3 | "files.associations": { 4 | "*.para": "java", 5 | "*.pi": "java", 6 | "*.jif": "java", 7 | "ios": "cpp" 8 | } 9 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 mcoot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-dll-injector 2 | 3 | Native node addon for dll injection in windows. 4 | 5 | Requires a working `node-gyp` setup to compile the native addon. 6 | 7 | ## Usage 8 | 9 | ```javascript 10 | const injector = require('node-dll-injector'); 11 | 12 | const isNotepadRunning = injector.isProcessRunning('notepad.exe'); 13 | 14 | if (isNotePadRunning) { 15 | const success = injector.inject('notepad.exe', 'mydll.dll'); 16 | 17 | if (success) { 18 | console.log('Successfully injected!'); 19 | } else { 20 | console.log('Injection failed. :('); 21 | } 22 | } 23 | 24 | ``` 25 | 26 | There are alternative versions of each function `injectPID` and `isProcessRunningPID` that inject based on process id. -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "include_dirs": [ 5 | " 2 | 3 | declare module 'node-dll-injector' { 4 | 5 | export function inject(processName: string, dllFile: string): number; 6 | export function injectPID(pid: number, dllFile: string): number; 7 | export function isProcessRunning(processName: string): boolean; 8 | export function isProcessRunningPID(pid: number): boolean; 9 | 10 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Only works on Windows! 4 | if (process.platform !== 'win32') return; 5 | 6 | const injector = require('bindings')('injector'); 7 | 8 | const inject = (processName, dllFile) => { 9 | return injector.inject(processName, dllFile); 10 | }; 11 | 12 | const injectPID = (pid, dllFile) => { 13 | return injector.injectPID(pid, dllFile); 14 | }; 15 | 16 | const isProcessRunning = (processName) => { 17 | return injector.isProcessRunning(processName); 18 | }; 19 | 20 | const isProcessRunningPID = (pid) => { 21 | return injector.isProcessRunningPID(pid); 22 | }; 23 | 24 | module.exports = { 25 | inject, 26 | injectPID, 27 | isProcessRunning, 28 | isProcessRunningPID 29 | }; -------------------------------------------------------------------------------- /injector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #define WIN32_LEAN_AND_MEAN 3 | #include 4 | #define UNICODE // Using unicode - so windows functions take wchars 5 | #include 6 | #include 7 | 8 | 9 | #include 10 | 11 | using namespace v8; 12 | 13 | ///////////////// Prototypes 14 | 15 | static HANDLE getProcess(const char* processName); 16 | static HANDLE getProcessPID(DWORD pid); 17 | static int injectHandle(HANDLE process, const char* dllFile); 18 | 19 | int injectInternal(const char* processName, const char* dllFile); 20 | bool isProcessRunningInternal(const char* processName); 21 | 22 | int injectInternalPID(DWORD pid, const char* dllFile); 23 | bool isProcessRunningInternalPID(DWORD pid); 24 | 25 | ///////////////// Functions 26 | 27 | // Get a handle to a running process based on name 28 | static HANDLE getProcess(const char* processName) { 29 | wchar_t wProcessName[MAX_PATH]; 30 | mbstate_t mbstate; 31 | mbsrtowcs_s(NULL, wProcessName, &processName, MAX_PATH, &mbstate); 32 | 33 | // Take a snapshot of processes currently running 34 | HANDLE runningProcesses = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 35 | if (runningProcesses == INVALID_HANDLE_VALUE) { 36 | return NULL; 37 | } 38 | 39 | PROCESSENTRY32 pe; 40 | pe.dwSize = sizeof(PROCESSENTRY32); 41 | 42 | // Find the desired process 43 | BOOL res = Process32First(runningProcesses, &pe); 44 | while (res) { 45 | if (wcscmp(pe.szExeFile, wProcessName) == 0) { 46 | // Found the process 47 | CloseHandle(runningProcesses); 48 | HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID); 49 | if (process == NULL) { 50 | // Process failed to open 51 | return NULL; 52 | } 53 | // Return a handle to this process 54 | return process; 55 | } 56 | res = Process32Next(runningProcesses, &pe); 57 | } 58 | 59 | // Couldn't find the process 60 | CloseHandle(runningProcesses); 61 | return NULL; 62 | } 63 | 64 | static HANDLE getProcessPID(DWORD pid) { 65 | // Take a snapshot of processes currently running 66 | HANDLE runningProcesses = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 67 | if (runningProcesses == INVALID_HANDLE_VALUE) { 68 | return NULL; 69 | } 70 | 71 | PROCESSENTRY32 pe; 72 | pe.dwSize = sizeof(PROCESSENTRY32); 73 | 74 | // Find the desired process 75 | BOOL res = Process32First(runningProcesses, &pe); 76 | while (res) { 77 | if (pe.th32ProcessID == pid) { 78 | // Found the process 79 | CloseHandle(runningProcesses); 80 | HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID); 81 | if (process == NULL) { 82 | // Process failed to open 83 | return NULL; 84 | } 85 | // Return a handle to this process 86 | return process; 87 | } 88 | res = Process32Next(runningProcesses, &pe); 89 | } 90 | 91 | // Couldn't find the process 92 | CloseHandle(runningProcesses); 93 | return NULL; 94 | } 95 | 96 | // Inject a DLL file into the given process 97 | static int injectHandle(HANDLE process, const char* dllFile) { 98 | if (process == NULL) { 99 | // Process is not open 100 | return 1; 101 | } 102 | 103 | // Get full DLL path 104 | char dllPath[MAX_PATH]; 105 | DWORD r = GetFullPathNameA(dllFile, MAX_PATH, dllPath, NULL); 106 | if (r == 0) { 107 | // Getting path name failed 108 | CloseHandle(process); 109 | return 2; 110 | } else if (r > MAX_PATH) { 111 | // Buffer too small for path name 112 | CloseHandle(process); 113 | return 3; 114 | } 115 | 116 | // Get the LoadLibraryA method from the kernel32 dll 117 | LPVOID LoadLib = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); 118 | 119 | // Allocate memory in the processs for the DLL path, and then write it there 120 | LPVOID remotePathSpace = VirtualAllocEx(process, NULL, strlen(dllPath) + 1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 121 | if (!remotePathSpace) { 122 | CloseHandle(process); 123 | // Failed to allocate memory 124 | return 4; 125 | } 126 | 127 | if (!WriteProcessMemory(process, remotePathSpace, dllPath, strlen(dllPath) + 1, NULL)) { 128 | // Failed to write memory 129 | CloseHandle(process); 130 | return 5; 131 | } 132 | 133 | // Load the DLL with CreateRemoteThread + LoadLibraryA 134 | HANDLE remoteThread = CreateRemoteThread(process, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLib, (LPVOID)remotePathSpace, NULL, NULL); 135 | 136 | if (remoteThread == NULL) { 137 | // Failed to create remote thread to load the DLL 138 | CloseHandle(process); 139 | return 6; 140 | } 141 | 142 | // Close the handle to the process 143 | CloseHandle(process); 144 | return 0; 145 | } 146 | 147 | // Returns true iff a process with the given name is running 148 | bool isProcessRunningInternal(const char* processName) { 149 | HANDLE process = getProcess(processName); 150 | if (process == NULL) { 151 | return false; 152 | } 153 | CloseHandle(process); 154 | return true; 155 | } 156 | 157 | // Returns true iff a process with the given pid is running 158 | bool isProcessRunningInternalPID(DWORD pid) { 159 | HANDLE process = getProcessPID(pid); 160 | if (process == NULL) { 161 | return false; 162 | } 163 | CloseHandle(process); 164 | return true; 165 | } 166 | 167 | // Inject a DLL file into the process with the given name 168 | int injectInternal(const char* processName, const char* dllFile) { 169 | return injectHandle(getProcess(processName), dllFile); 170 | } 171 | 172 | // Inject a DLL file into the process with the given pid 173 | int injectInternalPID(DWORD pid, const char* dllFile) { 174 | return injectHandle(getProcessPID(pid), dllFile); 175 | } 176 | 177 | 178 | ///////////////// NAN methods 179 | 180 | NAN_METHOD(inject) { 181 | if (info.Length() != 2) { 182 | return; 183 | } 184 | if (!info[0]->IsString() || !info[1]->IsString()) { 185 | return; 186 | } 187 | 188 | String::Utf8Value arg0(info[0]->ToString()); 189 | String::Utf8Value arg1(info[1]->ToString()); 190 | 191 | if (!(*arg0) || !(*arg1)) { 192 | return; 193 | } 194 | 195 | const char* processName = *arg0; 196 | const char* dllName = *arg1; 197 | 198 | int val = injectInternal(processName, dllName); 199 | 200 | Local res = Nan::New(val); 201 | info.GetReturnValue().Set(res); 202 | } 203 | 204 | 205 | NAN_METHOD(injectPID) { 206 | if (info.Length() != 2) { 207 | Local res = Nan::New(8); 208 | info.GetReturnValue().Set(res); 209 | return; 210 | } 211 | if (!info[0]->IsNumber() || !info[1]->IsString()) { 212 | Local res = Nan::New(9); 213 | info.GetReturnValue().Set(res); 214 | return; 215 | } 216 | 217 | DWORD arg0(info[0]->Uint32Value()); 218 | String::Utf8Value arg1(info[1]->ToString()); 219 | 220 | if (!(*arg1)) { 221 | Local res = Nan::New(10); 222 | info.GetReturnValue().Set(res); 223 | return; 224 | } 225 | const char* dllName = *arg1; 226 | 227 | int val = injectInternalPID(arg0, dllName); 228 | 229 | Local res = Nan::New(val); 230 | info.GetReturnValue().Set(res); 231 | } 232 | 233 | NAN_METHOD(isProcessRunning) { 234 | if (info.Length() != 1) { 235 | return; 236 | } 237 | if (!info[0]->IsString()) { 238 | return; 239 | } 240 | 241 | String::Utf8Value arg(info[0]->ToString()); 242 | 243 | if (!(*arg)) { 244 | return; 245 | } 246 | 247 | const char* processName = *arg; 248 | 249 | bool val = isProcessRunningInternal(processName); 250 | 251 | Local res = Nan::New(val); 252 | info.GetReturnValue().Set(res); 253 | } 254 | 255 | NAN_METHOD(isProcessRunningPID) { 256 | if (info.Length() != 1) { 257 | return; 258 | } 259 | if (!info[0]->IsUint32()) { 260 | return; 261 | } 262 | 263 | DWORD arg(info[0]->Uint32Value()); 264 | 265 | bool val = isProcessRunningInternalPID(arg); 266 | 267 | Local res = Nan::New(val); 268 | info.GetReturnValue().Set(res); 269 | } 270 | 271 | ///////////////// NAN setup 272 | 273 | NAN_MODULE_INIT(InitModule) { 274 | NAN_EXPORT(target, inject); 275 | NAN_EXPORT(target, injectPID); 276 | NAN_EXPORT(target, isProcessRunning); 277 | NAN_EXPORT(target, isProcessRunningPID); 278 | } 279 | 280 | // Create the module called "addon" and initialize it with `Initialize` function (created with NAN_MODULE_INIT macro) 281 | NODE_MODULE(injector, InitModule); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-dll-injector", 3 | "version": "0.2.3", 4 | "description": "LoadLibrary-based DLL injection for Windows", 5 | "main": "index.js", 6 | "types": "./lib/index.d.ts", 7 | "repository": "https://github.com/mcoot/node-dll-injector.git", 8 | "author": "Joseph Spearritt", 9 | "license": "MIT", 10 | "gypfile": "true", 11 | "scripts": { 12 | "compile": "node-gyp rebuild", 13 | "start": "node index.js" 14 | }, 15 | "dependencies": { 16 | "bindings": "^1.3.0", 17 | "nan": "^2.10.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /testdll.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcoot/node-dll-injector/d09eeeb180be254eb158c34654c1e5a85ea79252/testdll.dll -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | bindings@^1.3.0: 6 | version "1.3.0" 7 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" 8 | 9 | nan@^2.10.0: 10 | version "2.10.0" 11 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" 12 | --------------------------------------------------------------------------------