Linux memory manipulation using .NET Core
Tags: csharp, dotnet, linux
Published: 2020-08-06 22:30:56

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*.

```cpp
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:

```csharp
[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:

```cpp
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:

```csharp
[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.

```csharp
[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:

```csharp
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:

```csharp
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.