Rob-- / memoryjs

Read and write process memory in Node.js (Windows API functions exposed via Node bindings)
MIT License
632 stars 86 forks source link

Is that possible to find dynamic adresses or allocate and inject ? #80

Open aymericingargiola opened 3 years ago

aymericingargiola commented 3 years ago

I am building a run history tracker Electron app for The Binding of Isaac and actually i am parsing the logs but it miss a lot of informations (coins, damage, speed, bombs...) so i had the idea to read the game memory and then i found your project.

I am able to run memoryjs and it seems to work,

const memoryjs = require('memoryjs') const isaacProcessName = "isaac-ng.exe" const isaacExe = memoryjs.openProcess(isaacProcessName) console.log(isaacExe) const address = memoryjs.virtualAllocEx( isaacExe.handle, null, 0x60, memoryjs.MEM_RESERVE | memoryjs.MEM_COMMIT, memoryjs.PAGE_EXECUTE_READWRITE, ) console.log(Allocated address: 0x${address.toString(16).toUpperCase()})

{ dwSize: 304, th32ProcessID: 46544, cntThreads: 12, th32ParentProcessID: 37352, pcPriClassBase: 8, szExeFile: 'isaac-ng.exe', handle: 2908, modBaseAddr: 11206656 } Allocated address: 0x8A0000

But here is the tricky part... this game doesn't has static way to get information, so i found an cheat engine script that works and does :

` This script dumps player structure into sPlayer, then show important offsets like coins, keys, hearts, etc... [ENABLE] aobscanmodule(ReadMoney,isaac-ng.exe,FF B0 B0 12 00 00) // should be unique alloc(newmem,$1000) globalalloc(sPlayer,4) label(code) label(return)

newmem: mov [sPlayer],eax code: push [eax+000012B0] jmp return

ReadMoney: jmp newmem nop return: registersymbol(ReadMoney)

[DISABLE]

ReadMoney: db FF B0 B0 12 00 00

unregistersymbol(ReadMoney) dealloc(newmem)

{ // ORIGINAL CODE - INJECTION POINT: isaac-ng.exe+4AB53B

isaac-ng.exe+4AB512: 68 58 44 C3 00 - push isaac-ng.exe+7A4458 isaac-ng.exe+4AB517: 8D 45 98 - lea eax,[ebp-68] isaac-ng.exe+4AB51A: 8B CE - mov ecx,esi isaac-ng.exe+4AB51C: 50 - push eax isaac-ng.exe+4AB51D: E8 DE 96 B5 FF - call isaac-ng.epoxy_handle_external_wglMakeCurrent+2F20 isaac-ng.exe+4AB522: F3 0F 10 45 80 - movss xmm0,[ebp-80] isaac-ng.exe+4AB527: F3 0F 58 45 98 - addss xmm0,[ebp-68] isaac-ng.exe+4AB52C: 8B 45 94 - mov eax,[ebp-6C] isaac-ng.exe+4AB52F: 8B 0D 04 E4 C0 00 - mov ecx,[isaac-ng.exe+77E404] isaac-ng.exe+4AB535: 81 C1 00 B7 01 00 - add ecx,0001B700 // ---------- INJECTING HERE ---------- isaac-ng.exe+4AB53B: FF B0 B0 12 00 00 - push [eax+000012B0] // ---------- DONE INJECTING ---------- isaac-ng.exe+4AB541: F3 0F 11 45 A0 - movss [ebp-60],xmm0 isaac-ng.exe+4AB546: F3 0F 10 45 84 - movss xmm0,[ebp-7C] isaac-ng.exe+4AB54B: F3 0F 58 45 9C - addss xmm0,[ebp-64] isaac-ng.exe+4AB550: 51 - push ecx isaac-ng.exe+4AB551: 68 A0 01 00 00 - push 000001A0 isaac-ng.exe+4AB556: F3 0F 11 45 A4 - movss [ebp-5C],xmm0 isaac-ng.exe+4AB55B: E8 70 60 01 00 - call isaac-ng.exe+4C15D0 isaac-ng.exe+4AB560: F7 D8 - neg eax isaac-ng.exe+4AB562: BA C8 0F BA 00 - mov edx,isaac-ng.exe+710FC8 isaac-ng.exe+4AB567: B9 C0 0F BA 00 - mov ecx,isaac-ng.exe+710FC0 } `

image

Then sPlayer is the right adresse every time Is that possible to do this with memoryjs ? I search the whole evening and i'm a bit lost

Thank you

dsasmblr commented 2 years ago

Your question can be consolidated to this: "Can I use memoryjs to read the value in a register from an instruction located at a specific memory address?"

I don't think this library can do that at the moment; however, I think some of the core setup is there with the Debugger. A perhaps crude initial idea what this might look like in pseudocode, I see something as follows:

function getContext(address, accessType) {
    // Set BP on `address` to fire on `accessType` (read, write, or access).
    // When fired, capture data in registers (and maybe a stack snapshot of a specified size).
    // Detatch BP when finished so execution continues without a hitch.
    // Return the information that was captured
}

Then the usage might be something like this:

const procName = "isaac-ng.exe";
const procObj = memoryjs.openProcess(procName);
const address = memoryjs.processObject.modBaseAddr + 0x4AB53B;  // isaac-ng.exe+4AB53B

const ctx = memoryjs.getContext(address, "read");

const sPlayer = ctx.registers.eax; // sPlayer would now have the data from eax.

I don't mean to oversimply that or anything. I'm not sure what all might actually be involved with making something like this work between sync/async, hooking a specific instruction and capturing that subroutine's context on access, deciding on how to reference various register aliases (rax, eax, ax, ah, al), etc., etc.

Does this sound feasible to you at all, @Rob--?

Rob-- commented 2 years ago

@dsasmblr Pushed some WIP commit to a new branch get-registers (a470393). Seems like it is possible to return register values: when I view the registers in Visual Studio while debugging the target process they seem to align with the register values I see being returned here.

It includes a function to get register values arbitrarily:

const memoryjs = require('./index');

const processName = "testing.exe";
const processObject = memoryjs.openProcess(processName);

const address = 0x000000CC8A71FD20;
const ctx = memoryjs.getContext(processObject.th32ProcessID, address, memoryjs.TRIGGER_ACCESS);
console.log(ctx);

Produces the output:

$ node test
{
  P1Home: 2319,
  P2Home: 140708667870361,
  P3Home: 2449467113472,
  P4Home: 819450599993,
  P5Home: 2449495186944,
  P6Home: 2449495187152,
  ContextFlags: 1363828432,
  MxCsr: 570,
  SegCs: 8192,
  SegDs: 0,
  SegEs: 0,
  SegFs: 0,
  SegGs: 48784,
  SegSs: 20799,
  EFlags: 570,
  Dr0: 0,
  Dr1: 2449467696784,
  Dr2: 513,
  Dr3: 2449495187168,
  Dr6: 0,
  Dr7: 8192,
  Rax: 2449467155188,
  Rcx: 819450600400,
  Rdx: 0,
  Rbx: 1023,
  Rsp: 140694906068688,
  Rbp: 2449495120800,
  Rsi: 2449495195360,
  Rdi: 0,
  R8: 1,
  R9: 1024,
  R10: 2449495187152,
  R11: 2449467113472,
  R12: 140708667858611,
  R13: 2449467113472,
  R14: 2449467113472,
  R15: 2319,
  Rip: 1024,
  VectorControl: 0,
  DebugControl: 0,
  LastBranchToRip: 2449467680944,
  LastBranchFromRip: 0,
  LastExceptionToRip: 140694919478920,
  LastExceptionFromRip: 3
}

This context object is also now included with debug events:

{
  processId: 38804,
  threadId: 39180,
  exceptionCode: 2147483652,
  exceptionFlags: 0,
  exceptionAddress: 140701280637937,
  hardwareRegister: 0,
  context: {
    P1Home: 0,
    P2Home: 0,
    P3Home: 0,
    P4Home: 0,
    ....
  }
}
dsasmblr commented 2 years ago

@Rob-- Dude, thank you so much for cooking up that branch! Getting this pinned down will drastically reduce the need to scan for multilevel pointers beforehand to use within memoryjs scripts.

Okay, I pulled your branch down, compiled, etc. and I can't quite seem to get it to work correctly (which is probably due to my own error(s), lol).

PROBLEM: The main issue I'm having is that the breakpoint appears to immediately fire, thus pausing execution of the game until I kill the script running in the terminal. debugEvent comes back as null, which I've gathered is the expected result of no debug event occurring.

I parted out the getContext function you wrote and created a standalone script based on it so I could do some logging from within. Here's what I've got:

import * as memoryjs from "memoryjs";

function getContext(processId, address, trigger) {
    memoryjs.Debugger.attach(processId);
    const register = memoryjs.Debugger.setHardwareBreakpoint(processId, address, trigger, memoryjs.BYTE);

    const debugEvent = memoryjs.awaitDebugEvent(register, 1000);

    if (debugEvent) {
        memoryjs.handleDebugEvent(debugEvent.processId, debugEvent.threadId);
        memoryjs.Debugger.detach(processId);

        return debugEvent.context;
    }

    // console.log({debugEvent})

    // memoryjs.Debugger.detach(processId);
}

const processName = "game.exe";
const process = memoryjs.openProcess(processName);

const baseAddress = process.modBaseAddr;
const address = baseAddress + 0x25C49CA;

const ctx = getContext(process.th32ProcessID, address, memoryjs.TRIGGER_EXECUTE);

console.log(ctx);

I'm using TypeScript, so I made some slight temporary modifications on my end like removing the callback param in the function definition and the block related to it, etc. This also means I'm using ts-node to run scripts in the terminal (ex. ts-node debugger.ts) which I don't believe makes a difference, but noting it just in case you've heard of any issues.

Anyway, from within the function definition, I've logged all the arguments I've passed to it to verify everything is good there.

debugEvent is always null when I log it anywhere in the function, thus execution never flows through the if block. Adding in the memoryjs.Debugger.detach(processId); after the if block was just to verify that, with it, execution will continue normally after running the script as opposed to having to manually kill it for the game to resume execution after the breakpoint is hit.

Grasping at straws, I've tried changing the trigger to ACCESS, WRITE, EXECUTE, and a few other memory flags by their manual codes (like 0x10, as defined via the src/consts.js file) just to see what would happen since this is an instruction in an address that gets executed (I've checked via Cheat Engine that the address's default permissions are read and execute).

The instruction, specifically, is: movsd xmm5,[rbx+128]

I experience the same behavior of the breakpoint activating. Accordingly, the result of console.log(ctx); is always undefined.

Finally, I tried rewriting a version of the script using the manual example provided here: https://github.com/Rob--/memoryjs/blob/master/examples/debugging.js

The result is the same.

I'm at a bit of a loss at the moment as to what to try, so I figured I'd chime in with what all I've tried and see what you think. When I set a breakpoint via Cheat Engine just to make sure that it doesn't activate until I do something in the game that would make execution flow through that instruction, it works properly.

Thanks for your time, Rob!