memoryjs
プロセス メモリの読み取りと書き込みを行うための NPM パッケージです。
機能 • はじめに • 使用法 • ドキュメント • デバッグ
TODO:
これは Node アドオン (最後にv14.15.0
で動作することがテストされました) であるため、使用するには node-gyp が必要です。
また、次の手順に従って、 node-gyp
インストールしてセットアップする必要がある場合もあります。
npm install memoryjs
Memoryjs を使用する場合、ターゲット プロセスは、実行中のノード バージョンのプラットフォーム アーキテクチャと一致する必要があります。たとえば、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);
プロセス オブジェクトがどのようなものであるかを確認するには、この README のドキュメント セクションを参照してください。
// 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) => {});
モジュール オブジェクトがどのようなものであるかを確認するには、この README のドキュメント セクションを参照してください。
// 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
値については、この README の「ドキュメント」セクションを参照してください。
// 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);
これらの関数のパラメータと戻り値の詳細については、この README の「ドキュメント」セクションを参照してください。
// sync: change/set the protection on a region of memory
const oldProtection = memoryjs.virtualProtectEx(handle, address, size, protection);
protection
値については、この README の「ドキュメント」セクションを参照してください。
// 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 は 3 つの float のデータ構造です。
const vector3 = { x: 0.0, y: 0.0, z: 0.0 };
memoryjs.writeMemory(handle, address, vector3, memoryjs.VEC3);
Vector4 は 4 つの float 型のデータ構造です。
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 ドキュメントを参照してください。
このライブラリを使用すると、「string」または「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;
ここからは、このアドレスを使用してメモリの書き込みと読み取りを行うことができます。
ただし、メモリ内の文字列を読み取るときに注意すべき点が 1 つあります。ただし、ライブラリは文字列の長さがわからないため、最初の NULL ターミネータが見つかるまで読み取りを続けます。無限ループを防ぐため、100 万文字を超えても NULL ターミネータが見つからない場合は読み取りを停止します。
将来的にこの制限を回避する 1 つの方法は、ユーザーが最大文字数を設定できるパラメーターを許可することです。
パターンをスキャンするときは、署名タイプに対してフラグを立てる必要があります。署名タイプ パラメータは次のいずれかである必要があります。
0x0
または通常の署名を示すmemoryjs.NORMAL
。
0x1
またはmemoryjs.READ
アドレスのメモリを読み取ります。
0x2
またはmemoryjs.SUBSTRACT
アドレスからイメージ ベースを減算します。
複数のフラグを立てるには、ビットごとの OR 演算子を使用しますmemoryjs.READ | memoryjs.SUBTRACT
。
このライブラリは、メモリ マップ ファイルへのハンドルを取得し、メモリ マップ ファイルを読み取る関数を公開します。
openFileMapping(ファイル名)
詳細については、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
を使用してファイルを現在のノード プロセスにマップできます。
const processObject = memoryjs.openProcess(process.pid);
リモート関数の実行は、引数の配列を構築し、ターゲット プロセスに挿入されて実行されるシェルコードを動的に生成することによって機能します。このため、クラッシュが発生する可能性があります。
プロセス内で関数を呼び出すには、 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
渡すことはサポートされていませんが、double を返すことはサポートされています。
この機能を可能にしてくれたさまざまな貢献者に深く感謝します。
ハードウェア ブレークポイントは、デバッガをプロセスに接続し、特定のアドレスにブレークポイントを設定し、トリガー タイプ (アドレスへの書き込み時のブレークポイントなど) を宣言し、デバッグ イベントが発生するのを継続的に待ちます (その後、そのイベントを処理します)。
このライブラリは主要な関数を公開していますが、プロセスを簡素化するためのラッパー クラスも含まれています。完全なコード例については、デバッグ例を確認してください。
ブレークポイントを設定するときは、トリガー タイプを渡す必要があります。
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 で、プロジェクトのルート ディレクトリのbuild
フォルダーにあるbinding.sln
ソリューションを開きます。
node.exe
ファイルの場所 (例: C:nodejsnode.exe
) に設定します。C:projecttest.js
) に設定します。Visual Studio でプロジェクト ファイルを探索します (ソリューション エクスプローラーで..
を展開し、次にlib
展開します)。ヘッダー ファイルは、 Alt
を押しながらソース コード ファイルの上部にあるヘッダー ファイル名をクリックすると表示できます。
ブレークポイントは、行番号の左側をクリックして設定します。
F5
押すか、ツールバーの「デバッグ」をクリックしてから「デバッグの開始」をクリックするか、「ローカル Windows デバッガー」をクリックしてデバッグを開始します。
手順 4 でコマンド引数として設定したスクリプトが実行され、Visual Studio は設定されたブレークポイントで一時停止し、コードを 1 行ずつステップ実行して変数を検査できるようになります。