memoryjs
est un package NPM pour lire et écrire la mémoire du processus !
Fonctionnalités • Mise en route • Utilisation • Documentation • Débogage
FAIRE:
Il s'agit d'un module complémentaire Node (testé pour la dernière fois pour fonctionner sur v14.15.0
) et nécessite donc son utilisation par node-gyp.
Vous devrez peut-être également suivre ces étapes pour installer et configurer node-gyp
.
npm install memoryjs
Lors de l'utilisation de memoryjs, le processus cible doit correspondre à l'architecture de plate-forme de la version de Node en cours d'exécution. Par exemple, si vous souhaitez cibler un processus 64 bits, vous devez essayer d'utiliser une version 64 bits de Node.
Vous devez également recompiler la bibliothèque et cibler la plateforme souhaitée. Accédez au répertoire du module de nœud memoryjs, ouvrez un terminal et exécutez l'un des scripts de compilation suivants :
# 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 vous envisagez d'utiliser ce module avec Node Webkit ou Electron, jetez un œil aux notes de construction de Liam Mitchell ici.
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);
Consultez la section Documentation de ce README pour voir à quoi ressemble un objet de processus.
// 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) => {});
Consultez la section Documentation de ce README pour voir à quoi ressemble un objet module.
// 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) => {});
Consultez la section Documentation de ce README pour voir quelles valeurs peut être 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);
Consultez la section Documentation de ce README pour voir les détails sur les paramètres et les valeurs de retour de ces fonctions.
// sync: change/set the protection on a region of memory
const oldProtection = memoryjs.virtualProtectEx(handle, address, size, protection);
Consultez la section Documentation de ce README pour voir ce que peut être protection
des valeurs.
// 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) => {});
Cliquez ici pour voir à quoi ressemble un objet de résultat.
Cliquez ici pour plus de détails sur la façon de formater les arguments et le type de retour.
// 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);
Remarque : cette documentation est actuellement en cours de mise à jour, reportez-vous au Wiki pour plus d'informations.
{ dwSize: 304,
th32ProcessID: 10316,
cntThreads: 47,
th32ParentProcessID: 7804,
pcPriClassBase: 8,
szExeFile: "csgo.exe",
modBaseAddr: 1673789440,
handle: 808 }
Les propriétés handle
et modBaseAddr
ne sont disponibles que lors de l’ouverture d’un processus et non lors de la liste des processus.
{ 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 }
Cet objet est renvoyé lorsqu'une fonction est exécutée dans un processus distant :
returnValue
est la valeur renvoyée par la fonction appeléeexitCode
est l'état de fin du thread Lors de l'utilisation des fonctions d'écriture ou de lecture, le paramètre de type de données (dataType) doit faire référence à une constante provenant de la bibliothèque :
Constante | Octets | Alias | Gamme |
---|---|---|---|
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 | n / A | -9 223 372 036 854 775 808 à 9 223 372 036 854 775 807 |
memoryjs.UINT64 | 8 | n / A | 0 à 18 446 744 073 709 551 615 |
memoryjs.FLOAT | 4 | n / A | 3,4E +/- 38 (7 chiffres) |
memoryjs.DOUBLE | 8 | n / A | 1,7E +/- 308 (15 chiffres) |
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 |
Remarques :
_BE
au type de données. Par exemple : memoryjs.DOUBLE_BE
.INT64
, UINT64
, INT64_BE
, UINT64_BE
), vous devrez fournir un BigInt. Lors de la lecture d'un entier de 64 bits, vous recevrez un BigInt.Ces types de données sont utilisés pour désigner le type de données lues ou écrites.
Exemple d'entier 64 bits :
const value = memoryjs.readMemory(handle, address, memoryjs.INT64);
console.log(typeof value); // bigint
memoryjs.writeMemory(handle, address, value + 1n, memoryjs.INT64);
Vector3 est une structure de données de trois flottants :
const vector3 = { x: 0.0, y: 0.0, z: 0.0 };
memoryjs.writeMemory(handle, address, vector3, memoryjs.VEC3);
Vector4 est une structure de données de quatre flottants :
const vector4 = { w: 0.0, x: 0.0, y: 0.0, z: 0.0 };
memoryjs.writeMemory(handle, address, vector4, memoryjs.VEC4);
Si vous souhaitez écrire une structure en mémoire, vous pouvez utiliser des tampons. Pour un exemple sur la façon de procéder, consultez l'exemple de tampons.
Pour écrire/lire une structure vers/depuis la mémoire, vous pouvez utiliser structron pour définir vos structures et les utiliser pour écrire ou analyser des tampons.
Si vous souhaitez lire un std::string
en utilisant structron
, la bibliothèque expose un type personnalisé qui peut être utilisé pour lire/écrire des chaînes :
// 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');
Alternativement, vous pouvez utiliser les bibliothèques concentrer et dissoudre pour obtenir la même chose. Un ancien exemple de ceci est ici.
Le type de protection est une valeur DWORD d’indicateur binaire.
Ce paramètre doit référencer une constante de la bibliothèque :
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
Reportez-vous aux constantes de protection de la mémoire MSDN pour plus d’informations.
Le type d’allocation de mémoire est une valeur DWORD d’indicateur binaire.
Ce paramètre doit référencer un constat de la bibliothèque :
memoryjs.MEM_COMMIT, memoryjs.MEM_RESERVE, memoryjs.MEM_RESET, memoryjs.MEM_RESET_UNDO
Reportez-vous à la documentation VirtualAllocEx de MSDN pour plus d'informations.
Vous pouvez utiliser cette bibliothèque pour lire soit une "chaîne", soit un "char*" et écrire une chaîne.
Dans les deux cas, vous souhaitez obtenir l'adresse du tableau 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;
À partir de là, vous pouvez simplement utiliser cette adresse pour écrire et lire la mémoire.
Il y a une mise en garde lors de la lecture d'une chaîne en mémoire, cependant, étant donné que la bibliothèque ne sait pas combien de temps dure la chaîne, elle continuera à lire jusqu'à ce qu'elle trouve le premier terminateur nul. Pour éviter une boucle infinie, il arrêtera la lecture s'il n'a pas trouvé de terminateur nul après 1 million de caractères.
Une façon de contourner cette limitation à l'avenir serait d'autoriser un paramètre permettant aux utilisateurs de définir le nombre maximum de caractères.
Lors de l'analyse de modèles, des indicateurs doivent être levés pour les types de signature. Le paramètre de type de signature doit être l'un des suivants :
0x0
ou memoryjs.NORMAL
qui désigne une signature normale.
0x1
ou memoryjs.READ
qui lira la mémoire à l'adresse.
0x2
ou memoryjs.SUBSTRACT
qui soustraira la base image de l'adresse.
Pour lever plusieurs indicateurs, utilisez l'opérateur OR au niveau du bit : memoryjs.READ | memoryjs.SUBTRACT
.
La bibliothèque expose des fonctions pour mapper, obtenir un handle et lire un fichier mappé en mémoire.
openFileMapping (nom de fichier)
Reportez-vous à la documentation OpenFileMappingA de MSDN pour plus d'informations.
mapViewOfFile (processHandle, nom de fichier)
memoryjs.openFileMapping
constants.PAGE_READONLY
.mapViewOfFile (processHandle, fileName, offset, viewSize, pageProtection)
memoryjs.openFileMapping
number
ou bigint
) : le décalage depuis le début du fichier (doit être un multiple de 64 Ko)number
ou bigint
) : le nombre d'octets à mapper (si 0
, le fichier entier sera lu, quel que soit le décalage)Reportez-vous à la documentation MapViewOfFile2 de MSDN pour plus d'informations.
Voir Type de protection pour les types de protection de page.
Nous avons un processus qui crée un mappage de fichiers :
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");
Nous pouvons mapper le fichier sur un processus cible spécifié et lire le fichier avec 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 vous souhaitez lire un fichier mappé en mémoire sans avoir de processus cible sur lequel mapper le fichier, vous pouvez le mapper au processus Node actuel avec la variable globale process.pid
:
const processObject = memoryjs.openProcess(process.pid);
L'exécution de fonctions à distance fonctionne en créant un tableau d'arguments et en générant dynamiquement un shellcode qui est injecté dans le processus cible et exécuté, pour cette raison des plantages peuvent survenir.
Pour appeler une fonction dans un processus, la fonction callFunction
peut être utilisée. La bibliothèque prend en charge la transmission d'arguments à la fonction et doit être au format suivant :
[{ type: T_INT, value: 4 }]
La bibliothèque s'attend à ce que les arguments soient un tableau d'objets où chaque objet a un type
qui indique le type de données de l'argument et une value
qui est la valeur réelle de l'argument. Les différents types de données pris en charge peuvent être trouvés ci-dessous.
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,
Lorsque vous utilisez callFunction
, vous devez également fournir le type de retour de la fonction, qui doit encore une fois être l'une des valeurs ci-dessus.
Par exemple, étant donné la fonction C++ suivante :
int add(int a, int b) {
return a + b;
}
Vous appelleriez cette fonction ainsi :
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 }
Consultez la documentation de l'objet résultat pour plus de détails sur ce que renvoie callFunction
.
Notes : actuellement, passer un double
comme argument n'est pas pris en charge, mais en renvoyer un l'est.
Un grand merci aux différents contributeurs qui ont rendu cette fonctionnalité possible.
Les points d'arrêt matériels fonctionnent en attachant un débogueur au processus, en définissant un point d'arrêt sur une certaine adresse et en déclarant un type de déclencheur (par exemple, un point d'arrêt lors de l'écriture sur l'adresse), puis en attendant continuellement qu'un événement de débogage se produise (puis en le gérant par conséquent).
Cette bibliothèque expose les fonctions principales, mais inclut également une classe wrapper pour simplifier le processus. Pour un exemple de code complet, consultez notre exemple de débogage.
Lors de la définition d'un point d'arrêt, vous devez transmettre un type de déclencheur :
memoryjs.TRIGGER_ACCESS
- le point d'arrêt se produit lors de l'accès à l'adressememoryjs.TRIGGER_WRITE
- le point d'arrêt se produit lorsque l'adresse est écrite Notez que lors de la surveillance d'une adresse contenant une chaîne, le paramètre size
de la fonction setHardwareBreakpoint
doit être la longueur de la chaîne. Lorsque vous utilisez la classe wrapper Debugger
, le wrapper déterminera automatiquement la taille de la chaîne en essayant de la lire.
Pour résumer :
Lors de l'utilisation de la classe Debugger
:
size
à setHardwareBreakpoint
setHardwareBreakpoint
renvoie le registre utilisé pour le point d'arrêtLors de l'utilisation manuelle des fonctions du débogueur :
size
est la taille de la variable en mémoire (par exemple int32 = 4 octets). Pour une chaîne, ce paramètre est la longueur de la chaînememoryjs.DR0
via memoryhs.DR3
). Seuls 4 registres matériels sont disponibles (certains processeurs peuvent même en avoir moins de 4 disponibles). Cela signifie que seuls 4 points d'arrêt peuvent être définis à un moment donné.setHardwareBreakpoint
renvoie un booléen indiquant si l'opération a réussiPour en savoir plus sur le débogage et les points d'arrêt matériels, consultez les liens suivants :
Le wrapper Debugger contient ces fonctions que vous devez utiliser :
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);
Remarque : une boucle n'est pas requise, par exemple aucune boucle n'est requise si vous souhaitez simplement attendre la première détection de l'adresse à laquelle on accède ou où on écrit.
Accédez au répertoire racine du module et exécutez l'une des commandes suivantes :
# 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
pour exiger le module de débogage Accédez au répertoire racine et modifiez la ligne dans index.js
à partir de :
const memoryjs = require('./build/Release/memoryjs');
Aux éléments suivants :
const memoryjs = require('./build/Debug/memoryjs');
Ouvrez la solution binding.sln
dans Visual Studio, trouvée dans le dossier build
du répertoire racine du projet.
node.exe
(par exemple C:nodejsnode.exe
)C:projecttest.js
) Explorez les fichiers du projet dans Visual Studio (en développant ..
puis lib
dans l'Explorateur de solutions). Les fichiers d'en-tête peuvent être visualisés en maintenant Alt
et en cliquant sur les noms des fichiers d'en-tête en haut des fichiers de code source.
Les points d'arrêt sont définis en cliquant à gauche du numéro de ligne.
Démarrez le débogage en appuyant soit sur F5
, en cliquant sur "Déboguer" dans la barre d'outils puis sur "Démarrer le débogage", ou en cliquant sur "Débogueur Windows local".
Le script que vous avez défini comme argument de commande à l'étape 4 sera exécuté et Visual Studio s'arrêtera sur les points d'arrêt définis et vous permettra de parcourir le code ligne par ligne et d'inspecter les variables.