memoryjs
— это пакет NPM для чтения и записи памяти процесса!
Возможности • Начало работы • Использование • Документация • Отладка
ЗАДАЧА:
Это надстройка Node (последний раз тестировалась для работы с v14.15.0
), поэтому для ее использования требуется node-gyp.
Вам также может потребоваться выполнить следующие действия для установки и настройки node-gyp
.
npm install memoryjs
При использовании Memoryjs целевой процесс должен соответствовать архитектуре платформы работающей версии Node. Например, если вы хотите настроить 64-битный процесс, вам следует попробовать использовать 64-битную версию Node.
Вам также необходимо перекомпилировать библиотеку и настроить ее на нужную платформу. Перейдите в каталог модуля узла 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, ознакомьтесь с примечаниями по сборке Лиама Митчелла здесь.
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) => {});
См. раздел «Документация» этого README, чтобы узнать, какие значения 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);
Подробную информацию о параметрах и возвращаемых значениях этих функций см. в разделе «Документация» данного файла README.
// sync: change/set the protection on a region of memory
const oldProtection = memoryjs.virtualProtectEx(handle, address, size, protection);
См. раздел «Документация» данного README, чтобы узнать, какой может быть 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 для определения ваших структур и использовать их для записи или анализа буферов.
Если вы хотите прочитать std::string
с помощью structron
, библиотека предоставляет собственный тип, который можно использовать для чтения/записи строк:
// 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*» и записи строки.
В обоих случаях вы хотите получить адрес массива символов:
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 миллиона символов.
Одним из способов обойти это ограничение в будущем было бы разрешить параметр, позволяющий пользователям устанавливать максимальное количество символов.
При сканировании шаблонов необходимо устанавливать флаги для типов подписей. Параметр типа подписи должен быть одним из следующих:
0x0
или memoryjs.NORMAL
, что обозначает обычную подпись.
0x1
или memoryjs.READ
, который будет читать память по адресу.
0x2
или memoryjs.SUBSTRACT
, который вычитает базу изображения из адреса.
Чтобы поднять несколько флагов, используйте побитовый оператор ИЛИ: memoryjs.READ | memoryjs.SUBTRACT
.
Библиотека предоставляет функции для сопоставления, получения дескриптора и чтения файла, отображенного в памяти.
openFileMapping (имя файла)
Дополнительную информацию см. в документации MSDN OpenFileMappingA.
MapViewOfFile (processHandle, имя_файла)
memoryjs.openFileMapping
constants.PAGE_READONLY
.MapViewOfFile (processHandle, имя_файла, смещение, viewSize, pageProtection)
memoryjs.openFileMapping
number
или bigint
): смещение от начала файла (должно быть кратно 64 КБ)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);
Если вы хотите прочитать файл, отображенный в памяти, не имея целевого процесса для сопоставления файла, вы можете сопоставить его с текущим процессом Node с помощью глобальной 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
— точка останова возникает при записи адреса Обратите внимание, что при мониторинге адреса, содержащего строку, параметр size
функции setHardwareBreakpoint
должен быть длиной строки. При использовании класса-оболочки Debugger
оболочка автоматически определяет размер строки, пытаясь ее прочитать.
Подводя итог:
При использовании класса Debugger
:
size
в setHardwareBreakpoint
setHardwareBreakpoint
возвращает регистр, который использовался для точки останова.При ручном использовании функций отладчика:
size
— это размер переменной в памяти (например, int32 = 4 байта). Для строки этот параметр представляет собой длину строки.memoryjs.DR0
– memoryhs.DR3
). Доступны только 4 аппаратных регистра (некоторые процессоры могут иметь даже менее 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');
Откройте binding.sln
в Visual Studio, которое находится в папке build
в корневом каталоге проекта.
node.exe
(например, C:nodejsnode.exe
).C:projecttest.js
). Изучите файлы проекта в Visual Studio (расширив ..
, а затем lib
в обозревателе решений). Файлы заголовков можно просмотреть, удерживая Alt
и щелкая имена файлов заголовков вверху файлов исходного кода.
Точки останова устанавливаются щелчком слева от номера строки.
Запустите отладку, нажав F5
, нажав «Отладка» на панели инструментов, а затем «Начать отладку» или нажав «Локальный отладчик Windows».
Сценарий, который вы установили в качестве аргумента команды на шаге 4, будет запущен, а Visual Studio остановится на установленных точках останова и позволит вам пройти по коду построчно и проверить переменные.