How to Read and Write Other Process Memory
I recently put together a little game memory cheat tool called MemDig. It can find the address of a particular game value (score, lives, gold, etc.) after being given that value at different points in time. With the address, it can then modify that value to whatever is desired.
I’ve been using tools like this going back 20 years, but I never tried to write one myself until now. There are many memory cheat tools to pick from these days, the most prominent being Cheat Engine. These tools use the platform’s debugging API, so of course any good debugger could do the same thing, though a debugger won’t be specialized appropriately (e.g. locating the particular address and locking its value).
My motivation was bypassing an in-app purchase in a single player Windows game. I wanted to convince the game I had made the purchase when, in fact, I hadn’t. Once I had it working successfully, I ported MemDig to Linux since I thought it would be interesting to compare. I’ll start with Windows for this article.
Only three Win32 functions are needed, and you could almost guess at how it works.
It’s very straightforward
and, for this purpose, is probably the
simplest API for any platform (see update).
As you probably guessed, you first need to open the process, given its
process ID (integer). You’ll need to select the desired access bit a
bit set. To read memory, you need the
PROCESS_QUERY_INFORMATION rights. To write memory, you need the
PROCESS_VM_OPERATION rights. Alternatively
you could just ask for all rights with
PROCESS_ALL_ACCESS, but I
prefer to be precise.
DWORD access = PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_VM_WRITE | PROCESS_VM_OPERATION; HANDLE proc = OpenProcess(access, FALSE, pid);
And then to read or write:
void *addr; // target process address SIZE_T written; ReadProcessMemory(proc, addr, &value, sizeof(value), &written); // or WriteProcessMemory(proc, addr, &value, sizeof(value), &written);
Don’t forget to check the return value and verify
don’t forget to close it when you’re done.
That’s all there is to it. For the full cheat tool you’d need to find the mapped regions of memory, via VirtualQueryEx. It’s not as simple, but I’ll leave that for another article.
Unfortunately there’s no standard, cross-platform debugging API for unix-like systems. Most have a ptrace() system call, though each works a little differently. Note that ptrace() is not part of POSIX, but appeared in System V Release 4 (SVr4) and BSD, then copied elsewhere. The following will all be specific to Linux, though the procedure is similar on other unix-likes.
In typical Linux fashion, if it involves other processes, you use the standard file API on the /proc filesystem. Each process has a directory under /proc named as its process ID. In this directory is a virtual file called “mem”, which is a file view of that process’ entire address space, including unmapped regions.
char file; sprintf(file, "/proc/%ld/mem", (long)pid); int fd = open(file, O_RDWR);
The catch is that while you can open this file, you can’t actually
read or write on that file without attaching to the process as a
debugger. You’ll just get EIO errors. To attach, use ptrace() with
PTRACE_ATTACH. This asynchronously delivers a
SIGSTOP signal to
the target, which has to be waited on with waitpid().
You could select the target address with lseek(), but it’s cleaner and more efficient just to do it all in one system call with pread() and pwrite(). I’ve left out the error checking, but the return value of each function should be checked:
ptrace(PTRACE_ATTACH, pid, 0, 0); waitpid(pid, NULL, 0); off_t addr = ...; // target process address pread(fd, &value, sizeof(value), addr); // or pwrite(fd, &value, sizeof(value), addr); ptrace(PTRACE_DETACH, pid, 0, 0);
The process will (and must) be stopped during this procedure, so do your reads/writes quickly and get out. The kernel will deliver the writes to the other process’ virtual memory.
Like before, don’t forget to close.
To find the mapped regions in the real cheat tool, you would read and parse the virtual text file /proc/pid/maps. I don’t know if I’d call this stringly-typed method elegant — the kernel converts the data into string form and the caller immediately converts it right back — but that’s the official API.
Update: Konstantin Khlebnikov has pointed out the process_vm_readv() and process_vm_writev() system calls, available since Linux 3.2 (January 2012) and glibc 2.15 (March 2012). These system calls not require ptrace(), nor does the remote process need to be stopped. They’re equivalent to ReadProcessMemory() and WriteProcessMemory(), except there’s no requirement to first “open” the process.