Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pattern scan always returns same (wrong) address #48

Closed
MelerEcckmanLawler opened this issue Oct 29, 2019 · 15 comments
Closed

Pattern scan always returns same (wrong) address #48

MelerEcckmanLawler opened this issue Oct 29, 2019 · 15 comments

Comments

@MelerEcckmanLawler
Copy link

MelerEcckmanLawler commented Oct 29, 2019

const memoryjs = require('memoryjs')

memoryjs.openProcess('notepad++.exe', (error, process) => {
  if (error) { console.log(error) }
  memoryjs.findPattern(process.handle, 'notepad++.exe', '65 6C 6C ? 20 57 6F 72 6C 64 21', memoryjs.NORMAL, 0, 0, (error, offset) => {
    console.log(offset)
  })
});

No matter what process name I use, or pattern, the above code will always output this:

18446744073709552000

Which is of course incorrect. And looking at notepad++.exe's memory using Cheat Engine I can see Hello World! is definitely in the its memory, infact in three different locations, and not in utf16 format (it doesn't have 00 between each character).

But using the address found via Cheat Engine to read the text I typed in notepad++.exe works correctly:

memoryjs.readMemory(process.handle, 0x1A338732FB0, 'string', (error, data) => {
    console.log(data)
  })

Will output:

Hello World!

@Rob--
Copy link
Owner

Rob-- commented Oct 30, 2019

Hey, is the process 32 or 64 bit? And did you recompile the library for the target architecture?

@MelerEcckmanLawler
Copy link
Author

MelerEcckmanLawler commented Oct 30, 2019

The process is 64bit. By "recompile for target architecture", do you mean running npm i memoryjs --arch=ia64? I get an error, gyp ERR! stack Error: C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe failed with exit code: 1.

I recall getting gyp to work in the first place was an absolute nightmare, is it possible I can download the 64bit compiled file(s) somewhere without having to compile them myself?

EDIT: Spent over an hour trying to solve the gyp error, still no luck.

@MelerEcckmanLawler
Copy link
Author

I forgot to add, I am on Windows 10 64-bit.

@Rob--
Copy link
Owner

Rob-- commented Nov 1, 2019

Go inside node_modules/memoryjs and run the command npm run build64 (or just run node-gyp clean configure build --arch=x64) and it should recompile the library to work for 64 bit targets.

@MelerEcckmanLawler
Copy link
Author

MelerEcckmanLawler commented Nov 1, 2019

Funny how I found this out myself before I checked your response, but it's still not working correctly.

I have a notepad++.exe (64bit) window open with the text WALDO in it. I run this code:

const memoryjs = require('memoryjs')
const toPattern = str => Buffer.from(str).toString('hex').replace(/..\B/g, '$& ').toUpperCase()
memoryjs.openProcess('notepad++.exe', (error, p) => {
  console.log(`Module: ${JSON.stringify(p, null, 2)}`)
  let text = 'WALDO'
  let pattern = toPattern(text).replace(/ /g, '')
  console.log(`Pattern scanning ${text} as ${pattern}`)
  memoryjs.findPattern(p.handle, 'notepad++.exe', pattern, memoryjs.READ, 0, 0, (error, offset) => {
    console.log(`Offset found: ${offset} (Cheat Engine says: ${0x1D3035D12E4})`)
    memoryjs.readMemory(p.handle, offset, memoryjs.STRING, (error, value) => {
      console.log(`Data read: ${value}`)
    })
  })
})

And the console windows outputs this:

Module: {
  "dwSize": 304,
  "th32ProcessID": 5552,
  "cntThreads": 5,
  "th32ParentProcessID": 5736,
  "pcPriClassBase": 8,
  "szExeFile": "notepad++.exe",
  "handle": 480,
  "modBaseAddr": 140698537820160
}
Pattern scanning WALDO as 57414C444F
Offset found: 12894362189 (Cheat Engine says: 2005806158564)

The memoryjs.readMemory function never even outputs anything, the process automatically terminates within 5 seconds.

But if instead of using the offset variable I use the address Cheat Engine gave me after finding WALDO, it does read the text WALDO, so it works if you give it the address. It just can't find the correct address itself.

Not sure how to get the code markup to work since I used backticks in my code. Fixed.

By the way I know the compilation worked correctly because the memoryjs.node file has the PE d string near the header, as viewed in a hex editor, meaning it is indeed 64 bit.

Although if I download your memoryjs project as a .zip file and build it as 64bit, the memoryjs.node file is 150kb whereas when built after being installed as a dependency of my project, it is only 138kb, which is strange.

@MelerEcckmanLawler
Copy link
Author

MelerEcckmanLawler commented Nov 1, 2019

Could you perhaps try the experiment yourself? How would you find the text WALDO in an open notepad++.exe window using your project?

Would be greatly appreciated ...

@MelerEcckmanLawler
Copy link
Author

MelerEcckmanLawler commented Nov 1, 2019

I've been needing this project to work so bad for 3 months lol ... I need to grab all text from the memory of a game and save it to a file. I can do this with a slightly modified Lua script inside Cheat Engine after the game is over but the text is not in chronological order (I don't know if this is intentional obfuscation or just an inherent aspect of Random Access Memory?) and I don't know Lua enough to make it do what your project does. I even tried OCR with capture2text but because it's 99% accurate instead of 100% it caused some insurmountable problems that would take too long to explain here.

@yanmingsohu
Copy link

lib/memoryjs.cc 693:
uintptr_t address = -1;
736:
Local<Value> argv[argc] = { String::NewFromUtf8(isolate, errorMessage), Number::New(isolate, address) };

https://v8docs.nodesource.com/node-10.15/d9/d29/classv8_1_1_number.html:
static Local< Number > | New (Isolate *isolate, double value)

After conversion to double, the value of address in js is 18446744073709552000.000000, obviously the data overflowed.

Comparing signed and unsigned numbers produces a logical error.Exceptions may never be thrown?

@zatmonkey
Copy link

@MelerEcckmanLawler did you figure this out? I'm having the same issue.

@epopcop
Copy link

epopcop commented Apr 26, 2021

Hey,
I'm not the best at c, so I'm not qualified enough to explain what I found, But I'll try.
The function findPattern will return an uintptr_t (unsigned int). In case the function doesn't find the module - the address returned is -1, and if the pattern wasn't found - it will return -2. Since unsigned ints don't support negative numbers, they are parsed wrong.

I changed memoryjs::findPattern and pattern::findPattern functions to return signed integers(intptr_t). after the change, the address returned was actually -2, so I understood there's an issue with my pattern.
I think there's something wrong with the implantation. that said.. you can still check the error message in a callback, and not rely on the address returned (Haven't tested it).

In my case, it still didn't help. I'm searching the memory of an emulator, so I have no module name since the actual data is allocated somewhere. I'm working on a new function that will receive a start address and size to search in. I also want to implement pattern search using std::search, since it gave me really good results very quickly. I will share it if it works at all 😄

@dsasmblr
Copy link

import { findPattern, openProcess, readMemory } from 'memoryjs';

const processName = "Notepad.exe";
const process = openProcess(processName);

const pattern = "64 00 65 00 65 00 7A 00";
const deez = findPattern(process.handle, process.szExeFile, pattern, 0, 0, 0).toString(16).toUpperCase();
console.log(deez)

const deezMem = readMemory(process.handle, 0x2B7502D1AE0, "string");
console.log(deezMem);

By happenstance, I spent the evening attempting the same type of task, but in Notepad.exe in Windows 11. It's a 64-bit target, which I've properly compiled for.

If I search for my string of interest ("deez", which I typed as the only word into a new instance of Notepad) via Cheat Engine, I see it in two memory addresses (scanning RWX memory and for Unicode strings).

I've noted elsewhere here that memoryjs doesn't support Unicode (yet?), so I thought that pattern-scanning for the bytes that make up the string would be a workaround, but that doesn't appear to be the case.

If I use the readMemory() method and read bytes 0, 2, 4, and 6 directly from each of the two memory addresses I found via Cheat Engine, I can verify that the letters are 'd', 'e', 'e', and 'z' -- which matches the endianness I've specified as my byte pattern.

I'm not sure at this point if the issue is my understanding of utilizing the library thus far, or perhaps if there's an issue with the findPattern() method (like the algorithm being used, if there's some type of conflict with how a Unicode pattern is interpreted even with this method, or something else).

I just wanted to chime in here and gather some thoughts. Thanks, everyone! (And thank you for the library, Rob--; I've really been enjoying using much of it thus far!)

@Rob--
Copy link
Owner

Rob-- commented Jan 23, 2022

@dsasmblr thanks, glad you've found it useful! I've also taken a look at the same example. The implementation of findPattern seems correct, it's just that in this specific case the address lies outside of a module...

Opening up notepad.exe, typing deez and doing a Search for text with UTF-16 checked shows two results:

0x2E84C1CB240
0x2E84C1FBBA0

Running this script (using one of the two addresses) shows the address doesn't lie inside of any modules, but lies inside of a single region:

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

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

const address = 0x2E84C1CB240;

const matchingModules = memoryjs
  .getModules(processObject.th32ProcessID)
  .filter(mod => address >= mod.modBaseAddr && address <= (mod.modBaseAddr + mod.modBaseSize));

const matchingRegions = memoryjs
  .getRegions(processObject.handle)
  .filter(region => address >= region.BaseAddress && address <= (region.BaseAddress + region.RegionSize));

console.log('modules', matchingModules);
console.log('regions', matchingRegions);

const pattern = "64 00 65 00 65 00 7A 00";
memoryjs.findPattern(processObject.handle, pattern, 0, 0, (error, address) => {
  console.log(`error: ${error}, address: 0x${address.toString(16).toUpperCase()}`);
});

I've edited the source code so that findPattern searches both modules and regions, so the output in this case finds a match:

$ node test
modules []
regions [
  {
    BaseAddress: 3196732375040,
    AllocationBase: 3196732375040,
    AllocationProtect: 4,
    RegionSize: 659456,
    State: 4096,
    Protect: 4,
    Type: 131072
  }
]
error: , address: 0x2E84C1CB240

Searching all regions & modules takes too long, so it might be worth having a findPattern that searches everything, and a findPattern that will search just a specified module or region...

I think I'll change findPattern to have the following implementations:

// search all modules + all regions
findPattern(handle, pattern, flags, patternOffset[, callback])

// search a specific module
findPattern(handle, moduleName, pattern, flags, patternOffset[, callback])

// search a specific region (will find the region or module the base address lies inside)
findPattern(handle, baseAddress, pattern, flags, patternOffset[, callback])

@dsasmblr
Copy link

Hey, @Rob--! Thanks so much for taking the time to test and reply with all of that. I totally didn't think about exploring regions like that. I was incorrectly assuming that regions relative to the application's modules were already taken into consideration. Thanks for clarifying!

I like the findPattern changes you've suggested, too. I think all three would be respectively beneficial.

Thanks again, Rob!

@Rob--
Copy link
Owner

Rob-- commented Jan 24, 2022

These changes are implemented in a32ceee.

New implementation of findPattern is:

// search all modules + all regions
findPattern(handle, pattern, flags, patternOffset[, callback])

// search a specific module
findPattern(handle, moduleName, pattern, flags, patternOffset[, callback])

// search a specific region (will find the region or module the base address lies inside)
findPattern(handle, baseAddress, pattern, flags, patternOffset[, callback])

The signatureOffset parameter has been removed from findPattern to reduce the parameter count. The value of the parameter was just added to the memory address returned, so does not need to happen inside of this function.

I'll leave the issue open to take comments until I publish all the recent library changes to NPM.

@Rob--
Copy link
Owner

Rob-- commented Oct 16, 2022

Closing the issue since v3.5.1 has been published to NPM and includes these changes.

@Rob-- Rob-- closed this as completed Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants