memoryjs
是一个用于读写进程内存的 NPM 包!
功能 • 入门 • 使用 • 文档 • 调试
待办事项:
这是一个 Node 附加组件(上次测试可在v14.15.0
上运行),因此需要使用 node-gyp。
您可能还需要按照以下步骤安装和设置node-gyp
。
npm install memoryjs
使用memoryjs时,目标进程应与运行的Node版本的平台架构相匹配。例如,如果您想以 64 位进程为目标,则应该尝试使用 64 位版本的 Node.js。
您还需要重新编译该库并针对您想要的平台。前往 memoryjs 节点模块目录,打开终端并运行以下编译脚本之一:
# will 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
如果您计划将此模块与 Node Webkit 或 Electron 一起使用,请查看 Liam Mitchell 的构建说明。
const memoryjs = require('memoryjs');
const processName = "csgo.exe";
// sync: open a process
const processObject = memoryjs.openProcess(processName);
// async: open a process
memoryjs.openProcess(processName, (error, processObject) => {});
// sync: get all processes
const processes = memoryjs.getProcesses();
// async: get all processes
memoryjs.getProcesses((error, processes) => {});
// close a process (release handle)
memoryjs.closeHandle(handle);
请参阅本自述文件的文档部分,了解流程对象的外观。
// sync: find a module
const moduleObject = memoryjs.findModule(moduleName, processId);
// async: find a module
memoryjs.findModule(moduleName, processId, (error, moduleObject) => {});
// sync: get all modules
const modules = memoryjs.getModules(processId);
// async: get all modules
memoryjs.getModules(processId, (error, modules) => {});
请参阅本自述文件的文档部分,了解模块对象的外观。
// sync: read data type from memory
const value = memoryjs.readMemory(handle, address, dataType);
// async: read data type from memory
memoryjs.readMemory(handle, address, dataType, (error, value) => {});
// sync: read buffer from memory
const buffer = memoryjs.readBuffer(handle, address, size);
// async: read buffer from memory
memoryjs.readBuffer(handle, address, size, (error, buffer) => {});
// sync: write data type to memory
memoryjs.writeMemory(handle, address, value, dataType);
// sync: write buffer to memory
memoryjs.writeBuffer(handle, address, buffer);
// sync: fetch memory regions
const regions = memoryjs.getRegions(handle);
// async: fetch memory regions
memoryjs.getRegions(handle, (regions) => {});
请参阅本自述文件的文档部分,了解dataType
可以是什么值。
// sync: open a named file mapping object
const fileHandle = memoryjs.openFileMapping(fileName);
// sync: map entire file into a specified process
const baseAddress = memoryjs.mapViewOfFile(processHandle, fileName);
// sync: map portion of a file into a specified process
const baseAddress = memoryjs.mapViewOfFile(processHandle, fileName, offset, viewSize, pageProtection);
// sync: close handle to a file mapping object
const success = memoryjs.closeHandle(fileHandle);
请参阅本自述文件的文档部分,了解有关这些函数的参数和返回值的详细信息。
// sync: change/set the protection on a region of memory
const oldProtection = memoryjs.virtualProtectEx(handle, address, size, protection);
请参阅本自述文件的文档部分,了解protection
值。
// 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) => {});
// sync: execute a function in a remote process
const result = memoryjs.callFunction(handle, args, returnType, address);
// async: execute a function in a remote process
memoryjs.callFunction(handle, args, returnType, address, (error, result) => {});
单击此处查看结果对象的外观。
单击此处了解有关如何设置参数格式和返回类型的详细信息。
// 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) => {});
// sync: attach debugger
const success = memoryjs.attachDebugger(processId, exitOnDetach);
// sync: detach debugger
const success = memoryjs.detachDebugger(processId);
// sync: wait for debug event
const success = memoryjs.awaitDebugEvent(hardwareRegister, millisTimeout);
// sync: handle debug event
const success = memoryjs.handleDebugEvent(processId, threadId);
// sync: set hardware breakpoint
const success = memoryjs.setHardwareBreakpoint(processId, address, hardwareRegister, trigger, length);
// sync: remove hardware breakpoint
const success = memoryjs.removeHardwareBreakpoint(processId, hardwareRegister);
注意:该文档当前正在更新,请参阅 Wiki 了解更多信息。
{ dwSize: 304,
th32ProcessID: 10316,
cntThreads: 47,
th32ParentProcessID: 7804,
pcPriClassBase: 8,
szExeFile: "csgo.exe",
modBaseAddr: 1673789440,
handle: 808 }
handle
和modBaseAddr
属性仅在打开进程时可用,而在列出进程时不可用。
{ modBaseAddr: 468123648,
modBaseSize: 80302080,
szExePath: 'c:\program files (x86)\steam\steamapps\common\counter-strike global offensive\csgo\bin\client.dll',
szModule: 'client.dll',
th32ProcessID: 10316,
GlblcntUsage: 2 }
{ returnValue: 1.23,
exitCode: 2 }
当在远程进程中执行函数时返回该对象:
returnValue
是从被调用的函数返回的值exitCode
是线程的终止状态使用写入或读取函数时,数据类型 (dataType) 参数应引用库内的常量:
持续的 | 字节 | 别名 | 范围 |
---|---|---|---|
memoryjs.BOOL | 1 | memoryjs.BOOLEAN | 0 到 1 |
memoryjs.INT8 | 1 | memoryjs.BYTE , memoryjs.CHAR | -128 至 127 |
memoryjs.UINT8 | 1 | memoryjs.UBYTE , memoryjs.UCHAR | 0 至 255 |
memoryjs.INT16 | 2 | memoryjs.SHORT | -32,768 至 32,767 |
memoryjs.UINT16 | 2 | memoryjs.USHORT , memoryjs.WORD | 0 至 65,535 |
memoryjs.INT32 | 4 | memoryjs.INT , memoryjs.LONG | -2,147,483,648 至 2,147,483,647 |
memoryjs.UINT32 | 4 | memoryjs.UINT , memoryjs.ULONG , memoryjs.DWORD | 0 至 4,294,967,295 |
memoryjs.INT64 | 8 | 不适用 | -9,223,372,036,854,775,808 至 9,223,372,036,854,775,807 |
memoryjs.UINT64 | 8 | 不适用 | 0 至 18,446,744,073,709,551,615 |
memoryjs.FLOAT | 4 | 不适用 | 3.4E +/- 38(7 位数字) |
memoryjs.DOUBLE | 8 | 不适用 | 1.7E +/- 308(15 位数字) |
memoryjs.PTR | 4/8 | memoryjs.POINTER | 不适用 |
memoryjs.UPTR | 4/8 | memoryjs.UPOINTER | 不适用 |
memoryjs.STR | 不适用 | memoryjs.STRING | 不适用 |
memoryjs.VEC3 | 12 | memoryjs.VECTOR3 | 不适用 |
memoryjs.VEC4 | 16 | memoryjs.VECTOR4 | 不适用 |
笔记:
_BE
附加到数据类型。例如: memoryjs.DOUBLE_BE
。INT64
、 UINT64
、 INT64_BE
、 UINT64_BE
)时,您需要提供 BigInt。当读取 64 位整数时,您将收到一个 BigInt。这些数据类型用于表示正在读取或写入的数据的类型。
64 位整数示例:
const value = memoryjs.readMemory(handle, address, memoryjs.INT64);
console.log(typeof value); // bigint
memoryjs.writeMemory(handle, address, value + 1n, memoryjs.INT64);
Vector3是三个浮点数的数据结构:
const vector3 = { x: 0.0, y: 0.0, z: 0.0 };
memoryjs.writeMemory(handle, address, vector3, memoryjs.VEC3);
Vector4 是四个浮点数的数据结构:
const vector4 = { w: 0.0, x: 0.0, y: 0.0, z: 0.0 };
memoryjs.writeMemory(handle, address, vector4, memoryjs.VEC4);
如果您想将结构写入内存,则可以使用缓冲区。有关如何执行此操作的示例,请查看缓冲区示例。
要将结构写入内存或从内存中读取结构,您可以使用 structron 来定义结构并使用它们来写入或解析缓冲区。
如果您想使用structron
读取std::string
,该库公开了一个可用于读取/写入字符串的自定义类型:
// 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(string, 'name');
或者,您可以使用浓缩库和溶解库来实现相同的目的。这里有一个古老的例子。
保护类型是位标志 DWORD 值。
此参数应引用库中的常量:
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
有关详细信息,请参阅 MSDN 的内存保护常量。
内存分配类型是位标志 DWORD 值。
此参数应引用库中的常量:
memoryjs.MEM_COMMIT, memoryjs.MEM_RESERVE, memoryjs.MEM_RESET, memoryjs.MEM_RESET_UNDO
有关详细信息,请参阅 MSDN 的 VirtualAllocEx 文档。
您可以使用此库读取“字符串”或“char*”并写入字符串。
在这两种情况下,您都想获取 char 数组的地址:
std::string str1 = "hello";
std::cout << "Address: 0x" << hex << (DWORD) str1.c_str() << dec << std::endl;
char* str2 = "hello";
std::cout << "Address: 0x" << hex << (DWORD) str2 << dec << std::endl;
从这里您可以简单地使用该地址来写入和读取内存。
然而,在读取内存中的字符串时有一个警告,由于库不知道字符串有多长,因此它将继续读取,直到找到第一个空终止符。为了防止无限循环,如果在 100 万个字符后仍未找到空终止符,它将停止读取。
将来绕过此限制的一种方法是允许用户设置最大字符数的参数。
模式扫描时,需要针对签名类型提出标志。签名类型参数需要是以下之一:
0x0
或memoryjs.NORMAL
表示正常签名。
0x1
或memoryjs.READ
将读取该地址的内存。
0x2
或memoryjs.SUBSTRACT
将从地址中减去图像基址。
要引发多个标志,请使用按位 OR 运算符: memoryjs.READ | memoryjs.SUBTRACT
。
该库公开函数来映射获取句柄并读取内存映射文件。
打开文件映射(文件名)
有关详细信息,请参阅 MSDN 的 OpenFileMappingA 文档。
mapViewOfFile(进程句柄,文件名)
memoryjs.openFileMapping
获取constants.PAGE_READONLY
。mapViewOfFile(进程句柄,文件名,偏移量,视图大小,页面保护)
memoryjs.openFileMapping
获取number
或bigint
): 距文件开头的偏移量(必须是 64KB 的倍数)number
或bigint
):要映射的字节数(如果为0
,则将读取整个文件,无论偏移量如何)有关详细信息,请参阅 MSDN 的 MapViewOfFile2 文档。
有关页面保护类型,请参阅保护类型。
我们有一个创建文件映射的过程:
HANDLE fileHandle = CreateFileA("C:\foo.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
HANDLE fileMappingHandle = CreateFileMappingA(fileHandle, NULL, PAGE_READONLY, 0, 0, "MappedFooFile");
我们可以将文件映射到指定的目标进程并使用memoryjs
读取该文件:
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 baseAddress = memoryjs.mapViewOfFile(processObject.handle, fileHandle.handle, 65536, 10, constants.PAGE_READONLY);
const buffer = memoryjs.readBuffer(processObject.handle, baseAddress, 10);
const data = buffer.toString();
const success = memoryjs.closeHandle(fileHandle);
如果您想读取内存映射文件,但没有目标进程来映射该文件,则可以使用全局变量process.pid
将其映射到当前 Node 进程:
const processObject = memoryjs.openProcess(process.pid);
远程函数执行的工作原理是构建参数数组并动态生成 shellcode,将其注入目标进程并执行,因此可能会发生崩溃。
要调用进程中的函数,可以使用callFunction
函数。该库支持向函数传递参数,并且需要采用以下格式:
[{ type: T_INT, value: 4 }]
该库期望参数是一个对象数组,其中每个对象都有一个表示参数数据类型的type
和一个表示参数实际值的value
。下面可以找到各种支持的数据类型。
memoryjs.T_VOID = 0x0,
memoryjs.T_STRING = 0x1,
memoryjs.T_CHAR = 0x2,
memoryjs.T_BOOL = 0x3,
memoryjs.T_INT = 0x4,
memoryjs.T_DOUBLE = 0x5,
memoryjs.T_FLOAT = 0x6,
使用callFunction
时,您还需要提供函数的返回类型,该返回类型也需要是上述值之一。
例如,给定以下 C++ 函数:
int add(int a, int b) {
return a + b;
}
您可以这样调用该函数:
const args = [{
type: memoryjs.T_INT,
value: 2,
}, {
type: memoryjs.T_INT,
value: 5,
}];
const returnType = T_INT;
> memoryjs.callFunction(handle, args, returnType, address);
{ returnValue: 7, exitCode: 7 }
有关callFunction
返回内容的详细信息,请参阅结果对象文档。
注意:当前不支持传递double
作为参数,但返回 1 是支持的。
非常感谢使此功能成为可能的各个贡献者。
硬件断点的工作原理是将调试器附加到进程,在某个地址上设置断点并声明触发类型(例如写入该地址时的断点),然后持续等待调试事件出现(然后对其进行处理)。
该库公开了主要功能,但还包含一个包装类来简化过程。有关完整的代码示例,请查看我们的调试示例。
设置断点时,需要传递一个触发类型:
memoryjs.TRIGGER_ACCESS
- 访问地址时发生断点memoryjs.TRIGGER_WRITE
- 写入地址时发生断点请注意,当监视包含字符串的地址时, setHardwareBreakpoint
函数的size
参数应为字符串的长度。使用Debugger
包装器类时,包装器将通过尝试读取字符串来自动确定字符串的大小。
总结一下:
使用Debugger
类时:
size
参数传递给setHardwareBreakpoint
setHardwareBreakpoint
返回用于断点的寄存器手动使用调试器功能时:
size
参数是内存中变量的大小(例如 int32 = 4 字节)。对于字符串,该参数是字符串的长度memoryjs.DR0
到memoryhs.DR3
)。只有 4 个可用的硬件寄存器(某些 CPU 甚至可能少于 4 个可用)。这意味着在任何给定时间只能设置 4 个断点setHardwareBreakpoint
返回一个布尔值,表明操作是否成功有关调试和硬件断点的更多信息,请查看以下链接:
调试器包装器包含您应该使用的以下函数:
class Debugger {
attach(processId, killOnDetach = false);
detach(processId);
setHardwareBreakpoint(processId, address, trigger, dataType);
removeHardwareBreakpoint(processId, register);
}
const hardwareDebugger = memoryjs.Debugger;
hardwareDebugger.attach(processId);
const address = 0xDEADBEEF;
const trigger = memoryjs.TRIGGER_ACCESS;
const dataType = memoryjs.INT;
const register = hardwareDebugger.setHardwareBreakpoint(processId, address, trigger, dataType);
// `debugEvent` event emission catches debug events from all registers
hardwareDebugger.on('debugEvent', ({ register, event }) => {
console.log(`Hardware Register ${register} breakpoint`);
console.log(event);
});
// You can listen to debug events from specific hardware registers
// by listening to whatever register was returned from `setHardwareBreakpoint`
hardwareDebugger.on(register, (event) => {
console.log(event);
});
const hardwareDebugger = memoryjs.Debugger;
hardwareDebugger.attach(processId);
// available registers: DR0 through DR3
const register = memoryjs.DR0;
// int = 4 bytes
const size = 4;
const address = 0xDEADBEEF;
const trigger = memoryjs.TRIGGER_ACCESS;
const dataType = memoryjs.INT;
const success = memoryjs.setHardwareBreakpoint(processId, address, register, trigger, size);
const timeout = 100;
setInterval(() => {
// `debugEvent` can be null if no event occurred
const debugEvent = memoryjs.awaitDebugEvent(register, timeout);
// If a breakpoint occurred, handle it
if (debugEvent) {
memoryjs.handleDebugEvent(debugEvent.processId, debugEvent.threadId);
}
}, timeout);
注意:不需要循环,例如,如果您只想等到第一次检测到正在访问或写入的地址,则不需要循环。
转到模块的根目录并运行以下命令之一:
# will automatically compile based on the detected Node architecture
npm run debug
# compile to target 32 bit processes
npm run debug32
# compile to target 64 bit processes
npm run debug64
index.js
文件以需要调试模块转到根目录并将index.js
中的行更改为:
const memoryjs = require('./build/Release/memoryjs');
至以下内容:
const memoryjs = require('./build/Debug/memoryjs');
在 Visual Studio 中打开binding.sln
解决方案,该解决方案位于项目根目录的build
文件夹中。
node.exe
文件的位置(例如C:nodejsnode.exe
)C:projecttest.js
)在 Visual Studio 中浏览项目文件(通过展开..
,然后在解决方案资源管理器中展开lib
)。可以通过按住Alt
并单击源代码文件顶部的头文件名来查看头文件。
通过单击行号左侧来设置断点。
通过按F5
、单击工具栏中的“调试”然后单击“开始调试”或单击“本地 Windows 调试器”来开始调试。
将运行您在步骤 4 中设置为命令参数的脚本,Visual Studio 将在设置的断点处暂停,并允许您逐行单步执行代码并检查变量。