Recently, I wanted to port my .NET Framework Windows application to Linux. Obviously, since .NET Core is there, I decided to use it instead of Mono. It wasn’t that much of pain until I had to re-implement a class to manipulate the memory of a remote process. I did a quick research and at first it looked pretty complicated. Apparently, most of the guides I found on the internet were implementing memory manipulation using ptrace
. Luckily, since Linux 3.2 we can use process_vm_readv
and process_vm_writev
.
Implementation
Referring to the man page, you can see that first of all we have to implement the iovec
struct.
struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
The first field is a pointer. As a replacement for size_t
, I’ll be using int because that’s what Unsafe.SizeOf<T>()
returns. Here’s the final C# implementation:
[StructLayout(LayoutKind.Sequential)]
unsafe struct iovec
{
public void* iov_base;
public int iov_len;
}
Let’s implement process_vm_readv
now. The man page shows the following method signature:
ssize_t process_vm_readv(pid_t pid,
const struct iovec *local_iov,
unsigned long liovcnt,
const struct iovec *remote_iov,
unsigned long riovcnt,
unsigned long flags);
pid
is the Process ID accessible from the Id
property of the System.Diagnostics.Process
class. The next parameter is a pointer to the local iovec
struct holding result of the operation. liovcnt
is the count of local structs. This parameter is useful when we want to perform multiple memory operations, but my implementation doesn’t do that. The following param is again a pointer to the iovec
struct, but this time it’s the remote one that holds information about the memory fragment we’re trying to read. riovcnt
is the remote equivalent to liovcnt
. According to the man page, the flags argument is currently unused and does nothing. process_vm_readv
on success returns the number of bytes read.
Here’s the C# Pinvoke implementation:
[DllImport("libc")]
private static extern unsafe int process_vm_readv(int pid,
iovec* local_iov,
ulong liovcnt,
iovec* remote_iov,
ulong riovcnt,
ulong flags);
process_vm_writev
has exactly the same signature as process_vm_readv
. The big difference lies in what these two calls do. process_vm_readv
transfers the data from remote_iov
to local_iov
while process_vm_writev
does the opposite.
[DllImport("libc")]
private static extern unsafe int process_vm_readv(int pid,
iovec* local_iov,
ulong liovcnt,
iovec* remote_iov,
ulong riovcnt,
ulong flags);
Usage
Now as we got the API ready, let’s create some methods to access it.
Read:
public unsafe bool Read<T>(IntPtr address, out T value) where T : unmanaged
{
var size = Unsafe.SizeOf<T>();
var ptr = stackalloc byte[size];
var localIo = new iovec
{
iov_base = ptr,
iov_len = size
};
var remoteIo = new iovec
{
iov_base = address.ToPointer(),
iov_len = size
};
var res = process_vm_readv(_process.Id, &localIo, 1, &remoteIo, 1, 0);
value = *(T*)ptr;
return res != -1;
}
Write:
public unsafe bool Write<T>(T value, IntPtr address) where T : unmanaged
{
var ptr = &value;
var size = Unsafe.SizeOf<T>();
var localIo = new iovec
{
iov_base = ptr,
iov_len = size
};
var remoteIo = new iovec
{
iov_base = address.ToPointer(),
iov_len = size
};
var res = process_vm_writev(_process.Id, &localIo, 1, &remoteIo, 1, 0);
return res != -1;
}
Make sure to enable unsafe code for these examples to work.
Top comments (0)