memoryjs
es un paquete NPM para leer y escribir memoria de proceso.
Funciones • Introducción • Uso • Documentación • Depuración
HACER:
Este es un complemento de Node (se probó por última vez que funciona en v14.15.0
) y, por lo tanto, requiere node-gyp para su uso.
Es posible que también deba seguir estos pasos para instalar y configurar node-gyp
.
npm install memoryjs
Cuando se utiliza MemoryJS, el proceso de destino debe coincidir con la arquitectura de la plataforma de la versión de Node en ejecución. Por ejemplo, si desea apuntar a un proceso de 64 bits, debe intentar utilizar una versión de 64 bits de Node.
También debe volver a compilar la biblioteca y apuntar a la plataforma que desee. Dirígete al directorio del módulo del nodo Memoryjs, abre una terminal y ejecuta uno de los siguientes scripts de compilación:
# 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
Si planea utilizar este módulo con Node Webkit o Electron, consulte las notas de compilación de Liam Mitchell aquí.
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);
Consulte la sección de Documentación de este README para ver cómo se ve un objeto de proceso.
// 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) => {});
Consulte la sección de Documentación de este archivo README para ver cómo se ve un objeto de módulo.
// 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) => {});
Consulte la sección de Documentación de este archivo README para ver qué valores puede tener 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);
Consulte la sección Documentación de este archivo README para ver detalles sobre los parámetros y valores de retorno de estas funciones.
// sync: change/set the protection on a region of memory
const oldProtection = memoryjs.virtualProtectEx(handle, address, size, protection);
Consulte la sección de Documentación de este README para ver qué valores puede ser 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) => {});
Haga clic aquí para ver cómo se ve un objeto de resultado.
Haga clic aquí para obtener detalles sobre cómo formatear los argumentos y el tipo de devolución.
// 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);
Nota: esta documentación se está actualizando actualmente; consulte la Wiki para obtener más información.
{ dwSize: 304,
th32ProcessID: 10316,
cntThreads: 47,
th32ParentProcessID: 7804,
pcPriClassBase: 8,
szExeFile: "csgo.exe",
modBaseAddr: 1673789440,
handle: 808 }
Las propiedades handle
y modBaseAddr
solo están disponibles al abrir un proceso y no al enumerar procesos.
{ 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 }
Este objeto se devuelve cuando se ejecuta una función en un proceso remoto:
returnValue
es el valor devuelto por la función que se llamóexitCode
es el estado de terminación del hilo Cuando se utilizan las funciones de escritura o lectura, el parámetro de tipo de datos (dataType) debe hacer referencia a una constante dentro de la biblioteca:
Constante | bytes | Alias | Rango |
---|---|---|---|
memoryjs.BOOL | 1 | memoryjs.BOOLEAN | 0 a 1 |
memoryjs.INT8 | 1 | memoryjs.BYTE , memoryjs.CHAR | -128 a 127 |
memoryjs.UINT8 | 1 | memoryjs.UBYTE , memoryjs.UCHAR | 0 a 255 |
memoryjs.INT16 | 2 | memoryjs.SHORT | -32.768 a 32.767 |
memoryjs.UINT16 | 2 | memoryjs.USHORT , memoryjs.WORD | 0 a 65.535 |
memoryjs.INT32 | 4 | memoryjs.INT , memoryjs.LONG | -2.147.483.648 a 2.147.483.647 |
memoryjs.UINT32 | 4 | memoryjs.UINT , memoryjs.ULONG , memoryjs.DWORD | 0 a 4.294.967.295 |
memoryjs.INT64 | 8 | n / A | -9.223.372.036.854.775.808 a 9.223.372.036.854.775.807 |
memoryjs.UINT64 | 8 | n / A | 0 a 18.446.744.073.709.551.615 |
memoryjs.FLOAT | 4 | n / A | 3.4E +/- 38 (7 dígitos) |
memoryjs.DOUBLE | 8 | n / A | 1.7E +/- 308 (15 dígitos) |
memoryjs.PTR | 4/8 | memoryjs.POINTER | n / A |
memoryjs.UPTR | 4/8 | memoryjs.UPOINTER | n / A |
memoryjs.STR | n / A | memoryjs.STRING | n / A |
memoryjs.VEC3 | 12 | memoryjs.VECTOR3 | n / A |
memoryjs.VEC4 | 16 | memoryjs.VECTOR4 | n / A |
Notas:
_BE
al tipo de datos. Por ejemplo: memoryjs.DOUBLE_BE
.INT64
, UINT64
, INT64_BE
, UINT64_BE
), deberá proporcionar un BigInt. Al leer un número entero de 64 bits, recibirá un BigInt.Estos tipos de datos se utilizan para indicar el tipo de datos que se leen o escriben.
Ejemplo de entero de 64 bits:
const value = memoryjs.readMemory(handle, address, memoryjs.INT64);
console.log(typeof value); // bigint
memoryjs.writeMemory(handle, address, value + 1n, memoryjs.INT64);
Vector3 es una estructura de datos de tres flotantes:
const vector3 = { x: 0.0, y: 0.0, z: 0.0 };
memoryjs.writeMemory(handle, address, vector3, memoryjs.VEC3);
Vector4 es una estructura de datos de cuatro flotantes:
const vector4 = { w: 0.0, x: 0.0, y: 0.0, z: 0.0 };
memoryjs.writeMemory(handle, address, vector4, memoryjs.VEC4);
Si tiene una estructura que desea escribir en la memoria, puede utilizar buffers. Para ver un ejemplo de cómo hacer esto, vea el ejemplo de buffers.
Para escribir/leer una estructura hacia/desde la memoria, puede usar structron para definir sus estructuras y usarlas para escribir o analizar buffers.
Si desea leer un std::string
usando structron
, la biblioteca expone un tipo personalizado que se puede usar para leer/escribir cadenas:
// 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');
Alternativamente, puede utilizar las bibliotecas de concentrado y disolución para lograr lo mismo. Un viejo ejemplo de esto está aquí.
El tipo de protección es un valor DWORD de indicador de bits.
Este parámetro debe hacer referencia a una constante de la biblioteca:
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
Consulte Constantes de protección de memoria de MSDN para obtener más información.
El tipo de asignación de memoria es un valor DWORD de indicador de bits.
Este parámetro debe hacer referencia a una constante de la biblioteca:
memoryjs.MEM_COMMIT, memoryjs.MEM_RESERVE, memoryjs.MEM_RESET, memoryjs.MEM_RESET_UNDO
Consulte la documentación de VirtualAllocEx de MSDN para obtener más información.
Puede utilizar esta biblioteca para leer una "cadena" o "char*" y escribir una cadena.
En ambos casos desea obtener la dirección de la matriz de caracteres:
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;
Desde aquí puedes simplemente usar esta dirección para escribir y leer la memoria.
Sin embargo, hay una advertencia al leer una cadena en la memoria: debido al hecho de que la biblioteca no sabe cuánto mide la cadena, continuará leyendo hasta que encuentre el primer terminador nulo. Para evitar un bucle infinito, dejará de leer si no ha encontrado un terminador nulo después de 1 millón de caracteres.
Una forma de evitar esta limitación en el futuro sería permitir un parámetro que permita a los usuarios establecer el número máximo de caracteres.
Al escanear patrones, es necesario activar banderas para los tipos de firma. El parámetro de tipo de firma debe ser uno de los siguientes:
0x0
o memoryjs.NORMAL
que denota una firma normal.
0x1
o memoryjs.READ
que leerá la memoria en la dirección.
0x2
o memoryjs.SUBSTRACT
que restará la base de la imagen de la dirección.
Para generar varios indicadores, utilice el operador OR bit a bit: memoryjs.READ | memoryjs.SUBTRACT
.
La biblioteca expone funciones para asignar, obtener un identificador y leer un archivo asignado en memoria.
openFileMapping (nombre de archivo)
Consulte la documentación OpenFileMappingA de MSDN para obtener más información.
mapViewOfFile(procesoHandle, nombre de archivo)
memoryjs.openFileMapping
constants.PAGE_READONLY
.mapViewOfFile(processHandle, fileName, offset, viewSize, pageProtection)
memoryjs.openFileMapping
number
o bigint
): el desplazamiento desde el principio del archivo (debe ser múltiplo de 64 KB)number
o bigint
): el número de bytes a asignar (si es 0
, se leerá el archivo completo, independientemente del desplazamiento)Consulte la documentación MapViewOfFile2 de MSDN para obtener más información.
Consulte Tipo de protección para conocer los tipos de protección de páginas.
Tenemos un proceso que crea un mapeo de archivos:
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");
Podemos asignar el archivo a un proceso de destino específico y leer el archivo con 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);
Si desea leer un archivo asignado en memoria sin tener un proceso de destino al que asignar el archivo, puede asignarlo al proceso de Nodo actual con la variable global process.pid
:
const processObject = memoryjs.openProcess(process.pid);
La ejecución remota de funciones funciona construyendo una serie de argumentos y generando dinámicamente un código de shell que se inyecta en el proceso de destino y se ejecuta; por esta razón, pueden ocurrir fallas.
Para llamar a una función en un proceso, se puede utilizar la función callFunction
. La biblioteca admite el paso de argumentos a la función y debe tener el siguiente formato:
[{ type: T_INT, value: 4 }]
La biblioteca espera que los argumentos sean una matriz de objetos donde cada objeto tiene un type
que denota el tipo de datos del argumento y un value
que es el valor real del argumento. Los distintos tipos de datos admitidos se pueden encontrar a continuación.
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,
Cuando utilice callFunction
, también deberá proporcionar el tipo de retorno de la función, que nuevamente debe ser uno de los valores anteriores.
Por ejemplo, dada la siguiente función de C++:
int add(int a, int b) {
return a + b;
}
Llamarías a esta función así:
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 }
Consulte la documentación del objeto de resultado para obtener detalles sobre lo que devuelve callFunction
.
Notas: actualmente no se admite pasar un double
como argumento, pero sí se admite devolver uno.
Muchas gracias a los diversos contribuyentes que hicieron posible esta función.
Los puntos de interrupción de hardware funcionan adjuntando un depurador al proceso, estableciendo un punto de interrupción en una dirección determinada y declarando un tipo de activador (por ejemplo, punto de interrupción al escribir en la dirección) y luego esperando continuamente a que surja un evento de depuración (y luego, en consecuencia, manejándolo).
Esta biblioteca expone las funciones principales, pero también incluye una clase contenedora para simplificar el proceso. Para ver un ejemplo de código completo, consulte nuestro ejemplo de depuración.
Al establecer un punto de interrupción, se le solicita que pase un tipo de activador:
memoryjs.TRIGGER_ACCESS
: el punto de interrupción se produce cuando se accede a la direcciónmemoryjs.TRIGGER_WRITE
: el punto de interrupción se produce cuando se escribe la dirección en Tenga en cuenta que al monitorear una dirección que contiene una cadena, el parámetro size
de la función setHardwareBreakpoint
debe ser la longitud de la cadena. Cuando se utiliza la clase contenedora Debugger
, el contenedor determinará automáticamente el tamaño de la cadena al intentar leerla.
Para resumir:
Cuando se utiliza la clase Debugger
:
size
a setHardwareBreakpoint
setHardwareBreakpoint
devuelve el registro que se utilizó para el punto de interrupciónAl utilizar manualmente las funciones del depurador:
size
es el tamaño de la variable en la memoria (por ejemplo, int32 = 4 bytes). Para una cadena, este parámetro es la longitud de la cadena.memoryjs.DR0
a través de memoryhs.DR3
). Sólo hay 4 registros de hardware disponibles (algunas CPU pueden incluso tener menos de 4 disponibles). Esto significa que solo se pueden establecer 4 puntos de interrupción en un momento dadosetHardwareBreakpoint
devuelve un valor booleano que indica si la operación fue exitosaPara obtener más información sobre depuración y puntos de interrupción de hardware, consulte los siguientes enlaces:
El contenedor del depurador contiene estas funciones que debe utilizar:
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);
Nota: no se requiere un bucle, por ejemplo, no se requiere ningún bucle si simplemente desea esperar hasta la primera detección de la dirección a la que se accede o se escribe.
Vaya al directorio raíz del módulo y ejecute uno de los siguientes comandos:
# 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
para requerir el módulo de depuración. Vaya al directorio raíz y cambie la línea en index.js
de:
const memoryjs = require('./build/Release/memoryjs');
A lo siguiente:
const memoryjs = require('./build/Debug/memoryjs');
Abra la solución binding.sln
en Visual Studio, que se encuentra en la carpeta de build
en el directorio raíz del proyecto.
node.exe
(por ejemplo, C:nodejsnode.exe
).C:projecttest.js
). Explore los archivos del proyecto en Visual Studio (expandiendo ..
y luego lib
en el Explorador de soluciones). Los archivos de encabezado se pueden ver manteniendo presionada Alt
y haciendo clic en los nombres de los archivos de encabezado en la parte superior de los archivos de código fuente.
Los puntos de interrupción se establecen haciendo clic a la izquierda del número de línea.
Comience a depurar presionando F5
, haciendo clic en "Depurar" en la barra de herramientas y luego en "Iniciar depuración" o haciendo clic en "Depurador local de Windows".
Se ejecutará el script que configuró como argumento del comando en el paso 4 y Visual Studio se detendrá en los puntos de interrupción establecidos y le permitirá recorrer el código línea por línea e inspeccionar las variables.