Skip to content

Instantly share code, notes, and snippets.

@jduncanator
Last active August 28, 2024 23:31
Show Gist options
  • Save jduncanator/ab17e4e476300d3eb0b7c19f6f38429a to your computer and use it in GitHub Desktop.
Save jduncanator/ab17e4e476300d3eb0b7c19f6f38429a to your computer and use it in GitHub Desktop.
Execute a InterlockedCompareExchange128 natively from C#
using System;
using System.Runtime.InteropServices;
namespace NativeExec
{
class Program
{
delegate byte InterlockedCompareExchange128(IntPtr dest, long exchangeHigh, long exchangeLow, IntPtr comparand);
static unsafe void Main(string[] args)
{
var data = new byte[32];
data[0] = data[16] = 0x11;
data[1] = data[17] = 0x22;
data[2] = data[18] = 0x33;
data[3] = data[19] = 0x44;
// Uncomment me to make the exchange "fail"
// data[19] = 0x00;
// Compares the value in RDX:RAX with the provided pointer (R10).
// If the values are equal, RCX:RBX is copied to the pointer (R10).
// Otherwise, the value at the pointer is copied into RDX:RAX.
// RCX = dest RDX = exchangeHigh R8 = exchangeLow R9 = comparand
var asmCmpXchg16b = new byte[] {
0x48, 0x89, 0x5C, 0x24, 0x08, // MOV [RSP+0x8], RBX
0x49, 0x8B, 0x01, // MOV RAX, [R9]
0x49, 0x89, 0xCA, // MOV R10, RCX
0x48, 0x89, 0xD1, // MOV RCX, RDX
0x4C, 0x89, 0xC3, // MOV RBX, R8
0x49, 0x8B, 0x51, 0x08, // MOV RDX, [R9+0x8]
0xF0, 0x49, 0x0F, 0xC7, 0x0A, // LOCK CMPXCHG16B [R10]
0x48, 0x8B, 0x5C, 0x24, 0x08, // MOV RBX, [RSP+0x8]
0x49, 0x89, 0x01, // MOV [R9], RAX
0x0F, 0x94, 0xC0, // SETE AL
0x49, 0x89, 0x51, 0x08, // MOV [R9+0x8], RDX
0xC2, 0x00, 0x00 // RET 0
};
fixed (byte* dataPtr = &data[0])
fixed (byte* ptr = &asmCmpXchg16b[0])
{
var interlockedCompareExchange = CreateDelegate<InterlockedCompareExchange128>(ptr, asmCmpXchg16b.Length);
var returnValue = interlockedCompareExchange((IntPtr)dataPtr, 0xff, 0xee, (IntPtr)(dataPtr + 16));
Console.WriteLine("Return Value: {0}", returnValue);
Console.Read();
}
}
private unsafe static T CreateDelegate<T>(void* addr, int size)
{
return CreateDelegate<T>((IntPtr)addr, size);
}
private static T CreateDelegate<T>(IntPtr addr, int size)
{
EnsureMemoryIsExecutable(addr, size);
return Marshal.GetDelegateForFunctionPointer<T>(addr);
}
private static bool MemoryIsExecutable(IntPtr addr)
{
var memInfo = Native.VirtualQuery(addr);
return memInfo.Protect.HasFlag(Native.MemoryProtection.PAGE_EXECUTE) ||
memInfo.Protect.HasFlag(Native.MemoryProtection.PAGE_EXECUTE_READ) ||
memInfo.Protect.HasFlag(Native.MemoryProtection.PAGE_EXECUTE_READWRITE) ||
memInfo.Protect.HasFlag(Native.MemoryProtection.PAGE_EXECUTE_WRITECOPY);
}
private static void EnsureMemoryIsExecutable(IntPtr addr, int size)
{
if (!MemoryIsExecutable(addr))
{
if (!Native.VirtualProtect(addr, (uint)size, Native.MemoryProtection.PAGE_EXECUTE_READWRITE))
throw new InvalidOperationException("Unable to mark memory as executable");
}
}
}
internal static class Native
{
[Flags]
public enum MemoryProtection
{
PAGE_NOACCESS = 0x01,
PAGE_READONLY = 0x02,
PAGE_READWRITE = 0x04,
PAGE_WRITECOPY = 0x08,
PAGE_EXECUTE = 0x10,
PAGE_EXECUTE_READ = 0x20,
PAGE_EXECUTE_READWRITE = 0x40,
PAGE_EXECUTE_WRITECOPY = 0x80,
PAGE_GUARD = 0x100,
PAGE_NOCACHE = 0x200,
PAGE_WRITECOMBINE = 0x400
}
[Flags]
public enum MemoryState
{
MEM_COMMIT = 0x1000,
MEM_FREE = 0x10000,
MEM_RESERVE = 0x2000
}
[Flags]
public enum MemoryType
{
MEM_IMAGE = 0x1000000,
MEM_MAPPED = 0x40000,
MEM_PRIVATE = 0x20000
}
public class MemoryInformation
{
public IntPtr BaseAddress { get; set; }
public IntPtr AllocationBase { get; set; }
public MemoryProtection AllocationProtect { get; set; }
public MemoryState State { get; set; }
public MemoryProtection Protect { get; set; }
public MemoryType Type { get; set; }
}
public static MemoryInformation VirtualQuery(IntPtr p)
{
NativeImpl.MEMORY_BASIC_INFORMATION mbi;
if (NativeImpl.VirtualQuery(p, out mbi, Marshal.SizeOf<NativeImpl.MEMORY_BASIC_INFORMATION>()) != 0)
{
return new MemoryInformation
{
BaseAddress = mbi.BaseAddress,
AllocationBase = mbi.AllocationBase,
AllocationProtect = (MemoryProtection)mbi.Protect,
State = (MemoryState)mbi.State,
Protect = (MemoryProtection)mbi.Protect,
Type = (MemoryType)mbi.Type
};
}
else
{
return null;
}
}
public static bool VirtualProtect(IntPtr p, uint size, MemoryProtection flags)
{
return NativeImpl.VirtualProtect(p, size, (uint)flags, out _);
}
private static class NativeImpl
{
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public uint AllocationProtect;
public IntPtr RegionSize;
public uint State;
public uint Protect;
public uint Type;
}
[DllImport("kernel32.dll", EntryPoint = "VirtualQuery")]
public static extern int VirtualQuery(
IntPtr lpAddress,
out MEMORY_BASIC_INFORMATION lpBuffer,
int dwLength
);
[DllImport("kernel32.dll", EntryPoint = "VirtualProtect")]
public static extern bool VirtualProtect(
IntPtr lpAddress,
uint dwSize,
uint flNewProtect,
out uint lpflOldProtect
);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment