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 將在設定的斷點處暫停,並允許您逐行單步執行程式碼並檢查變數。