├── assets └── logo.png ├── .gitignore ├── .npmignore ├── .eslintrc.js ├── lib ├── memoryjs.h ├── module.h ├── memory.cc ├── process.h ├── functions.cc ├── pattern.h ├── debugger.h ├── process.cc ├── dll.h ├── memory.h ├── module.cc ├── pattern.cc ├── debugger.cc ├── functions.h └── memoryjs.cc ├── test ├── allocationTest.js ├── src │ ├── functionTest.cpp │ ├── MemoryTest.cpp │ └── protectionTest.cpp ├── protectionTest.js ├── functionTest.js ├── queryTest.js ├── debuggerTest.js ├── memoryTest.js ├── project.sln └── vcxproj │ ├── FunctionTest.vcxproj │ ├── MemoryTest.vcxproj │ └── ProtectionTest.vcxproj ├── binding.gyp ├── LICENSE.md ├── package.json ├── scripts ├── debug.js └── install.js ├── examples ├── vectors.js ├── buffers.js ├── debugging.js └── general.js ├── src ├── consts.js ├── utils.js └── debugger.js ├── index.js └── README.md /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rob--/memoryjs/HEAD/assets/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | test.js 3 | npm-debug.log 4 | .vscode 5 | .vs 6 | node_modules 7 | test/vcxproj/Debug 8 | test/vcxproj/Release 9 | test/*.exe 10 | test/.vs -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build 2 | test.js 3 | npm-debug.log 4 | .vscode 5 | .vs 6 | node_modules 7 | test/vcxproj/Debug 8 | test/vcxproj/Release 9 | test/*.exe 10 | test/.vs -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | sourceType: 'module' 5 | }, 6 | extends: 'airbnb-base', 7 | rules: { 8 | 'linebreak-style': 0, 9 | 'import/no-unresolved': 0, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /lib/memoryjs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef MEMORYJS_H 3 | #define MEMORYJS_H 4 | #define WIN32_LEAN_AND_MEAN 5 | 6 | #include 7 | 8 | class memoryjs { 9 | 10 | public: 11 | memoryjs(); 12 | ~memoryjs(); 13 | }; 14 | #endif 15 | #pragma once 16 | -------------------------------------------------------------------------------- /test/allocationTest.js: -------------------------------------------------------------------------------- 1 | const memoryjs = require('../index'); 2 | const processName = 'notepad.exe'; 3 | 4 | const processObject = memoryjs.openProcess(processName); 5 | 6 | const address = memoryjs.virtualAllocEx( 7 | processObject.handle, 8 | null, 9 | 0x60, 10 | memoryjs.MEM_RESERVE | memoryjs.MEM_COMMIT, 11 | memoryjs.PAGE_EXECUTE_READWRITE, 12 | ); 13 | 14 | console.log(`Allocated address: 0x${address.toString(16).toUpperCase()}`); 15 | 16 | memoryjs.closeProcess(processObject.handle); 17 | -------------------------------------------------------------------------------- /test/src/functionTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | float testAdd(float a) { 6 | std::cout << a << std::endl; 7 | return a; 8 | } 9 | 10 | int main() { 11 | DWORD offset = (DWORD)testAdd - (DWORD)GetModuleHandle(NULL); 12 | std::cout << "Function offset from base: 0x" << std::hex << offset << std::dec << std::endl; 13 | std::cout << "Absolute: 0x" << std::hex << (DWORD)testAdd << std::dec << std::endl; 14 | 15 | getchar(); 16 | return 0; 17 | } -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "memoryjs", 5 | "include_dirs" : [ 6 | " 7 | #include 8 | #include 9 | 10 | namespace module { 11 | DWORD64 getBaseAddress(const char* processName, DWORD processId); 12 | MODULEENTRY32 findModule(const char* moduleName, DWORD processId, char** errorMessage); 13 | std::vector getModules(DWORD processId, char** errorMessage); 14 | std::vector getThreads(DWORD processId, char** errorMessage); 15 | 16 | }; 17 | #endif 18 | #pragma once 19 | -------------------------------------------------------------------------------- /lib/memory.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "memory.h" 6 | 7 | memory::memory() {} 8 | memory::~memory() {} 9 | 10 | std::vector memory::getRegions(HANDLE hProcess) { 11 | std::vector regions; 12 | 13 | MEMORY_BASIC_INFORMATION region; 14 | DWORD64 address; 15 | 16 | for (address = 0; VirtualQueryEx(hProcess, (LPVOID)address, ®ion, sizeof(region)) == sizeof(region); address += region.RegionSize) { 17 | regions.push_back(region); 18 | } 19 | 20 | return regions; 21 | } -------------------------------------------------------------------------------- /lib/process.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef PROCESS_H 3 | #define PROCESS_H 4 | #define WIN32_LEAN_AND_MEAN 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class process { 11 | public: 12 | struct Pair { 13 | HANDLE handle; 14 | PROCESSENTRY32 process; 15 | }; 16 | 17 | process(); 18 | ~process(); 19 | 20 | Pair openProcess(const char* processName, char** errorMessage); 21 | Pair openProcess(DWORD processId, char** errorMessage); 22 | void closeProcess(HANDLE hProcess); 23 | std::vector getProcesses(char** errorMessage); 24 | }; 25 | 26 | #endif 27 | #pragma once 28 | -------------------------------------------------------------------------------- /lib/functions.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "functions.h" 6 | 7 | char functions::readChar(HANDLE hProcess, DWORD64 address) { 8 | char value; 9 | ReadProcessMemory(hProcess, (LPVOID)address, &value, sizeof(char), NULL); 10 | return value; 11 | } 12 | 13 | LPVOID functions::reserveString(HANDLE hProcess, const char* value, SIZE_T size) { 14 | LPVOID memoryAddress = VirtualAllocEx(hProcess, NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 15 | WriteProcessMemory(hProcess, memoryAddress, value, size, NULL); 16 | return memoryAddress; 17 | } 18 | -------------------------------------------------------------------------------- /test/functionTest.js: -------------------------------------------------------------------------------- 1 | const memoryjs = require('../index'); 2 | const processName = 'FunctionTest.exe'; 3 | 4 | // TODO: Start the target process and obtain the absolute address of 5 | // the function that you want to call and update the variable below. 6 | 7 | const processObject = memoryjs.openProcess(processName); 8 | 9 | const args = [{ type: memoryjs.T_FLOAT, value: 12.34 }]; 10 | const returnType = memoryjs.T_FLOAT; 11 | 12 | const { 13 | returnValue, 14 | exitCode, 15 | } = memoryjs.callFunction(processObject.handle, args, returnType, address); 16 | 17 | console.log(`Return value: ${returnValue}`); 18 | console.log(`Exit code: ${exitCode}`); 19 | 20 | memoryjs.closeProcess(processObject.handle); 21 | -------------------------------------------------------------------------------- /lib/pattern.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef PATTERN_H 3 | #define PATTERN_H 4 | #define WIN32_LEAN_AND_MEAN 5 | 6 | #include 7 | #include 8 | 9 | class pattern { 10 | public: 11 | pattern(); 12 | ~pattern(); 13 | 14 | // Signature/pattern types 15 | enum { 16 | // normal: normal 17 | // read: read memory at pattern 18 | // subtract: subtract module base 19 | ST_NORMAL = 0x0, 20 | ST_READ = 0x1, 21 | ST_SUBTRACT = 0x2 22 | }; 23 | 24 | bool search(HANDLE handle, std::vector regions, DWORD64 searchAddress, const char* pattern, short flags, uint32_t patternOffset, uintptr_t* pAddress); 25 | bool search(HANDLE handle, std::vector modules, DWORD64 searchAddress, const char* pattern, short flags, uint32_t patternOffset, uintptr_t* pAddress); 26 | bool findPattern(HANDLE handle, uintptr_t memoryBase, unsigned char* module, DWORD memorySize, const char* pattern, short flags, uint32_t patternOffset, uintptr_t* pAddress); 27 | bool compareBytes(const unsigned char* bytes, const char* pattern); 28 | }; 29 | 30 | #endif 31 | #pragma once 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Robert Valentyne 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 | 23 | -------------------------------------------------------------------------------- /test/src/MemoryTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | int main() 8 | { 9 | cout << "type\taddress\t\tvalue" << endl; 10 | 11 | int _int = -2147483647; 12 | cout << "int\t0x" << hex << &_int << dec << "\t" << _int << endl; 13 | 14 | DWORD _dword = 2147483647; 15 | cout << "dword\t0x" << hex << &_dword << dec << "\t" << _dword << endl; 16 | 17 | short _short = -32768; 18 | cout << "short\t0x" << hex << &_short << dec << "\t" << _short << endl; 19 | 20 | long _long = -2147483647; 21 | cout << "long\t0x" << hex << &_long << dec << "\t" << _long << endl; 22 | 23 | float _float = 3.402823466e+38F / 2; 24 | cout << "float\t0x" << hex << &_float << dec << "\t" << _float << endl; 25 | 26 | double _double = 2.2250738585072014e-308; 27 | cout << "double\t0x" << hex << &_double << dec << "\t" << _double << endl; 28 | 29 | intptr_t _intptr_t = 2147483647; 30 | cout << "pointer\t0x" << hex << &_intptr_t << dec << "\t" << _intptr_t << endl; 31 | 32 | bool _bool = true; 33 | cout << "bool\t0x" << hex << &_bool << dec << "\t" << _bool << endl; 34 | 35 | string _string = "robert"; 36 | cout << "string\t0x" << hex << (DWORD64)_string.c_str() << dec << "\t" << _string << endl; 37 | 38 | getchar(); 39 | 40 | return 0; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memoryjs", 3 | "version": "3.5.1", 4 | "description": "Node add-on for memory reading and writing!", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "install": "npm run build", 9 | "build": "node ./scripts/install.js", 10 | "build32": "node-gyp clean configure build --arch=ia32", 11 | "build64": "node-gyp clean configure build --arch=x64", 12 | "buildtest": "cd test && MSBuild.exe project.sln //p:Configuration=Release", 13 | "debug": "node ./scripts/debug.js", 14 | "debug32": "node-gyp configure rebuild --debug --arch=xia32", 15 | "debug64": "node-gyp configure rebuild --debug --arch=x64" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/Rob--/memoryjs.git" 20 | }, 21 | "keywords": [ 22 | "memory", 23 | "reading", 24 | "writing", 25 | "management", 26 | "addon" 27 | ], 28 | "author": "Rob--", 29 | "license": "MIT", 30 | "gypfile": true, 31 | "bugs": { 32 | "url": "https://github.com/Rob--/memoryjs/issues" 33 | }, 34 | "homepage": "https://github.com/Rob--/memoryjs#readme", 35 | "dependencies": { 36 | "eslint": "^8.5.0", 37 | "eslint-config-airbnb-base": "^12.1.0", 38 | "node-addon-api": "^3.2.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scripts/debug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [npm debug script] 3 | * Purpose is to automatically detect Node process architecture and run the 4 | * corresponding script to build the library for debugging. 5 | * Defaults to `node-gyp rebuild` if unable to detect the architecture. 6 | */ 7 | 8 | /* eslint-disable no-console */ 9 | const { spawn } = require('child_process'); 10 | 11 | function run(script) { 12 | console.log(`Node architecture is ${process.arch}: running "${script}"`); 13 | 14 | const program = script.split(' ')[0]; 15 | const args = script.split(' ').slice(1); 16 | 17 | // inherit stdio to print colour (helpful for warnings/errors readability) 18 | const child = spawn(program, args, { stdio: 'inherit' }); 19 | 20 | child.on('close', code => console.log(`Script "${script}" exited with ${code}`)); 21 | } 22 | 23 | const buildScripts = { 24 | x64: 'run debug64', 25 | ia32: 'run debug32', 26 | }; 27 | 28 | if (Object.prototype.hasOwnProperty.call(buildScripts, process.arch)) { 29 | // on Windows, npm is actually `npm.cmd` 30 | const npm = /^win/.test(process.platform) ? 'npm.cmd' : 'npm'; 31 | run(`${npm} ${buildScripts[process.arch]}`); 32 | } else { 33 | console.log('Unfamiliar architecture detected, this library is probably not compatible with your OS.'); 34 | run('node-gyp --debug configure rebuild'); 35 | } 36 | -------------------------------------------------------------------------------- /scripts/install.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [npm install script] 3 | * Purpose is to automatically detect Node process architecture and run the 4 | * corresponding script to build the library to target the appropriate architecture. 5 | * Defaults to `node-gyp rebuild` if unable to detect the architecture. 6 | */ 7 | 8 | /* eslint-disable no-console */ 9 | const { spawn } = require('child_process'); 10 | 11 | function run(script) { 12 | console.log(`Node architecture is ${process.arch}: running "${script}"`); 13 | 14 | const program = script.split(' ')[0]; 15 | const args = script.split(' ').slice(1); 16 | 17 | // inherit stdio to print colour (helpful for warnings/errors readability) 18 | const child = spawn(program, args, { stdio: 'inherit' }); 19 | 20 | child.on('close', code => console.log(`Script "${script}" exited with ${code}`)); 21 | } 22 | 23 | const buildScripts = { 24 | x64: 'run build64', 25 | ia32: 'run build32', 26 | }; 27 | 28 | if (Object.prototype.hasOwnProperty.call(buildScripts, process.arch)) { 29 | // on Windows, npm is actually `npm.cmd` 30 | const npm = /^win/.test(process.platform) ? 'npm.cmd' : 'npm'; 31 | run(`${npm} ${buildScripts[process.arch]}`); 32 | } else { 33 | console.log('Unfamiliar architecture detected, this library is probably not compatible with your OS.'); 34 | run('node-gyp rebuild'); 35 | } 36 | -------------------------------------------------------------------------------- /examples/vectors.js: -------------------------------------------------------------------------------- 1 | const memoryjs = require('./index'); 2 | const processName = 'Test Project.exe'; 3 | 4 | const processObject = memoryjs.openProcess(processName); 5 | console.log(JSON.stringify(processObject, null, 3)); 6 | 7 | const modules = memoryjs.getModules(processObject.th32ProcessID); 8 | modules.forEach(module => console.log(module)); 9 | 10 | /* How to read a "string" 11 | * C++ code: 12 | * ``` 13 | * std::string str = "this is a sample string"; 14 | * std::cout << "Address: " << (DWORD) str.c_str() << ", value: " << str.c_str() << std::endl; 15 | * ``` 16 | * This will create a string and the address of the string and the value of the string. 17 | */ 18 | 19 | const str = memoryjs.readMemory(0x69085bc0, memoryjs.STRING); 20 | console.log(str); // "this is a sample string"; 21 | 22 | /* How to read and write vectors 23 | * C++ code: 24 | * ``` 25 | * struct Vector3 { float x, y, z; }; 26 | * struct Vector4 { float w, x, y, z; }; 27 | * Vector3 vec3 = { 1.0f, 2.0f, 3.0f }; 28 | * Vector4 vec4 = { 1.0f, 2.0f, 3.0f, 4.0f }; 29 | * std::cout << "[Vec3] Address: " << &vec3 << std::endl; 30 | * std::cout << "[Vec4] Address: " << &vec4 << std::endl; 31 | */ 32 | 33 | let vec3 = { 34 | x: 0, y: 0, z: 0, 35 | }; 36 | memoryjs.writeMemory(0x000001, vec3, memoryjs.VEC3); 37 | vec3 = memoryjs.readMemory(0x000001, memoryjs.VEC3); // { x, y, z } 38 | console.log(vec3); 39 | 40 | let vec4 = { 41 | w: 0, x: 0, y: 0, z: 0, 42 | }; 43 | memoryjs.writeMemory(0x000002, vec4, memoryjs.VEC4); 44 | vec4 = memoryjs.readMemory(0x000002, memoryjs.VEC4); // { w, x, y, z } 45 | console.log(vec4); 46 | 47 | memoryjs.closeProcess(processObject.handle); 48 | -------------------------------------------------------------------------------- /test/queryTest.js: -------------------------------------------------------------------------------- 1 | const memoryjs = require('../index'); 2 | const processName = 'chrome.exe'; 3 | 4 | const processObject = memoryjs.openProcess(processName); 5 | 6 | const regions = memoryjs.getRegions(processObject.handle).reverse().slice(0, 40); 7 | 8 | // Minimum lengths for each column 9 | const lengths = { 10 | BaseAddress: 'BaseAddress'.length, 11 | AllocationBase: 'AllocationBase'.length, 12 | AllocationProtect: 'AllocationProtect'.length, 13 | RegionSize: 'RegionSize'.length, 14 | State: 'State'.length, 15 | Protect: 'Protect'.length, 16 | Type: 'Type'.length, 17 | szExeFile: 'szExeFile'.length, 18 | }; 19 | 20 | // Calculate maximum lengths 21 | regions.forEach((region) => { 22 | Object.entries(region).forEach(([key, value]) => { 23 | const formatted = `0x${value.toString(16)}`; 24 | if (formatted.length > lengths[key]) { 25 | lengths[key] = formatted.length; 26 | } 27 | }); 28 | }); 29 | 30 | let text = ''; 31 | Object.entries(lengths).forEach(([key, value]) => { 32 | if (key === 'szExeFile') { 33 | text += ` ${key}`.padEnd(value + 2, ' '); 34 | } else { 35 | text += key.padStart(value + 2, ' '); 36 | text += ' |'; 37 | } 38 | }); 39 | console.log(text); 40 | 41 | regions.forEach((region) => { 42 | let text = ''; 43 | Object.entries(region).forEach(([key, value]) => { 44 | if (key === 'szExeFile') { 45 | text += ` ${value}`.padEnd(lengths[key] + 2, ' '); 46 | } else { 47 | text += `0x${value.toString(16)}`.padStart(lengths[key] + 2, ' '); 48 | text += ' |'; 49 | } 50 | }); 51 | 52 | console.log(text); 53 | }); 54 | 55 | memoryjs.closeProcess(processObject.handle); 56 | -------------------------------------------------------------------------------- /test/debuggerTest.js: -------------------------------------------------------------------------------- 1 | const memoryjs = require('../index'); 2 | const processName = 'Testing Things.exe'; 3 | 4 | const processObject = memoryjs.openProcess(processName); 5 | const processId = processObject.th32ProcessID; 6 | 7 | // Address of variable 8 | const address = 0xEFFBF0; 9 | 10 | // When should we breakpoint? On read, write or execute 11 | const trigger = memoryjs.TRIGGER_ACCESS; 12 | 13 | memoryjs.attachDebugger(processId); 14 | 15 | // There are 4 hardware registers: 16 | // `memoryjs.DR0` through `memoryjs.DR3` 17 | const registerToUse = memoryjs.DR0; 18 | 19 | // Our `address` references an integer variable. An integer 20 | // is 4 bytes therefore we pass `4` to the `size` parameter. 21 | const size = 4; 22 | memoryjs.setHardwareBreakpoint(processId, address, registerToUse, trigger, size); 23 | 24 | // How long to wait for the debug event before timing out 25 | const timeout = 100; 26 | 27 | // The interval duration must be the same or larger than the `timeout` value. 28 | // `awaitDebugEvent` works by waiting a certain amount of time before timing out, 29 | // therefore we only want to call the method again when we're sure the previous 30 | // call has already timed out. 31 | setInterval(() => { 32 | // `debugEvent` can be null if no event occurred 33 | const debugEvent = memoryjs.awaitDebugEvent(registerToUse, timeout); 34 | 35 | // If a breakpoint occurred, handle it 36 | if (debugEvent) { 37 | memoryjs.handleDebugEvent(debugEvent.processId, debugEvent.threadId); 38 | } 39 | }, timeout); 40 | 41 | // Don't forget to detatch the debugger! 42 | // memoryjs.detatchDebugger(processId); 43 | 44 | memoryjs.closeProcess(processObject.handle); 45 | -------------------------------------------------------------------------------- /test/memoryTest.js: -------------------------------------------------------------------------------- 1 | const memoryjs = require('../index'); 2 | const processName = 'MemoryTest.exe'; 3 | 4 | // TODO: Start the MemoryTest process, and check it's output against the outputs of this 5 | 6 | /* Example Output: 7 | 8 | $ node test/memoryTest 9 | type address value 10 | int 0x3AFCB4 2003818640 11 | dword 0x3AFCA8 2648673792 12 | short 0x3AFC9C 0 13 | long 0x3AFC90 0 14 | float 0x3AFC84 0 15 | double 0x3AFC74 4.031792002834e-312 16 | pointer 0x3AFC68 816043786240 17 | bool 0x3AFC5F false 18 | string 0xB1FAA4 robert 19 | */ 20 | 21 | const processObject = memoryjs.openProcess(processName); 22 | 23 | const data = [{ 24 | type: memoryjs.INT, 25 | name: 'int', 26 | address: 0x003AFCB4, 27 | }, { 28 | type: memoryjs.DWORD, 29 | name: 'dword', 30 | address: 0x003AFCA8, 31 | }, { 32 | type: memoryjs.SHORT, 33 | name: 'short', 34 | address: 0x003AFC9C, 35 | }, { 36 | type: memoryjs.LONG, 37 | name: 'long', 38 | address: 0x003AFC90, 39 | }, { 40 | type: memoryjs.FLOAT, 41 | name: 'float', 42 | address: 0x003AFC84, 43 | }, { 44 | type: memoryjs.DOUBLE, 45 | name: 'double', 46 | address: 0x003AFC74, 47 | }, { 48 | type: memoryjs.POINTER, 49 | name: 'pointer', 50 | address: 0x003AFC68, 51 | }, { 52 | type: memoryjs.BOOL, 53 | name: 'bool', 54 | address: 0x003AFC5F, 55 | }, { 56 | type: memoryjs.STRING, 57 | name: 'string', 58 | address: 0xb1faa4, 59 | }]; 60 | 61 | console.log('type\taddress\t\tvalue'); 62 | 63 | data.forEach(({ type, name, address }) => { 64 | const result = memoryjs.readMemory(processObject.handle, address, type); 65 | console.log(`${name}\t0x${address.toString(16).toUpperCase()}\t${result}`); 66 | }); 67 | 68 | memoryjs.closeProcess(processObject.handle); 69 | -------------------------------------------------------------------------------- /lib/debugger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef debugger_H 3 | #define debugger_H 4 | #define WIN32_LEAN_AND_MEAN 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | enum class Register { 11 | Invalid = -0x1, 12 | DR0 = 0x0, 13 | DR1 = 0x1, 14 | DR2 = 0x2, 15 | DR3 = 0x3 16 | }; 17 | 18 | struct DebugRegister6 { 19 | union { 20 | uintptr_t Value; 21 | struct { 22 | unsigned DR0 : 1; 23 | unsigned DR1 : 1; 24 | unsigned DR2 : 1; 25 | unsigned DR3 : 1; 26 | unsigned Reserved : 9; 27 | unsigned BD : 1; 28 | unsigned BS : 1; 29 | unsigned BT : 1; 30 | }; 31 | }; 32 | }; 33 | 34 | struct DebugRegister7 { 35 | union { 36 | uintptr_t Value; 37 | struct { 38 | unsigned G0 : 1; 39 | unsigned L0 : 1; 40 | unsigned G1 : 1; 41 | unsigned L1 : 1; 42 | unsigned G2 : 1; 43 | unsigned L2 : 1; 44 | unsigned G3 : 1; 45 | unsigned L3 : 1; 46 | unsigned GE : 1; 47 | unsigned LE : 1; 48 | unsigned Reserved : 6; 49 | unsigned RW0 : 2; 50 | unsigned Len0 : 2; 51 | unsigned RW1 : 2; 52 | unsigned Len1 : 2; 53 | unsigned RW2 : 2; 54 | unsigned Len2 : 2; 55 | unsigned RW3 : 2; 56 | unsigned Len3 : 2; 57 | }; 58 | }; 59 | }; 60 | 61 | struct DebugEvent { 62 | DWORD processId; 63 | DWORD threadId; 64 | DWORD exceptionCode; 65 | DWORD exceptionFlags; 66 | void* exceptionAddress; 67 | Register hardwareRegister; 68 | }; 69 | 70 | namespace debugger { 71 | bool attach(DWORD processId, bool killOnDetatch); 72 | bool detatch(DWORD processId); 73 | bool setHardwareBreakpoint(DWORD processId, DWORD64 address, Register reg, int trigger, int size); 74 | bool awaitDebugEvent(DWORD millisTimeout, DebugEvent *info); 75 | bool handleDebugEvent(DWORD processId, DWORD threadId); 76 | } 77 | 78 | #endif 79 | #pragma once 80 | -------------------------------------------------------------------------------- /test/src/protectionTest.cpp: -------------------------------------------------------------------------------- 1 | // TestTarget.cpp : Defines the entry point for the console application. 2 | // 3 | #include 4 | #include "stdio.h" 5 | #include 6 | 7 | int value = 0; 8 | 9 | __declspec(noinline) void routine() { 10 | value = 100; 11 | } 12 | 13 | 14 | int main() 15 | { 16 | printf("This program will return an exit code of 0 if you successfuly modify the memory in the given time frame.\n\ 17 | \nWe want to modify the .code section of this exe.\n\ 18 | Address of operation is likely around: 0x%08X.\n\ 19 | Look for the MOV opcode + from its address to get the value address and modify it's memory you will need to change protection on the section of memory.\n", &routine); 20 | 21 | 22 | // Assert that the program is compiled and running with PAGE_EXECUTE_READ on the routine method. 23 | MEMORY_BASIC_INFORMATION mbi; 24 | if (VirtualQuery(&routine, &mbi, sizeof(mbi)) == 0) { 25 | printf("Unable to query memory protection for some reason.\n"); 26 | return 1; 27 | } 28 | if (! (mbi.Protect & PAGE_EXECUTE_READ) ) { 29 | printf("Warning: Expecting memory to be EXECUTE_READ\n"); 30 | return 1; 31 | } 32 | 33 | printf("The address of the value is at 0x%08X but please modify the code in the routine that is setting it to 100 to set it to something else.\n", &value); 34 | printf("On MSVC 2017 Release mode x86 I'm getting this address 0x%08X.\n", ((char*)&routine) + 6); 35 | 36 | 37 | int counter = 0; 38 | while (1) { 39 | // Intentionally set the value to this before and after the call. 40 | value = 0xDEADBEEF; 41 | 42 | routine(); 43 | 44 | if (value != 100) { 45 | routine(); 46 | if (value != 100) { 47 | return 0; 48 | } 49 | else { 50 | printf("Please modify the code not the value in memory.\n"); 51 | } 52 | } 53 | 54 | value = 0xDEADBEEF; 55 | 56 | Sleep(1000); 57 | counter++; 58 | 59 | if (value != 0xDEADBEEF) { 60 | printf("You must modify the code not the value.\n"); 61 | } 62 | 63 | if (counter > 60) { 64 | break; 65 | } 66 | } 67 | 68 | printf("Attempt time expired closing application as failed.\n"); 69 | return 1; 70 | } 71 | 72 | -------------------------------------------------------------------------------- /test/project.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30503.244 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MemoryTest", "vcxproj\MemoryTest.vcxproj", "{67039DFF-7CB0-4034-8A19-470C8F8CADE9}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FunctionTest", "vcxproj\FunctionTest.vcxproj", "{D2E35EBA-E7AE-424D-B022-52440B65B235}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProtectionTest", "vcxproj\ProtectionTest.vcxproj", "{8E68E3F6-3D5C-4D4B-AFCC-F13A9DA4D539}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|x86 = Debug|x86 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {67039DFF-7CB0-4034-8A19-470C8F8CADE9}.Debug|x86.ActiveCfg = Debug|Win32 19 | {67039DFF-7CB0-4034-8A19-470C8F8CADE9}.Debug|x86.Build.0 = Debug|Win32 20 | {67039DFF-7CB0-4034-8A19-470C8F8CADE9}.Release|x86.ActiveCfg = Release|Win32 21 | {67039DFF-7CB0-4034-8A19-470C8F8CADE9}.Release|x86.Build.0 = Release|Win32 22 | {D2E35EBA-E7AE-424D-B022-52440B65B235}.Debug|x86.ActiveCfg = Debug|Win32 23 | {D2E35EBA-E7AE-424D-B022-52440B65B235}.Debug|x86.Build.0 = Debug|Win32 24 | {D2E35EBA-E7AE-424D-B022-52440B65B235}.Release|x86.ActiveCfg = Release|Win32 25 | {D2E35EBA-E7AE-424D-B022-52440B65B235}.Release|x86.Build.0 = Release|Win32 26 | {8E68E3F6-3D5C-4D4B-AFCC-F13A9DA4D539}.Debug|x86.ActiveCfg = Debug|Win32 27 | {8E68E3F6-3D5C-4D4B-AFCC-F13A9DA4D539}.Debug|x86.Build.0 = Debug|Win32 28 | {8E68E3F6-3D5C-4D4B-AFCC-F13A9DA4D539}.Release|x86.ActiveCfg = Release|Win32 29 | {8E68E3F6-3D5C-4D4B-AFCC-F13A9DA4D539}.Release|x86.Build.0 = Release|Win32 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {403312F9-8A55-4219-A498-9B79B46EC898} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /test/vcxproj/FunctionTest.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | Win32 6 | 7 | 8 | Release 9 | Win32 10 | 11 | 12 | 13 | {D2E35EBA-E7AE-424D-B022-52440B65B235} 14 | 15 | 16 | 17 | Application 18 | v142 19 | v141 20 | 21 | 22 | false 23 | $(SolutionDir) 24 | $(Configuration)\$(ProjectName)\ 25 | $(ProjectName) 26 | 27 | 28 | false 29 | $(SolutionDir) 30 | $(Configuration)\$(ProjectName)\ 31 | $(ProjectName) 32 | 33 | 34 | 35 | false 36 | 37 | 38 | 39 | 40 | false 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/vcxproj/MemoryTest.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | Win32 6 | 7 | 8 | Release 9 | Win32 10 | 11 | 12 | 13 | {67039DFF-7CB0-4034-8A19-470C8F8CADE9} 14 | 15 | 16 | 17 | Application 18 | v142 19 | v141 20 | 21 | 22 | false 23 | $(SolutionDir) 24 | $(Configuration)\$(ProjectName)\ 25 | $(ProjectName) 26 | 27 | 28 | false 29 | $(SolutionDir) 30 | $(Configuration)\$(ProjectName)\ 31 | $(ProjectName) 32 | 33 | 34 | 35 | false 36 | 37 | 38 | 39 | 40 | false 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/vcxproj/ProtectionTest.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | Win32 6 | 7 | 8 | Release 9 | Win32 10 | 11 | 12 | 13 | {8E68E3F6-3D5C-4D4B-AFCC-F13A9DA4D539} 14 | 15 | 16 | 17 | Application 18 | v142 19 | v141 20 | 21 | 22 | false 23 | $(SolutionDir) 24 | $(Configuration)\$(ProjectName)\ 25 | $(ProjectName) 26 | 27 | 28 | false 29 | $(SolutionDir) 30 | $(Configuration)\$(ProjectName)\ 31 | $(ProjectName) 32 | 33 | 34 | 35 | false 36 | 37 | 38 | 39 | 40 | false 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/buffers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This example uses the following structs defined in C++: 3 | struct vector { 4 | float x, y, z; 5 | }; 6 | 7 | struct player { 8 | vector position; 9 | double health; 10 | std::string name; 11 | float distance; 12 | bool alive; 13 | }; 14 | */ 15 | 16 | // https://github.com/LordVonAdel/structron 17 | const Struct = require('structron'); 18 | const memoryjs = require('../index'); 19 | 20 | const processObject = memoryjs.openProcess('Testing.exe'); 21 | const structAddress = 0x000000DEADBEEF; 22 | 23 | // -- Step 1: define the structures 24 | 25 | // Custom double consumer/producer (since it's not yet implemented in `structron`) 26 | const double = { 27 | read(buffer, offset) { 28 | return buffer.readDoubleLE(offset); 29 | }, 30 | write(value, context, offset) { 31 | context.buffer.writeDoubleLE(value, offset); 32 | }, 33 | SIZE: 8, 34 | }; 35 | 36 | // Use string consumer/producer provided by the library (custom implementation for `std::string`), 37 | // pass process handle and base address of structure so the library can read/write the string, 38 | // also requires passing the platform architecture to determine the structure of `std::string` 39 | const string = memoryjs.STRUCTRON_TYPE_STRING(processObject.handle, structAddress, '64'); 40 | 41 | // Define vector structure 42 | const Vector = new Struct() 43 | .addMember(Struct.TYPES.FLOAT, 'x') // 4 bytes 44 | .addMember(Struct.TYPES.FLOAT, 'y') // 4 bytes 45 | .addMember(Struct.TYPES.FLOAT, 'z'); // 4 bytes 46 | 47 | // Define player structure 48 | const Player = new Struct() 49 | .addMember(Vector, 'position') // 12 bytes 50 | .addMember(Struct.TYPES.SKIP(4), 'unused') // compiler padding to put member on 8 byte boundary 51 | .addMember(double, 'health') // 8 bytes 52 | .addMember(string, 'name') // 32 bytes (in 64bit process, 24 bytes in 32bit process) 53 | .addMember(Struct.TYPES.FLOAT, 'distance') // 4 bytes 54 | .addMember(Struct.TYPES.BYTE, 'alive'); // 1 byte 55 | 56 | // -- Step 2: create object to write to memory 57 | const object = { 58 | position: { 59 | x: 1.23, y: 4.56, z: 7.89, 60 | }, 61 | health: 80.12, 62 | name: 'Example Name 1234567890', 63 | distance: 4.20, 64 | alive: false, 65 | }; 66 | 67 | // -- Step 3: create buffer from object and write to memory 68 | let context = Player.write(object); 69 | memoryjs.writeBuffer(processObject.handle, structAddress, context.buffer); 70 | 71 | // -- Step 4: read buffer from memory and parse 72 | const buffer = memoryjs.readBuffer(processObject.handle, structAddress, context.buffer.length); 73 | 74 | context = Player.readContext(buffer); 75 | 76 | if (!context.hasErrors()) { 77 | console.log(context.data); 78 | } 79 | -------------------------------------------------------------------------------- /lib/process.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "process.h" 6 | #include "memoryjs.h" 7 | 8 | process::process() {} 9 | process::~process() {} 10 | 11 | using v8::Exception; 12 | using v8::Isolate; 13 | using v8::String; 14 | 15 | process::Pair process::openProcess(const char* processName, char** errorMessage){ 16 | PROCESSENTRY32 process; 17 | HANDLE handle = NULL; 18 | 19 | // A list of processes (PROCESSENTRY32) 20 | std::vector processes = getProcesses(errorMessage); 21 | 22 | for (std::vector::size_type i = 0; i != processes.size(); i++) { 23 | // Check to see if this is the process we want. 24 | if (!strcmp(processes[i].szExeFile, processName)) { 25 | handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processes[i].th32ProcessID); 26 | process = processes[i]; 27 | break; 28 | } 29 | } 30 | 31 | if (handle == NULL) { 32 | *errorMessage = "unable to find process"; 33 | } 34 | 35 | return { 36 | handle, 37 | process, 38 | }; 39 | } 40 | 41 | process::Pair process::openProcess(DWORD processId, char** errorMessage) { 42 | PROCESSENTRY32 process; 43 | HANDLE handle = NULL; 44 | 45 | // A list of processes (PROCESSENTRY32) 46 | std::vector processes = getProcesses(errorMessage); 47 | 48 | for (std::vector::size_type i = 0; i != processes.size(); i++) { 49 | // Check to see if this is the process we want. 50 | if (processId == processes[i].th32ProcessID) { 51 | handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processes[i].th32ProcessID); 52 | process = processes[i]; 53 | break; 54 | } 55 | } 56 | 57 | if (handle == NULL) { 58 | *errorMessage = "unable to find process"; 59 | } 60 | 61 | return { 62 | handle, 63 | process, 64 | }; 65 | } 66 | 67 | std::vector process::getProcesses(char** errorMessage) { 68 | // Take a snapshot of all processes. 69 | HANDLE hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); 70 | PROCESSENTRY32 pEntry; 71 | 72 | if (hProcessSnapshot == INVALID_HANDLE_VALUE) { 73 | *errorMessage = "method failed to take snapshot of the process"; 74 | } 75 | 76 | // Before use, set the structure size. 77 | pEntry.dwSize = sizeof(pEntry); 78 | 79 | // Exit if unable to find the first process. 80 | if (!Process32First(hProcessSnapshot, &pEntry)) { 81 | CloseHandle(hProcessSnapshot); 82 | *errorMessage = "method failed to retrieve the first process"; 83 | } 84 | 85 | std::vector processes; 86 | 87 | // Loop through processes. 88 | do { 89 | // Add the process to the vector 90 | processes.push_back(pEntry); 91 | } while (Process32Next(hProcessSnapshot, &pEntry)); 92 | 93 | CloseHandle(hProcessSnapshot); 94 | return processes; 95 | } 96 | -------------------------------------------------------------------------------- /lib/dll.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef DLL_H 3 | #define DLL_H 4 | #define WIN32_LEAN_AND_MEAN 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace dll { 12 | bool inject(HANDLE handle, std::string dllPath, char** errorMessage, LPDWORD moduleHandle) { 13 | // allocate space in target process memory for DLL path 14 | LPVOID targetProcessPath = VirtualAllocEx(handle, NULL, dllPath.length() + 1, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 15 | 16 | if (targetProcessPath == NULL) { 17 | *errorMessage = "unable to allocate memory in target process"; 18 | return false; 19 | } 20 | 21 | // write DLL path to reserved memory space 22 | if (WriteProcessMemory(handle, targetProcessPath, dllPath.c_str(), dllPath.length() + 1, 0) == 0) { 23 | *errorMessage = "unable to to write dll path to target process"; 24 | VirtualFreeEx(handle, targetProcessPath, 0, MEM_RELEASE); 25 | return false; 26 | } 27 | 28 | HMODULE kernel32 = LoadLibrary("kernel32"); 29 | 30 | if (kernel32 == 0) { 31 | VirtualFreeEx(handle, targetProcessPath, 0, MEM_RELEASE); 32 | *errorMessage = "unable to load kernel32"; 33 | return false; 34 | } 35 | 36 | // call LoadLibrary from target process 37 | LPTHREAD_START_ROUTINE loadLibraryAddress = (LPTHREAD_START_ROUTINE) GetProcAddress(kernel32, "LoadLibraryA"); 38 | HANDLE thread = CreateRemoteThread(handle, NULL, NULL, loadLibraryAddress, targetProcessPath, NULL, NULL); 39 | 40 | if (thread == NULL) { 41 | *errorMessage = "unable to call LoadLibrary from target process"; 42 | VirtualFreeEx(handle, targetProcessPath, 0, MEM_RELEASE); 43 | return false; 44 | } 45 | 46 | WaitForSingleObject(thread, INFINITE); 47 | GetExitCodeThread(thread, moduleHandle); 48 | 49 | // free memory reserved in target process 50 | VirtualFreeEx(handle, targetProcessPath, 0, MEM_RELEASE); 51 | CloseHandle(thread); 52 | 53 | return *moduleHandle > 0; 54 | } 55 | 56 | bool unload(HANDLE handle, char** errorMessage, HMODULE moduleHandle) { 57 | HMODULE kernel32 = LoadLibrary("kernel32"); 58 | 59 | if (kernel32 == 0) { 60 | *errorMessage = "unable to load kernel32"; 61 | return false; 62 | } 63 | 64 | // call FreeLibrary from target process 65 | LPTHREAD_START_ROUTINE freeLibraryAddress = (LPTHREAD_START_ROUTINE) GetProcAddress(kernel32, "FreeLibrary"); 66 | HANDLE thread = CreateRemoteThread(handle, NULL, NULL, freeLibraryAddress, (void*)moduleHandle, NULL, NULL); 67 | 68 | if (thread == NULL) { 69 | *errorMessage = "unable to call FreeLibrary from target process"; 70 | return false; 71 | } 72 | 73 | WaitForSingleObject(thread, INFINITE); 74 | DWORD exitCode = -1; 75 | GetExitCodeThread(thread, &exitCode); 76 | CloseHandle(thread); 77 | 78 | return exitCode != 0; 79 | } 80 | } 81 | 82 | #endif 83 | #pragma once 84 | -------------------------------------------------------------------------------- /lib/memory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef MEMORY_H 3 | #define MEMORY_H 4 | #define WIN32_LEAN_AND_MEAN 5 | 6 | #include 7 | #include 8 | 9 | class memory { 10 | public: 11 | memory(); 12 | ~memory(); 13 | std::vector getRegions(HANDLE hProcess); 14 | 15 | template 16 | dataType readMemory(HANDLE hProcess, DWORD64 address) { 17 | dataType cRead; 18 | ReadProcessMemory(hProcess, (LPVOID)address, &cRead, sizeof(dataType), NULL); 19 | return cRead; 20 | } 21 | 22 | BOOL readBuffer(HANDLE hProcess, DWORD64 address, SIZE_T size, char* dstBuffer) { 23 | return ReadProcessMemory(hProcess, (LPVOID)address, dstBuffer, size, NULL); 24 | } 25 | 26 | char readChar(HANDLE hProcess, DWORD64 address) { 27 | char value; 28 | ReadProcessMemory(hProcess, (LPVOID)address, &value, sizeof(char), NULL); 29 | return value; 30 | } 31 | 32 | BOOL readString(HANDLE hProcess, DWORD64 address, std::string* pString) { 33 | int length = 0; 34 | int BATCH_SIZE = 256; 35 | char* data = (char*) malloc(sizeof(char) * BATCH_SIZE); 36 | while (length <= BATCH_SIZE * 4096) { 37 | BOOL success = readBuffer(hProcess, address + length, BATCH_SIZE, data); 38 | 39 | if (success == 0) { 40 | free(data); 41 | break; 42 | } 43 | 44 | for (const char* ptr = data; ptr - data < BATCH_SIZE; ++ptr) { 45 | if (*ptr == '\0') { 46 | length += ptr - data + 1; 47 | 48 | char* buffer = (char*) malloc(length); 49 | readBuffer(hProcess, address, length, buffer); 50 | 51 | *pString = std::string(buffer); 52 | 53 | free(data); 54 | free(buffer); 55 | 56 | return TRUE; 57 | } 58 | } 59 | 60 | length += BATCH_SIZE; 61 | } 62 | 63 | return FALSE; 64 | } 65 | 66 | template 67 | void writeMemory(HANDLE hProcess, DWORD64 address, dataType value) { 68 | WriteProcessMemory(hProcess, (LPVOID)address, &value, sizeof(dataType), NULL); 69 | } 70 | 71 | template 72 | void writeMemory(HANDLE hProcess, DWORD64 address, dataType value, SIZE_T size) { 73 | LPVOID buffer = value; 74 | 75 | if (typeid(dataType) != typeid(char*)) { 76 | buffer = &value; 77 | } 78 | 79 | WriteProcessMemory(hProcess, (LPVOID)address, buffer, size, NULL); 80 | } 81 | 82 | // Write String, Method 1: Utf8Value is converted to string, get pointer and length from string 83 | // template <> 84 | // void writeMemory(HANDLE hProcess, DWORD address, std::string value) { 85 | // WriteProcessMemory(hProcess, (LPVOID)address, value.c_str(), value.length(), NULL); 86 | // } 87 | 88 | // Write String, Method 2: get pointer and length from Utf8Value directly 89 | void writeMemory(HANDLE hProcess, DWORD64 address, char* value, SIZE_T size) { 90 | WriteProcessMemory(hProcess, (LPVOID)address, value, size, NULL); 91 | } 92 | }; 93 | #endif 94 | #pragma once -------------------------------------------------------------------------------- /lib/module.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "module.h" 6 | #include "process.h" 7 | #include "memoryjs.h" 8 | 9 | DWORD64 module::getBaseAddress(const char* processName, DWORD processId) { 10 | char* errorMessage = ""; 11 | MODULEENTRY32 baseModule = module::findModule(processName, processId, &errorMessage); 12 | return (DWORD64)baseModule.modBaseAddr; 13 | } 14 | 15 | MODULEENTRY32 module::findModule(const char* moduleName, DWORD processId, char** errorMessage) { 16 | MODULEENTRY32 module; 17 | bool found = false; 18 | 19 | std::vector moduleEntries = getModules(processId, errorMessage); 20 | 21 | // Loop over every module 22 | for (std::vector::size_type i = 0; i != moduleEntries.size(); i++) { 23 | // Check to see if this is the module we want. 24 | if (!strcmp(moduleEntries[i].szModule, moduleName)) { 25 | // module is returned and moduleEntry is used internally for reading/writing to memory 26 | module = moduleEntries[i]; 27 | found = true; 28 | break; 29 | } 30 | } 31 | 32 | if (!found) { 33 | *errorMessage = "unable to find module"; 34 | } 35 | 36 | return module; 37 | } 38 | 39 | std::vector module::getModules(DWORD processId, char** errorMessage) { 40 | // Take a snapshot of all modules inside a given process. 41 | HANDLE hModuleSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, processId); 42 | MODULEENTRY32 mEntry; 43 | 44 | if (hModuleSnapshot == INVALID_HANDLE_VALUE) { 45 | *errorMessage = "method failed to take snapshot of the modules"; 46 | } 47 | 48 | // Before use, set the structure size. 49 | mEntry.dwSize = sizeof(mEntry); 50 | 51 | // Exit if unable to find the first module. 52 | if (!Module32First(hModuleSnapshot, &mEntry)) { 53 | CloseHandle(hModuleSnapshot); 54 | *errorMessage = "method failed to retrieve the first module"; 55 | } 56 | 57 | std::vector modules; 58 | 59 | // Loop through modules. 60 | do { 61 | // Add the module to the vector 62 | modules.push_back(mEntry); 63 | } while (Module32Next(hModuleSnapshot, &mEntry)); 64 | 65 | CloseHandle(hModuleSnapshot); 66 | 67 | return modules; 68 | } 69 | 70 | std::vector module::getThreads(DWORD processId, char** errorMessage) { 71 | HANDLE hThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, processId); 72 | THREADENTRY32 mEntry; 73 | 74 | if (hThreadSnapshot == INVALID_HANDLE_VALUE) { 75 | *errorMessage = "method failed to take snapshot of the threads"; 76 | } 77 | 78 | mEntry.dwSize = sizeof(mEntry); 79 | 80 | if(!Thread32First(hThreadSnapshot, &mEntry)) { 81 | CloseHandle(hThreadSnapshot); 82 | *errorMessage = "method failed to retrieve the first thread"; 83 | } 84 | 85 | std::vector threads; 86 | 87 | do { 88 | threads.push_back(mEntry); 89 | } while (Thread32Next(hThreadSnapshot, &mEntry)); 90 | 91 | CloseHandle(hThreadSnapshot); 92 | 93 | return threads; 94 | } 95 | -------------------------------------------------------------------------------- /examples/debugging.js: -------------------------------------------------------------------------------- 1 | const memoryjs = require('../index'); 2 | const processName = 'Testing Things.exe'; 3 | 4 | const processObject = memoryjs.openProcess(processName); 5 | const processId = processObject.th32ProcessID; 6 | 7 | // Address of variable 8 | const address = 0xEFFBF0; 9 | 10 | // When should we breakpoint? On read, write or execute 11 | const trigger = memoryjs.TRIGGER_ACCESS; 12 | const dataType = memoryjs.INT; 13 | 14 | // Whether to end the process once debugging has finished 15 | const killOnDetatch = false; 16 | 17 | /** 18 | * Example 1: Using the `Debugger` wrapper class. 19 | * The library contanis a wrapper class for hardware debugging. 20 | * It works by simply registering a hardware breakpoint and 21 | * then listening for all debug events that are emitted when 22 | * a breakpoint occurs. 23 | */ 24 | 25 | const hardwareDebugger = memoryjs.Debugger; 26 | 27 | // Attach the debugger to the process 28 | hardwareDebugger.attach(processId, killOnDetatch); 29 | 30 | const registerUsed = hardwareDebugger.setHardwareBreakpoint(processId, address, trigger, dataType); 31 | 32 | // `debugEvent` event emission catches debug events from all registers 33 | hardwareDebugger.on('debugEvent', ({ register, event }) => { 34 | console.log(`Hardware Register ${register} breakpoint`); 35 | console.log(event); 36 | }); 37 | 38 | // You can listen to debug events from specific hardware registers 39 | // by listening to whatever register was returned from `setHardwareBreakpoint` 40 | hardwareDebugger.on(registerUsed, (event) => { 41 | console.log(event); 42 | }); 43 | 44 | // Don't forget to call `hardwareDebugger.detatch()` when you're done! 45 | 46 | /** 47 | * Example 2: Manually using the exposed functions 48 | * There are a few steps involved when not using the wrapper: 49 | * 50 | * 1. Attatch the debugger 51 | * 2. Set your hardware breakpoints by manually referencing 52 | * which register you want to set. It's important you keep 53 | * track of which hardware registers you use as there are only 4 54 | * meaning only 4 breakpoints can be set. 55 | * You also need to manually reference the size of the data type. 56 | * 3. Constantly call `awaitDebugEvent` to wait for debug events 57 | * 4. When a debug event occurs, call `handleDebugEvent` 58 | * 5. Don't forget to detatch the debugger via `memoryjs.detatch(processId)` 59 | */ 60 | 61 | memoryjs.attachDebugger(processId, killOnDetatch); 62 | 63 | // There are 4 hardware registers: 64 | // `memoryjs.DR0` through `memoryjs.DR3` 65 | const registerToUse = memoryjs.DR0; 66 | 67 | // Our `address` references an integer variable. An integer 68 | // is 4 bytes therefore we pass `4` to the `size` parameter. 69 | const size = 4; 70 | memoryjs.setHardwareBreakpoint(processId, address, registerToUse, trigger, size); 71 | 72 | // How long to wait for the debug event before timing out 73 | const timeout = 100; 74 | 75 | // The interval duration must be the same or larger than the `timeout` value. 76 | // `awaitDebugEvent` works by waiting a certain amount of time before timing out, 77 | // therefore we only want to call the method again when we're sure the previous 78 | // call has already timed out. 79 | setInterval(() => { 80 | // `debugEvent` can be null if no event occurred 81 | const debugEvent = memoryjs.awaitDebugEvent(registerToUse, timeout); 82 | 83 | // If a breakpoint occurred, handle it 84 | if (debugEvent) { 85 | memoryjs.handleDebugEvent(debugEvent.processId, debugEvent.threadId); 86 | } 87 | }, timeout); 88 | 89 | // Don't forget to detatch the debugger! 90 | // memoryjs.detatchDebugger(processId); 91 | 92 | memoryjs.closeProcess(processObject.handle); 93 | -------------------------------------------------------------------------------- /src/consts.js: -------------------------------------------------------------------------------- 1 | const dataTypes = { 2 | standard: { 3 | BYTE: 'byte', 4 | UBYTE: 'ubyte', 5 | CHAR: 'char', 6 | UCHAR: 'uchar', 7 | INT8: 'int8', 8 | UINT8: 'uint8', 9 | INT16: 'int16', 10 | INT16_BE: 'int16_be', 11 | UINT16: 'uint16', 12 | UINT16_BE: 'uint16_be', 13 | SHORT: 'short', 14 | SHORT_BE: 'short_be', 15 | USHORT: 'ushort', 16 | USHORT_BE: 'ushort_be', 17 | LONG: 'long', 18 | LONG_BE: 'long_be', 19 | ULONG: 'ulong', 20 | ULONG_BE: 'ulong_be', 21 | INT: 'int', 22 | INT_BE: 'int_be', 23 | UINT: 'uint', 24 | UINT_BE: 'uint_be', 25 | INT32: 'int32', 26 | INT32_BE: 'int32_be', 27 | UINT32: 'uint32', 28 | UINT32_BE: 'uint32_be', 29 | INT64: 'int64', 30 | INT64_BE: 'int64_be', 31 | UINT64: 'uint64', 32 | UINT64_BE: 'uint64_be', 33 | WORD: 'word', 34 | DWORD: 'dword', 35 | FLOAT: 'float', 36 | FLOAT_BE: 'float_be', 37 | DOUBLE: 'double', 38 | DOUBLE_BE: 'double_be', 39 | BOOL: 'bool', 40 | BOOLEAN: 'boolean', 41 | PTR: 'ptr', 42 | POINTER: 'pointer', 43 | UPTR: 'uptr', 44 | UPOINTER: 'upointer', 45 | STR: 'str', 46 | STRING: 'string', 47 | VEC3: 'vec3', 48 | VECTOR3: 'vector3', 49 | VEC4: 'vec4', 50 | VECTOR4: 'vector4', 51 | }, 52 | function: { 53 | T_VOID: 0x0, 54 | T_STRING: 0x1, 55 | T_CHAR: 0x2, 56 | T_BOOL: 0x3, 57 | T_INT: 0x4, 58 | T_DOUBLE: 0x5, 59 | T_FLOAT: 0x6, 60 | }, 61 | }; 62 | 63 | const signatureTypes = { 64 | NORMAL: 0x0, 65 | READ: 0x1, 66 | SUBTRACT: 0x2, 67 | }; 68 | 69 | const memoryFlags = { 70 | // see: https://docs.microsoft.com/en-gb/windows/desktop/Memory/memory-protection-constants 71 | access: { 72 | PAGE_NOACCESS: 0x01, 73 | PAGE_READONLY: 0x02, 74 | PAGE_READWRITE: 0x04, 75 | PAGE_WRITECOPY: 0x08, 76 | PAGE_EXECUTE: 0x10, 77 | PAGE_EXECUTE_READ: 0x20, 78 | PAGE_EXECUTE_READWRITE: 0x40, 79 | PAGE_EXECUTE_WRITECOPY: 0x80, 80 | PAGE_GUARD: 0x100, 81 | PAGE_NOCACHE: 0x200, 82 | PAGE_WRITECOMBINE: 0x400, 83 | PAGE_ENCLAVE_UNVALIDATED: 0x20000000, 84 | PAGE_TARGETS_NO_UPDATE: 0x40000000, 85 | PAGE_TARGETS_INVALID: 0x40000000, 86 | PAGE_ENCLAVE_THREAD_CONTROL: 0x80000000, 87 | }, 88 | 89 | // see: https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex#parameters 90 | allocation: { 91 | MEM_COMMIT: 0x00001000, 92 | MEM_RESERVE: 0x00002000, 93 | MEM_RESET: 0x00080000, 94 | MEM_TOP_DOWN: 0x00100000, 95 | MEM_RESET_UNDO: 0x1000000, 96 | MEM_LARGE_PAGES: 0x20000000, 97 | MEM_PHYSICAL: 0x00400000, 98 | }, 99 | 100 | // see: https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_memory_basic_information 101 | page: { 102 | MEM_PRIVATE: 0x20000, 103 | MEM_MAPPED: 0x40000, 104 | MEM_IMAGE: 0x1000000, 105 | }, 106 | }; 107 | 108 | const hardwareDebug = { 109 | registers: { 110 | DR0: 0x0, 111 | DR1: 0x1, 112 | DR2: 0x2, 113 | DR3: 0x3, 114 | }, 115 | breakpointTriggerTypes: { 116 | TRIGGER_EXECUTE: 0x0, 117 | TRIGGER_ACCESS: 0x3, 118 | TRIGGER_WRITE: 0x1, 119 | }, 120 | }; 121 | 122 | module.exports = { 123 | // data type constants 124 | ...dataTypes.standard, 125 | ...dataTypes.function, 126 | 127 | // pattern scanning flags 128 | ...signatureTypes, 129 | 130 | // memory flags 131 | ...memoryFlags.access, 132 | ...memoryFlags.allocation, 133 | ...memoryFlags.page, 134 | 135 | // debugger consts 136 | ...hardwareDebug.registers, 137 | ...hardwareDebug.breakpointTriggerTypes, 138 | }; 139 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const SIZEOF_STDSTRING_32BIT = 24; 2 | const SIZEOF_STDSTRING_64BIT = 32; 3 | const STDSTRING_LENGTH_OFFSET = 0x10; 4 | 5 | /** 6 | * Custom string consumer/producer for Structron (due to complexity of `std::string`) 7 | * `std::string` is a container for a string which makes reading/writing to it tricky, 8 | * it will either store the string itself, or a pointer to the string, based on the 9 | * length of the string. When we want to read from or write to a buffer, we need 10 | * to determine if the string is in the buffer itself, or if the buffer 11 | * just contains a pointer to the string. Based on one of these options, 12 | * we can read from or write to the string. 13 | * 14 | * @param handle the handle to the process 15 | * @param structAddress the base address of the structure in memory 16 | * @param platform the architecture of the process, either "32" or "64" 17 | * @param encoding the encoding type of the string 18 | */ 19 | const STRUCTRON_TYPE_STRING = memoryjs => (handle, structAddress, platform, encoding = 'utf8') => ({ 20 | read(buffer, offset) { 21 | // get string length from `std::string` container 22 | const length = buffer.readUInt32LE(offset + STDSTRING_LENGTH_OFFSET); 23 | 24 | // if length > 15, `std::string` has a pointer to the string 25 | if (length > 15) { 26 | const pointer = platform === '64' ? buffer.readBigInt64LE(offset) : buffer.readUInt32LE(offset); 27 | return memoryjs.readMemory(handle, Number(pointer), memoryjs.STRING); 28 | } 29 | 30 | // if length <= 15, `std::string` directly contains the string 31 | return buffer.toString(encoding, offset, offset + length); 32 | }, 33 | write(value, context, offset) { 34 | // address containing the length of the string 35 | const lengthAddress = structAddress + offset + STDSTRING_LENGTH_OFFSET; 36 | 37 | // get existing `std::string` buffer 38 | const bufferSize = platform === '64' ? SIZEOF_STDSTRING_64BIT : SIZEOF_STDSTRING_32BIT; 39 | const existingBuffer = memoryjs.readBuffer(handle, structAddress + offset, bufferSize); 40 | 41 | // fetch length of string in memory (to determine if it's pointer based) 42 | const length = memoryjs.readMemory(handle, lengthAddress, memoryjs.INT); 43 | 44 | if ((length > 15 && value.length <= 15) || (length <= 15 && value.length > 15)) { 45 | // there are two ways strings are stored: directly or with a pointer, 46 | // we can't go from one to the other (without introducing more complexity), 47 | // so just skip the bytes to prevent crashing. if a pointer is used, we could 48 | // technically write any length, but the next time we try writing, we will read 49 | // the length and assume it's not stored via pointer and will lead to crashes 50 | 51 | // write existing buffer without changes 52 | existingBuffer.copy(context.buffer, offset); 53 | return; 54 | } 55 | 56 | // write new length 57 | memoryjs.writeMemory(handle, lengthAddress, value.length, memoryjs.UINT32); 58 | existingBuffer.writeUInt32LE(value.length, STDSTRING_LENGTH_OFFSET); 59 | 60 | if (length > 15 && value.length > 15) { 61 | // write new string in memory 62 | const pointer = memoryjs.readMemory(handle, structAddress + offset, memoryjs.POINTER); 63 | memoryjs.writeMemory(handle, pointer, value, memoryjs.STRING); 64 | } else if (length <= 15 && value.length <= 15) { 65 | // write new string directly into buffer 66 | existingBuffer.write(value, encoding); 67 | } 68 | 69 | // write our new `std::string` buffer into the buffer we are creating 70 | existingBuffer.copy(context.buffer, offset); 71 | }, 72 | SIZE: platform === '64' ? SIZEOF_STDSTRING_64BIT : SIZEOF_STDSTRING_32BIT, 73 | }); 74 | 75 | module.exports = { STRUCTRON_TYPE_STRING }; 76 | -------------------------------------------------------------------------------- /src/debugger.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | 3 | const lengths = { 4 | byte: 1, 5 | int: 4, 6 | int32: 4, 7 | uint32: 4, 8 | int64: 8, 9 | uint64: 8, 10 | dword: 4, 11 | short: 2, 12 | long: 8, 13 | float: 4, 14 | double: 8, 15 | bool: 1, 16 | boolean: 1, 17 | ptr: 4, 18 | pointer: 4, 19 | // str: 0, 20 | // string: 0, 21 | // vec3: 0, 22 | // vector3: 0, 23 | // vec4: 0, 24 | // vector4: 0, 25 | }; 26 | 27 | // Tracks used and unused registers 28 | class Registers { 29 | constructor() { 30 | this.registers = Object.freeze({ 31 | DR0: 0x0, 32 | DR1: 0x1, 33 | DR2: 0x2, 34 | DR3: 0x3, 35 | }); 36 | 37 | this.used = []; 38 | } 39 | 40 | getRegister() { 41 | const unused = Object 42 | .values(this.registers) 43 | .filter(r => !this.used.includes(r)); 44 | 45 | return unused[0]; 46 | } 47 | 48 | busy(register) { 49 | this.used.push(register); 50 | } 51 | 52 | unbusy(register) { 53 | this.used.splice(this.used.indexOf(register), 1); 54 | } 55 | } 56 | 57 | class Debugger extends EventEmitter { 58 | constructor(memoryjs) { 59 | super(); 60 | this.memoryjs = memoryjs; 61 | this.registers = new Registers(); 62 | this.attached = false; 63 | this.intervals = []; 64 | } 65 | 66 | attach(processId, killOnDetach = false) { 67 | const success = this.memoryjs.attachDebugger(processId, killOnDetach); 68 | 69 | if (success) { 70 | this.attached = true; 71 | } 72 | 73 | return success; 74 | } 75 | 76 | detach(processId) { 77 | this.intervals.map(({ id }) => clearInterval(id)); 78 | return this.memoryjs.detachDebugger(processId); 79 | } 80 | 81 | removeHardwareBreakpoint(processId, register) { 82 | const success = this.memoryjs.removeHardwareBreakpoint(processId, register); 83 | 84 | if (success) { 85 | this.registers.unbusy(register); 86 | } 87 | 88 | // Find the register's corresponding interval and delete it 89 | this.intervals.forEach(({ register: r, id }) => { 90 | if (r === register) { 91 | clearInterval(id); 92 | } 93 | }); 94 | 95 | return success; 96 | } 97 | 98 | setHardwareBreakpoint(processId, address, trigger, dataType) { 99 | let size = lengths[dataType]; 100 | 101 | // If we are breakpointing a string, we need to determine the length of it 102 | if (dataType === 'str' || dataType === 'string') { 103 | const { handle } = this.memoryjs.openProcess(processId); 104 | const value = this.memoryjs.readMemory(handle, address, this.memoryjs.STRING); 105 | 106 | size = value.length; 107 | 108 | this.memoryjs.closeProcess(handle); 109 | } 110 | 111 | // Obtain an available register 112 | const register = this.registers.getRegister(); 113 | const success = this.memoryjs 114 | .setHardwareBreakpoint(processId, address, register, trigger, size); 115 | 116 | // If the breakpoint was set, mark this register as busy 117 | if (success) { 118 | this.registers.busy(register); 119 | this.monitor(register); 120 | } 121 | 122 | return register; 123 | } 124 | 125 | monitor(register, timeout = 100) { 126 | const id = setInterval(() => { 127 | const debugEvent = this.memoryjs.awaitDebugEvent(register, timeout); 128 | 129 | if (debugEvent) { 130 | this.memoryjs.handleDebugEvent(debugEvent.processId, debugEvent.threadId); 131 | 132 | // Global event for all registers 133 | this.emit('debugEvent', { 134 | register, 135 | event: debugEvent, 136 | }); 137 | 138 | // Event per register 139 | this.emit(register, debugEvent); 140 | } 141 | }, 100); 142 | 143 | this.intervals.push({ 144 | register, 145 | id, 146 | }); 147 | } 148 | } 149 | 150 | module.exports = Debugger; 151 | -------------------------------------------------------------------------------- /lib/pattern.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "pattern.h" 6 | #include "memoryjs.h" 7 | #include "process.h" 8 | #include "memory.h" 9 | 10 | #define INRANGE(x,a,b) (x >= a && x <= b) 11 | #define getBits( x ) (INRANGE(x,'0','9') ? (x - '0') : ((x&(~0x20)) - 'A' + 0xa)) 12 | #define getByte( x ) (getBits(x[0]) << 4 | getBits(x[1])) 13 | 14 | pattern::pattern() {} 15 | pattern::~pattern() {} 16 | 17 | bool pattern::search(HANDLE handle, std::vector regions, DWORD64 searchAddress, const char* pattern, short flags, uint32_t patternOffset, uintptr_t* pAddress) { 18 | for (std::vector::size_type i = 0; i != regions.size(); i++) { 19 | uintptr_t baseAddress = (uintptr_t) regions[i].BaseAddress; 20 | DWORD baseSize = regions[i].RegionSize; 21 | 22 | // if `searchAddress` has been set, only pattern match if the address lies inside of this region 23 | if (searchAddress != 0 && (searchAddress < baseAddress || searchAddress > (baseAddress + baseSize))) { 24 | continue; 25 | } 26 | 27 | // read memory region to pattern match inside 28 | std::vector regionBytes = std::vector(baseSize); 29 | ReadProcessMemory(handle, (LPVOID)baseAddress, ®ionBytes[0], baseSize, nullptr); 30 | unsigned char* byteBase = const_cast(®ionBytes.at(0)); 31 | 32 | if (findPattern(handle, baseAddress, byteBase, baseSize, pattern, flags, patternOffset, pAddress)) { 33 | return true; 34 | } 35 | } 36 | 37 | return false; 38 | } 39 | 40 | bool pattern::search(HANDLE handle, std::vector modules, DWORD64 searchAddress, const char* pattern, short flags, uint32_t patternOffset, uintptr_t* pAddress) { 41 | for (std::vector::size_type i = 0; i != modules.size(); i++) { 42 | uintptr_t baseAddress = (uintptr_t) modules[i].modBaseAddr; 43 | DWORD baseSize = modules[i].modBaseSize; 44 | 45 | // if `searchAddress` has been set, only pattern match if the address lies inside of this module 46 | if (searchAddress != 0 && (searchAddress < baseAddress || searchAddress > (baseAddress + baseSize))) { 47 | continue; 48 | } 49 | 50 | // read memory region occupied by the module to pattern match inside 51 | std::vector moduleBytes = std::vector(baseSize); 52 | ReadProcessMemory(handle, (LPVOID)baseAddress, &moduleBytes[0], baseSize, nullptr); 53 | unsigned char* byteBase = const_cast(&moduleBytes.at(0)); 54 | 55 | if (findPattern(handle, baseAddress, byteBase, baseSize, pattern, flags, patternOffset, pAddress)) { 56 | return true; 57 | } 58 | } 59 | 60 | return false; 61 | } 62 | 63 | /* based off Y3t1y3t's implementation */ 64 | bool pattern::findPattern(HANDLE handle, uintptr_t memoryBase, unsigned char* byteBase, DWORD memorySize, const char* pattern, short flags, uint32_t patternOffset, uintptr_t* pAddress) { 65 | // uintptr_t moduleBase = (uintptr_t)module.hModule; 66 | auto maxOffset = memorySize - 0x1000; 67 | 68 | for (uintptr_t offset = 0; offset < maxOffset; ++offset) { 69 | if (compareBytes(byteBase + offset, pattern)) { 70 | uintptr_t address = memoryBase + offset + patternOffset; 71 | 72 | if (flags & ST_READ) { 73 | ReadProcessMemory(handle, LPCVOID(address), &address, sizeof(uintptr_t), nullptr); 74 | } 75 | 76 | if (flags & ST_SUBTRACT) { 77 | address -= memoryBase; 78 | } 79 | 80 | *pAddress = address; 81 | 82 | return true; 83 | } 84 | } 85 | 86 | return false; 87 | }; 88 | 89 | bool pattern::compareBytes(const unsigned char* bytes, const char* pattern) { 90 | for (; *pattern; *pattern != ' ' ? ++bytes : bytes, ++pattern) { 91 | if (*pattern == ' ' || *pattern == '?') { 92 | continue; 93 | } 94 | 95 | if (*bytes != getByte(pattern)) { 96 | return false; 97 | } 98 | 99 | ++pattern; 100 | } 101 | 102 | return true; 103 | } -------------------------------------------------------------------------------- /examples/general.js: -------------------------------------------------------------------------------- 1 | const memoryjs = require('./index'); 2 | const processName = 'csgo.exe'; 3 | let clientModule; 4 | const offset = 0x00A9D44C; 5 | 6 | // open a process (sync) 7 | const processObject = memoryjs.openProcess(processName); 8 | 9 | // open a process (async) 10 | memoryjs.openProcess(processName, (error, processObject) => { 11 | console.log(JSON.stringify(processObject, null, 3)); 12 | 13 | if (process.szExeFile) { 14 | console.log('Successfully opened handle on', processName); 15 | 16 | memoryjs.closeProcess(processObject.handle); 17 | console.log('Closed handle on', processName); 18 | } else { 19 | console.log('Unable to open handle on', processName); 20 | } 21 | }); 22 | 23 | // get all processes (sync) 24 | const processes = memoryjs.getProcesses(); 25 | console.log('\nGetting all processes (sync)\n---\n'); 26 | processes.forEach(({ szExeFile }) => console.log(szExeFile)); 27 | 28 | // get all processes (async) 29 | console.log('\nGetting all processes (async)\n---\n'); 30 | memoryjs.getProcesses((error, processes) => { 31 | processes.forEach(({ szExeFile }) => console.log(szExeFile)); 32 | }); 33 | 34 | /* process = 35 | { cntThreads: 47, 36 | szExeFile: "csgo.exe", 37 | th32ProcessID: 10316, 38 | th32ParentProcessID: 7804, 39 | pcPriClassBase: 8 } */ 40 | 41 | // get all modules (sync) 42 | console.log('\nGetting all modules (sync)\n---\n'); 43 | const modules = memoryjs.getModules(processObject.th32ProcessID); 44 | modules.forEach(({ szExeFile }) => console.log(szExeFile)); 45 | 46 | // get all modules (async) 47 | console.log('\nGetting all modules (async)\n---\n'); 48 | memoryjs.getModules(processObject.th32ProcessID, (error, modules) => { 49 | modules.forEach(({ szExeFile }) => console.log(szExeFile)); 50 | }); 51 | 52 | // find a module associated with a process (sync) 53 | console.log('\nFinding module "client.dll" (sync)\n---\n'); 54 | console.log(memoryjs.findModule('client.dll', processObject.th32ProcessID)); 55 | 56 | // find a module associated with a process (async) 57 | console.log('\nFinding module "client.dll" (async)\n---\n'); 58 | memoryjs.findModule('client.dll', processObject.th32ProcessID, (error, module) => { 59 | console.log(module.szModule); 60 | clientModule = module; 61 | }); 62 | 63 | /* module = 64 | { modBaseAddr: 468123648, 65 | modBaseSize: 80302080, 66 | szExePath: 'c:\\program files (x86)\\steam\\steamapps\\common\\counter-strike global offensive\\csgo\\bin\\client.dll', 67 | szModule: 'client.dll', 68 | th32ProcessID: 10316 } */ 69 | 70 | const address = clientModule.modBaseAddr + offset; 71 | 72 | // read memory (sync) 73 | console.log(`value of 0x${address.toString(16)}: ${memoryjs.readMemory(processObject.handle, address, memoryjs.INT)}`); 74 | 75 | // read memory (async) 76 | memoryjs.readMemory(processObject.handle, address, memoryjs.INT, (error, result) => { 77 | console.log(`value of 0x${address.toString(16)}: ${result}`); 78 | }); 79 | 80 | // write memory 81 | memoryjs.writeMemory(processObject.handle, address, 1, memoryjs.INT); 82 | 83 | // pattern reading 84 | const signature = 'A3 ? ? ? ? C7 05 ? ? ? ? ? ? ? ? E8 ? ? ? ? 59 C3 6A'; 85 | const signatureTypes = memoryjs.READ | memoryjs.SUBTRACT; 86 | const patternOffset = 0x1; 87 | const addressOffset = 0x10; 88 | const dwLocalPlayer = memoryjs.findPattern(processObject.handle, clientModule.szModule, signature, signatureTypes, patternOffset, addressOffset); 89 | console.log(`value of dwLocalPlayer: 0x${dwLocalPlayer.toString(16)}`); 90 | 91 | // inject dll 92 | memoryjs.injectDll(processObject.handle, 'C:\\TestDLL.dll', (error, success) => { 93 | console.log(`injected TestDLL.dll into process: ${success} ${error}`); 94 | }); 95 | 96 | // unload dll (by name) 97 | memoryjs.unloadDll(processObject.handle, 'TestDLL.dll', (error, success) => { 98 | console.log(`unloaded TestDLL.dll from process: ${success} ${error}`); 99 | }); 100 | 101 | // unload dll (by module base address) 102 | // const testDll = memoryjs.findModule('TestDLL.dll', processObject.th32ProcessID); 103 | // const success = memoryjs.unloadDll(processObject.handle, testDll.modBaseAddr); 104 | // console.log(`unloaded TestDLL.dll from process: ${success}`); 105 | -------------------------------------------------------------------------------- /lib/debugger.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * Hardware debugger for memory.js 3 | * A lot of the hardware debugging code is based on ReClass.NET 4 | * https://github.com/ReClassNET/ReClass.NET 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "debugger.h" 12 | #include "module.h" 13 | 14 | bool debugger::attach(DWORD processId, bool killOnDetatch) { 15 | if (DebugActiveProcess(processId) == 0) { 16 | return false; 17 | } 18 | 19 | DebugSetProcessKillOnExit(killOnDetatch); 20 | return true; 21 | } 22 | 23 | bool debugger::detatch(DWORD processId) { 24 | return DebugActiveProcessStop(processId) != 0; 25 | } 26 | 27 | bool debugger::setHardwareBreakpoint(DWORD processId, DWORD64 address, Register reg, int trigger, int size) { 28 | char* errorMessage = ""; 29 | std::vector threads = module::getThreads(0, &errorMessage); 30 | 31 | if (strcmp(errorMessage, "")) { 32 | return false; 33 | } 34 | 35 | for (std::vector::size_type i = 0; i != threads.size(); i++) { 36 | if (threads[i].th32OwnerProcessID != processId) { 37 | continue; 38 | } 39 | 40 | HANDLE threadHandle = OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, false, threads[i].th32ThreadID); 41 | 42 | if (threadHandle == 0) { 43 | continue; 44 | } 45 | 46 | SuspendThread(threadHandle); 47 | 48 | CONTEXT context = { 0 }; 49 | context.ContextFlags = CONTEXT_DEBUG_REGISTERS; 50 | GetThreadContext(threadHandle, &context); 51 | 52 | DebugRegister7 dr7; 53 | dr7.Value = context.Dr7; 54 | 55 | if (reg == Register::DR0) { 56 | context.Dr0 = address; 57 | dr7.G0 = true; 58 | dr7.RW0 = trigger; 59 | dr7.Len0 = size; 60 | } 61 | 62 | if (reg == Register::DR1) { 63 | context.Dr1 = address; 64 | dr7.G1 = true; 65 | dr7.RW1 = trigger; 66 | dr7.Len1 = size; 67 | } 68 | 69 | if (reg == Register::DR2) { 70 | context.Dr2 = address; 71 | dr7.G2 = true; 72 | dr7.RW2 = trigger; 73 | dr7.Len2 = size; 74 | } 75 | 76 | if (reg == Register::DR3) { 77 | context.Dr3 = address; 78 | dr7.G3 = true; 79 | dr7.RW3 = trigger; 80 | dr7.Len3 = size; 81 | } 82 | 83 | context.Dr7 = dr7.Value; 84 | 85 | SetThreadContext(threadHandle, &context); 86 | ResumeThread(threadHandle); 87 | CloseHandle(threadHandle); 88 | 89 | return true; 90 | } 91 | 92 | return false; 93 | } 94 | 95 | bool debugger::awaitDebugEvent(DWORD millisTimeout, DebugEvent *info) { 96 | DEBUG_EVENT debugEvent = {}; 97 | 98 | if (WaitForDebugEvent(&debugEvent, millisTimeout) == 0) { 99 | return false; 100 | } 101 | 102 | if (debugEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) { 103 | CloseHandle(debugEvent.u.CreateProcessInfo.hFile); 104 | } 105 | 106 | if (debugEvent.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT) { 107 | CloseHandle(debugEvent.u.LoadDll.hFile); 108 | } 109 | 110 | if (debugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { 111 | EXCEPTION_DEBUG_INFO exception = debugEvent.u.Exception; 112 | 113 | info->processId = debugEvent.dwProcessId; 114 | info->threadId = debugEvent.dwThreadId; 115 | info->exceptionAddress = exception.ExceptionRecord.ExceptionAddress; 116 | info->exceptionCode = exception.ExceptionRecord.ExceptionCode; 117 | info->exceptionFlags = exception.ExceptionRecord.ExceptionFlags; 118 | 119 | HANDLE handle = OpenThread(THREAD_GET_CONTEXT, false, debugEvent.dwThreadId); 120 | 121 | if (handle == 0) { 122 | return false; 123 | } 124 | 125 | CONTEXT context = {}; 126 | context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_DEBUG_REGISTERS; 127 | GetThreadContext(handle, &context); 128 | 129 | DebugRegister6 dr6; 130 | dr6.Value = context.Dr6; 131 | 132 | if (dr6.DR0) { 133 | info->hardwareRegister = Register::DR0; 134 | } 135 | 136 | if (dr6.DR1) { 137 | info->hardwareRegister = Register::DR1; 138 | } 139 | 140 | if (dr6.DR2) { 141 | info->hardwareRegister = Register::DR2; 142 | } 143 | 144 | if (dr6.DR3) { 145 | info->hardwareRegister = Register::DR3; 146 | } 147 | 148 | if (!dr6.DR0 && !dr6.DR1 && !dr6.DR2 && !dr6.DR3) { 149 | info->hardwareRegister = Register::Invalid; 150 | } 151 | 152 | CloseHandle(handle); 153 | } else { 154 | ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE); 155 | } 156 | 157 | return true; 158 | } 159 | 160 | bool debugger::handleDebugEvent(DWORD processId, DWORD threadId) { 161 | return ContinueDebugEvent(processId, threadId, DBG_CONTINUE); 162 | // if (status == DebugContinueStatus::Handled) { 163 | // return ContinueDebugEvent(processId, threadId, DBG_CONTINUE) != 0; 164 | // } 165 | 166 | // if (status == DebugContinueStatus::NotHandled) { 167 | // return ContinueDebugEvent(processId, threadId, DBG_EXCEPTION_NOT_HANDLED) != 0; 168 | // } 169 | } -------------------------------------------------------------------------------- /lib/functions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef FUNCTIONS_H 3 | #define FUNCTIONS_H 4 | #define WIN32_LEAN_AND_MEAN 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | struct Call { 11 | int returnValue; 12 | std::string returnString; 13 | DWORD exitCode; 14 | }; 15 | 16 | namespace functions { 17 | enum class Type { 18 | T_VOID = 0x0, 19 | T_STRING = 0x1, 20 | T_CHAR = 0x2, 21 | T_BOOL = 0x3, 22 | T_INT = 0x4, 23 | T_DOUBLE = 0x5, 24 | T_FLOAT = 0x6 25 | }; 26 | 27 | struct Arg { 28 | Type type; 29 | LPVOID value; 30 | }; 31 | 32 | LPVOID reserveString(HANDLE hProcess, const char* value, SIZE_T size); 33 | char readChar(HANDLE hProcess, DWORD64 address); 34 | 35 | template 36 | Call call(HANDLE pHandle, std::vector args, Type returnType, DWORD64 address, char** errorMessage) { 37 | std::vector argShellcode; 38 | 39 | std::reverse(args.begin(), args.end()); 40 | 41 | for (auto &arg : args) { 42 | // 0x68: PUSH imm16/imm32 43 | // 0x6A: PUSH imm8 44 | 45 | if (arg.type == Type::T_INT || arg.type == Type::T_FLOAT) { 46 | argShellcode.push_back(0x68); 47 | int value = *static_cast(arg.value); 48 | 49 | // Little endian representation 50 | for (int i = 0; i < 4; i++) { 51 | int shifted = (value >> (i * 8)) & 0xFF; 52 | argShellcode.push_back(shifted); 53 | } 54 | 55 | continue; 56 | } 57 | 58 | if (arg.type == Type::T_STRING) { 59 | argShellcode.push_back(0x68); 60 | std::string value = *static_cast(arg.value); 61 | LPVOID address = functions::reserveString(pHandle, value.c_str(), value.length()); 62 | 63 | // Little endian representation 64 | for (int i = 0; i < 4; i++) { 65 | int shifted = ((int)address >> (i * 8)) & 0xFF; 66 | argShellcode.push_back(shifted); 67 | } 68 | 69 | continue; 70 | } 71 | 72 | argShellcode.push_back(0x6A); 73 | unsigned char value = *static_cast(arg.value); 74 | argShellcode.push_back(value); 75 | } 76 | 77 | // 83: ADD r/m16/32 imm8 78 | std::vector callShellcode = { 79 | // call 0x00000000 80 | 0xE8, 0x00, 0x00, 0x00, 0x00, 81 | // add esp, [arg count * 4] 82 | 0x83, 0xC4, (unsigned char)(args.size() * 0x4), 83 | }; 84 | 85 | LPVOID returnValuePointer = 0; 86 | if (returnType != Type::T_VOID) { 87 | // We will reserve memory for where we want to store the result, 88 | // and move the return value to this address. 89 | returnValuePointer = VirtualAllocEx(pHandle, NULL, sizeof(returnDataType), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 90 | 91 | if (returnType == Type::T_FLOAT) { 92 | // fstp DWORD PTR [0x12345678] 93 | // when `call` is executed, if return type is float, it's stored 94 | // in fpu registers (st0 through st7). we can use the `fst` 95 | // instruction to move st0 to a place in memory 96 | // D9 FSTP m32real ST Store Floating Point Value and Pop 97 | // D9 = for m32 98 | callShellcode.push_back(0xD9); 99 | callShellcode.push_back(0x1C); 100 | callShellcode.push_back(0x25); 101 | } else if (returnType == Type::T_DOUBLE) { 102 | // fstp QWORD PTR [0x12345678] 103 | // DD FSTP m64real ST Store Floating Point Value and Pop 104 | // DD = for m64 105 | callShellcode.push_back(0xDD); 106 | callShellcode.push_back(0x1C); 107 | callShellcode.push_back(0x25); 108 | } else { 109 | // mov [0x1234], eax 110 | // A3: MOVE moffs16/32 eAX 111 | // Call routines places return value inside EAX 112 | callShellcode.push_back(0xA3); 113 | } 114 | 115 | for (int i = 0; i < 4; i++) { 116 | int shifted = ((DWORD)returnValuePointer >> (i * 8)) & 0xFF; 117 | callShellcode.push_back(shifted); 118 | } 119 | } 120 | 121 | // C3: return 122 | callShellcode.push_back(0xC3); 123 | 124 | // concatenate the arg shellcode with the calling shellcode 125 | std::vector shellcode; 126 | shellcode.reserve(argShellcode.size() + callShellcode.size()); 127 | shellcode.insert(shellcode.end(), argShellcode.begin(), argShellcode.end()); 128 | shellcode.insert(shellcode.end(), callShellcode.begin(), callShellcode.end()); 129 | 130 | // 5 = 0xE8 (call) + 4 empty bytes for where the address will go 131 | int addessShellcodeOffset = argShellcode.size() + 5; 132 | 133 | // Allocate space for the shellcode 134 | SIZE_T size = shellcode.size() * sizeof(unsigned char); 135 | LPVOID pShellcode = VirtualAllocEx(pHandle, NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 136 | 137 | // `call` opcode takes relative address, so calculate the relative address 138 | // taking into account where the shellcode will be written in memory 139 | DWORD64 relative = address - (uintptr_t)pShellcode - addessShellcodeOffset; 140 | 141 | // Write the relative address to the shellcode 142 | for (int i = 0; i < 4; i++) { 143 | int shifted = (relative >> (i * 8)) & 0xFF; 144 | 145 | // argShellcode.size() will offset to the `0xE8` opcode, add 1 to offset that instruction 146 | shellcode.at(argShellcode.size() + 1 + i) = shifted; 147 | } 148 | 149 | // Write the shellcode 150 | WriteProcessMemory(pHandle, pShellcode, shellcode.data(), size, NULL); 151 | 152 | // Execute the shellcode 153 | HANDLE thread = CreateRemoteThread(pHandle, NULL, NULL, (LPTHREAD_START_ROUTINE)pShellcode, NULL, NULL, NULL); 154 | 155 | Call data = { 0, "", (DWORD) -1 }; 156 | 157 | if (thread == NULL) { 158 | *errorMessage = "unable to create remote thread."; 159 | return data; 160 | } 161 | 162 | WaitForSingleObject(thread, INFINITE); 163 | GetExitCodeThread(thread, &data.exitCode); 164 | 165 | if (returnType != Type::T_VOID && returnType != Type::T_STRING) { 166 | ReadProcessMemory(pHandle, (LPVOID)returnValuePointer, &data.returnValue, sizeof(int), NULL); 167 | VirtualFreeEx(pHandle, returnValuePointer, 0, MEM_RELEASE); 168 | } 169 | 170 | if (returnType == Type::T_STRING) { 171 | // String is stored in memory somewhere 172 | // When returning a string, the address of the string is placed in EAX. 173 | // So we read the current returnValuePointer address to get the actual address of the string 174 | ReadProcessMemory(pHandle, (LPVOID)returnValuePointer, &returnValuePointer, sizeof(int), NULL); 175 | 176 | std::vector chars; 177 | int offset = 0x0; 178 | while (true) { 179 | char c = functions::readChar(pHandle, (DWORD64)returnValuePointer + offset); 180 | chars.push_back(c); 181 | 182 | // break at 1 million chars 183 | if (offset == (sizeof(char) * 1000000)) { 184 | chars.clear(); 185 | break; 186 | } 187 | 188 | // break at terminator (end of string) 189 | if (c == '\0') { 190 | break; 191 | } 192 | 193 | // go to next char 194 | offset += sizeof(char); 195 | } 196 | 197 | std::string str(chars.begin(), chars.end()); 198 | // TODO: pass str as LPVOID and cast back to string 199 | data.returnString = str; 200 | } 201 | 202 | VirtualFreeEx(pHandle, pShellcode, 0, MEM_RELEASE); 203 | 204 | return data; 205 | } 206 | } 207 | 208 | #endif 209 | #pragma once 210 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const memoryjs = require('./build/Release/memoryjs'); 3 | const Debugger = require('./src/debugger'); 4 | const constants = require('./src/consts'); 5 | const { STRUCTRON_TYPE_STRING } = require('./src/utils'); 6 | 7 | /* TODO: 8 | * - remove callbacks from all functions and implement promise support using Napi 9 | * - validate argument types in JS space instead of C++ 10 | * - refactor read/write memory functions to use buffers instead? 11 | * - remove `closeProcess` in favour of `closeHandle` 12 | */ 13 | 14 | function openProcess(processIdentifier, callback) { 15 | if (arguments.length === 1) { 16 | return memoryjs.openProcess(processIdentifier); 17 | } 18 | 19 | return memoryjs.openProcess(processIdentifier, callback); 20 | } 21 | 22 | function closeProcess(handle) { 23 | return memoryjs.closeHandle(handle); 24 | } 25 | 26 | function getProcesses(callback) { 27 | if (arguments.length === 0) { 28 | return memoryjs.getProcesses(); 29 | } 30 | 31 | return memoryjs.getProcesses(callback); 32 | } 33 | 34 | function findModule(moduleName, processId, callback) { 35 | if (arguments.length === 2) { 36 | return memoryjs.findModule(moduleName, processId); 37 | } 38 | 39 | return memoryjs.findModule(moduleName, processId, callback); 40 | } 41 | 42 | function getModules(processId, callback) { 43 | if (arguments.length === 1) { 44 | return memoryjs.getModules(processId); 45 | } 46 | 47 | return memoryjs.getModules(processId, callback); 48 | } 49 | 50 | function readMemory(handle, address, dataType, callback) { 51 | if (dataType.toLowerCase().endsWith('_be')) { 52 | return readMemoryBE(handle, address, dataType, callback); 53 | } 54 | 55 | if (arguments.length === 3) { 56 | return memoryjs.readMemory(handle, address, dataType.toLowerCase()); 57 | } 58 | 59 | return memoryjs.readMemory(handle, address, dataType.toLowerCase(), callback); 60 | } 61 | 62 | function readMemoryBE(handle, address, dataType, callback) { 63 | let value = null; 64 | 65 | switch (dataType) { 66 | case constants.INT64_BE: 67 | value = readBuffer(handle, address, 8).readBigInt64BE(); 68 | break; 69 | 70 | case constants.UINT64_BE: 71 | value = readBuffer(handle, address, 8).readBigUInt64BE(); 72 | break; 73 | 74 | case constants.INT32_BE: 75 | case constants.INT_BE: 76 | case constants.LONG_BE: 77 | value = readBuffer(handle, address, 4).readInt32BE(); 78 | break; 79 | 80 | case constants.UINT32_BE: 81 | case constants.UINT_BE: 82 | case constants.ULONG_BE: 83 | value = readBuffer(handle, address, 4).readUInt32BE(); 84 | break; 85 | 86 | case constants.INT16_BE: 87 | case constants.SHORT_BE: 88 | value = readBuffer(handle, address, 2).readInt16BE(); 89 | break; 90 | 91 | case constants.UINT16_BE: 92 | case constants.USHORT_BE: 93 | value = readBuffer(handle, address, 2).readUInt16BE(); 94 | break; 95 | 96 | case constants.FLOAT_BE: 97 | value = readBuffer(handle, address, 4).readFloatBE(); 98 | break; 99 | 100 | case constants.DOUBLE_BE: 101 | value = readBuffer(handle, address, 8).readDoubleBE(); 102 | break; 103 | } 104 | 105 | if (typeof callback !== 'function') { 106 | if (value === null) { 107 | throw new Error('Invalid data type argument!'); 108 | } 109 | 110 | return value; 111 | } 112 | 113 | callback(value === null ? 'Invalid data type argument!' : '', value); 114 | } 115 | 116 | function readBuffer(handle, address, size, callback) { 117 | if (arguments.length === 3) { 118 | return memoryjs.readBuffer(handle, address, size); 119 | } 120 | 121 | return memoryjs.readBuffer(handle, address, size, callback); 122 | } 123 | 124 | function writeMemory(handle, address, value, dataType) { 125 | let dataValue = value; 126 | if (dataType === constants.STR || dataType === constants.STRING) { 127 | dataValue += '\0'; // add terminator 128 | } 129 | 130 | const bigintTypes = [constants.INT64, constants.INT64_BE, constants.UINT64, constants.UINT64_BE]; 131 | if (bigintTypes.indexOf(dataType) != -1 && typeof value !== 'bigint') { 132 | throw new Error(`${dataType.toUpperCase()} expects type BigInt`); 133 | } 134 | 135 | if (dataType.endsWith('_be')) { 136 | return writeMemoryBE(handle, address, dataValue, dataType); 137 | } 138 | 139 | return memoryjs.writeMemory(handle, address, dataValue, dataType.toLowerCase()); 140 | } 141 | 142 | function writeMemoryBE(handle, address, value, dataType) { 143 | let buffer = null; 144 | 145 | switch (dataType) { 146 | case constants.INT64_BE: 147 | if (typeof value !== 'bigint') { 148 | throw new Error('INT64_BE expects type BigInt'); 149 | } 150 | buffer = Buffer.alloc(8); 151 | buffer.writeBigInt64BE(value); 152 | break; 153 | 154 | case constants.UINT64_BE: 155 | if (typeof value !== 'bigint') { 156 | throw new Error('UINT64_BE expects type BigInt'); 157 | } 158 | buffer = Buffer.alloc(8); 159 | buffer.writeBigUInt64BE(value); 160 | break; 161 | 162 | case constants.INT32_BE: 163 | case constants.INT_BE: 164 | case constants.LONG_BE: 165 | buffer = Buffer.alloc(4); 166 | buffer.writeInt32BE(value); 167 | break; 168 | 169 | case constants.UINT32_BE: 170 | case constants.UINT_BE: 171 | case constants.ULONG_BE: 172 | buffer = Buffer.alloc(4); 173 | buffer.writeUInt32BE(value); 174 | break; 175 | 176 | case constants.INT16_BE: 177 | case constants.SHORT_BE: 178 | buffer = Buffer.alloc(2); 179 | buffer.writeInt16BE(value); 180 | break; 181 | 182 | case constants.UINT16_BE: 183 | case constants.USHORT_BE: 184 | buffer = Buffer.alloc(2); 185 | buffer.writeUInt16BE(value); 186 | break; 187 | 188 | case constants.FLOAT_BE: 189 | buffer = Buffer.alloc(4); 190 | buffer.writeFloatBE(value); 191 | break; 192 | 193 | case constants.DOUBLE_BE: 194 | buffer = Buffer.alloc(8); 195 | buffer.writeDoubleBE(value); 196 | break; 197 | } 198 | 199 | if (buffer == null) { 200 | throw new Error('Invalid data type argument!'); 201 | } 202 | 203 | writeBuffer(handle, address, buffer); 204 | } 205 | 206 | function writeBuffer(handle, address, buffer) { 207 | return memoryjs.writeBuffer(handle, address, buffer); 208 | } 209 | 210 | function findPattern() { 211 | const findPattern = ['number', 'string', 'number', 'number'].toString(); 212 | const findPatternByModule = ['number', 'string', 'string', 'number', 'number'].toString(); 213 | const findPatternByAddress = ['number', 'number', 'string', 'number', 'number'].toString(); 214 | 215 | const args = Array.from(arguments).map(arg => typeof arg); 216 | 217 | if (args.slice(0, 4).toString() === findPattern) { 218 | if (args.length === 4 || (args.length === 5 && args[4] === 'function')) { 219 | return memoryjs.findPattern(...arguments); 220 | } 221 | } 222 | 223 | if (args.slice(0, 5).toString() === findPatternByModule) { 224 | if (args.length === 5 || (args.length === 6 && args[5] === 'function')) { 225 | return memoryjs.findPatternByModule(...arguments); 226 | } 227 | } 228 | 229 | if (args.slice(0, 5).toString() === findPatternByAddress) { 230 | if (args.length === 5 || (args.length === 6 && args[5] === 'function')) { 231 | return memoryjs.findPatternByAddress(...arguments); 232 | } 233 | } 234 | 235 | throw new Error('invalid arguments!'); 236 | } 237 | 238 | function callFunction(handle, args, returnType, address, callback) { 239 | if (arguments.length === 4) { 240 | return memoryjs.callFunction(handle, args, returnType, address); 241 | } 242 | 243 | return memoryjs.callFunction(handle, args, returnType, address, callback); 244 | } 245 | 246 | function virtualAllocEx(handle, address, size, allocationType, protection, callback) { 247 | if (arguments.length === 5) { 248 | return memoryjs.virtualAllocEx(handle, address, size, allocationType, protection); 249 | } 250 | 251 | return memoryjs.virtualAllocEx(handle, address, size, allocationType, protection, callback); 252 | } 253 | 254 | function virtualProtectEx(handle, address, size, protection, callback) { 255 | if (arguments.length === 4) { 256 | return memoryjs.virtualProtectEx(handle, address, size, protection); 257 | } 258 | 259 | return memoryjs.virtualProtectEx(handle, address, size, protection, callback); 260 | } 261 | 262 | function getRegions(handle, getOffsets, callback) { 263 | if (arguments.length === 1) { 264 | return memoryjs.getRegions(handle); 265 | } 266 | 267 | return memoryjs.getRegions(handle, callback); 268 | } 269 | 270 | function virtualQueryEx(handle, address, callback) { 271 | if (arguments.length === 2) { 272 | return memoryjs.virtualQueryEx(handle, address); 273 | } 274 | 275 | return memoryjs.virtualQueryEx(handle, address, callback); 276 | } 277 | 278 | function injectDll(handle, dllPath, callback) { 279 | if (!dllPath.endsWith('.dll')) { 280 | throw new Error("Given path is invalid: file is not of type 'dll'."); 281 | } 282 | 283 | if (!fs.existsSync(dllPath)) { 284 | throw new Error('Given path is invaild: file does not exist.'); 285 | } 286 | 287 | if (arguments.length === 2) { 288 | return memoryjs.injectDll(handle, dllPath); 289 | } 290 | 291 | return memoryjs.injectDll(handle, dllPath, callback); 292 | } 293 | 294 | function unloadDll(handle, module, callback) { 295 | if (arguments.length === 2) { 296 | return memoryjs.unloadDll(handle, module); 297 | } 298 | 299 | return memoryjs.unloadDll(handle, module, callback); 300 | } 301 | 302 | function openFileMapping(fileName) { 303 | if (arguments.length !== 1 || typeof fileName !== 'string') { 304 | throw new Error('invalid arguments!'); 305 | } 306 | 307 | return memoryjs.openFileMapping(fileName); 308 | } 309 | 310 | function mapViewOfFile(processHandle, fileHandle, offset, viewSize, pageProtection) { 311 | const validArgs = [ 312 | ['number', 'number'], 313 | ['number', 'number', 'number', 'number', 'number'], 314 | ['number', 'number', 'bigint', 'bigint', 'number'] 315 | ]; 316 | const receivedArgs = Array.from(arguments).map(arg => typeof arg); 317 | 318 | if (!validArgs.some(args => args.join(",") == receivedArgs.join(","))) { 319 | throw new Error('invalid arguments!'); 320 | } 321 | 322 | if (arguments.length == 2) { 323 | return memoryjs.mapViewOfFile(processHandle, fileHandle, 0, 0, constants.PAGE_READONLY); 324 | } 325 | 326 | return memoryjs.mapViewOfFile(processHandle, fileHandle, offset, viewSize, pageProtection); 327 | } 328 | 329 | const library = { 330 | openProcess, 331 | closeProcess, 332 | getProcesses, 333 | findModule, 334 | getModules, 335 | readMemory, 336 | readBuffer, 337 | writeMemory, 338 | writeBuffer, 339 | findPattern, 340 | callFunction, 341 | virtualAllocEx, 342 | virtualProtectEx, 343 | getRegions, 344 | virtualQueryEx, 345 | injectDll, 346 | unloadDll, 347 | openFileMapping, 348 | mapViewOfFile, 349 | attachDebugger: memoryjs.attachDebugger, 350 | detachDebugger: memoryjs.detachDebugger, 351 | awaitDebugEvent: memoryjs.awaitDebugEvent, 352 | handleDebugEvent: memoryjs.handleDebugEvent, 353 | setHardwareBreakpoint: memoryjs.setHardwareBreakpoint, 354 | removeHardwareBreakpoint: memoryjs.removeHardwareBreakpoint, 355 | Debugger: new Debugger(memoryjs), 356 | }; 357 | 358 | module.exports = { 359 | ...constants, 360 | ...library, 361 | STRUCTRON_TYPE_STRING: STRUCTRON_TYPE_STRING(library), 362 | }; 363 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 | memoryjs is a an NPM package to read and write process memory! 5 |

6 | 7 |

8 | GitHub License 9 | NPM Version 10 | NPM Downloads 11 |

12 | 13 | --- 14 | 15 |

16 | Features • 17 | Getting Started • 18 | Usage • 19 | Documentation • 20 | Debug 21 |

22 | 23 | 24 | # Features 25 | 26 | - List all open processes 27 | - List all modules associated with a process 28 | - Close process/file handles 29 | - Find a specific module within a process 30 | - Read and write process memory (w/big-endian support) 31 | - Read and write buffers (arbitrary structs) 32 | - Change memory protection 33 | - Reserve/allocate, commit or change regions of memory 34 | - Fetch a list of memory regions within a process 35 | - Pattern scanning 36 | - Execute a function within a process 37 | - Hardware breakpoints (find out what accesses/writes to this address, etc) 38 | - Inject & unload DLLs 39 | - Read memory mapped files 40 | 41 | TODO: 42 | - WriteFile support (for driver interactions) 43 | - Async/await support 44 | 45 | # Getting Started 46 | 47 | ## Install 48 | 49 | This is a Node add-on (last tested to be working on `v14.15.0`) and therefore requires [node-gyp](https://github.com/nodejs/node-gyp) to use. 50 | 51 | You may also need to [follow these steps](https://github.com/nodejs/node-gyp#user-content-installation) to install and setup `node-gyp`. 52 | 53 | ```bash 54 | npm install memoryjs 55 | ``` 56 | 57 | When using memoryjs, the target process should match the platform architecture of the Node version running. 58 | For example if you want to target a 64 bit process, you should try and use a 64 bit version of Node. 59 | 60 | You also need to recompile the library and target the platform you want. Head to the memoryjs node module directory, open up a terminal and run one of the following compile scripts: 61 | 62 | ```bash 63 | # will automatically compile based on the detected Node architecture 64 | npm run build 65 | 66 | # compile to target 32 bit processes 67 | npm run build32 68 | 69 | # compile to target 64 bit processes 70 | npm run build64 71 | ``` 72 | 73 | ## Node Webkit / Electron 74 | 75 | If you are planning to use this module with Node Webkit or Electron, take a look at [Liam Mitchell](https://github.com/LiamKarlMitchell)'s build notes [here](https://github.com/Rob--/memoryjs/issues/23). 76 | 77 | # Usage 78 | 79 | ## Initialise 80 | ``` javascript 81 | const memoryjs = require('memoryjs'); 82 | const processName = "csgo.exe"; 83 | ``` 84 | 85 | ## Processes 86 | - Open a process 87 | - Get all processes 88 | - Close a process (release handle) 89 | 90 | ```javascript 91 | // sync: open a process 92 | const processObject = memoryjs.openProcess(processName); 93 | 94 | // async: open a process 95 | memoryjs.openProcess(processName, (error, processObject) => {}); 96 | 97 | 98 | // sync: get all processes 99 | const processes = memoryjs.getProcesses(); 100 | 101 | // async: get all processes 102 | memoryjs.getProcesses((error, processes) => {}); 103 | 104 | 105 | // close a process (release handle) 106 | memoryjs.closeHandle(handle); 107 | ``` 108 | 109 | See the [Documentation](#user-content-process-object) section of this README to see what a process object looks like. 110 | 111 | ## Modules 112 | - Find a module 113 | - Get all modules 114 | 115 | ``` javascript 116 | // sync: find a module 117 | const moduleObject = memoryjs.findModule(moduleName, processId); 118 | 119 | // async: find a module 120 | memoryjs.findModule(moduleName, processId, (error, moduleObject) => {}); 121 | 122 | 123 | // sync: get all modules 124 | const modules = memoryjs.getModules(processId); 125 | 126 | // async: get all modules 127 | memoryjs.getModules(processId, (error, modules) => {}); 128 | ``` 129 | 130 | See the [Documentation](#user-content-module-object) section of this README to see what a module object looks like. 131 | 132 | ## Memory 133 | - Read data type from memory 134 | - Read buffer from memory 135 | - Write data type to memory 136 | - Write buffer to memory 137 | - Fetch memory regions 138 | 139 | ``` javascript 140 | // sync: read data type from memory 141 | const value = memoryjs.readMemory(handle, address, dataType); 142 | 143 | // async: read data type from memory 144 | memoryjs.readMemory(handle, address, dataType, (error, value) => {}); 145 | 146 | 147 | // sync: read buffer from memory 148 | const buffer = memoryjs.readBuffer(handle, address, size); 149 | 150 | // async: read buffer from memory 151 | memoryjs.readBuffer(handle, address, size, (error, buffer) => {}); 152 | 153 | 154 | // sync: write data type to memory 155 | memoryjs.writeMemory(handle, address, value, dataType); 156 | 157 | 158 | // sync: write buffer to memory 159 | memoryjs.writeBuffer(handle, address, buffer); 160 | 161 | 162 | // sync: fetch memory regions 163 | const regions = memoryjs.getRegions(handle); 164 | 165 | // async: fetch memory regions 166 | memoryjs.getRegions(handle, (regions) => {}); 167 | ``` 168 | 169 | See the [Documentation](#user-content-documentation) section of this README to see what values `dataType` can be. 170 | 171 | ## Memory Mapped Files 172 | - Open a named file mapping object 173 | - Map a view of a file into a specified process 174 | - Close handle to the file mapping object 175 | 176 | ```javascript 177 | // sync: open a named file mapping object 178 | const fileHandle = memoryjs.openFileMapping(fileName); 179 | 180 | 181 | // sync: map entire file into a specified process 182 | const baseAddress = memoryjs.mapViewOfFile(processHandle, fileName); 183 | 184 | 185 | // sync: map portion of a file into a specified process 186 | const baseAddress = memoryjs.mapViewOfFile(processHandle, fileName, offset, viewSize, pageProtection); 187 | 188 | 189 | // sync: close handle to a file mapping object 190 | const success = memoryjs.closeHandle(fileHandle); 191 | ``` 192 | 193 | See the [Documentation](#user-content-documentation) section of this README to see details on the parameters and return values for these functions. 194 | 195 | ## Protection 196 | - Change/set the protection on a region of memory 197 | 198 | ```javascript 199 | // sync: change/set the protection on a region of memory 200 | const oldProtection = memoryjs.virtualProtectEx(handle, address, size, protection); 201 | ``` 202 | 203 | See the [Documentation](#user-content-protection-type) section of this README to see what values `protection` can be. 204 | 205 | ## Pattern Scanning 206 | - Pattern scan all modules and memory regions 207 | - Pattern scan a given module 208 | - Pattern scan a memory region or module at the given base address 209 | 210 | ```javascript 211 | // sync: pattern scan all modules and memory regions 212 | const address = memoryjs.findPattern(handle, pattern, flags, patternOffset); 213 | 214 | // async: pattern scan all modules and memory regions 215 | memoryjs.findPattern(handle, pattern, flags, patternOffset, (error, address) => {}); 216 | 217 | 218 | // sync: pattern scan a given module 219 | const address = memoryjs.findPattern(handle, moduleName, pattern, flags, patternOffset); 220 | 221 | // async: pattern scan a given module 222 | memoryjs.findPattern(handle, moduleName, pattern, flags, patternOffset, (error, address) => {}); 223 | 224 | 225 | // sync: pattern scan a memory region or module at the given base address 226 | const address = memoryjs.findPattern(handle, baseAddress, pattern, flags, patternOffset); 227 | 228 | // async: pattern scan a memory region or module at the given base address 229 | memoryjs.findPattern(handle, baseAddress, pattern, flags, patternOffset, (error, address) => {}); 230 | ``` 231 | 232 | ## Function Execution 233 | - Execute a function in a remote process 234 | 235 | ``` javascript 236 | // sync: execute a function in a remote process 237 | const result = memoryjs.callFunction(handle, args, returnType, address); 238 | 239 | // async: execute a function in a remote process 240 | memoryjs.callFunction(handle, args, returnType, address, (error, result) => {}); 241 | ``` 242 | 243 | Click [here](#user-content-result-object) to see what a result object looks like. 244 | 245 | Click [here](#user-content-function-execution-1) for details about how to format the arguments and the return type. 246 | 247 | ## DLL Injection 248 | - Inject a DLL 249 | - Unload a DLL by module base address 250 | - Unload a DLL by module name 251 | 252 | ```javascript 253 | // sync: inject a DLL 254 | const success = memoryjs.injectDll(handle, dllPath); 255 | 256 | // async: inject a DLL 257 | memoryjs.injectDll(handle, dllPath, (error, success) => {}); 258 | 259 | 260 | // sync: unload a DLL by module base address 261 | const success = memoryjs.unloadDll(handle, moduleBaseAddress); 262 | 263 | // async: unload a DLL by module base address 264 | memoryjs.unloadDll(handle, moduleBaseAddress, (error, success) => {}); 265 | 266 | 267 | // sync: unload a DLL by module name 268 | const success = memoryjs.unloadDll(handle, moduleName); 269 | 270 | // async: unload a DLL by module name 271 | memoryjs.unloadDll(handle, moduleName, (error, success) => {}); 272 | ``` 273 | 274 | ## Hardware Breakpoints 275 | - Attach debugger 276 | - Detach debugger 277 | - Wait for debug event 278 | - Handle debug event 279 | - Set hardware breakpoint 280 | - Remove hardware breakpoint 281 | 282 | ``` javascript 283 | // sync: attach debugger 284 | const success = memoryjs.attachDebugger(processId, exitOnDetach); 285 | 286 | // sync: detach debugger 287 | const success = memoryjs.detachDebugger(processId); 288 | 289 | // sync: wait for debug event 290 | const success = memoryjs.awaitDebugEvent(hardwareRegister, millisTimeout); 291 | 292 | // sync: handle debug event 293 | const success = memoryjs.handleDebugEvent(processId, threadId); 294 | 295 | // sync: set hardware breakpoint 296 | const success = memoryjs.setHardwareBreakpoint(processId, address, hardwareRegister, trigger, length); 297 | 298 | // sync: remove hardware breakpoint 299 | const success = memoryjs.removeHardwareBreakpoint(processId, hardwareRegister); 300 | ``` 301 | 302 | # Documentation 303 | 304 | Note: this documentation is currently being updated, refer to the [Wiki](https://github.com/Rob--/memoryjs/wiki) for more information. 305 | 306 | ## Process Object 307 | ``` javascript 308 | { dwSize: 304, 309 | th32ProcessID: 10316, 310 | cntThreads: 47, 311 | th32ParentProcessID: 7804, 312 | pcPriClassBase: 8, 313 | szExeFile: "csgo.exe", 314 | modBaseAddr: 1673789440, 315 | handle: 808 } 316 | ``` 317 | 318 | The `handle` and `modBaseAddr` properties are only available when opening a process and not when listing processes. 319 | 320 | ## Module Object 321 | ``` javascript 322 | { modBaseAddr: 468123648, 323 | modBaseSize: 80302080, 324 | szExePath: 'c:\\program files (x86)\\steam\\steamapps\\common\\counter-strike global offensive\\csgo\\bin\\client.dll', 325 | szModule: 'client.dll', 326 | th32ProcessID: 10316, 327 | GlblcntUsage: 2 } 328 | ``` 329 | 330 | ## Result Object 331 | ``` javascript 332 | { returnValue: 1.23, 333 | exitCode: 2 } 334 | ``` 335 | 336 | This object is returned when a function is executed in a remote process: 337 | - `returnValue` is the value returned from the function that was called 338 | - `exitCode` is the termination status of the thread 339 | 340 | ## Data Types 341 | 342 | When using the write or read functions, the data type (dataType) parameter should reference a constant from within the library: 343 | 344 | | Constant | Bytes | Aliases | Range | 345 | |-------------------|-------|------------------------------------|-------| 346 | | `memoryjs.BOOL` | 1 | `memoryjs.BOOLEAN` | 0 to 1 | 347 | | `memoryjs.INT8` | 1 | `memoryjs.BYTE`, `memoryjs.CHAR` | -128 to 127 | 348 | | `memoryjs.UINT8` | 1 | `memoryjs.UBYTE`, `memoryjs.UCHAR` | 0 to 255 | 349 | | `memoryjs.INT16` | 2 | `memoryjs.SHORT` | -32,768 to 32,767 | 350 | | `memoryjs.UINT16` | 2 | `memoryjs.USHORT`, `memoryjs.WORD` | 0 to 65,535 | 351 | | `memoryjs.INT32` | 4 | `memoryjs.INT`, `memoryjs.LONG` | -2,147,483,648 to 2,147,483,647 | 352 | | `memoryjs.UINT32` | 4 | `memoryjs.UINT`, `memoryjs.ULONG`, `memoryjs.DWORD` | 0 to 4,294,967,295 | 353 | | `memoryjs.INT64` | 8 | n/a | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | 354 | | `memoryjs.UINT64` | 8 | n/a | 0 to 18,446,744,073,709,551,615 | 355 | | `memoryjs.FLOAT` | 4 | n/a | 3.4E +/- 38 (7 digits) | 356 | | `memoryjs.DOUBLE` | 8 | n/a | 1.7E +/- 308 (15 digits) | 357 | | `memoryjs.PTR` | 4/8 | `memoryjs.POINTER` | n/a | 358 | | `memoryjs.UPTR` | 4/8 | `memoryjs.UPOINTER` | n/a | 359 | | `memoryjs.STR` | n/a | `memoryjs.STRING` | n/a | 360 | | `memoryjs.VEC3` | 12 | `memoryjs.VECTOR3` | n/a | 361 | | `memoryjs.VEC4` | 16 | `memoryjs.VECTOR4` | n/a | 362 | 363 | 364 | Notes: 365 | - all functions that accept an address also accept the address as a BigInt 366 | - pointer will be 4 bytes in a 32 bit build, and 8 bytes in a 64 bit build. 367 | - to read in big-endian mode, append `_BE` to the data type. For example: `memoryjs.DOUBLE_BE`. 368 | - when writing 64 bit integers (`INT64`, `UINT64`, `INT64_BE`, `UINT64_BE`) you will need to supply a [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). When reading a 64 bit integer, you will receive a BigInt. 369 | 370 | These data types are to used to denote the type of data being read or written. 371 | 372 | 64 bit integer example: 373 | ```javascript 374 | const value = memoryjs.readMemory(handle, address, memoryjs.INT64); 375 | console.log(typeof value); // bigint 376 | memoryjs.writeMemory(handle, address, value + 1n, memoryjs.INT64); 377 | ``` 378 | 379 | Vector3 is a data structure of three floats: 380 | ```javascript 381 | const vector3 = { x: 0.0, y: 0.0, z: 0.0 }; 382 | memoryjs.writeMemory(handle, address, vector3, memoryjs.VEC3); 383 | ``` 384 | 385 | Vector4 is a data structure of four floats: 386 | ```javascript 387 | const vector4 = { w: 0.0, x: 0.0, y: 0.0, z: 0.0 }; 388 | memoryjs.writeMemory(handle, address, vector4, memoryjs.VEC4); 389 | ``` 390 | 391 | ## Generic Structures 392 | 393 | If you have a structure you want to write to memory, you can use buffers. For an example on how to do this, view the [buffers example](https://github.com/Rob--/memoryjs/blob/master/examples/buffers.js). 394 | 395 | To write/read a structure to/from memory, you can use [structron](https://github.com/LordVonAdel/structron) to define your structures and use them to write or parse buffers. 396 | 397 | If you want to read a `std::string` using `structron`, the library exposes a custom type that can be used to read/write strings: 398 | ```javascript 399 | // To create the type, we need to pass the process handle, base address of the 400 | // structure, and the target process architecture (either "32" or "64"). 401 | const stringType = memoryjs.STRUCTRON_TYPE_STRING(processObject.handle, structAddress, '64'); 402 | 403 | // Create a custom structure using the custom type, full example in /examples/buffers.js 404 | const Struct = require('structron'); 405 | const Player = new Struct() 406 | .addMember(string, 'name'); 407 | ``` 408 | 409 | Alternatively, you can use the [concentrate](https://github.com/deoxxa/concentrate) and [dissolve](https://github.com/deoxxa/dissolve) libraries to achieve the same thing. An old example of this is [here](https://github.com/Rob--/memoryjs/blob/aa6ed7d302fb1ac315aaa90558db43d128746912/examples/buffers.js). 410 | 411 | ## Protection Type 412 | 413 | Protection type is a bit flag DWORD value. 414 | 415 | This parameter should reference a constant from the library: 416 | 417 | `memoryjs.PAGE_NOACCESS, memoryjs.PAGE_READONLY, memoryjs.PAGE_READWRITE, memoryjs.PAGE_WRITECOPY, memoryjs.PAGE_EXECUTE, memoryjs.PAGE_EXECUTE_READ, memoryjs.PAGE_EXECUTE_READWRITE, memoryjs.PAGE_EXECUTE_WRITECOPY, memoryjs.PAGE_GUARD, memoryjs.PAGE_NOCACHE, memoryjs.PAGE_WRITECOMBINE, memoryjs.PAGE_ENCLAVE_THREAD_CONTROL, memoryjs.PAGE_TARGETS_NO_UPDATE, memoryjs.PAGE_TARGETS_INVALID, memoryjs.PAGE_ENCLAVE_UNVALIDATED` 418 | 419 | Refer to MSDN's [Memory Protection Constants](https://docs.microsoft.com/en-gb/windows/desktop/Memory/memory-protection-constants) for more information. 420 | 421 | ## Memory Allocation Type 422 | 423 | Memory allocation type is a bit flag DWORD value. 424 | 425 | This parameter should reference a constat from the library: 426 | 427 | `memoryjs.MEM_COMMIT, memoryjs.MEM_RESERVE, memoryjs.MEM_RESET, memoryjs.MEM_RESET_UNDO` 428 | 429 | Refer to MSDN's [VirtualAllocEx](https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualallocex) documentation for more information. 430 | 431 | ## Strings 432 | 433 | You can use this library to read either a "string", or "char*" and to write a string. 434 | 435 | In both cases you want to get the address of the char array: 436 | 437 | ```c++ 438 | std::string str1 = "hello"; 439 | std::cout << "Address: 0x" << hex << (DWORD) str1.c_str() << dec << std::endl; 440 | 441 | char* str2 = "hello"; 442 | std::cout << "Address: 0x" << hex << (DWORD) str2 << dec << std::endl; 443 | ``` 444 | 445 | From here you can simply use this address to write and read memory. 446 | 447 | There is one caveat when reading a string in memory however, due to the fact that the library does not know 448 | how long the string is, it will continue reading until it finds the first null-terminator. To prevent an 449 | infinite loop, it will stop reading if it has not found a null-terminator after 1 million characters. 450 | 451 | One way to bypass this limitation in the future would be to allow a parameter to let users set the maximum 452 | character count. 453 | 454 | ### Signature Type 455 | 456 | When pattern scanning, flags need to be raised for the signature types. The signature type parameter needs to be one of the following: 457 | 458 | `0x0` or `memoryjs.NORMAL` which denotes a normal signature. 459 | 460 | `0x1` or `memoryjs.READ` which will read the memory at the address. 461 | 462 | `0x2` or `memoryjs.SUBSTRACT` which will subtract the image base from the address. 463 | 464 | To raise multiple flags, use the bitwise OR operator: `memoryjs.READ | memoryjs.SUBTRACT`. 465 | 466 | ## Memory Mapped Files 467 | 468 | The library exposes functions to map obtain a handle to and read a memory mapped file. 469 | 470 | **openFileMapping(fileName)** 471 | - *fileName*: name of the file mapping object to be opened 472 | - returns: handle to the file mapping object 473 | 474 | Refer to [MSDN's OpenFileMappingA](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-openfilemappinga) documentation for more information. 475 | 476 | **mapViewOfFile(processHandle, fileName)** 477 | - *processHandle*: the target process to map the file to 478 | - *fileHandle*: handle of the file mapping object, obtained by `memoryjs.openFileMapping` 479 | - Description: maps the entire file to target process' memory. Page protection defaults to `constants.PAGE_READONLY`. 480 | - Returns: the base address of the mapped file 481 | 482 | **mapViewOfFile(processHandle, fileName, offset, viewSize, pageProtection)** 483 | - *processHandle*: the target process to map the file to 484 | - *fileHandle*: handle of the file mapping object, obtained by `memoryjs.openFileMapping` 485 | - *offset* (`number` or `bigint`): the offset from the beginning of the file (has to be multiple of 64KB) 486 | - *viewSize* (`number` or `bigint`): the number of bytes to map (if `0`, the entire file will be read, regardless of offset) 487 | - *pageProtection*: desired page protection 488 | - Description: maps a view of the file to the target process' memory 489 | - Returns: the base address of the mapped file 490 | 491 | Refer to [MSDN's MapViewOfFile2](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile2) documentation for more information. 492 | 493 | See [Protection Type](#user-content-protection-type) for page protection types. 494 | 495 | ### Example 496 | We have a process that creates a file mapping: 497 | ```c++ 498 | HANDLE fileHandle = CreateFileA("C:\\foo.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 499 | HANDLE fileMappingHandle = CreateFileMappingA(fileHandle, NULL, PAGE_READONLY, 0, 0, "MappedFooFile"); 500 | ``` 501 | 502 | We can map the file to a specified target process and read the file with `memoryjs`: 503 | ```javascript 504 | const processObject = memoryjs.openProcess("example.exe"); 505 | const fileHandle = memoryjs.openFileMapping("MappedFooFile"); 506 | 507 | // read entire file 508 | const baseAddress = memoryjs.mapViewOfFile(processObject.handle, fileHandle.handle); 509 | const data = memoryjs.readMemory(processObject.handle, baseAddress, memoryjs.STR); 510 | 511 | // read 10 bytes after 64KB 512 | const baseAddress = memoryjs.mapViewOfFile(processObject.handle, fileHandle.handle, 65536, 10, constants.PAGE_READONLY); 513 | const buffer = memoryjs.readBuffer(processObject.handle, baseAddress, 10); 514 | const data = buffer.toString(); 515 | 516 | const success = memoryjs.closeHandle(fileHandle); 517 | ``` 518 | 519 | If you want to read a memory mapped file without having a target process to map the file to, you can map it to the current Node process with global variable `process.pid`: 520 | ```javascript 521 | const processObject = memoryjs.openProcess(process.pid); 522 | ``` 523 | 524 | ## Function Execution 525 | 526 | Remote function execution works by building an array of arguments and dynamically generating shellcode that is injected into the target process and executed, for this reason crashes may occur. 527 | 528 | To call a function in a process, the `callFunction` function can be used. The library supports passing arguments to the function and need to be in the following format: 529 | 530 | ```javascript 531 | [{ type: T_INT, value: 4 }] 532 | ``` 533 | 534 | The library expects the arguments to be an array of objects where each object has a `type` which denotes the data type of the argument, and a `value` which is the actual value of the argument. The various supported data types can be found below. 535 | 536 | 537 | ``` javascript 538 | memoryjs.T_VOID = 0x0, 539 | memoryjs.T_STRING = 0x1, 540 | memoryjs.T_CHAR = 0x2, 541 | memoryjs.T_BOOL = 0x3, 542 | memoryjs.T_INT = 0x4, 543 | memoryjs.T_DOUBLE = 0x5, 544 | memoryjs.T_FLOAT = 0x6, 545 | ``` 546 | 547 | When using `callFunction`, you also need to supply the return type of the function, which again needs to be one of the above values. 548 | 549 | For example, given the following C++ function: 550 | 551 | ``` c++ 552 | int add(int a, int b) { 553 | return a + b; 554 | } 555 | ``` 556 | 557 | You would call this function as so: 558 | 559 | ```javascript 560 | const args = [{ 561 | type: memoryjs.T_INT, 562 | value: 2, 563 | }, { 564 | type: memoryjs.T_INT, 565 | value: 5, 566 | }]; 567 | const returnType = T_INT; 568 | 569 | > memoryjs.callFunction(handle, args, returnType, address); 570 | { returnValue: 7, exitCode: 7 } 571 | ``` 572 | 573 | See the [result object documentation](user-content-result-object) for details on what `callFunction` returns. 574 | 575 | Notes: currently passing a `double` as an argument is not supported, but returning one is. 576 | 577 | Much thanks to the [various contributors](https://github.com/Rob--/memoryjs/issues/6) that made this feature possible. 578 | 579 | ## Hardware Breakpoints 580 | 581 | Hardware breakpoints work by attaching a debugger to the process, setting a breakpoint on a certain address and declaring a trigger type (e.g. breakpoint on writing to the address) and then continuously waiting for a debug event to arise (and then consequently handling it). 582 | 583 | This library exposes the main functions, but also includes a wrapper class to simplify the process. For a complete code example, checkout our [debugging example](https://github.com/Rob--/memoryjs/blob/master/examples/debugging.js). 584 | 585 | When setting a breakpoint, you are required to pass a trigger type: 586 | - `memoryjs.TRIGGER_ACCESS` - breakpoint occurs when the address is accessed 587 | - `memoryjs.TRIGGER_WRITE` - breakpoint occurs when the address is written to 588 | 589 | Do note that when monitoring an address containing a string, the `size` parameter of the `setHardwareBreakpoint` function should be the length of the string. When using the `Debugger` wrapper class, the wrapper will automatically determine the size of the string by attempting to read it. 590 | 591 | To summarise: 592 | - When using the `Debugger` class: 593 | - No need to pass the `size` parameter to `setHardwareBreakpoint` 594 | - No need to manually pick a hardware register 595 | - Debug events are picked up via an event listener 596 | - `setHardwareBreakpoint` returns the register that was used for the breakpoint 597 | 598 | - When manually using the debugger functions: 599 | - The `size` parameter is the size of the variable in memory (e.g. int32 = 4 bytes). For a string, this parameter is the length of the string 600 | - Manually need to pick a hardware register (via `memoryjs.DR0` through `memoryhs.DR3`). Only 4 hardware registers are available (some CPUs may even has less than 4 available). This means only 4 breakpoints can be set at any given time 601 | - Need to manually wait for debug and handle debug events 602 | - `setHardwareBreakpoint` returns a boolean stating whether the operation as successful 603 | 604 | For more reading about debugging and hardware breakpoints, checkout the following links: 605 | - [DebugActiveProcess](https://msdn.microsoft.com/en-us/library/windows/desktop/ms679295(v=vs.85).aspx) - attaching the debugger 606 | - [DebugSetProcessKillOnExit](https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-debugsetprocesskillonexit) - kill the process when detaching 607 | - [DebugActiveProcessStop](https://msdn.microsoft.com/en-us/library/windows/desktop/ms679296(v=vs.85).aspx) - detaching the debugger 608 | - [WaitForDebugEvent](https://msdn.microsoft.com/en-us/library/windows/desktop/ms681423(v=vs.85).aspx) - waiting for the breakpoint to be triggered 609 | - [ContinueDebugEvent](https://msdn.microsoft.com/en-us/library/windows/desktop/ms679285(v=vs.85).aspx) - handling the event 610 | 611 | ### Using the Debugger Wrapper: 612 | 613 | The Debugger wrapper contains these functions you should use: 614 | 615 | ``` javascript 616 | class Debugger { 617 | attach(processId, killOnDetach = false); 618 | detach(processId); 619 | setHardwareBreakpoint(processId, address, trigger, dataType); 620 | removeHardwareBreakpoint(processId, register); 621 | } 622 | ``` 623 | 624 | 1. Attach the debugger 625 | ``` javascript 626 | const hardwareDebugger = memoryjs.Debugger; 627 | hardwareDebugger.attach(processId); 628 | ``` 629 | 630 | 2. Set a hardware breakpoint 631 | ``` javascript 632 | const address = 0xDEADBEEF; 633 | const trigger = memoryjs.TRIGGER_ACCESS; 634 | const dataType = memoryjs.INT; 635 | const register = hardwareDebugger.setHardwareBreakpoint(processId, address, trigger, dataType); 636 | ``` 637 | 638 | 3. Create an event listener for debug events (breakpoints) 639 | ``` javascript 640 | // `debugEvent` event emission catches debug events from all registers 641 | hardwareDebugger.on('debugEvent', ({ register, event }) => { 642 | console.log(`Hardware Register ${register} breakpoint`); 643 | console.log(event); 644 | }); 645 | 646 | // You can listen to debug events from specific hardware registers 647 | // by listening to whatever register was returned from `setHardwareBreakpoint` 648 | hardwareDebugger.on(register, (event) => { 649 | console.log(event); 650 | }); 651 | ``` 652 | 653 | ### When Manually Debugging: 654 | 655 | 1. Attach the debugger 656 | ``` javascript 657 | const hardwareDebugger = memoryjs.Debugger; 658 | hardwareDebugger.attach(processId); 659 | ``` 660 | 661 | 2. Set a hardware breakpoint (determine which register to use and the size of the data type) 662 | ``` javascript 663 | // available registers: DR0 through DR3 664 | const register = memoryjs.DR0; 665 | // int = 4 bytes 666 | const size = 4; 667 | 668 | const address = 0xDEADBEEF; 669 | const trigger = memoryjs.TRIGGER_ACCESS; 670 | const dataType = memoryjs.INT; 671 | 672 | const success = memoryjs.setHardwareBreakpoint(processId, address, register, trigger, size); 673 | ``` 674 | 675 | 3. Create the await/handle debug event loop 676 | ``` javascript 677 | const timeout = 100; 678 | 679 | setInterval(() => { 680 | // `debugEvent` can be null if no event occurred 681 | const debugEvent = memoryjs.awaitDebugEvent(register, timeout); 682 | 683 | // If a breakpoint occurred, handle it 684 | if (debugEvent) { 685 | memoryjs.handleDebugEvent(debugEvent.processId, debugEvent.threadId); 686 | } 687 | }, timeout); 688 | ``` 689 | 690 | Note: a loop is not required, e.g. no loop required if you want to simply wait until the first detection of the address being accessed or written to. 691 | 692 | # Debug 693 | 694 | ### 1. Re-compile the project to be debugged 695 | 696 | Go to the root directory of the module and run one of the following commands: 697 | ```bash 698 | # will automatically compile based on the detected Node architecture 699 | npm run debug 700 | 701 | # compile to target 32 bit processes 702 | npm run debug32 703 | 704 | # compile to target 64 bit processes 705 | npm run debug64 706 | ``` 707 | 708 | ### 2. Change the `index.js` file to require the debug module 709 | 710 | Go to the root directory and change the line in `index.js` from: 711 | ```javascript 712 | const memoryjs = require('./build/Release/memoryjs'); 713 | ``` 714 | 715 | To the following: 716 | ```javascript 717 | const memoryjs = require('./build/Debug/memoryjs'); 718 | ``` 719 | 720 | ### 3. Open the project in Visual Studio 721 | 722 | Open the `binding.sln` solution in Visual Studio, found in the `build` folder in the project's root directory. 723 | 724 | ### 4. Setup Visual Studio debug configuration 725 | 726 | 1. In the toolbar, click "Project" then "Properties" 727 | 2. Under "Configuration Properties", click "Debugging" 728 | 3. Set the "Command" property to the location of your `node.exe` file (e.g. `C:\nodejs\node.exe`) 729 | 4. Set the "Command Arguments" property to the location of your script file (e.g. `C:\project\test.js`) 730 | 731 | ### 5. Set breakpoints 732 | 733 | Explore the project files in Visual Studio (by expanding `..` and then `lib` in the Solution Explorer). Header files can be viewed by holding `Alt` and clicking on the header file names at the top of the source code files. 734 | 735 | Breakpoints are set by clicking to the left of the line number. 736 | 737 | ### 6. Run the debugger 738 | 739 | Start debugging by either pressing `F5`, by clicking "Debug" in the toolbar and then "Start Debugging", or by clicking "Local Windows Debugger". 740 | 741 | The script you've set as the command argument in step 4 will be run, and Visual Studio will pause on the breakpoints set and allow you to step through the code line by line and inspect variables. 742 | -------------------------------------------------------------------------------- /lib/memoryjs.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "module.h" 6 | #include "process.h" 7 | #include "memoryjs.h" 8 | #include "memory.h" 9 | #include "pattern.h" 10 | #include "functions.h" 11 | #include "dll.h" 12 | #include "debugger.h" 13 | 14 | #pragma comment(lib, "psapi.lib") 15 | #pragma comment(lib, "onecore.lib") 16 | 17 | 18 | process Process; 19 | // module Module; 20 | memory Memory; 21 | pattern Pattern; 22 | // functions Functions; 23 | 24 | struct Vector3 { 25 | float x, y, z; 26 | }; 27 | 28 | struct Vector4 { 29 | float w, x, y, z; 30 | }; 31 | 32 | Napi::Value openProcess(const Napi::CallbackInfo& args) { 33 | Napi::Env env = args.Env(); 34 | 35 | if (args.Length() != 1 && args.Length() != 2) { 36 | Napi::Error::New(env, "requires 1 argument, or 2 arguments if a callback is being used").ThrowAsJavaScriptException(); 37 | return env.Null(); 38 | } 39 | 40 | if (!args[0].IsString() && !args[0].IsNumber()) { 41 | Napi::Error::New(env, "first argument must be a string or a number").ThrowAsJavaScriptException(); 42 | return env.Null(); 43 | } 44 | 45 | if (args.Length() == 2 && !args[1].IsFunction()) { 46 | Napi::Error::New(env, "second argument must be a function").ThrowAsJavaScriptException(); 47 | return env.Null(); 48 | } 49 | 50 | // Define error message that may be set by the function that opens the process 51 | char* errorMessage = ""; 52 | 53 | process::Pair pair; 54 | 55 | if (args[0].IsString()) { 56 | std::string processName(args[0].As().Utf8Value()); 57 | pair = Process.openProcess(processName.c_str(), &errorMessage); 58 | 59 | // In case it failed to open, let's keep retrying 60 | // while(!strcmp(process.szExeFile, "")) { 61 | // process = Process.openProcess((char*) *(processName), &errorMessage); 62 | // }; 63 | } 64 | 65 | if (args[0].IsNumber()) { 66 | pair = Process.openProcess(args[0].As().Uint32Value(), &errorMessage); 67 | 68 | // In case it failed to open, let's keep retrying 69 | // while(!strcmp(process.szExeFile, "")) { 70 | // process = Process.openProcess(info[0].As().Uint32Value(), &errorMessage); 71 | // }; 72 | } 73 | 74 | // If an error message was returned from the function that opens the process, throw the error. 75 | // Only throw an error if there is no callback (if there's a callback, the error is passed there). 76 | if (strcmp(errorMessage, "") && args.Length() != 2) { 77 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 78 | return env.Null(); 79 | } 80 | 81 | // Create a v8 Object (JSON) to store the process information 82 | Napi::Object processInfo = Napi::Object::New(env); 83 | 84 | processInfo.Set(Napi::String::New(env, "dwSize"), Napi::Value::From(env, (int)pair.process.dwSize)); 85 | processInfo.Set(Napi::String::New(env, "th32ProcessID"), Napi::Value::From(env, (int)pair.process.th32ProcessID)); 86 | processInfo.Set(Napi::String::New(env, "cntThreads"), Napi::Value::From(env, (int)pair.process.cntThreads)); 87 | processInfo.Set(Napi::String::New(env, "th32ParentProcessID"), Napi::Value::From(env, (int)pair.process.th32ParentProcessID)); 88 | processInfo.Set(Napi::String::New(env, "pcPriClassBase"), Napi::Value::From(env, (int)pair.process.pcPriClassBase)); 89 | processInfo.Set(Napi::String::New(env, "szExeFile"), Napi::String::New(env, pair.process.szExeFile)); 90 | processInfo.Set(Napi::String::New(env, "handle"), Napi::Value::From(env, (uintptr_t)pair.handle)); 91 | 92 | DWORD64 base = module::getBaseAddress(pair.process.szExeFile, pair.process.th32ProcessID); 93 | processInfo.Set(Napi::String::New(env, "modBaseAddr"), Napi::Value::From(env, (uintptr_t)base)); 94 | 95 | // openProcess can either take one argument or can take 96 | // two arguments for asychronous use (second argument is the callback) 97 | if (args.Length() == 2) { 98 | // Callback to let the user handle with the information 99 | Napi::Function callback = args[1].As(); 100 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), processInfo }); 101 | return env.Null(); 102 | } else { 103 | // return JSON 104 | return processInfo; 105 | } 106 | } 107 | 108 | Napi::Value closeHandle(const Napi::CallbackInfo& args) { 109 | Napi::Env env = args.Env(); 110 | BOOL success = CloseHandle((HANDLE)args[0].As().Int64Value()); 111 | return Napi::Boolean::New(env, success); 112 | } 113 | 114 | Napi::Value getProcesses(const Napi::CallbackInfo& args) { 115 | Napi::Env env = args.Env(); 116 | 117 | if (args.Length() > 1) { 118 | Napi::Error::New(env, "requires either 0 arguments or 1 argument if a callback is being used").ThrowAsJavaScriptException(); 119 | return env.Null(); 120 | } 121 | 122 | if (args.Length() == 1 && !args[0].IsFunction()) { 123 | Napi::Error::New(env, "first argument must be a function").ThrowAsJavaScriptException(); 124 | return env.Null(); 125 | } 126 | 127 | // Define error message that may be set by the function that gets the processes 128 | char* errorMessage = ""; 129 | 130 | std::vector processEntries = Process.getProcesses(&errorMessage); 131 | 132 | // If an error message was returned from the function that gets the processes, throw the error. 133 | // Only throw an error if there is no callback (if there's a callback, the error is passed there). 134 | if (strcmp(errorMessage, "") && args.Length() != 1) { 135 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 136 | return env.Null(); 137 | } 138 | 139 | // Creates v8 array with the size being that of the processEntries vector processes is an array of JavaScript objects 140 | Napi::Array processes = Napi::Array::New(env, processEntries.size()); 141 | 142 | // Loop over all processes found 143 | for (std::vector::size_type i = 0; i != processEntries.size(); i++) { 144 | // Create a v8 object to store the current process' information 145 | Napi::Object process = Napi::Object::New(env); 146 | 147 | process.Set(Napi::String::New(env, "cntThreads"), Napi::Value::From(env, (int)processEntries[i].cntThreads)); 148 | process.Set(Napi::String::New(env, "szExeFile"), Napi::String::New(env, processEntries[i].szExeFile)); 149 | process.Set(Napi::String::New(env, "th32ProcessID"), Napi::Value::From(env, (int)processEntries[i].th32ProcessID)); 150 | process.Set(Napi::String::New(env, "th32ParentProcessID"), Napi::Value::From(env, (int)processEntries[i].th32ParentProcessID)); 151 | process.Set(Napi::String::New(env, "pcPriClassBase"), Napi::Value::From(env, (int)processEntries[i].pcPriClassBase)); 152 | 153 | // Push the object to the array 154 | processes.Set(i, process); 155 | } 156 | 157 | /* getProcesses can either take no arguments or one argument 158 | one argument is for asychronous use (the callback) */ 159 | if (args.Length() == 1) { 160 | // Callback to let the user handle with the information 161 | Napi::Function callback = args[0].As(); 162 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), processes }); 163 | return env.Null(); 164 | } else { 165 | // return JSON 166 | return processes; 167 | } 168 | } 169 | 170 | Napi::Value getModules(const Napi::CallbackInfo& args) { 171 | Napi::Env env = args.Env(); 172 | 173 | if (args.Length() != 1 && args.Length() != 2) { 174 | Napi::Error::New(env, "requires 1 argument, or 2 arguments if a callback is being used").ThrowAsJavaScriptException(); 175 | return env.Null(); 176 | } 177 | 178 | if (!args[0].IsNumber()) { 179 | Napi::Error::New(env, "first argument must be a number").ThrowAsJavaScriptException(); 180 | return env.Null(); 181 | } 182 | 183 | if (args.Length() == 2 && !args[1].IsFunction()) { 184 | Napi::Error::New(env, "first argument must be a number, second argument must be a function").ThrowAsJavaScriptException(); 185 | return env.Null(); 186 | } 187 | 188 | // Define error message that may be set by the function that gets the modules 189 | char* errorMessage = ""; 190 | 191 | std::vector moduleEntries = module::getModules(args[0].As().Int32Value(), &errorMessage); 192 | 193 | // If an error message was returned from the function getting the modules, throw the error. 194 | // Only throw an error if there is no callback (if there's a callback, the error is passed there). 195 | if (strcmp(errorMessage, "") && args.Length() != 2) { 196 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 197 | return env.Null(); 198 | } 199 | 200 | // Creates v8 array with the size being that of the moduleEntries vector 201 | // modules is an array of JavaScript objects 202 | Napi::Array modules = Napi::Array::New(env, moduleEntries.size()); 203 | 204 | // Loop over all modules found 205 | for (std::vector::size_type i = 0; i != moduleEntries.size(); i++) { 206 | // Create a v8 object to store the current module's information 207 | Napi::Object module = Napi::Object::New(env); 208 | 209 | module.Set(Napi::String::New(env, "modBaseAddr"), Napi::Value::From(env, (uintptr_t)moduleEntries[i].modBaseAddr)); 210 | module.Set(Napi::String::New(env, "modBaseSize"), Napi::Value::From(env, (int)moduleEntries[i].modBaseSize)); 211 | module.Set(Napi::String::New(env, "szExePath"), Napi::String::New(env, moduleEntries[i].szExePath)); 212 | module.Set(Napi::String::New(env, "szModule"), Napi::String::New(env, moduleEntries[i].szModule)); 213 | module.Set(Napi::String::New(env, "th32ProcessID"), Napi::Value::From(env, (int)moduleEntries[i].th32ProcessID)); 214 | module.Set(Napi::String::New(env, "GlblcntUsage"), Napi::Value::From(env, (int)moduleEntries[i].GlblcntUsage)); 215 | 216 | // Push the object to the array 217 | modules.Set(i, module); 218 | } 219 | 220 | // getModules can either take one argument or two arguments 221 | // one/two arguments is for asychronous use (the callback) 222 | if (args.Length() == 2) { 223 | // Callback to let the user handle with the information 224 | Napi::Function callback = args[1].As(); 225 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), modules }); 226 | return env.Null(); 227 | } else { 228 | // return JSON 229 | return modules; 230 | } 231 | } 232 | 233 | Napi::Value findModule(const Napi::CallbackInfo& args) { 234 | Napi::Env env = args.Env(); 235 | 236 | if (args.Length() != 1 && args.Length() != 2 && args.Length() != 3) { 237 | Napi::Error::New(env, "requires 1 argument, 2 arguments, or 3 arguments if a callback is being used").ThrowAsJavaScriptException(); 238 | return env.Null(); 239 | } 240 | 241 | if (!args[0].IsString() && !args[1].IsNumber()) { 242 | Napi::Error::New(env, "first argument must be a string, second argument must be a number").ThrowAsJavaScriptException(); 243 | return env.Null(); 244 | } 245 | 246 | if (args.Length() == 3 && !args[2].IsFunction()) { 247 | Napi::Error::New(env, "third argument must be a function").ThrowAsJavaScriptException(); 248 | return env.Null(); 249 | } 250 | 251 | std::string moduleName(args[0].As().Utf8Value()); 252 | 253 | // Define error message that may be set by the function that gets the modules 254 | char* errorMessage = ""; 255 | 256 | MODULEENTRY32 module = module::findModule(moduleName.c_str(), args[1].As().Int32Value(), &errorMessage); 257 | 258 | // If an error message was returned from the function getting the module, throw the error. 259 | // Only throw an error if there is no callback (if there's a callback, the error is passed there). 260 | if (strcmp(errorMessage, "") && args.Length() != 3) { 261 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 262 | return env.Null(); 263 | } 264 | 265 | // In case it failed to open, let's keep retrying 266 | while (!strcmp(module.szExePath, "")) { 267 | module = module::findModule(moduleName.c_str(), args[1].As().Int32Value(), &errorMessage); 268 | }; 269 | 270 | // Create a v8 Object (JSON) to store the process information 271 | Napi::Object moduleInfo = Napi::Object::New(env); 272 | 273 | moduleInfo.Set(Napi::String::New(env, "modBaseAddr"), Napi::Value::From(env, (uintptr_t)module.modBaseAddr)); 274 | moduleInfo.Set(Napi::String::New(env, "modBaseSize"), Napi::Value::From(env, (int)module.modBaseSize)); 275 | moduleInfo.Set(Napi::String::New(env, "szExePath"), Napi::String::New(env, module.szExePath)); 276 | moduleInfo.Set(Napi::String::New(env, "szModule"), Napi::String::New(env, module.szModule)); 277 | moduleInfo.Set(Napi::String::New(env, "th32ProcessID"), Napi::Value::From(env, (int)module.th32ProcessID)); 278 | moduleInfo.Set(Napi::String::New(env, "GlblcntUsage"), Napi::Value::From(env, (int)module.GlblcntUsage)); 279 | 280 | // findModule can either take one or two arguments, 281 | // three arguments for asychronous use (third argument is the callback) 282 | if (args.Length() == 3) { 283 | // Callback to let the user handle with the information 284 | Napi::Function callback = args[2].As(); 285 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), moduleInfo }); 286 | return env.Null(); 287 | } else { 288 | // return JSON 289 | return moduleInfo; 290 | } 291 | } 292 | 293 | Napi::Value readMemory(const Napi::CallbackInfo& args) { 294 | Napi::Env env = args.Env(); 295 | 296 | if (args.Length() != 3 && args.Length() != 4) { 297 | Napi::Error::New(env, "requires 3 arguments, or 4 arguments if a callback is being used").ThrowAsJavaScriptException(); 298 | return env.Null(); 299 | } 300 | 301 | if (!args[0].IsNumber() && !args[1].IsNumber() && !args[2].IsString()) { 302 | Napi::Error::New(env, "first and second argument must be a number, third argument must be a string").ThrowAsJavaScriptException(); 303 | return env.Null(); 304 | } 305 | 306 | if (args.Length() == 4 && !args[3].IsFunction()) { 307 | Napi::Error::New(env, "fourth argument must be a function").ThrowAsJavaScriptException(); 308 | return env.Null(); 309 | } 310 | 311 | std::string dataTypeArg(args[2].As().Utf8Value()); 312 | const char* dataType = dataTypeArg.c_str(); 313 | 314 | // Define the error message that will be set if no data type is recognised 315 | char* errorMessage = ""; 316 | Napi::Value retVal = env.Null(); 317 | 318 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 319 | 320 | DWORD64 address; 321 | if (args[1].As().IsBigInt()) { 322 | bool lossless; 323 | address = args[1].As().Uint64Value(&lossless); 324 | } else { 325 | address = args[1].As().Int64Value(); 326 | } 327 | 328 | if (!strcmp(dataType, "int8") || !strcmp(dataType, "byte") || !strcmp(dataType, "char")) { 329 | 330 | int8_t result = Memory.readMemory(handle, address); 331 | retVal = Napi::Value::From(env, result); 332 | 333 | } else if (!strcmp(dataType, "uint8") || !strcmp(dataType, "ubyte") || !strcmp(dataType, "uchar")) { 334 | 335 | uint8_t result = Memory.readMemory(handle, address); 336 | retVal = Napi::Value::From(env, result); 337 | 338 | } else if (!strcmp(dataType, "int16") || !strcmp(dataType, "short")) { 339 | 340 | int16_t result = Memory.readMemory(handle, address); 341 | retVal = Napi::Value::From(env, result); 342 | 343 | } else if (!strcmp(dataType, "uint16") || !strcmp(dataType, "ushort") || !strcmp(dataType, "word")) { 344 | 345 | uint16_t result = Memory.readMemory(handle, address); 346 | retVal = Napi::Value::From(env, result); 347 | 348 | } else if (!strcmp(dataType, "int32") || !strcmp(dataType, "int") || !strcmp(dataType, "long")) { 349 | 350 | int32_t result = Memory.readMemory(handle, address); 351 | retVal = Napi::Value::From(env, result); 352 | 353 | } else if (!strcmp(dataType, "uint32") || !strcmp(dataType, "uint") || !strcmp(dataType, "ulong") || !strcmp(dataType, "dword")) { 354 | 355 | uint32_t result = Memory.readMemory(handle, address); 356 | retVal = Napi::Value::From(env, result); 357 | 358 | } else if (!strcmp(dataType, "int64")) { 359 | 360 | int64_t result = Memory.readMemory(handle, address); 361 | retVal = Napi::Value::From(env, Napi::BigInt::New(env, result)); 362 | 363 | } else if (!strcmp(dataType, "uint64")) { 364 | 365 | uint64_t result = Memory.readMemory(handle, address); 366 | retVal = Napi::Value::From(env, Napi::BigInt::New(env, result)); 367 | 368 | } else if (!strcmp(dataType, "float")) { 369 | 370 | float result = Memory.readMemory(handle, address); 371 | retVal = Napi::Value::From(env, result); 372 | 373 | } else if (!strcmp(dataType, "double")) { 374 | 375 | double result = Memory.readMemory(handle, address); 376 | retVal = Napi::Value::From(env, result); 377 | 378 | } else if (!strcmp(dataType, "ptr") || !strcmp(dataType, "pointer")) { 379 | 380 | intptr_t result = Memory.readMemory(handle, address); 381 | 382 | if (sizeof(intptr_t) == 8) { 383 | retVal = Napi::Value::From(env, Napi::BigInt::New(env, (int64_t) result)); 384 | } else { 385 | retVal = Napi::Value::From(env, result); 386 | } 387 | 388 | } else if (!strcmp(dataType, "uptr") || !strcmp(dataType, "upointer")) { 389 | 390 | uintptr_t result = Memory.readMemory(handle, address); 391 | 392 | if (sizeof(uintptr_t) == 8) { 393 | retVal = Napi::Value::From(env, Napi::BigInt::New(env, (uint64_t) result)); 394 | } else { 395 | retVal = Napi::Value::From(env, result); 396 | } 397 | 398 | } else if (!strcmp(dataType, "bool") || !strcmp(dataType, "boolean")) { 399 | 400 | bool result = Memory.readMemory(handle, address); 401 | retVal = Napi::Boolean::New(env, result); 402 | 403 | } else if (!strcmp(dataType, "string") || !strcmp(dataType, "str")) { 404 | 405 | std::string str; 406 | if (!Memory.readString(handle, address, &str)) { 407 | errorMessage = "unable to read string"; 408 | } else { 409 | retVal = Napi::String::New(env, str); 410 | } 411 | 412 | } else if (!strcmp(dataType, "vector3") || !strcmp(dataType, "vec3")) { 413 | 414 | Vector3 result = Memory.readMemory(handle, address); 415 | Napi::Object moduleInfo = Napi::Object::New(env); 416 | moduleInfo.Set(Napi::String::New(env, "x"), Napi::Value::From(env, result.x)); 417 | moduleInfo.Set(Napi::String::New(env, "y"), Napi::Value::From(env, result.y)); 418 | moduleInfo.Set(Napi::String::New(env, "z"), Napi::Value::From(env, result.z)); 419 | retVal = moduleInfo; 420 | 421 | } else if (!strcmp(dataType, "vector4") || !strcmp(dataType, "vec4")) { 422 | 423 | Vector4 result = Memory.readMemory(handle, address); 424 | Napi::Object moduleInfo = Napi::Object::New(env); 425 | moduleInfo.Set(Napi::String::New(env, "w"), Napi::Value::From(env, result.w)); 426 | moduleInfo.Set(Napi::String::New(env, "x"), Napi::Value::From(env, result.x)); 427 | moduleInfo.Set(Napi::String::New(env, "y"), Napi::Value::From(env, result.y)); 428 | moduleInfo.Set(Napi::String::New(env, "z"), Napi::Value::From(env, result.z)); 429 | retVal = moduleInfo; 430 | 431 | } else { 432 | errorMessage = "unexpected data type"; 433 | } 434 | 435 | if (strcmp(errorMessage, "") && args.Length() != 4) { 436 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 437 | return env.Null(); 438 | } 439 | 440 | if (args.Length() == 4) { 441 | Napi::Function callback = args[3].As(); 442 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), retVal }); 443 | return env.Null(); 444 | } else { 445 | return retVal; 446 | } 447 | } 448 | 449 | Napi::Value readBuffer(const Napi::CallbackInfo& args) { 450 | Napi::Env env = args.Env(); 451 | 452 | if (args.Length() != 3 && args.Length() != 4) { 453 | Napi::Error::New(env, "requires 3 arguments, or 4 arguments if a callback is being used").ThrowAsJavaScriptException(); 454 | return env.Null(); 455 | } 456 | 457 | if (!args[0].IsNumber() && !args[1].IsNumber() && !args[2].IsNumber()) { 458 | Napi::Error::New(env, "first, second and third arguments must be a number").ThrowAsJavaScriptException(); 459 | return env.Null(); 460 | } 461 | 462 | if (args.Length() == 4 && !args[3].IsFunction()) { 463 | Napi::Error::New(env, "fourth argument must be a function").ThrowAsJavaScriptException(); 464 | return env.Null(); 465 | } 466 | 467 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 468 | 469 | DWORD64 address; 470 | if (args[1].As().IsBigInt()) { 471 | bool lossless; 472 | address = args[1].As().Uint64Value(&lossless); 473 | } else { 474 | address = args[1].As().Int64Value(); 475 | } 476 | 477 | SIZE_T size = args[2].As().Int64Value(); 478 | 479 | // To fix the memory leak problem that was happening here, we need to release the 480 | // temporary buffer we create after we're done creating a Napi::Buffer from it. 481 | // Napi::Buffer::New doesn't free the memory, so it has be done manually 482 | // but it can segfault when the memory is freed before being accessed. 483 | // The solution is to use Napi::Buffer::Copy, and then we can manually free it. 484 | // 485 | // see: https://github.com/nodejs/node/issues/40936 486 | // see: https://sagivo.com/2015/09/30/Go-Native-Calling-C-From-NodeJS.html 487 | char* data = (char*) malloc(sizeof(char) * size); 488 | Memory.readBuffer(handle, address, size, data); 489 | 490 | Napi::Buffer buffer = Napi::Buffer::Copy(env, data, size); 491 | free(data); 492 | 493 | if (args.Length() == 4) { 494 | Napi::Function callback = args[3].As(); 495 | callback.Call(env.Global(), { Napi::String::New(env, ""), buffer }); 496 | return env.Null(); 497 | } else { 498 | return buffer; 499 | } 500 | } 501 | 502 | Napi::Value writeMemory(const Napi::CallbackInfo& args) { 503 | Napi::Env env = args.Env(); 504 | 505 | if (args.Length() != 4) { 506 | Napi::Error::New(env, "requires 4 arguments").ThrowAsJavaScriptException(); 507 | return env.Null(); 508 | } 509 | 510 | if (!args[0].IsNumber() && !args[1].IsNumber() && !args[3].IsString()) { 511 | Napi::Error::New(env, "first and second argument must be a number, third argument must be a string").ThrowAsJavaScriptException(); 512 | return env.Null(); 513 | } 514 | 515 | std::string dataTypeArg(args[3].As().Utf8Value()); 516 | const char* dataType = dataTypeArg.c_str(); 517 | 518 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 519 | 520 | DWORD64 address; 521 | if (args[1].As().IsBigInt()) { 522 | bool lossless; 523 | address = args[1].As().Uint64Value(&lossless); 524 | } else { 525 | address = args[1].As().Int64Value(); 526 | } 527 | 528 | if (!strcmp(dataType, "int8") || !strcmp(dataType, "byte") || !strcmp(dataType, "char")) { 529 | 530 | Memory.writeMemory(handle, address, args[2].As().Int32Value()); 531 | 532 | } else if (!strcmp(dataType, "uint8") || !strcmp(dataType, "ubyte") || !strcmp(dataType, "uchar")) { 533 | 534 | Memory.writeMemory(handle, address, args[2].As().Uint32Value()); 535 | 536 | } else if (!strcmp(dataType, "int16") || !strcmp(dataType, "short")) { 537 | 538 | Memory.writeMemory(handle, address, args[2].As().Int32Value()); 539 | 540 | } else if (!strcmp(dataType, "uint16") || !strcmp(dataType, "ushort") || !strcmp(dataType, "word")) { 541 | 542 | Memory.writeMemory(handle, address, args[2].As().Uint32Value()); 543 | 544 | } else if (!strcmp(dataType, "int32") || !strcmp(dataType, "int") || !strcmp(dataType, "long")) { 545 | 546 | Memory.writeMemory(handle, address, args[2].As().Int32Value()); 547 | 548 | } else if (!strcmp(dataType, "uint32") || !strcmp(dataType, "uint") || !strcmp(dataType, "ulong") || !strcmp(dataType, "dword")) { 549 | 550 | Memory.writeMemory(handle, address, args[2].As().Uint32Value()); 551 | 552 | } else if (!strcmp(dataType, "int64")) { 553 | 554 | if (args[2].As().IsBigInt()) { 555 | bool lossless; 556 | Memory.writeMemory(handle, address, args[2].As().Int64Value(&lossless)); 557 | } else { 558 | Memory.writeMemory(handle, address, args[2].As().Int64Value()); 559 | } 560 | 561 | } else if (!strcmp(dataType, "uint64")) { 562 | 563 | if (args[2].As().IsBigInt()) { 564 | bool lossless; 565 | Memory.writeMemory(handle, address, args[2].As().Uint64Value(&lossless)); 566 | } else { 567 | Memory.writeMemory(handle, address, args[2].As().Int64Value()); 568 | } 569 | 570 | } else if (!strcmp(dataType, "float")) { 571 | 572 | Memory.writeMemory(handle, address, args[2].As().FloatValue()); 573 | 574 | } else if (!strcmp(dataType, "double")) { 575 | 576 | Memory.writeMemory(handle, address, args[2].As().DoubleValue()); 577 | 578 | } else if (!strcmp(dataType, "ptr") || !strcmp(dataType, "pointer")) { 579 | 580 | Napi::BigInt valueBigInt = args[2].As(); 581 | 582 | if (sizeof(intptr_t) == 8 && !valueBigInt.IsBigInt()) { 583 | std::string error = "Writing memoryjs.PTR or memoryjs.POINTER on 64 bit target build requires you to supply a BigInt."; 584 | error += " Rebuild the library with `npm run build32` to target 32 bit applications."; 585 | Napi::Error::New(env, error).ThrowAsJavaScriptException(); 586 | return env.Null(); 587 | } 588 | 589 | if (valueBigInt.IsBigInt()) { 590 | bool lossless; 591 | Memory.writeMemory(handle, address, valueBigInt.Int64Value(&lossless)); 592 | } else { 593 | Memory.writeMemory(handle, address, args[2].As().Int32Value()); 594 | } 595 | 596 | } else if (!strcmp(dataType, "uptr") || !strcmp(dataType, "upointer")) { 597 | 598 | Napi::BigInt valueBigInt = args[2].As(); 599 | 600 | if (sizeof(uintptr_t) == 8 && !valueBigInt.IsBigInt()) { 601 | std::string error = "Writing memoryjs.PTR or memoryjs.POINTER on 64 bit target build requires you to supply a BigInt."; 602 | error += " Rebuild the library with `npm run build32` to target 32 bit applications."; 603 | Napi::Error::New(env, error).ThrowAsJavaScriptException(); 604 | return env.Null(); 605 | } 606 | 607 | if (valueBigInt.IsBigInt()) { 608 | bool lossless; 609 | Memory.writeMemory(handle, address, valueBigInt.Uint64Value(&lossless)); 610 | } else { 611 | Memory.writeMemory(handle, address, args[2].As().Uint32Value()); 612 | } 613 | 614 | } else if (!strcmp(dataType, "bool") || !strcmp(dataType, "boolean")) { 615 | 616 | Memory.writeMemory(handle, address, args[2].As().Value()); 617 | 618 | } else if (!strcmp(dataType, "string") || !strcmp(dataType, "str")) { 619 | 620 | std::string valueParam(args[2].As().Utf8Value()); 621 | valueParam.append("", 1); 622 | 623 | // Write String, Method 1 624 | //Memory.writeMemory(handle, address, std::string(*valueParam)); 625 | 626 | // Write String, Method 2 627 | Memory.writeMemory(handle, address, (char*) valueParam.data(), valueParam.size()); 628 | 629 | } else if (!strcmp(dataType, "vector3") || !strcmp(dataType, "vec3")) { 630 | 631 | Napi::Object value = args[2].As(); 632 | Vector3 vector = { 633 | value.Get(Napi::String::New(env, "x")).As().FloatValue(), 634 | value.Get(Napi::String::New(env, "y")).As().FloatValue(), 635 | value.Get(Napi::String::New(env, "z")).As().FloatValue() 636 | }; 637 | Memory.writeMemory(handle, address, vector); 638 | 639 | } else if (!strcmp(dataType, "vector4") || !strcmp(dataType, "vec4")) { 640 | 641 | Napi::Object value = args[2].As(); 642 | Vector4 vector = { 643 | value.Get(Napi::String::New(env, "w")).As().FloatValue(), 644 | value.Get(Napi::String::New(env, "x")).As().FloatValue(), 645 | value.Get(Napi::String::New(env, "y")).As().FloatValue(), 646 | value.Get(Napi::String::New(env, "z")).As().FloatValue() 647 | }; 648 | Memory.writeMemory(handle, address, vector); 649 | 650 | } else { 651 | Napi::Error::New(env, "unexpected data type").ThrowAsJavaScriptException(); 652 | } 653 | 654 | return env.Null(); 655 | } 656 | 657 | Napi::Value writeBuffer(const Napi::CallbackInfo& args) { 658 | Napi::Env env = args.Env(); 659 | 660 | if (args.Length() != 3) { 661 | Napi::Error::New(env, "required 3 arguments").ThrowAsJavaScriptException(); 662 | return env.Null(); 663 | } 664 | 665 | if (!args[0].IsNumber() && !args[1].IsNumber()) { 666 | Napi::Error::New(env, "first and second argument must be a number").ThrowAsJavaScriptException(); 667 | return env.Null(); 668 | } 669 | 670 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 671 | 672 | DWORD64 address; 673 | if (args[1].As().IsBigInt()) { 674 | bool lossless; 675 | address = args[1].As().Uint64Value(&lossless); 676 | } else { 677 | address = args[1].As().Int64Value(); 678 | } 679 | 680 | SIZE_T length = args[2].As>().Length(); 681 | char* data = args[2].As>().Data(); 682 | Memory.writeMemory(handle, address, data, length); 683 | 684 | return env.Null(); 685 | } 686 | 687 | // Napi::Value findPattern(const Napi::CallbackInfo& args) { 688 | // Napi::Env env = args.Env(); 689 | 690 | // HANDLE handle = (HANDLE)args[0].As().Int64Value(); 691 | // DWORD64 baseAddress = args[1].As().Int64Value(); 692 | // DWORD64 baseSize = args[2].As().Int64Value(); 693 | // std::string signature(args[3].As().Utf8Value()); 694 | // short flags = args[4].As().Uint32Value(); 695 | // uint32_t patternOffset = args[5].As().Uint32Value(); 696 | 697 | // // matching address 698 | // uintptr_t address = 0; 699 | // char* errorMessage = ""; 700 | 701 | // // read memory region occupied by the module to pattern match inside 702 | // std::vector moduleBytes = std::vector(baseSize); 703 | // ReadProcessMemory(handle, (LPVOID)baseAddress, &moduleBytes[0], baseSize, nullptr); 704 | // unsigned char* byteBase = const_cast(&moduleBytes.at(0)); 705 | 706 | // Pattern.findPattern(handle, baseAddress, byteBase, baseSize, signature.c_str(), flags, patternOffset, &address); 707 | 708 | // if (address == 0) { 709 | // errorMessage = "unable to match pattern inside any modules or regions"; 710 | // } 711 | 712 | // if (args.Length() == 5) { 713 | // Napi::Function callback = args[4].As(); 714 | // callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Value::From(env, address) }); 715 | // return env.Null(); 716 | // } else { 717 | // return Napi::Value::From(env, address); 718 | // } 719 | // } 720 | 721 | Napi::Value findPattern(const Napi::CallbackInfo& args) { 722 | Napi::Env env = args.Env(); 723 | 724 | if (args.Length() != 4 && args.Length() != 5) { 725 | Napi::Error::New(env, "requires 4 arguments, 5 with callback").ThrowAsJavaScriptException(); 726 | return env.Null(); 727 | } 728 | 729 | if (!args[0].IsNumber() || !args[1].IsString() || !args[2].IsNumber() || !args[3].IsNumber()) { 730 | Napi::Error::New(env, "expected: number, string, string, number").ThrowAsJavaScriptException(); 731 | return env.Null(); 732 | } 733 | 734 | if (args.Length() == 5 && !args[4].IsFunction()) { 735 | Napi::Error::New(env, "callback argument must be a function").ThrowAsJavaScriptException(); 736 | return env.Null(); 737 | } 738 | 739 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 740 | std::string pattern(args[1].As().Utf8Value()); 741 | short flags = args[2].As().Uint32Value(); 742 | uint32_t patternOffset = args[3].As().Uint32Value(); 743 | 744 | // matching address 745 | uintptr_t address = 0; 746 | char* errorMessage = ""; 747 | 748 | std::vector modules = module::getModules(GetProcessId(handle), &errorMessage); 749 | Pattern.search(handle, modules, 0, pattern.c_str(), flags, patternOffset, &address); 750 | 751 | // if no match found inside any modules, search memory regions 752 | if (address == 0) { 753 | std::vector regions = Memory.getRegions(handle); 754 | Pattern.search(handle, regions, 0, pattern.c_str(), flags, patternOffset, &address); 755 | } 756 | 757 | if (address == 0) { 758 | errorMessage = "unable to match pattern inside any modules or regions"; 759 | } 760 | 761 | if (args.Length() == 5) { 762 | Napi::Function callback = args[4].As(); 763 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Value::From(env, address) }); 764 | return env.Null(); 765 | } else { 766 | return Napi::Value::From(env, address); 767 | } 768 | } 769 | 770 | Napi::Value findPatternByModule(const Napi::CallbackInfo& args) { 771 | Napi::Env env = args.Env(); 772 | 773 | if (args.Length() != 5 && args.Length() != 6) { 774 | Napi::Error::New(env, "requires 5 arguments, 6 with callback").ThrowAsJavaScriptException(); 775 | return env.Null(); 776 | } 777 | 778 | if (!args[0].IsNumber() || !args[1].IsString() || !args[2].IsString() || !args[3].IsNumber() || !args[4].IsNumber()) { 779 | Napi::Error::New(env, "expected: number, string, string, number, number").ThrowAsJavaScriptException(); 780 | return env.Null(); 781 | } 782 | 783 | if (args.Length() == 6 && !args[5].IsFunction()) { 784 | Napi::Error::New(env, "callback argument must be a function").ThrowAsJavaScriptException(); 785 | return env.Null(); 786 | } 787 | 788 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 789 | std::string moduleName(args[1].As().Utf8Value()); 790 | std::string pattern(args[2].As().Utf8Value()); 791 | short flags = args[3].As().Uint32Value(); 792 | uint32_t patternOffset = args[4].As().Uint32Value(); 793 | 794 | // matching address 795 | uintptr_t address = 0; 796 | char* errorMessage = ""; 797 | 798 | MODULEENTRY32 module = module::findModule(moduleName.c_str(), GetProcessId(handle), &errorMessage); 799 | 800 | uintptr_t baseAddress = (uintptr_t) module.modBaseAddr; 801 | DWORD baseSize = module.modBaseSize; 802 | 803 | // read memory region occupied by the module to pattern match inside 804 | std::vector moduleBytes = std::vector(baseSize); 805 | ReadProcessMemory(handle, (LPVOID)baseAddress, &moduleBytes[0], baseSize, nullptr); 806 | unsigned char* byteBase = const_cast(&moduleBytes.at(0)); 807 | 808 | Pattern.findPattern(handle, baseAddress, byteBase, baseSize, pattern.c_str(), flags, patternOffset, &address); 809 | 810 | if (address == 0) { 811 | errorMessage = "unable to match pattern inside any modules or regions"; 812 | } 813 | 814 | if (args.Length() == 6) { 815 | Napi::Function callback = args[5].As(); 816 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Value::From(env, address) }); 817 | return env.Null(); 818 | } else { 819 | return Napi::Value::From(env, address); 820 | } 821 | } 822 | 823 | Napi::Value findPatternByAddress(const Napi::CallbackInfo& args) { 824 | Napi::Env env = args.Env(); 825 | 826 | if (args.Length() != 5 && args.Length() != 6) { 827 | Napi::Error::New(env, "requires 5 arguments, 6 with callback").ThrowAsJavaScriptException(); 828 | return env.Null(); 829 | } 830 | 831 | if (!args[0].IsNumber() || !args[1].IsNumber() || !args[2].IsString() || !args[3].IsNumber() || !args[4].IsNumber()) { 832 | Napi::Error::New(env, "expected: number, number, string, number, number").ThrowAsJavaScriptException(); 833 | return env.Null(); 834 | } 835 | 836 | if (args.Length() == 6 && !args[5].IsFunction()) { 837 | Napi::Error::New(env, "callback argument must be a function").ThrowAsJavaScriptException(); 838 | return env.Null(); 839 | } 840 | 841 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 842 | 843 | DWORD64 baseAddress; 844 | if (args[1].As().IsBigInt()) { 845 | bool lossless; 846 | baseAddress = args[1].As().Uint64Value(&lossless); 847 | } else { 848 | baseAddress = args[1].As().Int64Value(); 849 | } 850 | 851 | std::string pattern(args[2].As().Utf8Value()); 852 | short flags = args[3].As().Uint32Value(); 853 | uint32_t patternOffset = args[4].As().Uint32Value(); 854 | 855 | // matching address 856 | uintptr_t address = 0; 857 | char* errorMessage = ""; 858 | 859 | std::vector modules = module::getModules(GetProcessId(handle), &errorMessage); 860 | Pattern.search(handle, modules, baseAddress, pattern.c_str(), flags, patternOffset, &address); 861 | 862 | if (address == 0) { 863 | std::vector regions = Memory.getRegions(handle); 864 | Pattern.search(handle, regions, baseAddress, pattern.c_str(), flags, patternOffset, &address); 865 | } 866 | 867 | if (address == 0) { 868 | errorMessage = "unable to match pattern inside any modules or regions"; 869 | } 870 | 871 | if (args.Length() == 6) { 872 | Napi::Function callback = args[5].As(); 873 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Value::From(env, address) }); 874 | return env.Null(); 875 | } else { 876 | return Napi::Value::From(env, address); 877 | } 878 | } 879 | 880 | Napi::Value callFunction(const Napi::CallbackInfo& args) { 881 | Napi::Env env = args.Env(); 882 | 883 | if (args.Length() != 4 && args.Length() != 5) { 884 | Napi::Error::New(env, "requires 4 arguments, 5 with callback").ThrowAsJavaScriptException(); 885 | return env.Null(); 886 | } 887 | 888 | if (!args[0].IsNumber() && !args[1].IsObject() && !args[2].IsNumber() && !args[3].IsNumber()) { 889 | Napi::Error::New(env, "invalid arguments").ThrowAsJavaScriptException(); 890 | return env.Null(); 891 | } 892 | 893 | // TODO: temp (?) solution to forcing variables onto the heap 894 | // to ensure consistent addresses. copy everything to a vector, and use the 895 | // vector's instances of the variables as the addresses being passed to `functions.call()`. 896 | // Another solution: do `int x = new int(4)` and then use `&x` for the address 897 | std::vector heap; 898 | 899 | std::vector parsedArgs; 900 | Napi::Array arguments = args[1].As(); 901 | for (unsigned int i = 0; i < arguments.Length(); i++) { 902 | Napi::Object argument = arguments.Get(i).As(); 903 | 904 | functions::Type type = (functions::Type) argument.Get(Napi::String::New(env, "type")).As().Uint32Value(); 905 | 906 | if (type == functions::Type::T_STRING) { 907 | std::string stringValue = argument.Get(Napi::String::New(env, "value")).As().Utf8Value(); 908 | parsedArgs.push_back({ type, &stringValue }); 909 | } 910 | 911 | if (type == functions::Type::T_INT) { 912 | int data = argument.Get(Napi::String::New(env, "value")).As().Int32Value(); 913 | 914 | // As we only pass the addresses of the variable to the `call` function and not a copy 915 | // of the variable itself, we need to ensure that the variable stays alive and in a unique 916 | // memory location until the `call` function has been executed. So manually allocate memory, 917 | // track it, and then free it once the function has been called. 918 | // TODO: find a better solution? 919 | int* memory = (int*) malloc(sizeof(int)); 920 | *memory = data; 921 | heap.push_back(memory); 922 | 923 | parsedArgs.push_back({ type, memory }); 924 | } 925 | 926 | if (type == functions::Type::T_FLOAT) { 927 | float data = argument.Get(Napi::String::New(env, "value")).As().FloatValue(); 928 | 929 | float* memory = (float*) malloc(sizeof(float)); 930 | *memory = data; 931 | heap.push_back(memory); 932 | 933 | parsedArgs.push_back({ type, memory }); 934 | } 935 | } 936 | 937 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 938 | functions::Type returnType = (functions::Type) args[2].As().Uint32Value(); 939 | 940 | DWORD64 address; 941 | if (args[3].As().IsBigInt()) { 942 | bool lossless; 943 | address = args[3].As().Uint64Value(&lossless); 944 | } else { 945 | address = args[3].As().Int64Value(); 946 | } 947 | 948 | char* errorMessage = ""; 949 | Call data = functions::call(handle, parsedArgs, returnType, address, &errorMessage); 950 | 951 | // Free all the memory we allocated 952 | for (auto &memory : heap) { 953 | free(memory); 954 | } 955 | 956 | heap.clear(); 957 | 958 | if (strcmp(errorMessage, "") && args.Length() != 5) { 959 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 960 | return env.Null(); 961 | } 962 | 963 | Napi::Object info = Napi::Object::New(env); 964 | 965 | Napi::String keyString = Napi::String::New(env, "returnValue"); 966 | 967 | if (returnType == functions::Type::T_STRING) { 968 | info.Set(keyString, Napi::String::New(env, data.returnString.c_str())); 969 | } 970 | 971 | if (returnType == functions::Type::T_CHAR) { 972 | info.Set(keyString, Napi::Value::From(env, (char) data.returnValue)); 973 | } 974 | 975 | if (returnType == functions::Type::T_BOOL) { 976 | info.Set(keyString, Napi::Value::From(env, (bool) data.returnValue)); 977 | } 978 | 979 | if (returnType == functions::Type::T_INT) { 980 | info.Set(keyString, Napi::Value::From(env, (int) data.returnValue)); 981 | } 982 | 983 | if (returnType == functions::Type::T_FLOAT) { 984 | float value = *(float *)&data.returnValue; 985 | info.Set(keyString, Napi::Value::From(env, value)); 986 | } 987 | 988 | if (returnType == functions::Type::T_DOUBLE) { 989 | double value = *(double *)&data.returnValue; 990 | info.Set(keyString, Napi::Value::From(env, value)); 991 | } 992 | 993 | info.Set(Napi::String::New(env, "exitCode"), Napi::Value::From(env, data.exitCode)); 994 | 995 | if (args.Length() == 5) { 996 | // Callback to let the user handle with the information 997 | Napi::Function callback = args[2].As(); 998 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), info }); 999 | return env.Null(); 1000 | } else { 1001 | // return JSON 1002 | return info; 1003 | } 1004 | 1005 | } 1006 | 1007 | Napi::Value virtualProtectEx(const Napi::CallbackInfo& args) { 1008 | Napi::Env env = args.Env(); 1009 | 1010 | if (args.Length() != 4 && args.Length() != 5) { 1011 | Napi::Error::New(env, "requires 4 arguments, 5 with callback").ThrowAsJavaScriptException(); 1012 | return env.Null(); 1013 | } 1014 | 1015 | if (!args[0].IsNumber() && !args[1].IsNumber() && !args[2].IsNumber()) { 1016 | Napi::Error::New(env, "All arguments should be numbers.").ThrowAsJavaScriptException(); 1017 | return env.Null(); 1018 | } 1019 | 1020 | if (args.Length() == 5 && !args[4].IsFunction()) { 1021 | Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException(); 1022 | return env.Null(); 1023 | } 1024 | 1025 | DWORD result; 1026 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 1027 | DWORD64 address = args[1].As().Int64Value(); 1028 | SIZE_T size = args[2].As().Int64Value(); 1029 | DWORD protection = args[3].As().Uint32Value(); 1030 | 1031 | bool success = VirtualProtectEx(handle, (LPVOID) address, size, protection, &result); 1032 | 1033 | char* errorMessage = ""; 1034 | 1035 | if (success == 0) { 1036 | errorMessage = "an error occurred calling VirtualProtectEx"; 1037 | // errorMessage = GetLastErrorToString().c_str(); 1038 | } 1039 | 1040 | // If there is an error and there is no callback, throw the error 1041 | if (strcmp(errorMessage, "") && args.Length() != 5) { 1042 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 1043 | return env.Null(); 1044 | } 1045 | 1046 | if (args.Length() == 5) { 1047 | // Callback to let the user handle with the information 1048 | Napi::Function callback = args[5].As(); 1049 | callback.Call(env.Global(), { 1050 | Napi::String::New(env, errorMessage), 1051 | Napi::Value::From(env, result) 1052 | }); 1053 | return env.Null(); 1054 | } else { 1055 | return Napi::Value::From(env, result); 1056 | } 1057 | } 1058 | 1059 | Napi::Value getRegions(const Napi::CallbackInfo& args) { 1060 | Napi::Env env = args.Env(); 1061 | 1062 | if (args.Length() != 1 && args.Length() != 2) { 1063 | Napi::Error::New(env, "requires 1 argument, 2 with callback").ThrowAsJavaScriptException(); 1064 | return env.Null(); 1065 | } 1066 | 1067 | if (!args[0].IsNumber()) { 1068 | Napi::Error::New(env, "invalid arguments: first argument must be a number").ThrowAsJavaScriptException(); 1069 | return env.Null(); 1070 | } 1071 | 1072 | if (args.Length() == 2 && !args[1].IsFunction()) { 1073 | Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException(); 1074 | return env.Null(); 1075 | } 1076 | 1077 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 1078 | std::vector regions = Memory.getRegions(handle); 1079 | 1080 | Napi::Array regionsArray = Napi::Array::New(env, regions.size()); 1081 | 1082 | for (std::vector::size_type i = 0; i != regions.size(); i++) { 1083 | Napi::Object region = Napi::Object::New(env); 1084 | 1085 | region.Set(Napi::String::New(env, "BaseAddress"), Napi::Value::From(env, (DWORD64) regions[i].BaseAddress)); 1086 | region.Set(Napi::String::New(env, "AllocationBase"), Napi::Value::From(env, (DWORD64) regions[i].AllocationBase)); 1087 | region.Set(Napi::String::New(env, "AllocationProtect"), Napi::Value::From(env, (DWORD) regions[i].AllocationProtect)); 1088 | region.Set(Napi::String::New(env, "RegionSize"), Napi::Value::From(env, (SIZE_T) regions[i].RegionSize)); 1089 | region.Set(Napi::String::New(env, "State"), Napi::Value::From(env, (DWORD) regions[i].State)); 1090 | region.Set(Napi::String::New(env, "Protect"), Napi::Value::From(env, (DWORD) regions[i].Protect)); 1091 | region.Set(Napi::String::New(env, "Type"), Napi::Value::From(env, (DWORD) regions[i].Type)); 1092 | 1093 | char moduleName[MAX_PATH]; 1094 | DWORD size = GetModuleFileNameExA(handle, (HINSTANCE)regions[i].AllocationBase, moduleName, MAX_PATH); 1095 | 1096 | if (size != 0) { 1097 | region.Set(Napi::String::New(env, "szExeFile"), Napi::String::New(env, moduleName)); 1098 | } 1099 | 1100 | regionsArray.Set(i, region); 1101 | } 1102 | 1103 | if (args.Length() == 2) { 1104 | // Callback to let the user handle with the information 1105 | Napi::Function callback = args[1].As(); 1106 | callback.Call(env.Global(), { Napi::String::New(env, ""), regionsArray }); 1107 | return env.Null(); 1108 | } else { 1109 | // return JSON 1110 | return regionsArray; 1111 | } 1112 | } 1113 | 1114 | Napi::Value virtualQueryEx(const Napi::CallbackInfo& args) { 1115 | Napi::Env env = args.Env(); 1116 | 1117 | if (args.Length() != 2 && args.Length() != 3) { 1118 | Napi::Error::New(env, "requires 2 arguments, 3 with callback").ThrowAsJavaScriptException(); 1119 | return env.Null(); 1120 | } 1121 | 1122 | if (!args[0].IsNumber() || !args[1].IsNumber()) { 1123 | Napi::Error::New(env, "first and second argument need to be a number").ThrowAsJavaScriptException(); 1124 | return env.Null(); 1125 | } 1126 | 1127 | if (args.Length() == 3 && !args[2].IsFunction()) { 1128 | Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException(); 1129 | return env.Null(); 1130 | } 1131 | 1132 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 1133 | DWORD64 address = args[1].As().Int64Value(); 1134 | 1135 | MEMORY_BASIC_INFORMATION information; 1136 | SIZE_T result = VirtualQueryEx(handle, (LPVOID)address, &information, sizeof(information)); 1137 | 1138 | char* errorMessage = ""; 1139 | 1140 | if (result == 0 || result != sizeof(information)) { 1141 | errorMessage = "an error occurred calling VirtualQueryEx"; 1142 | // errorMessage = GetLastErrorToString().c_str(); 1143 | } 1144 | 1145 | // If there is an error and there is no callback, throw the error 1146 | if (strcmp(errorMessage, "") && args.Length() != 3) { 1147 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 1148 | return env.Null(); 1149 | } 1150 | 1151 | Napi::Object region = Napi::Object::New(env); 1152 | 1153 | region.Set(Napi::String::New(env, "BaseAddress"), Napi::Value::From(env, (DWORD64) information.BaseAddress)); 1154 | region.Set(Napi::String::New(env, "AllocationBase"), Napi::Value::From(env, (DWORD64) information.AllocationBase)); 1155 | region.Set(Napi::String::New(env, "AllocationProtect"), Napi::Value::From(env, (DWORD) information.AllocationProtect)); 1156 | region.Set(Napi::String::New(env, "RegionSize"), Napi::Value::From(env, (SIZE_T) information.RegionSize)); 1157 | region.Set(Napi::String::New(env, "State"), Napi::Value::From(env, (DWORD) information.State)); 1158 | region.Set(Napi::String::New(env, "Protect"), Napi::Value::From(env, (DWORD) information.Protect)); 1159 | region.Set(Napi::String::New(env, "Type"), Napi::Value::From(env, (DWORD) information.Type)); 1160 | 1161 | if (args.Length() == 3) { 1162 | // Callback to let the user handle with the information 1163 | Napi::Function callback = args[1].As(); 1164 | callback.Call(env.Global(), { Napi::String::New(env, ""), region }); 1165 | return env.Null(); 1166 | } else { 1167 | // return JSON 1168 | return region; 1169 | } 1170 | } 1171 | 1172 | Napi::Value virtualAllocEx(const Napi::CallbackInfo& args) { 1173 | Napi::Env env = args.Env(); 1174 | 1175 | if (args.Length() != 5 && args.Length() != 6) { 1176 | Napi::Error::New(env, "requires 5 arguments, 6 with callback").ThrowAsJavaScriptException(); 1177 | return env.Null(); 1178 | } 1179 | 1180 | if (!args[0].IsNumber() || !args[2].IsNumber() || !args[3].IsNumber() || !args[4].IsNumber()) { 1181 | Napi::Error::New(env, "invalid arguments: arguments 0, 2, 3 and 4 need to be numbers").ThrowAsJavaScriptException(); 1182 | return env.Null(); 1183 | } 1184 | 1185 | if (args.Length() == 6 && !args[5].IsFunction()) { 1186 | Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException(); 1187 | return env.Null(); 1188 | } 1189 | 1190 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 1191 | SIZE_T size = args[2].As().Int64Value(); 1192 | DWORD allocationType = args[3].As().Uint32Value(); 1193 | DWORD protection = args[4].As().Uint32Value(); 1194 | LPVOID address; 1195 | 1196 | // Means in the JavaScript space `null` was passed through. 1197 | if (args[1] == env.Null()) { 1198 | address = NULL; 1199 | } else { 1200 | address = (LPVOID) args[1].As().Int64Value(); 1201 | } 1202 | 1203 | LPVOID allocatedAddress = VirtualAllocEx(handle, address, size, allocationType, protection); 1204 | 1205 | char* errorMessage = ""; 1206 | 1207 | // If null, it means an error occurred 1208 | if (allocatedAddress == NULL) { 1209 | errorMessage = "an error occurred calling VirtualAllocEx"; 1210 | // errorMessage = GetLastErrorToString().c_str(); 1211 | } 1212 | 1213 | // If there is an error and there is no callback, throw the error 1214 | if (strcmp(errorMessage, "") && args.Length() != 6) { 1215 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 1216 | return env.Null(); 1217 | } 1218 | 1219 | if (args.Length() == 6) { 1220 | // Callback to let the user handle with the information 1221 | Napi::Function callback = args[5].As(); 1222 | callback.Call(env.Global(), { 1223 | Napi::String::New(env, errorMessage), 1224 | Napi::Value::From(env, (intptr_t)allocatedAddress) 1225 | }); 1226 | return env.Null(); 1227 | } else { 1228 | return Napi::Value::From(env, (intptr_t)allocatedAddress); 1229 | } 1230 | } 1231 | 1232 | Napi::Value attachDebugger(const Napi::CallbackInfo& args) { 1233 | Napi::Env env = args.Env(); 1234 | 1235 | if (args.Length() != 2) { 1236 | Napi::Error::New(env, "requires 2 arguments").ThrowAsJavaScriptException(); 1237 | return env.Null(); 1238 | } 1239 | 1240 | if (!args[0].IsNumber() || !args[1].IsBoolean()) { 1241 | Napi::Error::New(env, "first argument needs to be a number, second a boolean").ThrowAsJavaScriptException(); 1242 | return env.Null(); 1243 | } 1244 | 1245 | DWORD processId = args[0].As().Uint32Value(); 1246 | bool killOnExit = args[1].As().Value(); 1247 | 1248 | bool success = debugger::attach(processId, killOnExit); 1249 | return Napi::Boolean::New(env, success); 1250 | } 1251 | 1252 | Napi::Value detachDebugger(const Napi::CallbackInfo& args) { 1253 | Napi::Env env = args.Env(); 1254 | 1255 | DWORD processId = args[0].As().Uint32Value(); 1256 | 1257 | if (args.Length() != 1) { 1258 | Napi::Error::New(env, "requires only 1 argument").ThrowAsJavaScriptException(); 1259 | return env.Null(); 1260 | } 1261 | 1262 | if (!args[0].IsNumber()) { 1263 | Napi::Error::New(env, "only argument needs to be a number").ThrowAsJavaScriptException(); 1264 | return env.Null(); 1265 | } 1266 | 1267 | bool success = debugger::detatch(processId); 1268 | return Napi::Boolean::New(env, success); 1269 | } 1270 | 1271 | Napi::Value awaitDebugEvent(const Napi::CallbackInfo& args) { 1272 | Napi::Env env = args.Env(); 1273 | 1274 | if (args.Length() != 2) { 1275 | Napi::Error::New(env, "requires 2 arguments").ThrowAsJavaScriptException(); 1276 | return env.Null(); 1277 | } 1278 | 1279 | if (!args[0].IsNumber() || !args[1].IsNumber()) { 1280 | Napi::Error::New(env, "both arguments need to be a number").ThrowAsJavaScriptException(); 1281 | return env.Null(); 1282 | } 1283 | 1284 | int millisTimeout = args[1].As().Uint32Value(); 1285 | 1286 | DebugEvent debugEvent; 1287 | bool success = debugger::awaitDebugEvent(millisTimeout, &debugEvent); 1288 | 1289 | Register hardwareRegister = static_cast(args[0].As().Uint32Value()); 1290 | 1291 | if (success && debugEvent.hardwareRegister == hardwareRegister) { 1292 | Napi::Object info = Napi::Object::New(env); 1293 | 1294 | info.Set(Napi::String::New(env, "processId"), Napi::Value::From(env, (DWORD) debugEvent.processId)); 1295 | info.Set(Napi::String::New(env, "threadId"), Napi::Value::From(env, (DWORD) debugEvent.threadId)); 1296 | info.Set(Napi::String::New(env, "exceptionCode"), Napi::Value::From(env, (DWORD) debugEvent.exceptionCode)); 1297 | info.Set(Napi::String::New(env, "exceptionFlags"), Napi::Value::From(env, (DWORD) debugEvent.exceptionFlags)); 1298 | info.Set(Napi::String::New(env, "exceptionAddress"), Napi::Value::From(env, (DWORD64) debugEvent.exceptionAddress)); 1299 | info.Set(Napi::String::New(env, "hardwareRegister"), Napi::Value::From(env, static_cast(debugEvent.hardwareRegister))); 1300 | 1301 | return info; 1302 | } 1303 | 1304 | // If we aren't interested in passing this event back to the JS space, 1305 | // just silently handle it 1306 | if (success && debugEvent.hardwareRegister != hardwareRegister) { 1307 | debugger::handleDebugEvent(debugEvent.processId, debugEvent.threadId); 1308 | } 1309 | 1310 | return env.Null(); 1311 | } 1312 | 1313 | Napi::Value handleDebugEvent(const Napi::CallbackInfo& args) { 1314 | Napi::Env env = args.Env(); 1315 | 1316 | if (args.Length() != 2) { 1317 | Napi::Error::New(env, "requires 2 arguments").ThrowAsJavaScriptException(); 1318 | return env.Null(); 1319 | } 1320 | 1321 | if (!args[0].IsNumber() || !args[1].IsNumber()) { 1322 | Napi::Error::New(env, "both arguments need to be numbers").ThrowAsJavaScriptException(); 1323 | return env.Null(); 1324 | } 1325 | 1326 | DWORD processId = args[0].As().Uint32Value(); 1327 | DWORD threadId = args[1].As().Uint32Value(); 1328 | 1329 | bool success = debugger::handleDebugEvent(processId, threadId); 1330 | return Napi::Boolean::New(env, success); 1331 | } 1332 | 1333 | Napi::Value setHardwareBreakpoint(const Napi::CallbackInfo& args) { 1334 | Napi::Env env = args.Env(); 1335 | 1336 | if (args.Length() != 5) { 1337 | Napi::Error::New(env, "requires 5 arguments").ThrowAsJavaScriptException(); 1338 | return env.Null(); 1339 | } 1340 | 1341 | for (unsigned int i = 0; i < args.Length(); i++) { 1342 | if (!args[i].IsNumber()) { 1343 | Napi::Error::New(env, "all arguments need to be numbers").ThrowAsJavaScriptException(); 1344 | return env.Null(); 1345 | } 1346 | } 1347 | 1348 | DWORD processId = args[0].As().Uint32Value(); 1349 | DWORD64 address = args[1].As().Int64Value(); 1350 | Register hardwareRegister = static_cast(args[2].As().Uint32Value()); 1351 | 1352 | // Execute = 0x0 1353 | // Access = 0x3 1354 | // Writer = 0x1 1355 | int trigger = args[3].As().Uint32Value(); 1356 | 1357 | int length = args[4].As().Uint32Value(); 1358 | 1359 | bool success = debugger::setHardwareBreakpoint(processId, address, hardwareRegister, trigger, length); 1360 | return Napi::Boolean::New(env, success); 1361 | } 1362 | 1363 | Napi::Value removeHardwareBreakpoint(const Napi::CallbackInfo& args) { 1364 | Napi::Env env = args.Env(); 1365 | 1366 | if (args.Length() != 2) { 1367 | Napi::Error::New(env, "requires 2 arguments").ThrowAsJavaScriptException(); 1368 | return env.Null(); 1369 | } 1370 | 1371 | if (!args[0].IsNumber() || !args[1].IsNumber()) { 1372 | Napi::Error::New(env, "both arguments need to be numbers").ThrowAsJavaScriptException(); 1373 | return env.Null(); 1374 | } 1375 | 1376 | DWORD processId = args[0].As().Uint32Value(); 1377 | Register hardwareRegister = static_cast(args[1].As().Uint32Value()); 1378 | 1379 | bool success = debugger::setHardwareBreakpoint(processId, 0, hardwareRegister, 0, 0); 1380 | return Napi::Boolean::New(env, success); 1381 | } 1382 | 1383 | Napi::Value injectDll(const Napi::CallbackInfo& args) { 1384 | Napi::Env env = args.Env(); 1385 | 1386 | if (args.Length() != 2 && args.Length() != 3) { 1387 | Napi::Error::New(env, "requires 2 arguments, or 3 with a callback").ThrowAsJavaScriptException(); 1388 | return env.Null(); 1389 | } 1390 | 1391 | if (!args[0].IsNumber() || !args[1].IsString()) { 1392 | Napi::Error::New(env, "first argument needs to be a number, second argument needs to be a string").ThrowAsJavaScriptException(); 1393 | return env.Null(); 1394 | } 1395 | 1396 | if (args.Length() == 3 && !args[2].IsFunction()) { 1397 | Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException(); 1398 | return env.Null(); 1399 | } 1400 | 1401 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 1402 | std::string dllPath(args[1].As().Utf8Value()); 1403 | Napi::Function callback = args[2].As(); 1404 | 1405 | char* errorMessage = ""; 1406 | DWORD moduleHandle = -1; 1407 | bool success = dll::inject(handle, dllPath, &errorMessage, &moduleHandle); 1408 | 1409 | if (strcmp(errorMessage, "") && args.Length() != 3) { 1410 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 1411 | return env.Null(); 1412 | } 1413 | 1414 | // `moduleHandle` above is the return value of the `LoadLibrary` procedure, 1415 | // which we retrieve through `GetExitCode`. This value can become truncated 1416 | // in large address spaces such as 64 bit since `GetExitCode` just returns BOOL, 1417 | // so it's unreliable to use as the handle. Since the handle of a module is just a pointer 1418 | // to the address of the DLL mapped in the process' virtual address space, we can fetch 1419 | // this separately, so we won't return it to prevent it being passed to `unloadDll` 1420 | // and in some cases unexpectedly failing when it is truncated. 1421 | 1422 | if (args.Length() == 3) { 1423 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Boolean::New(env, success) }); 1424 | return env.Null(); 1425 | } else { 1426 | return Napi::Boolean::New(env, success); 1427 | } 1428 | } 1429 | 1430 | Napi::Value unloadDll(const Napi::CallbackInfo& args) { 1431 | Napi::Env env = args.Env(); 1432 | 1433 | if (args.Length() != 2 && args.Length() != 3) { 1434 | Napi::Error::New(env, "requires 2 arguments, or 3 with a callback").ThrowAsJavaScriptException(); 1435 | return env.Null(); 1436 | } 1437 | 1438 | if (!args[0].IsNumber()) { 1439 | Napi::Error::New(env, "first argument needs to be a number").ThrowAsJavaScriptException(); 1440 | return env.Null(); 1441 | } 1442 | 1443 | if (!args[1].IsNumber() && !args[1].IsString()) { 1444 | Napi::Error::New(env, "second argument needs to be a number or a string").ThrowAsJavaScriptException(); 1445 | return env.Null(); 1446 | } 1447 | 1448 | if (args.Length() == 3 && !args[2].IsFunction()) { 1449 | Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException(); 1450 | return env.Null(); 1451 | } 1452 | 1453 | HANDLE handle = (HANDLE)args[0].As().Int64Value(); 1454 | Napi::Function callback = args[2].As(); 1455 | 1456 | HMODULE moduleHandle; 1457 | 1458 | // get module handle (module base address) directly 1459 | if (args[1].IsNumber()) { 1460 | moduleHandle = (HMODULE)args[1].As().Int64Value(); 1461 | } 1462 | 1463 | // find module handle from name of DLL 1464 | if (args[1].IsString()) { 1465 | std::string moduleName(args[1].As().Utf8Value()); 1466 | char* errorMessage = ""; 1467 | 1468 | MODULEENTRY32 module = module::findModule(moduleName.c_str(), GetProcessId(handle), &errorMessage); 1469 | 1470 | if (strcmp(errorMessage, "")) { 1471 | if (args.Length() != 3) { 1472 | Napi::Error::New(env, "unable to find specified module").ThrowAsJavaScriptException(); 1473 | return env.Null(); 1474 | } else { 1475 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage) }); 1476 | return Napi::Boolean::New(env, false); 1477 | } 1478 | } 1479 | 1480 | moduleHandle = (HMODULE) module.modBaseAddr; 1481 | } 1482 | 1483 | char* errorMessage = ""; 1484 | bool success = dll::unload(handle, &errorMessage, moduleHandle); 1485 | 1486 | if (strcmp(errorMessage, "") && args.Length() != 3) { 1487 | Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException(); 1488 | return Napi::Boolean::New(env, false); 1489 | } 1490 | 1491 | if (args.Length() == 3) { 1492 | callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Boolean::New(env, success) }); 1493 | return env.Null(); 1494 | } else { 1495 | return Napi::Boolean::New(env, success); 1496 | } 1497 | } 1498 | 1499 | Napi::Value openFileMapping(const Napi::CallbackInfo& args) { 1500 | Napi::Env env = args.Env(); 1501 | 1502 | std::string fileName(args[0].As().Utf8Value()); 1503 | 1504 | HANDLE fileHandle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, fileName.c_str()); 1505 | 1506 | if (fileHandle == NULL) { 1507 | Napi::Error::New(env, Napi::String::New(env, "Error opening handle to file!")).ThrowAsJavaScriptException(); 1508 | return env.Null(); 1509 | } 1510 | 1511 | return Napi::Value::From(env, (uintptr_t) fileHandle); 1512 | } 1513 | 1514 | Napi::Value mapViewOfFile(const Napi::CallbackInfo& args) { 1515 | Napi::Env env = args.Env(); 1516 | 1517 | HANDLE processHandle = (HANDLE)args[0].As().Int64Value(); 1518 | HANDLE fileHandle = (HANDLE)args[1].As().Int64Value(); 1519 | 1520 | uint64_t offset; 1521 | if (args[2].As().IsBigInt()) { 1522 | bool lossless; 1523 | offset = args[2].As().Uint64Value(&lossless); 1524 | } else { 1525 | offset = args[2].As().Int64Value(); 1526 | } 1527 | 1528 | size_t viewSize; 1529 | if (args[3].As().IsBigInt()) { 1530 | bool lossless; 1531 | viewSize = args[3].As().Uint64Value(&lossless); 1532 | } else { 1533 | viewSize = args[3].As().Int64Value(); 1534 | } 1535 | 1536 | ULONG pageProtection = args[4].As().Int64Value(); 1537 | 1538 | LPVOID baseAddress = MapViewOfFile2(fileHandle, processHandle, offset, NULL, viewSize, 0, pageProtection); 1539 | 1540 | if (baseAddress == NULL) { 1541 | Napi::Error::New(env, Napi::String::New(env, "Error mapping file to process!")).ThrowAsJavaScriptException(); 1542 | return env.Null(); 1543 | } 1544 | 1545 | return Napi::Value::From(env, (uintptr_t) baseAddress); 1546 | } 1547 | 1548 | // https://stackoverflow.com/a/17387176 1549 | std::string GetLastErrorToString() { 1550 | DWORD errorMessageID = ::GetLastError(); 1551 | 1552 | // No error message, return empty string 1553 | if(errorMessageID == 0) { 1554 | return std::string(); 1555 | } 1556 | 1557 | LPSTR messageBuffer = nullptr; 1558 | 1559 | size_t size = FormatMessageA( 1560 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 1561 | NULL, 1562 | errorMessageID, 1563 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 1564 | (LPSTR)&messageBuffer, 1565 | 0, 1566 | NULL 1567 | ); 1568 | 1569 | std::string message(messageBuffer, size); 1570 | 1571 | // Free the buffer 1572 | LocalFree(messageBuffer); 1573 | return message; 1574 | } 1575 | 1576 | Napi::Object init(Napi::Env env, Napi::Object exports) { 1577 | exports.Set(Napi::String::New(env, "openProcess"), Napi::Function::New(env, openProcess)); 1578 | exports.Set(Napi::String::New(env, "getProcesses"), Napi::Function::New(env, getProcesses)); 1579 | exports.Set(Napi::String::New(env, "getModules"), Napi::Function::New(env, getModules)); 1580 | exports.Set(Napi::String::New(env, "findModule"), Napi::Function::New(env, findModule)); 1581 | exports.Set(Napi::String::New(env, "readMemory"), Napi::Function::New(env, readMemory)); 1582 | exports.Set(Napi::String::New(env, "readBuffer"), Napi::Function::New(env, readBuffer)); 1583 | exports.Set(Napi::String::New(env, "writeMemory"), Napi::Function::New(env, writeMemory)); 1584 | exports.Set(Napi::String::New(env, "writeBuffer"), Napi::Function::New(env, writeBuffer)); 1585 | exports.Set(Napi::String::New(env, "findPattern"), Napi::Function::New(env, findPattern)); 1586 | exports.Set(Napi::String::New(env, "findPatternByModule"), Napi::Function::New(env, findPatternByModule)); 1587 | exports.Set(Napi::String::New(env, "findPatternByAddress"), Napi::Function::New(env, findPatternByAddress)); 1588 | exports.Set(Napi::String::New(env, "virtualProtectEx"), Napi::Function::New(env, virtualProtectEx)); 1589 | exports.Set(Napi::String::New(env, "callFunction"), Napi::Function::New(env, callFunction)); 1590 | exports.Set(Napi::String::New(env, "virtualAllocEx"), Napi::Function::New(env, virtualAllocEx)); 1591 | exports.Set(Napi::String::New(env, "getRegions"), Napi::Function::New(env, getRegions)); 1592 | exports.Set(Napi::String::New(env, "virtualQueryEx"), Napi::Function::New(env, virtualQueryEx)); 1593 | exports.Set(Napi::String::New(env, "attachDebugger"), Napi::Function::New(env, attachDebugger)); 1594 | exports.Set(Napi::String::New(env, "detachDebugger"), Napi::Function::New(env, detachDebugger)); 1595 | exports.Set(Napi::String::New(env, "awaitDebugEvent"), Napi::Function::New(env, awaitDebugEvent)); 1596 | exports.Set(Napi::String::New(env, "handleDebugEvent"), Napi::Function::New(env, handleDebugEvent)); 1597 | exports.Set(Napi::String::New(env, "setHardwareBreakpoint"), Napi::Function::New(env, setHardwareBreakpoint)); 1598 | exports.Set(Napi::String::New(env, "removeHardwareBreakpoint"), Napi::Function::New(env, removeHardwareBreakpoint)); 1599 | exports.Set(Napi::String::New(env, "injectDll"), Napi::Function::New(env, injectDll)); 1600 | exports.Set(Napi::String::New(env, "unloadDll"), Napi::Function::New(env, unloadDll)); 1601 | exports.Set(Napi::String::New(env, "openFileMapping"), Napi::Function::New(env, openFileMapping)); 1602 | exports.Set(Napi::String::New(env, "mapViewOfFile"), Napi::Function::New(env, mapViewOfFile)); 1603 | return exports; 1604 | } 1605 | 1606 | NODE_API_MODULE(memoryjs, init) --------------------------------------------------------------------------------