memoryjs

repository·master·Indexed 20 days ago

https://github.com/rob--/memoryjs

A Node.js add-on for reading and writing process memory. It provides low-level access to processes, modules, and memory regions, featuring capabilities for pattern scanning, DLL injection, remote function execution via callFunction, and hardware breakpoint management through a debugger wrapper. It supports both 32-bit and 64-bit architectures and includes utilities for managing memory mapped files and modifying memory protection levels.

Tokens
10.5K
Snippets
30
Records
36
Agent score
71%

What's inside memoryjs

  1. Use the Debugger wrapper for hardware breakpoints

    master

    The Debugger wrapper class simplifies hardware breakpoint management by handling register selection and event listening automatically.

    Key Advantages:

    • No need to manually pick a hardware register (DR0-DR3).
    • No need to pass the size parameter for strings (the wrapper attempts to read the string to determine size).
    • Debug events are handled via standard event listeners.
    • setHardwareBreakpoint returns the specific register used for the breakpoint.

    Trigger Types:

    • memoryjs.TRIGGER_ACCESS: Breakpoint occurs when the address is accessed.
    • memoryjs.TRIGGER_WRITE: Breakpoint occurs when the address is written to.
    const hardwareDebugger = memoryjs.Debugger;
    
    // 1. Attach
    hardwareDebugger.attach(processId);
    
    // 2. Set breakpoint
    const address = 0xDEADBEEF;
    const trigger = memoryjs.TRIGGER_ACCESS;
    const dataType = memoryjs.INT;
    const register = hardwareDebugger.setHardwareBreakpoint(processId, address, trigger, dataType);
    
    // 3. Listen for events
    hardwareDebugger.on('debugEvent', ({ register, event }) => {
      console.log(`Hardware Register ${register} breakpoint`);
      console.log(event);
    });
    
    // Or listen to a specific register returned by setHardwareBreakpoint
    hardwareDebugger.on(register, (event) => {
      console.log(event);
    });
  2. Compile the project for debugging

    master

    To debug the memoryjs module itself using Visual Studio, you must first compile it in Debug mode using the appropriate architecture command.

    # Automatically compile based on detected Node architecture
    npm run debug
    
    # Compile to target 32 bit processes
    npm run debug32
    
    # Compile to target 64 bit processes
    npm run debug64
  3. Read and write strings

    master

    You can read or write strings by providing the address of the character array (e.g., the address returned by std::string::c_str() or a char* pointer in C++).

    Warning: When reading a string, the library reads until it finds a null-terminator. To prevent infinite loops in case of missing null-terminators, it will stop reading after 1,000,000 characters.

  4. Manually manage hardware breakpoints

    master

    If you choose not to use the Debugger wrapper, you must manually manage registers, data sizes, and the debug event loop.

    Constraints:

    • Only 4 hardware registers are available (memoryjs.DR0 through memoryjs.DR3). Only 4 breakpoints can be set at any given time.
    • You must manually provide the size of the variable in bytes (e.g., 4 for int32). For strings, use the string length.
    • You must implement a loop to call awaitDebugEvent and handleDebugEvent to process triggers.

    Return Values:

    • memoryjs.setHardwareBreakpoint returns a boolean indicating success.
    const hardwareDebugger = memoryjs.Debugger;
    
    // 1. Attach
    hardwareDebugger.attach(processId);
    
    // 2. Set breakpoint manually
    const register = memoryjs.DR0;
    const size = 4; // e.g. int32
    const address = 0xDEADBEEF;
    const trigger = memoryjs.TRIGGER_ACCESS;
    const dataType = memoryjs.INT;
    
    const success = memoryjs.setHardwareBreakpoint(processId, address, register, trigger, size);
    
    // 3. Manual event loop
    const timeout = 100;
    setInterval(() => {
      const debugEvent = memoryjs.awaitDebugEvent(register, timeout);
      if (debugEvent) {
        memoryjs.handleDebugEvent(debugEvent.processId, debugEvent.threadId);
      }
    }, timeout);
  5. Install memoryjs

    master

    Install memoryjs via npm. This is a Node add-on and requires node-gyp to be installed and configured on your system.

    Important Architecture Note: The target process architecture must match the architecture of the Node.js version you are running. For example, to target a 64-bit process, use a 64-bit version of Node.js.

    npm install memoryjs
  6. Configure Visual Studio to debug memoryjs

    master

    After compiling in Debug mode, follow these steps to debug the native module:

    1. Update index.js: Change the require path from the Release build to the Debug build: const memoryjs = require('./build/Debug/memoryjs');
    2. Open Solution: Open build/binding.sln in Visual Studio.
    3. Configure Debugging:
      • Right-click Project -> Properties -> Debugging.
      • Set Command to your node.exe path.
      • Set Command Arguments to your script path (e.g., C:\project\test.js).
    4. Run: Press F5 to start debugging.
  7. Compile memoryjs for specific architectures

    master

    Because memoryjs is a native Node add-on, you may need to recompile it to target specific platform architectures. Navigate to the memoryjs node module directory in your terminal and run one of the following commands:

    • npm run build: Automatically compiles based on the detected Node architecture.
    • npm run build32: Compiles to target 32-bit processes.
    • npm run build64: Compiles to target 64-bit processes.
    # automatically compile based on the detected Node architecture
    npm run build
    
    # compile to target 32 bit processes
    npm run build32
    
    # compile to target 64 bit processes
    npm run build64
  8. Read and write complex structures using buffers or structron

    master

    For custom data structures, you can use standard Node.js Buffer objects or the structron library.

    Using structron for std::string

    To read a std::string using structron, use the memoryjs.STRUCTRON_TYPE_STRING helper. This requires the process handle, the base address of the structure, and the target architecture ('32' or '64').

    const stringType = memoryjs.STRUCTRON_TYPE_STRING(processObject.handle, structAddress, '64');
    
    const Struct = require('structron');
    const Player = new Struct()
      .addMember(stringType, 'name');
    // To create the type, we need to pass the process handle, base address of the
    // structure, and the target process architecture (either "32" or "64").
    const stringType = memoryjs.STRUCTRON_TYPE_STRING(processObject.handle, structAddress, '64');
    
    // Create a custom structure using the custom type, full example in /examples/buffers.js
    const Struct = require('structron');
    const Player = new Struct()
      .addMember(stringType, 'name');
  9. Work with Memory Mapped Files

    master

    The library allows you to open and map file mappings into a process's memory space.

    API

    • openFileMapping(fileName): Opens a file mapping object. Returns a handle.
    • mapViewOfFile(processHandle, fileHandle, [offset], [viewSize], [pageProtection]):
      • Maps the file to the target process memory.
      • offset: Must be a multiple of 64KB.
      • viewSize: If 0, the entire file is mapped.
      • pageProtection: Defaults to memoryjs.PAGE_READONLY if not provided.

    To map a file to the current Node process instead of a target process, use process.pid as the processHandle.

    const processObject = memoryjs.openProcess("example.exe");
    const fileHandle = memoryjs.openFileMapping("MappedFooFile");
    
    // read entire file
    const baseAddress = memoryjs.mapViewOfFile(processObject.handle, fileHandle.handle);
    const data = memoryjs.readMemory(processObject.handle, baseAddress, memoryjs.STR);
    
    // read 10 bytes after 64KB
    const baseAddressOffset = memoryjs.mapViewOfFile(processObject.handle, fileHandle.handle, 65536, 10, memoryjs.PAGE_READONLY);
    const buffer = memoryjs.readBuffer(processObject.handle, baseAddressOffset, 10);
    const dataString = buffer.toString();
    
    const success = memoryjs.closeHandle(fileHandle);
  10. Perform pattern scanning

    master

    Search for byte patterns across modules or memory regions.

    • findPattern(handle, pattern, flags, patternOffset): Scans all modules and memory regions.
    • findPattern(handle, moduleName, pattern, flags, patternOffset): Scans a specific module.
    • findPattern(handle, baseAddress, pattern, flags, patternOffset): Scans a specific memory region or module at the provided baseAddress.
    // sync: pattern scan all modules and memory regions
    const address = memoryjs.findPattern(handle, pattern, flags, patternOffset);
    
    // async: pattern scan all modules and memory regions
    memoryjs.findPattern(handle, pattern, flags, patternOffset, (error, address) => {});
    
    // sync: pattern scan a given module
    const address = memoryjs.findPattern(handle, moduleName, pattern, flags, patternOffset);
    
    // async: pattern scan a given module
    memoryjs.findPattern(handle, moduleName, pattern, flags, patternOffset, (error, address) => {});
    
    // sync: pattern scan a memory region or module at the given base address
    const address = memoryjs.findPattern(handle, baseAddress, pattern, flags, patternOffset);
    
    // async: pattern scan a memory region or module at the given base address
    memoryjs.findPattern(handle, baseAddress, pattern, flags, patternOffset, (error, address) => {});
  11. Reference: Debugger wrapper API

    master

    The Debugger class provides a high-level interface for process debugging and hardware breakpoints.

    class Debugger {
      attach(processId, killOnDetach = false);
      detach(processId);
      setHardwareBreakpoint(processId, address, trigger, dataType);
      removeHardwareBreakpoint(processId, register);
    }
  12. Inject and unload DLLs

    master

    Inject a DLL into a process or unload an existing one.

    • injectDll(handle, dllPath): Injects a DLL into the target process.
    • unloadDll(handle, moduleBaseAddress): Unloads a DLL using its module base address.
    • unloadDll(handle, moduleName): Unloads a DLL using its module name.
    // sync: inject a DLL
    const success = memoryjs.injectDll(handle, dllPath);
    
    // async: inject a DLL
    memoryjs.injectDll(handle, dllPath, (error, success) => {});
    
    // sync: unload a DLL by module base address
    const success = memoryjs.unloadDll(handle, moduleBaseAddress);
    
    // async: unload a DLL by module base address
    memoryjs.unloadDll(handle, moduleBaseAddress, (error, success) => {});
    
    // sync: unload a DLL by module name
    const success = memoryjs.unloadDll(handle, moduleName);
    
    // async: unload a DLL by module name
    memoryjs.unloadDll(handle, moduleName, (error, success) => {});