To have access to the RAMvader namespace and its classes in your project, you should first choose one of the following options of deployment:
The library classes are contained into the RAMvader and RAMvader.CodeInjection (ramvader.sourceforge.net) namespaces. The main classes of the library are the RAMvaderTarget and Injector classes, which will be explained in this document.
The RAMvaderTarget class manages a "target", which is really just a process running into the user's local machine, whose memory you have interest in, for performing I/O operations. A single RAMvaderTarget instance may only handle up to one single Process at a time. If your application needs to perform memory operations in more than one single, "external" process at any time, you might instantiate more RAMvaderTarget objects as necessary (one for each process). Currently, RAMvaderTarget objects can be created by using their class' default constructor.
The Injector contains several features that might be useful to inject binary code and data ("variables", both managed and unmanaged ones) into the target process' memory space. The Injector class is optional. When you use an Injector object, it needs to be associated with a RAMvaderTarget object, as the Injector uses it "under the hood" to perform its injection operations.
The library has its own Exception-derived classes. The exceptions thrown by the library's code usually derive from the RAMvaderException class. Notice, though, that calls to some methods related to the Windows API and to the Process class are made inside the library's code, and they might throw their own, standard exceptions to your client code in some cases. Please, check the API documentation for each method to know which exceptions a specific method throws.
Now that you know the basic concepts behind the RAMvader library's architecture, let's see how we can actually write programs using the RAMvader library!
The first thing you should do is to "attach" an instance of the RAMvaderTarget class to one of the processes running into the local machine. When you attach a RAMvaderTarget, you allow it to open the target process' memory for reading and writing operations. This method returns a bool value indicating if the operation was successful. The attachment method requires a Process (from the System.Diagnostics namespace) object to be provided, indicating the target process to which the RAMvaderTarget instance will be attached.
using System.Diagnostics;
using RAMvader;
...
// Attach a RAMvaderTarget to process with PID 112233
Process targetProc = Process.GetProcessById( 112233 );
RAMvaderTarget myTarget = new RAMvaderTarget();
if ( myTarget.AttachToProcess( targetProc ) == false )
Console.WriteLine( "Error attaching to process!" );
There are some points to highlight for the attachment process:
Finally, when you're all done playing with the other process' memory (you'll learn how to do that soon!), it is a good idea to detach the RAMvaderTarget object from it, to release any used resources. For programmers experienced with the WinAPI, detaching from the target process really means that the Handle to the process which has been opened will now be closed. For inexperienced programmers, just know that it is always a good idea to detach from the target process whenever you don't need it anymore, to free precious resources for the Operating System. Process detachment is done by calling the "DetachFromProcess()" method, which returns a bool indicating if the operation was successful.
// Detach RAMvaderTarget from a
// previously attached process
if ( myTarget.DetachFromProcess() == false )
Console.WriteLine( "Failed to detach!!" );
After detaching from a process, the RAMvaderTarget object cleans itself up. This means you can reuse it to attach to another process safely. That is, if you want, you do not need to instantiate a new RAMvaderTarget to open another process after detaching from a previous one: just attach it to a new process.
You might check if a RAMvaderTarget instance is attached to a process by calling the "IsAttached()" method, which returns a flag indicating if the instance is currently attached to a process. You might also retrieve the Process object, corresponding to the attached process, by calling "GetAttachedProcess()". This last method might return a null value, if the RAMvaderTarget instance is currently not attached to any Process.
After attaching a RAMvaderTarget instance to a target Process, you can perform reading and writing operations to its memory. This is done by the "ReadFromTarget()" and "WriteToTarget()" methods. Both methods...:
You should read the API documentation for the "ReadFromTarget()" and "WriteToTarget()" method overloads and know them well, as these are probably the most important methods of the library - they are the core of the library's main operations. Notice that for the reading methods, with the exception of the byte-array-based overload, you have to pass references to the variables which will receive the data. So, don't forget to add the ref keyword to those calls.
When you start using the "ReadFromTarget()" and "WriteToTarget()" methods, you will realize that both methods receive a parameter of the MemoryAddress type. This is an abstract class which represents a memory address in the target process' memory space.
The RAMvader library provides specializations for that class which make it easier for you to reference addresses on the target process' memory space, including addresses that are "managed" by the RAMvader library, such as the addresses of injected variables and code caves. All you need is to use the right specialization of the MemoryAddress class to reference the right address. The library will take care of complex things like calculating the address where a variable has been injected, calculating an address relative to a module on the target process' memory space, etc.
To reference memory addresses in the target process' memory space, you can use the following specializations for the MemoryAddress class:
All of these classes have implemented the MemoryAddress.GetRealAddress() (ramvader.sourceforge.net) method with the corresponding logic that is used for calculating the address they correspond to in the target process' memory space. Notice that some of these specializations might throw exceptions if this method is called at a time where this logic is not ready to be performed.
The ModuleOffsetMemoryAddress require a RAMvaderTarget object to be attached to a valid process in order to obtain the list of modules for that process and calculate the address. Both the InjectedCodeCaveMemoryAddress and InjectedVariableMemoryAddress classes can only call the MemoryAddress.GetRealAddress() (ramvader.sourceforge.net) method if the Injector has already injected the variables/code caves into the target process' memory space, or during the injection process.
The following are examples of usage of reading and writing methods. For the sake of simplicity and code readability, we are not checking the bool values returned by these methods (we strongly recommend you to test them to see if the read/write operation has failed!). Also, consider the variables named myTarget as instances of the RAMvaderTarget class, which are already attached to a target Process:
// Read a Single (a.k.a. "float") value from address 0xAABBCCDD
// and store it in the variable "myVar".
MemoryAddress targetAddr = new AbsoluteMemoryAddress( 0xAABBCCDD );
Single myVar; // this variable will store the read value
myTarget.ReadFromTarget( targetAddr, ref myVar );
// Write an unsigned 64-bits integer with value 123 to 0xBABACA0
UInt64 dataToWrite = 123; // stores the data that will be written
// to the target Process' memory
IntPtr targetAddr = new IntPtr( new AbsoluteMemoryAddress( 0xBABACA0 ) );
myTarget.WriteToTarget( targetAddr, dataToWrite );
// Read an 32-bit integer from the address 0xF000BA22
// Then, multiply it by two, and store it back into this same address
MemoryAddress targetAddr = new AbsoluteMemoryAddress( 0xF000BA22 );
Int32 myVar;
myTarget.ReadFromTarget( targetAddr, ref myVar );
myVar *= 2;
myTarget.WriteToTarget( targetAddr, myVar );
// Read a sequence of 7 bytes of memory from 0x71727374
byte [] myBuffer = new byte[7];
myTarget.ReadFromTarget( new AbsoluteMemoryAddress( 0x71727374 ), myBuffer );
// Write 3 bytes of memory to 0x80808080
byte [] myBuffer = new byte[] { 10, 20, 30 };
myTarget.WriteToTarget( new AbsoluteMemoryAddress( 0x80808080 ), myBuffer );
// Write an integer to the address with an offset of +0xABC
// from an imaginary game's main module
int myIntVar = 123;
myTarget.WriteToTarget( new ModuleOffsetMemoryAddress( myTarget, "CoolImaginaryMainModule.exe", 0xABC ), myBuffer );