Last active
August 28, 2024 23:31
-
-
Save jduncanator/ab17e4e476300d3eb0b7c19f6f38429a to your computer and use it in GitHub Desktop.
Execute a InterlockedCompareExchange128 natively from C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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