memoryjs
는 프로세스 메모리를 읽고 쓰는 NPM 패키지입니다!
기능 • 시작하기 • 사용법 • 문서 • 디버그
할 일:
이것은 노드 추가 기능( 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);
프로세스 개체의 모양을 보려면 이 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은 세 개의 부동소수점으로 구성된 데이터 구조입니다.
const vector3 = { x: 0.0, y: 0.0, z: 0.0 };
memoryjs.writeMemory(handle, address, vector3, memoryjs.VEC3);
Vector4는 4개의 부동 소수점으로 구성된 데이터 구조입니다.
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;
여기서는 이 주소를 사용하여 메모리를 쓰고 읽을 수 있습니다.
그러나 메모리에서 문자열을 읽을 때 한 가지 주의 사항이 있습니다. 라이브러리는 문자열의 길이를 모르기 때문에 첫 번째 Null 종결자를 찾을 때까지 계속 읽습니다. 무한 루프를 방지하기 위해 100만 자 이후에도 null 종결자를 찾지 못하면 읽기를 중지합니다.
나중에 이 제한을 우회하는 한 가지 방법은 사용자가 최대 문자 수를 설정할 수 있도록 매개변수를 허용하는 것입니다.
패턴 스캔 시 서명 유형에 대한 플래그를 올려야 합니다. 서명 유형 매개변수는 다음 중 하나여야 합니다.
0x0
또는 일반 서명을 나타내는 memoryjs.NORMAL
입니다.
0x1
또는 memoryjs.READ
는 해당 주소의 메모리를 읽습니다.
0x2
또는 memoryjs.SUBSTRACT
는 주소에서 이미지 기반을 뺍니다.
여러 플래그를 발생시키려면 비트별 OR 연산자를 사용하세요. memoryjs.READ | memoryjs.SUBTRACT
.
라이브러리는 메모리 매핑된 파일에 대한 핸들을 얻고 읽는 함수를 매핑하는 기능을 노출합니다.
openFileMapping(파일 이름)
자세한 내용은 MSDN의 OpenFileMappingA 설명서를 참조하세요.
mapViewOfFile(프로세스 핸들, 파일 이름)
memoryjs.openFileMapping
에서 얻은 파일 매핑 개체의 핸들constants.PAGE_READONLY
입니다.mapViewOfFile(processHandle, fileName, offset, viewSize, pageProtection)
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
을 인수로 전달하는 것은 지원되지 않지만 반환하는 것은 지원됩니다.
이 기능을 가능하게 해준 다양한 기여자들에게 깊은 감사를 드립니다.
하드웨어 중단점은 프로세스에 디버거를 연결하고, 특정 주소에 중단점을 설정하고, 트리거 유형(예: 주소 쓰기 중단점)을 선언한 다음 디버그 이벤트가 발생할 때까지 계속해서 기다리는 방식으로 작동합니다(결과적으로 이를 처리함).
이 라이브러리는 주요 기능을 노출하지만 프로세스를 단순화하기 위한 래퍼 클래스도 포함합니다. 전체 코드 예제를 보려면 디버깅 예제를 확인하세요.
중단점을 설정할 때 트리거 유형을 전달해야 합니다.
memoryjs.TRIGGER_ACCESS
- 주소에 액세스할 때 중단점이 발생합니다.memoryjs.TRIGGER_WRITE
- 주소가 기록될 때 중단점이 발생합니다. 문자열이 포함된 주소를 모니터링할 때 setHardwareBreakpoint
함수의 size
매개변수는 문자열 길이여야 합니다. Debugger
래퍼 클래스를 사용하는 경우 래퍼는 문자열 읽기를 시도하여 문자열 크기를 자동으로 결정합니다.
요약하자면:
Debugger
클래스를 사용하는 경우:
setHardwareBreakpoint
에 size
매개변수를 전달할 필요가 없습니다.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는 중단점 설정에서 일시 중지되어 코드를 한 줄씩 단계별로 실행하고 변수를 검사할 수 있습니다.