HANDLE proc = OpenProcess(PROCESS_VM_READ, FALSE, );
ReadProcessMemory(proc, , buffer, 4096, &bytesRead);
I am putting aside many things here, for example, error checking to determine if you can really open the target process with the VM_READ permission, but it really is that simple. Virtual memory does not mean a single process I can not Accessing the memory of another, only means that the address space of each process is (by default) own, and if you want to access that of another person, you must request it.
Windows, by default, allows all processes that run as the same user and with the same level of integrity to obtain read access to the memory of others (processes of greater integrity can also read those of lesser integrity, although not the other way around). A process can change its own ACL or that of any other ACL that runs with the same or strictly minor privileges; This can be used to grant a process that runs under a different user access, if for any reason you wish to do so. Administrators (and anyone else with SeDebugPrivilege) can also access the memory of any another process (with the exception of some that have special "don't purge me!" indicators); That is why a debugger released as an administrator can debug (almost) anything.
Note that this code example also avoids two important steps: get the PID of the other process and get the address from which you want to read. The PID is quite easy (they are not especially secret, and anyone can easily list the processes that run as the same user), but obtaining the base address is more complicated. You can do, using debug symbols or simply XORing an expected base address with the current ASLR mask of the operating system (which is the same for all processes running on the machine, although it changes with each restart), but you must put something effort on it. If you request an address that is not assigned in the destination process, the
ReadProcessMemory The call will simply fail.
As a side note, APIs are Windows specific, but the concept is not. Each operating system that supports debugging has a way of reading the memory of another process. This includes Linux, MacOS, etc. Sometimes, the default permissions on a memory access between processes are different, but it is always possible for a sufficiently privileged process.