fpPS4-Temmie-s-Launcher/App/node_modules/memoryjs/lib/memoryjs.cc

1515 lines
No EOL
58 KiB
C++

#include <windows.h>
#include <psapi.h>
#include <napi.h>
#include <string>
#include "module.h"
#include "process.h"
#include "memoryjs.h"
#include "memory.h"
#include "pattern.h"
#include "functions.h"
#include "dll.h"
#include "debugger.h"
#pragma comment(lib, "psapi.lib")
process Process;
// module Module;
memory Memory;
pattern Pattern;
// functions Functions;
struct Vector3 {
float x, y, z;
};
struct Vector4 {
float w, x, y, z;
};
Napi::Value openProcess(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 1 && args.Length() != 2) {
Napi::Error::New(env, "requires 1 argument, or 2 arguments if a callback is being used").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsString() && !args[0].IsNumber()) {
Napi::Error::New(env, "first argument must be a string or a number").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 2 && !args[1].IsFunction()) {
Napi::Error::New(env, "second argument must be a function").ThrowAsJavaScriptException();
return env.Null();
}
// Define error message that may be set by the function that opens the process
char* errorMessage = "";
process::Pair pair;
if (args[0].IsString()) {
std::string processName(args[0].As<Napi::String>().Utf8Value());
pair = Process.openProcess(processName.c_str(), &errorMessage);
// In case it failed to open, let's keep retrying
// while(!strcmp(process.szExeFile, "")) {
// process = Process.openProcess((char*) *(processName), &errorMessage);
// };
}
if (args[0].IsNumber()) {
pair = Process.openProcess(args[0].As<Napi::Number>().Uint32Value(), &errorMessage);
// In case it failed to open, let's keep retrying
// while(!strcmp(process.szExeFile, "")) {
// process = Process.openProcess(info[0].As<Napi::Number>().Uint32Value(), &errorMessage);
// };
}
// If an error message was returned from the function that opens the process, throw the error.
// Only throw an error if there is no callback (if there's a callback, the error is passed there).
if (strcmp(errorMessage, "") && args.Length() != 2) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return env.Null();
}
// Create a v8 Object (JSON) to store the process information
Napi::Object processInfo = Napi::Object::New(env);
processInfo.Set(Napi::String::New(env, "dwSize"), Napi::Value::From(env, (int)pair.process.dwSize));
processInfo.Set(Napi::String::New(env, "th32ProcessID"), Napi::Value::From(env, (int)pair.process.th32ProcessID));
processInfo.Set(Napi::String::New(env, "cntThreads"), Napi::Value::From(env, (int)pair.process.cntThreads));
processInfo.Set(Napi::String::New(env, "th32ParentProcessID"), Napi::Value::From(env, (int)pair.process.th32ParentProcessID));
processInfo.Set(Napi::String::New(env, "pcPriClassBase"), Napi::Value::From(env, (int)pair.process.pcPriClassBase));
processInfo.Set(Napi::String::New(env, "szExeFile"), Napi::String::New(env, pair.process.szExeFile));
processInfo.Set(Napi::String::New(env, "handle"), Napi::Value::From(env, (uintptr_t)pair.handle));
DWORD64 base = module::getBaseAddress(pair.process.szExeFile, pair.process.th32ProcessID);
processInfo.Set(Napi::String::New(env, "modBaseAddr"), Napi::Value::From(env, (uintptr_t)base));
// openProcess can either take one argument or can take
// two arguments for asychronous use (second argument is the callback)
if (args.Length() == 2) {
// Callback to let the user handle with the information
Napi::Function callback = args[1].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), processInfo });
return env.Null();
} else {
// return JSON
return processInfo;
}
}
Napi::Value closeProcess(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 1) {
Napi::Error::New(env, "requires 1 argument").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber()) {
Napi::Error::New(env, "first argument must be a number").ThrowAsJavaScriptException();
return env.Null();
}
Process.closeProcess((HANDLE)args[0].As<Napi::Number>().Int64Value());
return env.Null();
}
Napi::Value getProcesses(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() > 1) {
Napi::Error::New(env, "requires either 0 arguments or 1 argument if a callback is being used").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 1 && !args[0].IsFunction()) {
Napi::Error::New(env, "first argument must be a function").ThrowAsJavaScriptException();
return env.Null();
}
// Define error message that may be set by the function that gets the processes
char* errorMessage = "";
std::vector<PROCESSENTRY32> processEntries = Process.getProcesses(&errorMessage);
// If an error message was returned from the function that gets the processes, throw the error.
// Only throw an error if there is no callback (if there's a callback, the error is passed there).
if (strcmp(errorMessage, "") && args.Length() != 1) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return env.Null();
}
// Creates v8 array with the size being that of the processEntries vector processes is an array of JavaScript objects
Napi::Array processes = Napi::Array::New(env, processEntries.size());
// Loop over all processes found
for (std::vector<PROCESSENTRY32>::size_type i = 0; i != processEntries.size(); i++) {
// Create a v8 object to store the current process' information
Napi::Object process = Napi::Object::New(env);
process.Set(Napi::String::New(env, "cntThreads"), Napi::Value::From(env, (int)processEntries[i].cntThreads));
process.Set(Napi::String::New(env, "szExeFile"), Napi::String::New(env, processEntries[i].szExeFile));
process.Set(Napi::String::New(env, "th32ProcessID"), Napi::Value::From(env, (int)processEntries[i].th32ProcessID));
process.Set(Napi::String::New(env, "th32ParentProcessID"), Napi::Value::From(env, (int)processEntries[i].th32ParentProcessID));
process.Set(Napi::String::New(env, "pcPriClassBase"), Napi::Value::From(env, (int)processEntries[i].pcPriClassBase));
// Push the object to the array
processes.Set(i, process);
}
/* getProcesses can either take no arguments or one argument
one argument is for asychronous use (the callback) */
if (args.Length() == 1) {
// Callback to let the user handle with the information
Napi::Function callback = args[0].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), processes });
return env.Null();
} else {
// return JSON
return processes;
}
}
Napi::Value getModules(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 1 && args.Length() != 2) {
Napi::Error::New(env, "requires 1 argument, or 2 arguments if a callback is being used").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber()) {
Napi::Error::New(env, "first argument must be a number").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 2 && !args[1].IsFunction()) {
Napi::Error::New(env, "first argument must be a number, second argument must be a function").ThrowAsJavaScriptException();
return env.Null();
}
// Define error message that may be set by the function that gets the modules
char* errorMessage = "";
std::vector<MODULEENTRY32> moduleEntries = module::getModules(args[0].As<Napi::Number>().Int32Value(), &errorMessage);
// If an error message was returned from the function getting the modules, throw the error.
// Only throw an error if there is no callback (if there's a callback, the error is passed there).
if (strcmp(errorMessage, "") && args.Length() != 2) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return env.Null();
}
// Creates v8 array with the size being that of the moduleEntries vector
// modules is an array of JavaScript objects
Napi::Array modules = Napi::Array::New(env, moduleEntries.size());
// Loop over all modules found
for (std::vector<MODULEENTRY32>::size_type i = 0; i != moduleEntries.size(); i++) {
// Create a v8 object to store the current module's information
Napi::Object module = Napi::Object::New(env);
module.Set(Napi::String::New(env, "modBaseAddr"), Napi::Value::From(env, (uintptr_t)moduleEntries[i].modBaseAddr));
module.Set(Napi::String::New(env, "modBaseSize"), Napi::Value::From(env, (int)moduleEntries[i].modBaseSize));
module.Set(Napi::String::New(env, "szExePath"), Napi::String::New(env, moduleEntries[i].szExePath));
module.Set(Napi::String::New(env, "szModule"), Napi::String::New(env, moduleEntries[i].szModule));
module.Set(Napi::String::New(env, "th32ProcessID"), Napi::Value::From(env, (int)moduleEntries[i].th32ProcessID));
module.Set(Napi::String::New(env, "GlblcntUsage"), Napi::Value::From(env, (int)moduleEntries[i].GlblcntUsage));
// Push the object to the array
modules.Set(i, module);
}
// getModules can either take one argument or two arguments
// one/two arguments is for asychronous use (the callback)
if (args.Length() == 2) {
// Callback to let the user handle with the information
Napi::Function callback = args[1].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), modules });
return env.Null();
} else {
// return JSON
return modules;
}
}
Napi::Value findModule(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 1 && args.Length() != 2 && args.Length() != 3) {
Napi::Error::New(env, "requires 1 argument, 2 arguments, or 3 arguments if a callback is being used").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsString() && !args[1].IsNumber()) {
Napi::Error::New(env, "first argument must be a string, second argument must be a number").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 3 && !args[2].IsFunction()) {
Napi::Error::New(env, "third argument must be a function").ThrowAsJavaScriptException();
return env.Null();
}
std::string moduleName(args[0].As<Napi::String>().Utf8Value());
// Define error message that may be set by the function that gets the modules
char* errorMessage = "";
MODULEENTRY32 module = module::findModule(moduleName.c_str(), args[1].As<Napi::Number>().Int32Value(), &errorMessage);
// If an error message was returned from the function getting the module, throw the error.
// Only throw an error if there is no callback (if there's a callback, the error is passed there).
if (strcmp(errorMessage, "") && args.Length() != 3) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return env.Null();
}
// In case it failed to open, let's keep retrying
while (!strcmp(module.szExePath, "")) {
module = module::findModule(moduleName.c_str(), args[1].As<Napi::Number>().Int32Value(), &errorMessage);
};
// Create a v8 Object (JSON) to store the process information
Napi::Object moduleInfo = Napi::Object::New(env);
moduleInfo.Set(Napi::String::New(env, "modBaseAddr"), Napi::Value::From(env, (uintptr_t)module.modBaseAddr));
moduleInfo.Set(Napi::String::New(env, "modBaseSize"), Napi::Value::From(env, (int)module.modBaseSize));
moduleInfo.Set(Napi::String::New(env, "szExePath"), Napi::String::New(env, module.szExePath));
moduleInfo.Set(Napi::String::New(env, "szModule"), Napi::String::New(env, module.szModule));
moduleInfo.Set(Napi::String::New(env, "th32ProcessID"), Napi::Value::From(env, (int)module.th32ProcessID));
moduleInfo.Set(Napi::String::New(env, "GlblcntUsage"), Napi::Value::From(env, (int)module.GlblcntUsage));
// findModule can either take one or two arguments,
// three arguments for asychronous use (third argument is the callback)
if (args.Length() == 3) {
// Callback to let the user handle with the information
Napi::Function callback = args[2].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), moduleInfo });
return env.Null();
} else {
// return JSON
return moduleInfo;
}
}
Napi::Value readMemory(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 3 && args.Length() != 4) {
Napi::Error::New(env, "requires 3 arguments, or 4 arguments if a callback is being used").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() && !args[1].IsNumber() && !args[2].IsString()) {
Napi::Error::New(env, "first and second argument must be a number, third argument must be a string").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 4 && !args[3].IsFunction()) {
Napi::Error::New(env, "fourth argument must be a function").ThrowAsJavaScriptException();
return env.Null();
}
std::string dataTypeArg(args[2].As<Napi::String>().Utf8Value());
const char* dataType = dataTypeArg.c_str();
// Define the error message that will be set if no data type is recognised
char* errorMessage = "";
Napi::Value retVal = env.Null();
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
DWORD64 address = args[1].As<Napi::Number>().Int64Value();
if (!strcmp(dataType, "int8") || !strcmp(dataType, "byte") || !strcmp(dataType, "char")) {
int8_t result = Memory.readMemory<int8_t>(handle, address);
retVal = Napi::Value::From(env, result);
} else if (!strcmp(dataType, "uint8") || !strcmp(dataType, "ubyte") || !strcmp(dataType, "uchar")) {
uint8_t result = Memory.readMemory<uint8_t>(handle, address);
retVal = Napi::Value::From(env, result);
} else if (!strcmp(dataType, "int16") || !strcmp(dataType, "short")) {
int16_t result = Memory.readMemory<int16_t>(handle, address);
retVal = Napi::Value::From(env, result);
} else if (!strcmp(dataType, "uint16") || !strcmp(dataType, "ushort") || !strcmp(dataType, "word")) {
uint16_t result = Memory.readMemory<uint16_t>(handle, address);
retVal = Napi::Value::From(env, result);
} else if (!strcmp(dataType, "int32") || !strcmp(dataType, "int") || !strcmp(dataType, "long")) {
int32_t result = Memory.readMemory<int32_t>(handle, address);
retVal = Napi::Value::From(env, result);
} else if (!strcmp(dataType, "uint32") || !strcmp(dataType, "uint") || !strcmp(dataType, "ulong") || !strcmp(dataType, "dword")) {
uint32_t result = Memory.readMemory<uint32_t>(handle, address);
retVal = Napi::Value::From(env, result);
} else if (!strcmp(dataType, "int64")) {
int64_t result = Memory.readMemory<int64_t>(handle, address);
retVal = Napi::Value::From(env, Napi::BigInt::New(env, result));
} else if (!strcmp(dataType, "uint64")) {
uint64_t result = Memory.readMemory<uint64_t>(handle, address);
retVal = Napi::Value::From(env, Napi::BigInt::New(env, result));
} else if (!strcmp(dataType, "float")) {
float result = Memory.readMemory<float>(handle, address);
retVal = Napi::Value::From(env, result);
} else if (!strcmp(dataType, "double")) {
double result = Memory.readMemory<double>(handle, address);
retVal = Napi::Value::From(env, result);
} else if (!strcmp(dataType, "ptr") || !strcmp(dataType, "pointer")) {
intptr_t result = Memory.readMemory<intptr_t>(handle, address);
if (sizeof(intptr_t) == 8) {
retVal = Napi::Value::From(env, Napi::BigInt::New(env, (int64_t) result));
} else {
retVal = Napi::Value::From(env, result);
}
} else if (!strcmp(dataType, "uptr") || !strcmp(dataType, "upointer")) {
uintptr_t result = Memory.readMemory<uintptr_t>(handle, address);
if (sizeof(uintptr_t) == 8) {
retVal = Napi::Value::From(env, Napi::BigInt::New(env, (uint64_t) result));
} else {
retVal = Napi::Value::From(env, result);
}
} else if (!strcmp(dataType, "bool") || !strcmp(dataType, "boolean")) {
bool result = Memory.readMemory<bool>(handle, address);
retVal = Napi::Boolean::New(env, result);
} else if (!strcmp(dataType, "string") || !strcmp(dataType, "str")) {
std::string str;
if (!Memory.readString(handle, address, &str)) {
errorMessage = "unable to read string";
} else {
retVal = Napi::String::New(env, str);
}
} else if (!strcmp(dataType, "vector3") || !strcmp(dataType, "vec3")) {
Vector3 result = Memory.readMemory<Vector3>(handle, address);
Napi::Object moduleInfo = Napi::Object::New(env);
moduleInfo.Set(Napi::String::New(env, "x"), Napi::Value::From(env, result.x));
moduleInfo.Set(Napi::String::New(env, "y"), Napi::Value::From(env, result.y));
moduleInfo.Set(Napi::String::New(env, "z"), Napi::Value::From(env, result.z));
retVal = moduleInfo;
} else if (!strcmp(dataType, "vector4") || !strcmp(dataType, "vec4")) {
Vector4 result = Memory.readMemory<Vector4>(handle, address);
Napi::Object moduleInfo = Napi::Object::New(env);
moduleInfo.Set(Napi::String::New(env, "w"), Napi::Value::From(env, result.w));
moduleInfo.Set(Napi::String::New(env, "x"), Napi::Value::From(env, result.x));
moduleInfo.Set(Napi::String::New(env, "y"), Napi::Value::From(env, result.y));
moduleInfo.Set(Napi::String::New(env, "z"), Napi::Value::From(env, result.z));
retVal = moduleInfo;
} else {
errorMessage = "unexpected data type";
}
if (strcmp(errorMessage, "") && args.Length() != 4) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 4) {
Napi::Function callback = args[3].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), retVal });
return env.Null();
} else {
return retVal;
}
}
Napi::Value readBuffer(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 3 && args.Length() != 4) {
Napi::Error::New(env, "requires 3 arguments, or 4 arguments if a callback is being used").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() && !args[1].IsNumber() && !args[2].IsNumber()) {
Napi::Error::New(env, "first, second and third arguments must be a number").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 4 && !args[3].IsFunction()) {
Napi::Error::New(env, "fourth argument must be a function").ThrowAsJavaScriptException();
return env.Null();
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
DWORD64 address = args[1].As<Napi::Number>().Int64Value();
SIZE_T size = args[2].As<Napi::Number>().Int64Value();
// To fix the memory leak problem that was happening here, we need to release the
// temporary buffer we create after we're done creating a Napi::Buffer from it.
// Napi::Buffer::New doesn't free the memory, so it has be done manually
// but it can segfault when the memory is freed before being accessed.
// The solution is to use Napi::Buffer::Copy, and then we can manually free it.
//
// see: https://github.com/nodejs/node/issues/40936
// see: https://sagivo.com/2015/09/30/Go-Native-Calling-C-From-NodeJS.html
char* data = (char*) malloc(sizeof(char) * size);
Memory.readBuffer(handle, address, size, data);
Napi::Buffer<char> buffer = Napi::Buffer<char>::Copy(env, data, size);
free(data);
if (args.Length() == 4) {
Napi::Function callback = args[3].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, ""), buffer });
return env.Null();
} else {
return buffer;
}
}
Napi::Value writeMemory(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 4) {
Napi::Error::New(env, "requires 4 arguments").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() && !args[1].IsNumber() && !args[3].IsString()) {
Napi::Error::New(env, "first and second argument must be a number, third argument must be a string").ThrowAsJavaScriptException();
return env.Null();
}
std::string dataTypeArg(args[3].As<Napi::String>().Utf8Value());
const char* dataType = dataTypeArg.c_str();
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
DWORD64 address = args[1].As<Napi::Number>().Int64Value();
if (!strcmp(dataType, "int8") || !strcmp(dataType, "byte") || !strcmp(dataType, "char")) {
Memory.writeMemory<int8_t>(handle, address, args[2].As<Napi::Number>().Int32Value());
} else if (!strcmp(dataType, "uint8") || !strcmp(dataType, "ubyte") || !strcmp(dataType, "uchar")) {
Memory.writeMemory<uint8_t>(handle, address, args[2].As<Napi::Number>().Uint32Value());
} else if (!strcmp(dataType, "int16") || !strcmp(dataType, "short")) {
Memory.writeMemory<int16_t>(handle, address, args[2].As<Napi::Number>().Int32Value());
} else if (!strcmp(dataType, "uint16") || !strcmp(dataType, "ushort") || !strcmp(dataType, "word")) {
Memory.writeMemory<uint16_t>(handle, address, args[2].As<Napi::Number>().Uint32Value());
} else if (!strcmp(dataType, "int32") || !strcmp(dataType, "int") || !strcmp(dataType, "long")) {
Memory.writeMemory<int32_t>(handle, address, args[2].As<Napi::Number>().Int32Value());
} else if (!strcmp(dataType, "uint32") || !strcmp(dataType, "uint") || !strcmp(dataType, "ulong") || !strcmp(dataType, "dword")) {
Memory.writeMemory<uint32_t>(handle, address, args[2].As<Napi::Number>().Uint32Value());
} else if (!strcmp(dataType, "int64")) {
Napi::BigInt bigInt = args[2].As<Napi::BigInt>();
bool lossless;
Memory.writeMemory<int64_t>(handle, address, bigInt.Int64Value(&lossless));
} else if (!strcmp(dataType, "uint64")) {
Napi::BigInt bigInt = args[2].As<Napi::BigInt>();
bool lossless;
Memory.writeMemory<uint64_t>(handle, address, bigInt.Uint64Value(&lossless));
} else if (!strcmp(dataType, "float")) {
Memory.writeMemory<float>(handle, address, args[2].As<Napi::Number>().FloatValue());
} else if (!strcmp(dataType, "double")) {
Memory.writeMemory<double>(handle, address, args[2].As<Napi::Number>().DoubleValue());
} else if (!strcmp(dataType, "ptr") || !strcmp(dataType, "pointer")) {
Napi::BigInt bigInt = args[2].As<Napi::BigInt>();
if (sizeof(intptr_t) == 8 && !bigInt.IsBigInt()) {
std::string error = "Writing memoryjs.PTR or memoryjs.POINTER on 64 bit target build requires you to supply a BigInt.";
error += " Rebuild the library with `npm run build32` to target 32 bit applications.";
Napi::Error::New(env, error).ThrowAsJavaScriptException();
return env.Null();
}
if (bigInt.IsBigInt()) {
bool lossless;
Memory.writeMemory<intptr_t>(handle, address, bigInt.Int64Value(&lossless));
} else {
Memory.writeMemory<intptr_t>(handle, address, args[2].As<Napi::Number>().Int32Value());
}
} else if (!strcmp(dataType, "uptr") || !strcmp(dataType, "upointer")) {
Napi::BigInt bigInt = args[2].As<Napi::BigInt>();
if (sizeof(uintptr_t) == 8 && !bigInt.IsBigInt()) {
std::string error = "Writing memoryjs.PTR or memoryjs.POINTER on 64 bit target build requires you to supply a BigInt.";
error += " Rebuild the library with `npm run build32` to target 32 bit applications.";
Napi::Error::New(env, error).ThrowAsJavaScriptException();
return env.Null();
}
if (bigInt.IsBigInt()) {
bool lossless;
Memory.writeMemory<uintptr_t>(handle, address, bigInt.Uint64Value(&lossless));
} else {
Memory.writeMemory<uintptr_t>(handle, address, args[2].As<Napi::Number>().Uint32Value());
}
} else if (!strcmp(dataType, "bool") || !strcmp(dataType, "boolean")) {
Memory.writeMemory<bool>(handle, address, args[2].As<Napi::Boolean>().Value());
} else if (!strcmp(dataType, "string") || !strcmp(dataType, "str")) {
std::string valueParam(args[2].As<Napi::String>().Utf8Value());
valueParam.append("", 1);
// Write String, Method 1
//Memory.writeMemory<std::string>(handle, address, std::string(*valueParam));
// Write String, Method 2
Memory.writeMemory(handle, address, (char*) valueParam.data(), valueParam.size());
} else if (!strcmp(dataType, "vector3") || !strcmp(dataType, "vec3")) {
Napi::Object value = args[2].As<Napi::Object>();
Vector3 vector = {
value.Get(Napi::String::New(env, "x")).As<Napi::Number>().FloatValue(),
value.Get(Napi::String::New(env, "y")).As<Napi::Number>().FloatValue(),
value.Get(Napi::String::New(env, "z")).As<Napi::Number>().FloatValue()
};
Memory.writeMemory<Vector3>(handle, address, vector);
} else if (!strcmp(dataType, "vector4") || !strcmp(dataType, "vec4")) {
Napi::Object value = args[2].As<Napi::Object>();
Vector4 vector = {
value.Get(Napi::String::New(env, "w")).As<Napi::Number>().FloatValue(),
value.Get(Napi::String::New(env, "x")).As<Napi::Number>().FloatValue(),
value.Get(Napi::String::New(env, "y")).As<Napi::Number>().FloatValue(),
value.Get(Napi::String::New(env, "z")).As<Napi::Number>().FloatValue()
};
Memory.writeMemory<Vector4>(handle, address, vector);
} else {
Napi::Error::New(env, "unexpected data type").ThrowAsJavaScriptException();
}
return env.Null();
}
Napi::Value writeBuffer(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 3) {
Napi::Error::New(env, "required 3 arguments").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() && !args[1].IsNumber()) {
Napi::Error::New(env, "first and second argument must be a number").ThrowAsJavaScriptException();
return env.Null();
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
DWORD64 address = args[1].As<Napi::Number>().Int64Value();
SIZE_T length = args[2].As<Napi::Buffer<char>>().Length();
char* data = args[2].As<Napi::Buffer<char>>().Data();
Memory.writeMemory<char*>(handle, address, data, length);
return env.Null();
}
// Napi::Value findPattern(const Napi::CallbackInfo& args) {
// Napi::Env env = args.Env();
// HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
// DWORD64 baseAddress = args[1].As<Napi::Number>().Int64Value();
// DWORD64 baseSize = args[2].As<Napi::Number>().Int64Value();
// std::string signature(args[3].As<Napi::String>().Utf8Value());
// short flags = args[4].As<Napi::Number>().Uint32Value();
// uint32_t patternOffset = args[5].As<Napi::Number>().Uint32Value();
// // matching address
// uintptr_t address = 0;
// char* errorMessage = "";
// // read memory region occupied by the module to pattern match inside
// std::vector<unsigned char> moduleBytes = std::vector<unsigned char>(baseSize);
// ReadProcessMemory(handle, (LPVOID)baseAddress, &moduleBytes[0], baseSize, nullptr);
// unsigned char* byteBase = const_cast<unsigned char*>(&moduleBytes.at(0));
// Pattern.findPattern(handle, baseAddress, byteBase, baseSize, signature.c_str(), flags, patternOffset, &address);
// if (address == 0) {
// errorMessage = "unable to match pattern inside any modules or regions";
// }
// if (args.Length() == 5) {
// Napi::Function callback = args[4].As<Napi::Function>();
// callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Value::From(env, address) });
// return env.Null();
// } else {
// return Napi::Value::From(env, address);
// }
// }
Napi::Value findPattern(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 4 && args.Length() != 5) {
Napi::Error::New(env, "requires 4 arguments, 5 with callback").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() || !args[1].IsString() || !args[2].IsNumber() || !args[3].IsNumber()) {
Napi::Error::New(env, "expected: number, string, string, number").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 5 && !args[4].IsFunction()) {
Napi::Error::New(env, "callback argument must be a function").ThrowAsJavaScriptException();
return env.Null();
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
std::string pattern(args[1].As<Napi::String>().Utf8Value());
short flags = args[2].As<Napi::Number>().Uint32Value();
uint32_t patternOffset = args[3].As<Napi::Number>().Uint32Value();
// matching address
uintptr_t address = 0;
char* errorMessage = "";
std::vector<MODULEENTRY32> modules = module::getModules(GetProcessId(handle), &errorMessage);
Pattern.search(handle, modules, 0, pattern.c_str(), flags, patternOffset, &address);
// if no match found inside any modules, search memory regions
if (address == 0) {
std::vector<MEMORY_BASIC_INFORMATION> regions = Memory.getRegions(handle);
Pattern.search(handle, regions, 0, pattern.c_str(), flags, patternOffset, &address);
}
if (address == 0) {
errorMessage = "unable to match pattern inside any modules or regions";
}
if (args.Length() == 5) {
Napi::Function callback = args[4].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Value::From(env, address) });
return env.Null();
} else {
return Napi::Value::From(env, address);
}
}
Napi::Value findPatternByModule(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 5 && args.Length() != 6) {
Napi::Error::New(env, "requires 5 arguments, 6 with callback").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() || !args[1].IsString() || !args[2].IsString() || !args[3].IsNumber() || !args[4].IsNumber()) {
Napi::Error::New(env, "expected: number, string, string, number, number").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 6 && !args[5].IsFunction()) {
Napi::Error::New(env, "callback argument must be a function").ThrowAsJavaScriptException();
return env.Null();
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
std::string moduleName(args[1].As<Napi::String>().Utf8Value());
std::string pattern(args[2].As<Napi::String>().Utf8Value());
short flags = args[3].As<Napi::Number>().Uint32Value();
uint32_t patternOffset = args[4].As<Napi::Number>().Uint32Value();
// matching address
uintptr_t address = 0;
char* errorMessage = "";
MODULEENTRY32 module = module::findModule(moduleName.c_str(), GetProcessId(handle), &errorMessage);
uintptr_t baseAddress = (uintptr_t) module.modBaseAddr;
DWORD baseSize = module.modBaseSize;
// read memory region occupied by the module to pattern match inside
std::vector<unsigned char> moduleBytes = std::vector<unsigned char>(baseSize);
ReadProcessMemory(handle, (LPVOID)baseAddress, &moduleBytes[0], baseSize, nullptr);
unsigned char* byteBase = const_cast<unsigned char*>(&moduleBytes.at(0));
Pattern.findPattern(handle, baseAddress, byteBase, baseSize, pattern.c_str(), flags, patternOffset, &address);
if (address == 0) {
errorMessage = "unable to match pattern inside any modules or regions";
}
if (args.Length() == 6) {
Napi::Function callback = args[5].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Value::From(env, address) });
return env.Null();
} else {
return Napi::Value::From(env, address);
}
}
Napi::Value findPatternByAddress(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 5 && args.Length() != 6) {
Napi::Error::New(env, "requires 5 arguments, 6 with callback").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() || !args[1].IsNumber() || !args[2].IsString() || !args[3].IsNumber() || !args[4].IsNumber()) {
Napi::Error::New(env, "expected: number, number, string, number, number").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 6 && !args[5].IsFunction()) {
Napi::Error::New(env, "callback argument must be a function").ThrowAsJavaScriptException();
return env.Null();
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
DWORD64 baseAddress = args[1].As<Napi::Number>().Int64Value();
std::string pattern(args[2].As<Napi::String>().Utf8Value());
short flags = args[3].As<Napi::Number>().Uint32Value();
uint32_t patternOffset = args[4].As<Napi::Number>().Uint32Value();
// matching address
uintptr_t address = 0;
char* errorMessage = "";
std::vector<MODULEENTRY32> modules = module::getModules(GetProcessId(handle), &errorMessage);
Pattern.search(handle, modules, baseAddress, pattern.c_str(), flags, patternOffset, &address);
if (address == 0) {
std::vector<MEMORY_BASIC_INFORMATION> regions = Memory.getRegions(handle);
Pattern.search(handle, regions, baseAddress, pattern.c_str(), flags, patternOffset, &address);
}
if (address == 0) {
errorMessage = "unable to match pattern inside any modules or regions";
}
if (args.Length() == 6) {
Napi::Function callback = args[5].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Value::From(env, address) });
return env.Null();
} else {
return Napi::Value::From(env, address);
}
}
Napi::Value callFunction(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 4 && args.Length() != 5) {
Napi::Error::New(env, "requires 4 arguments, 5 with callback").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() && !args[1].IsObject() && !args[2].IsNumber() && !args[3].IsNumber()) {
Napi::Error::New(env, "invalid arguments").ThrowAsJavaScriptException();
return env.Null();
}
// TODO: temp (?) solution to forcing variables onto the heap
// to ensure consistent addresses. copy everything to a vector, and use the
// vector's instances of the variables as the addresses being passed to `functions.call()`.
// Another solution: do `int x = new int(4)` and then use `&x` for the address
std::vector<LPVOID> heap;
std::vector<functions::Arg> parsedArgs;
Napi::Array arguments = args[1].As<Napi::Array>();
for (unsigned int i = 0; i < arguments.Length(); i++) {
Napi::Object argument = arguments.Get(i).As<Napi::Object>();
functions::Type type = (functions::Type) argument.Get(Napi::String::New(env, "type")).As<Napi::Number>().Uint32Value();
if (type == functions::Type::T_STRING) {
std::string stringValue = argument.Get(Napi::String::New(env, "value")).As<Napi::String>().Utf8Value();
parsedArgs.push_back({ type, &stringValue });
}
if (type == functions::Type::T_INT) {
int data = argument.Get(Napi::String::New(env, "value")).As<Napi::Number>().Int32Value();
// As we only pass the addresses of the variable to the `call` function and not a copy
// of the variable itself, we need to ensure that the variable stays alive and in a unique
// memory location until the `call` function has been executed. So manually allocate memory,
// track it, and then free it once the function has been called.
// TODO: find a better solution?
int* memory = (int*) malloc(sizeof(int));
*memory = data;
heap.push_back(memory);
parsedArgs.push_back({ type, memory });
}
if (type == functions::Type::T_FLOAT) {
float data = argument.Get(Napi::String::New(env, "value")).As<Napi::Number>().FloatValue();
float* memory = (float*) malloc(sizeof(float));
*memory = data;
heap.push_back(memory);
parsedArgs.push_back({ type, memory });
}
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
functions::Type returnType = (functions::Type) args[2].As<Napi::Number>().Uint32Value();
DWORD64 address = args[3].As<Napi::Number>().Int64Value();
char* errorMessage = "";
Call data = functions::call<int>(handle, parsedArgs, returnType, address, &errorMessage);
// Free all the memory we allocated
for (auto &memory : heap) {
free(memory);
}
heap.clear();
if (strcmp(errorMessage, "") && args.Length() != 5) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return env.Null();
}
Napi::Object info = Napi::Object::New(env);
Napi::String keyString = Napi::String::New(env, "returnValue");
if (returnType == functions::Type::T_STRING) {
info.Set(keyString, Napi::String::New(env, data.returnString.c_str()));
}
if (returnType == functions::Type::T_CHAR) {
info.Set(keyString, Napi::Value::From(env, (char) data.returnValue));
}
if (returnType == functions::Type::T_BOOL) {
info.Set(keyString, Napi::Value::From(env, (bool) data.returnValue));
}
if (returnType == functions::Type::T_INT) {
info.Set(keyString, Napi::Value::From(env, (int) data.returnValue));
}
if (returnType == functions::Type::T_FLOAT) {
float value = *(float *)&data.returnValue;
info.Set(keyString, Napi::Value::From(env, value));
}
if (returnType == functions::Type::T_DOUBLE) {
double value = *(double *)&data.returnValue;
info.Set(keyString, Napi::Value::From(env, value));
}
info.Set(Napi::String::New(env, "exitCode"), Napi::Value::From(env, data.exitCode));
if (args.Length() == 5) {
// Callback to let the user handle with the information
Napi::Function callback = args[2].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), info });
return env.Null();
} else {
// return JSON
return info;
}
}
Napi::Value virtualProtectEx(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 4 && args.Length() != 5) {
Napi::Error::New(env, "requires 4 arguments, 5 with callback").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() && !args[1].IsNumber() && !args[2].IsNumber()) {
Napi::Error::New(env, "All arguments should be numbers.").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 5 && !args[4].IsFunction()) {
Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException();
return env.Null();
}
DWORD result;
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
DWORD64 address = args[1].As<Napi::Number>().Int64Value();
SIZE_T size = args[2].As<Napi::Number>().Int64Value();
DWORD protection = args[3].As<Napi::Number>().Uint32Value();
bool success = VirtualProtectEx(handle, (LPVOID) address, size, protection, &result);
char* errorMessage = "";
if (success == 0) {
errorMessage = "an error occurred calling VirtualProtectEx";
// errorMessage = GetLastErrorToString().c_str();
}
// If there is an error and there is no callback, throw the error
if (strcmp(errorMessage, "") && args.Length() != 5) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 5) {
// Callback to let the user handle with the information
Napi::Function callback = args[5].As<Napi::Function>();
callback.Call(env.Global(), {
Napi::String::New(env, errorMessage),
Napi::Value::From(env, result)
});
return env.Null();
} else {
return Napi::Value::From(env, result);
}
}
Napi::Value getRegions(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 1 && args.Length() != 2) {
Napi::Error::New(env, "requires 1 argument, 2 with callback").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber()) {
Napi::Error::New(env, "invalid arguments: first argument must be a number").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 2 && !args[1].IsFunction()) {
Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException();
return env.Null();
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
std::vector<MEMORY_BASIC_INFORMATION> regions = Memory.getRegions(handle);
Napi::Array regionsArray = Napi::Array::New(env, regions.size());
for (std::vector<MEMORY_BASIC_INFORMATION>::size_type i = 0; i != regions.size(); i++) {
Napi::Object region = Napi::Object::New(env);
region.Set(Napi::String::New(env, "BaseAddress"), Napi::Value::From(env, (DWORD64) regions[i].BaseAddress));
region.Set(Napi::String::New(env, "AllocationBase"), Napi::Value::From(env, (DWORD64) regions[i].AllocationBase));
region.Set(Napi::String::New(env, "AllocationProtect"), Napi::Value::From(env, (DWORD) regions[i].AllocationProtect));
region.Set(Napi::String::New(env, "RegionSize"), Napi::Value::From(env, (SIZE_T) regions[i].RegionSize));
region.Set(Napi::String::New(env, "State"), Napi::Value::From(env, (DWORD) regions[i].State));
region.Set(Napi::String::New(env, "Protect"), Napi::Value::From(env, (DWORD) regions[i].Protect));
region.Set(Napi::String::New(env, "Type"), Napi::Value::From(env, (DWORD) regions[i].Type));
char moduleName[MAX_PATH];
DWORD size = GetModuleFileNameExA(handle, (HINSTANCE)regions[i].AllocationBase, moduleName, MAX_PATH);
if (size != 0) {
region.Set(Napi::String::New(env, "szExeFile"), Napi::String::New(env, moduleName));
}
regionsArray.Set(i, region);
}
if (args.Length() == 2) {
// Callback to let the user handle with the information
Napi::Function callback = args[1].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, ""), regionsArray });
return env.Null();
} else {
// return JSON
return regionsArray;
}
}
Napi::Value virtualQueryEx(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 2 && args.Length() != 3) {
Napi::Error::New(env, "requires 2 arguments, 3 with callback").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() || !args[1].IsNumber()) {
Napi::Error::New(env, "first and second argument need to be a number").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 3 && !args[2].IsFunction()) {
Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException();
return env.Null();
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
DWORD64 address = args[1].As<Napi::Number>().Int64Value();
MEMORY_BASIC_INFORMATION information;
SIZE_T result = VirtualQueryEx(handle, (LPVOID)address, &information, sizeof(information));
char* errorMessage = "";
if (result == 0 || result != sizeof(information)) {
errorMessage = "an error occurred calling VirtualQueryEx";
// errorMessage = GetLastErrorToString().c_str();
}
// If there is an error and there is no callback, throw the error
if (strcmp(errorMessage, "") && args.Length() != 3) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return env.Null();
}
Napi::Object region = Napi::Object::New(env);
region.Set(Napi::String::New(env, "BaseAddress"), Napi::Value::From(env, (DWORD64) information.BaseAddress));
region.Set(Napi::String::New(env, "AllocationBase"), Napi::Value::From(env, (DWORD64) information.AllocationBase));
region.Set(Napi::String::New(env, "AllocationProtect"), Napi::Value::From(env, (DWORD) information.AllocationProtect));
region.Set(Napi::String::New(env, "RegionSize"), Napi::Value::From(env, (SIZE_T) information.RegionSize));
region.Set(Napi::String::New(env, "State"), Napi::Value::From(env, (DWORD) information.State));
region.Set(Napi::String::New(env, "Protect"), Napi::Value::From(env, (DWORD) information.Protect));
region.Set(Napi::String::New(env, "Type"), Napi::Value::From(env, (DWORD) information.Type));
if (args.Length() == 3) {
// Callback to let the user handle with the information
Napi::Function callback = args[1].As<Napi::Function>();
callback.Call(env.Global(), { Napi::String::New(env, ""), region });
return env.Null();
} else {
// return JSON
return region;
}
}
Napi::Value virtualAllocEx(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 5 && args.Length() != 6) {
Napi::Error::New(env, "requires 5 arguments, 6 with callback").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() || !args[2].IsNumber() || !args[3].IsNumber() || !args[4].IsNumber()) {
Napi::Error::New(env, "invalid arguments: arguments 0, 2, 3 and 4 need to be numbers").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 6 && !args[5].IsFunction()) {
Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException();
return env.Null();
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
SIZE_T size = args[2].As<Napi::Number>().Int64Value();
DWORD allocationType = args[3].As<Napi::Number>().Uint32Value();
DWORD protection = args[4].As<Napi::Number>().Uint32Value();
LPVOID address;
// Means in the JavaScript space `null` was passed through.
if (args[1] == env.Null()) {
address = NULL;
} else {
address = (LPVOID) args[1].As<Napi::Number>().Int64Value();
}
LPVOID allocatedAddress = VirtualAllocEx(handle, address, size, allocationType, protection);
char* errorMessage = "";
// If null, it means an error occurred
if (allocatedAddress == NULL) {
errorMessage = "an error occurred calling VirtualAllocEx";
// errorMessage = GetLastErrorToString().c_str();
}
// If there is an error and there is no callback, throw the error
if (strcmp(errorMessage, "") && args.Length() != 6) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 6) {
// Callback to let the user handle with the information
Napi::Function callback = args[5].As<Napi::Function>();
callback.Call(env.Global(), {
Napi::String::New(env, errorMessage),
Napi::Value::From(env, (intptr_t)allocatedAddress)
});
return env.Null();
} else {
return Napi::Value::From(env, (intptr_t)allocatedAddress);
}
}
Napi::Value attachDebugger(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 2) {
Napi::Error::New(env, "requires 2 arguments").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() || !args[1].IsBoolean()) {
Napi::Error::New(env, "first argument needs to be a number, second a boolean").ThrowAsJavaScriptException();
return env.Null();
}
DWORD processId = args[0].As<Napi::Number>().Uint32Value();
bool killOnExit = args[1].As<Napi::Boolean>().Value();
bool success = debugger::attach(processId, killOnExit);
return Napi::Boolean::New(env, success);
}
Napi::Value detatchDebugger(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
DWORD processId = args[0].As<Napi::Number>().Uint32Value();
if (args.Length() != 1) {
Napi::Error::New(env, "requires only 1 argument").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber()) {
Napi::Error::New(env, "only argument needs to be a number").ThrowAsJavaScriptException();
return env.Null();
}
bool success = debugger::detatch(processId);
return Napi::Boolean::New(env, success);
}
Napi::Value awaitDebugEvent(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 2) {
Napi::Error::New(env, "requires 2 arguments").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() || !args[1].IsNumber()) {
Napi::Error::New(env, "both arguments need to be a number").ThrowAsJavaScriptException();
return env.Null();
}
int millisTimeout = args[1].As<Napi::Number>().Uint32Value();
DebugEvent debugEvent;
bool success = debugger::awaitDebugEvent(millisTimeout, &debugEvent);
Register hardwareRegister = static_cast<Register>(args[0].As<Napi::Number>().Uint32Value());
if (success && debugEvent.hardwareRegister == hardwareRegister) {
Napi::Object info = Napi::Object::New(env);
info.Set(Napi::String::New(env, "processId"), Napi::Value::From(env, (DWORD) debugEvent.processId));
info.Set(Napi::String::New(env, "threadId"), Napi::Value::From(env, (DWORD) debugEvent.threadId));
info.Set(Napi::String::New(env, "exceptionCode"), Napi::Value::From(env, (DWORD) debugEvent.exceptionCode));
info.Set(Napi::String::New(env, "exceptionFlags"), Napi::Value::From(env, (DWORD) debugEvent.exceptionFlags));
info.Set(Napi::String::New(env, "exceptionAddress"), Napi::Value::From(env, (DWORD64) debugEvent.exceptionAddress));
info.Set(Napi::String::New(env, "hardwareRegister"), Napi::Value::From(env, static_cast<int>(debugEvent.hardwareRegister)));
return info;
}
// If we aren't interested in passing this event back to the JS space,
// just silently handle it
if (success && debugEvent.hardwareRegister != hardwareRegister) {
debugger::handleDebugEvent(debugEvent.processId, debugEvent.threadId);
}
return env.Null();
}
Napi::Value handleDebugEvent(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 2) {
Napi::Error::New(env, "requires 2 arguments").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() || !args[1].IsNumber()) {
Napi::Error::New(env, "both arguments need to be numbers").ThrowAsJavaScriptException();
return env.Null();
}
DWORD processId = args[0].As<Napi::Number>().Uint32Value();
DWORD threadId = args[1].As<Napi::Number>().Uint32Value();
bool success = debugger::handleDebugEvent(processId, threadId);
return Napi::Boolean::New(env, success);
}
Napi::Value setHardwareBreakpoint(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 5) {
Napi::Error::New(env, "requires 5 arguments").ThrowAsJavaScriptException();
return env.Null();
}
for (unsigned int i = 0; i < args.Length(); i++) {
if (!args[i].IsNumber()) {
Napi::Error::New(env, "all arguments need to be numbers").ThrowAsJavaScriptException();
return env.Null();
}
}
DWORD processId = args[0].As<Napi::Number>().Uint32Value();
DWORD64 address = args[1].As<Napi::Number>().Int64Value();
Register hardwareRegister = static_cast<Register>(args[2].As<Napi::Number>().Uint32Value());
// Execute = 0x0
// Access = 0x3
// Writer = 0x1
int trigger = args[3].As<Napi::Number>().Uint32Value();
int length = args[4].As<Napi::Number>().Uint32Value();
bool success = debugger::setHardwareBreakpoint(processId, address, hardwareRegister, trigger, length);
return Napi::Boolean::New(env, success);
}
Napi::Value removeHardwareBreakpoint(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 2) {
Napi::Error::New(env, "requires 2 arguments").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() || !args[1].IsNumber()) {
Napi::Error::New(env, "both arguments need to be numbers").ThrowAsJavaScriptException();
return env.Null();
}
DWORD processId = args[0].As<Napi::Number>().Uint32Value();
Register hardwareRegister = static_cast<Register>(args[1].As<Napi::Number>().Uint32Value());
bool success = debugger::setHardwareBreakpoint(processId, 0, hardwareRegister, 0, 0);
return Napi::Boolean::New(env, success);
}
Napi::Value injectDll(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 2 && args.Length() != 3) {
Napi::Error::New(env, "requires 2 arguments, or 3 with a callback").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber() || !args[1].IsString()) {
Napi::Error::New(env, "first argument needs to be a number, second argument needs to be a string").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 3 && !args[2].IsFunction()) {
Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException();
return env.Null();
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
std::string dllPath(args[1].As<Napi::String>().Utf8Value());
Napi::Function callback = args[2].As<Napi::Function>();
char* errorMessage = "";
DWORD moduleHandle = -1;
bool success = dll::inject(handle, dllPath, &errorMessage, &moduleHandle);
if (strcmp(errorMessage, "") && args.Length() != 3) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return env.Null();
}
// `moduleHandle` above is the return value of the `LoadLibrary` procedure,
// which we retrieve through `GetExitCode`. This value can become truncated
// in large address spaces such as 64 bit since `GetExitCode` just returns BOOL,
// so it's unreliable to use as the handle. Since the handle of a module is just a pointer
// to the address of the DLL mapped in the process' virtual address space, we can fetch
// this separately, so we won't return it to prevent it being passed to `unloadDll`
// and in some cases unexpectedly failing when it is truncated.
if (args.Length() == 3) {
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Boolean::New(env, success) });
return env.Null();
} else {
return Napi::Boolean::New(env, success);
}
}
Napi::Value unloadDll(const Napi::CallbackInfo& args) {
Napi::Env env = args.Env();
if (args.Length() != 2 && args.Length() != 3) {
Napi::Error::New(env, "requires 2 arguments, or 3 with a callback").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[0].IsNumber()) {
Napi::Error::New(env, "first argument needs to be a number").ThrowAsJavaScriptException();
return env.Null();
}
if (!args[1].IsNumber() && !args[1].IsString()) {
Napi::Error::New(env, "second argument needs to be a number or a string").ThrowAsJavaScriptException();
return env.Null();
}
if (args.Length() == 3 && !args[2].IsFunction()) {
Napi::Error::New(env, "callback needs to be a function").ThrowAsJavaScriptException();
return env.Null();
}
HANDLE handle = (HANDLE)args[0].As<Napi::Number>().Int64Value();
Napi::Function callback = args[2].As<Napi::Function>();
HMODULE moduleHandle;
// get module handle (module base address) directly
if (args[1].IsNumber()) {
moduleHandle = (HMODULE)args[1].As<Napi::Number>().Int64Value();
}
// find module handle from name of DLL
if (args[1].IsString()) {
std::string moduleName(args[1].As<Napi::String>().Utf8Value());
char* errorMessage = "";
MODULEENTRY32 module = module::findModule(moduleName.c_str(), GetProcessId(handle), &errorMessage);
if (strcmp(errorMessage, "")) {
if (args.Length() != 3) {
Napi::Error::New(env, "unable to find specified module").ThrowAsJavaScriptException();
return env.Null();
} else {
callback.Call(env.Global(), { Napi::String::New(env, errorMessage) });
return Napi::Boolean::New(env, false);
}
}
moduleHandle = (HMODULE) module.modBaseAddr;
}
char* errorMessage = "";
bool success = dll::unload(handle, &errorMessage, moduleHandle);
if (strcmp(errorMessage, "") && args.Length() != 3) {
Napi::Error::New(env, errorMessage).ThrowAsJavaScriptException();
return Napi::Boolean::New(env, false);
}
if (args.Length() == 3) {
callback.Call(env.Global(), { Napi::String::New(env, errorMessage), Napi::Boolean::New(env, success) });
return env.Null();
} else {
return Napi::Boolean::New(env, success);
}
}
// https://stackoverflow.com/a/17387176
std::string GetLastErrorToString() {
DWORD errorMessageID = ::GetLastError();
// No error message, return empty string
if(errorMessageID == 0) {
return std::string();
}
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errorMessageID,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&messageBuffer,
0,
NULL
);
std::string message(messageBuffer, size);
// Free the buffer
LocalFree(messageBuffer);
return message;
}
Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "openProcess"), Napi::Function::New(env, openProcess));
exports.Set(Napi::String::New(env, "closeProcess"), Napi::Function::New(env, closeProcess));
exports.Set(Napi::String::New(env, "getProcesses"), Napi::Function::New(env, getProcesses));
exports.Set(Napi::String::New(env, "getModules"), Napi::Function::New(env, getModules));
exports.Set(Napi::String::New(env, "findModule"), Napi::Function::New(env, findModule));
exports.Set(Napi::String::New(env, "readMemory"), Napi::Function::New(env, readMemory));
exports.Set(Napi::String::New(env, "readBuffer"), Napi::Function::New(env, readBuffer));
exports.Set(Napi::String::New(env, "writeMemory"), Napi::Function::New(env, writeMemory));
exports.Set(Napi::String::New(env, "writeBuffer"), Napi::Function::New(env, writeBuffer));
exports.Set(Napi::String::New(env, "findPattern"), Napi::Function::New(env, findPattern));
exports.Set(Napi::String::New(env, "findPatternByModule"), Napi::Function::New(env, findPatternByModule));
exports.Set(Napi::String::New(env, "findPatternByAddress"), Napi::Function::New(env, findPatternByAddress));
exports.Set(Napi::String::New(env, "virtualProtectEx"), Napi::Function::New(env, virtualProtectEx));
exports.Set(Napi::String::New(env, "callFunction"), Napi::Function::New(env, callFunction));
exports.Set(Napi::String::New(env, "virtualAllocEx"), Napi::Function::New(env, virtualAllocEx));
exports.Set(Napi::String::New(env, "getRegions"), Napi::Function::New(env, getRegions));
exports.Set(Napi::String::New(env, "virtualQueryEx"), Napi::Function::New(env, virtualQueryEx));
exports.Set(Napi::String::New(env, "attachDebugger"), Napi::Function::New(env, attachDebugger));
exports.Set(Napi::String::New(env, "detatchDebugger"), Napi::Function::New(env, detatchDebugger));
exports.Set(Napi::String::New(env, "awaitDebugEvent"), Napi::Function::New(env, awaitDebugEvent));
exports.Set(Napi::String::New(env, "handleDebugEvent"), Napi::Function::New(env, handleDebugEvent));
exports.Set(Napi::String::New(env, "setHardwareBreakpoint"), Napi::Function::New(env, setHardwareBreakpoint));
exports.Set(Napi::String::New(env, "removeHardwareBreakpoint"), Napi::Function::New(env, removeHardwareBreakpoint));
exports.Set(Napi::String::New(env, "injectDll"), Napi::Function::New(env, injectDll));
exports.Set(Napi::String::New(env, "unloadDll"), Napi::Function::New(env, unloadDll));
return exports;
}
NODE_API_MODULE(memoryjs, init)