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

Scanner Needs Options To Scan Through Range of Virtual Memory Address Space #3

Open
tojo423 opened this issue Sep 24, 2021 · 6 comments

Comments

@tojo423
Copy link

tojo423 commented Sep 24, 2021

The Scanner class needs functionality to scan between memory regions starting at address A, and ending at address B. This is easy to implement by oneself, by simply walking all the memory regions of the process, reading bytes from the region, and using the scanner as is. BUT, it would be much more convenient to have this functionality built-in.

@slxdy
Copy link

slxdy commented Aug 1, 2024

@Sewer56

Any plans on adding this? There is no option to specify a custom range for an external process at all, which is VERY limiting.

@Sewer56 Sewer56 changed the title Scanner Needs Options To Scan Through Range Scanner Needs Options To Scan Through Range of Virtual Memory Address Space Aug 1, 2024
@Sewer56
Copy link
Member

Sewer56 commented Aug 1, 2024

@slxdy It's more likely going to happen in the Rust port a few months from now.

The scanner can already do address ranges, technically speaking.

What's really being asked here is to scan through the virtual address space through all memory between a specific minimum and maximum address.

So we're talking:

  • VirtualQueryEx on Windows
  • Parsing /proc/{id}/maps on Linux
  • mach_vm_region on macOS

etc.

Then walk through the pages returned to find all used pages, and scan the memory within them.

I'm actually already doing this sort of thing in reloaded-memory-buffers [both C# and Rust versions]; albeit doing the opposite to find free pages.

The library itself should really only contain the scanning code; but functionality like this would be very useful in an extension library. Some design/research would be handy there, as you may for example want to only scan for pages with specific permissions (e.g. executable for game code). Some OSes might also have specific page types other might not. Mapped memory for example is common in emulators, because it helps with taking quick savestates.

If I were to do it myself, I'd do it in the Rust port a few months from now; after the Reloaded-III spec is done, and I finish work on Reloaded.Hooks-rs

@slxdy
Copy link

slxdy commented Aug 1, 2024

May I at least suggest a new constructor that would allow us to iterate through the memory pages manually?

public Scanner(Process proc, nuint startAddress, nuint length) { }

@Sewer56
Copy link
Member

Sewer56 commented Aug 1, 2024


Forgot to add this.

There are also some edge cases you'd need to consider here.

For example if you're scanning through the address space of a process, it's possible another thread could unmap a part of memory that is currently being scanned.

If that happened you'd get an Access Violation (SIGSEGV on Linux); and your process would die. While the probability of that is very unlikely, it could happen. Imagine you just happened to be scanning through a thread's stack, and that thread has exit at just the right time.

Some mechanism for suspending threads, like the one in Cheat Engine would probably be needed, and it would need to be cross platform. Some research into edge cases would be needed.

@slxdy
Copy link

slxdy commented Aug 1, 2024

Reading the memory of another process should not cause any exceptions, other than the read function failing as far as I know.

Even then, same can be said for reading module memory. What if a module is suddenly freed? Would result in the same issue

@Sewer56
Copy link
Member

Sewer56 commented Aug 1, 2024

Of an external process, it might be ok, as the OS itself would just return an error and not kill the whole process. Though it would be much, much slower, because you perform a memory copy to get the data out in the first place.

What if a module is suddenly freed? Would result in the same issue

Yeah, it would. Though modules are very rarely unloaded in any software in practice.
I originally built this library with the main purpose of scanning the main executable's code in-process; i.e. searching for code across game versions. So usage with external processes is not as ideal as it could be.

In any case, check these constructors

/// <summary>
/// Creates a signature scanner given the data in which patterns are to be found.
/// </summary>
/// <param name="data">The data to look for signatures inside.</param>
public Scanner(byte[] data)
{
_gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
_dataPtr = (byte*)_gcHandle.Value.AddrOfPinnedObject();
_dataLength = data.Length;
}

/// <summary>
/// Creates a signature scanner given a process and a module (EXE/DLL)
/// from which the signatures are to be found.
/// </summary>
/// <param name="process">The process from which to scan patterns in. (Not Null)</param>
/// <param name="module">An individual module of the given process, which denotes the start and end of memory region scanned.</param>
#if NET5_0_OR_GREATER
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("windows")]
#endif
public Scanner(Process process, ProcessModule module)
{
// Optimization
if (process.Id == _currentProcessId)
{
_dataPtr = (byte*) module.BaseAddress;
_dataLength = module.ModuleMemorySize;
}
else
{
var externalProcess = new ExternalMemory(process);
var data = externalProcess.ReadRaw((nuint)(nint)module.BaseAddress, module.ModuleMemorySize);
_gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
_dataPtr = (byte*)_gcHandle.Value.AddrOfPinnedObject();
_dataLength = data.Length;
}
}

If you see the else branch, it pretty much shows how you could do a custom range from an external process today.
It's just a memory read, and then use of the first constructor.

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

3 participants