I'm trying to pinvoke a function that receives a couple of WCHAR <paramName>[1] parameters.
From what I've read in multiple places, in C/C++ you can't actually pass arrays to functions, instead they get converted to a pointer holding the first element of the array, which means that the array length becomes irrelevant and therefore WCHAR <paramName>[1] is the same as WCHAR* <paramName>.
So normally I'd declare this as a StringBuilder in C# and marshal it as LPWStr, but in this particular case that throws all sorts of errors.
Basically, the function above receives a GET_VIRTUAL_DISK_INFO, which in turn holds a union of some structs and other loose fields. And it is a couple of the structs in the union that receive a WCHAR <paramName>[1].
When I try to marshal it as LPWStr and declare my field as StringBuilder or string I get the error [...] contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.
I've also tried declaring it as an IntPtr and then using Marshal.PtrToStringAuto(<IntPtr>) but I get an empty string.
My C# code with all the structs, enums etc:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Permissions;
namespace ConsoleApplication1
{
class Program
{
public static Guid VirtualStorageTypeVendorMicrosoft = new Guid("EC984AEC-A0F9-47e9-901F-71415A66345B");
static void Main(string[] args)
{
var handle = new VirtualDiskSafeHandle();
var storageType = new VirtualStorageType
{
DeviceId = VirtualStorageDeviceType.Vhdx,
VendorId = VirtualStorageTypeVendorMicrosoft
};
var parameters = new OpenVirtualDiskParameters
{
Version = OpenVirtualDiskVersion.Version2
};
var result = OpenVirtualDisk(ref storageType, "D:\\Test2.vhdx", VirtualDiskAccessMask.None, OpenVirtualDiskFlag.None,
ref parameters, ref handle);
if (result != 0)
{
throw new Win32Exception((int) result);
}
var info = new GetVirtualDiskInfo {Version = GetVirtualDiskInfoVersion.PhysicalDisk};
var infoSize = (uint) Marshal.SizeOf(info);
uint sizeUsed = 0;
result = GetVirtualDiskInformation(handle, ref infoSize, ref info, ref sizeUsed);
if (result != 0)
{
throw new Win32Exception((int) result);
}
Console.WriteLine($"LogicalSizeSector = {info.Union.PhysicalDisk.LogicalSectorSize}");
Console.WriteLine($"PhysicalSizeSector = {info.Union.PhysicalDisk.PhysicalSectorSize}");
Console.ReadLine();
}
[DllImport("virtdisk.dll", CharSet = CharSet.Unicode)]
public static extern uint OpenVirtualDisk(
[In] ref VirtualStorageType virtualStorageType,
[In] string path,
[In] VirtualDiskAccessMask virtualDiskAccessMask,
[In] OpenVirtualDiskFlag flags,
[In] ref OpenVirtualDiskParameters parameters,
[In, Out] ref VirtualDiskSafeHandle handle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(
[In] IntPtr hObject);
[DllImport("virtdisk.dll", CharSet = CharSet.Unicode)]
public static extern uint GetVirtualDiskInformation(
[In] VirtualDiskSafeHandle virtualDiskHandle,
[In, Out] ref uint virtualDiskInfoSize,
[In, Out] ref GetVirtualDiskInfo virtualDiskInfo,
[In, Out] ref uint sizeUsed);
[SecurityPermission(SecurityAction.Demand)]
public class VirtualDiskSafeHandle : SafeHandle
{
public VirtualDiskSafeHandle() : base(IntPtr.Zero, true) { }
public override bool IsInvalid => IsClosed || (handle == IntPtr.Zero);
public bool IsOpen => !IsInvalid;
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
public override string ToString()
{
return handle.ToString();
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OpenVirtualDiskParameters
{
public OpenVirtualDiskVersion Version; //OPEN_VIRTUAL_DISK_VERSION
public OpenVirtualDiskParametersUnion Union;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct OpenVirtualDiskParametersUnion
{
[FieldOffset(0)]
public OpenVirtualDiskParametersVersion1 Version1;
[FieldOffset(0)]
public OpenVirtualDiskParametersVersion2 Version2;
[FieldOffset(0)]
public OpenVirtualDiskParametersVersion3 Version3;
}
/// <summary>
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OpenVirtualDiskParametersVersion1
{
public uint RWDepth; //ULONG
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OpenVirtualDiskParametersVersion2
{
public bool GetInfoOnly; //BOOL
public bool ReadOnly; //BOOL
public Guid ResiliencyGuid; //GUID
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OpenVirtualDiskParametersVersion3
{
public bool GetInfoOnly; //BOOL
public bool ReadOnly; //BOOL
public Guid ResiliencyGuid; //GUID
public Guid SnapshotId; //GUID
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfo
{
public GetVirtualDiskInfoVersion Version; //GET_VIRTUAL_DISK_INFO_VERSION
public GetVirtualDiskInfoUnion Union;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfoUnion
{
[FieldOffset(0)] public GetVirtualDiskInfoSize Size;
[FieldOffset(0)] public Guid Identifier; //GUID
[FieldOffset(0)] public GetVirtualDiskInfoParentLocation ParentLocation;
[FieldOffset(0)] public Guid ParentIdentifier; //GUID
[FieldOffset(0)] public uint ParentTimestamp; //ULONG
[FieldOffset(0)] public VirtualStorageType VirtualStorageType; //VIRTUAL_STORAGE_TYPE
[FieldOffset(0)] public uint ProviderSubtype; //ULONG
[FieldOffset(0)] public bool Is4kAligned; //BOOL
[FieldOffset(0)] public bool IsLoaded; //BOOL
[FieldOffset(0)] public GetVirtualDiskInfoPhysicalDisk PhysicalDisk;
[FieldOffset(0)] public uint VhdPhysicalSectorSize; //ULONG
[FieldOffset(0)] public ulong SmallestSafeVirtualSize; //ULONGLONG
[FieldOffset(0)] public uint FragmentationPercentage; //ULONG
[FieldOffset(0)] public Guid VirtualDiskId; //GUID
[FieldOffset(0)] public GetVirtualDiskInfoChangeTrackingState ChangeTrackingState;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfoSize
{
public ulong VirtualSize; //ULONGLONG
public ulong PhysicalSize; //ULONGLONG
public uint BlockSize; //ULONG
public uint SectorSize; //ULONG
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfoParentLocation
{
public bool ParentResolved; //BOOL
public IntPtr ParentLocationBuffer; //WCHAR[1]
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfoPhysicalDisk
{
public uint LogicalSectorSize; //ULONG
public uint PhysicalSectorSize; //ULONG
public bool IsRemote; //BOOL
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct GetVirtualDiskInfoChangeTrackingState
{
public bool Enabled; //BOOL
public bool NewerChanges; //BOOL
public IntPtr MostRecentId; //WCHAR[1]
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct VirtualStorageType
{
public VirtualStorageDeviceType DeviceId; //ULONG
public Guid VendorId; //GUID
}
public enum GetVirtualDiskInfoVersion
{
Unspecified = 0,
Size = 1,
Identifier = 2,
ParentLocation = 3,
ParentIdentifier = 4,
ParentTimestamp = 5,
VirtualStorageType = 6,
ProviderSubtype = 7,
Is4KAligned = 8,
PhysicalDisk = 9,
VhdPhysicalSectorSize = 10,
SmallestSafeVirtualSize = 11,
Fragmentation = 12,
IsLoaded = 13,
VirtualDiskId = 14,
ChangeTrackingState = 15
}
public enum VirtualStorageDeviceType
{
Unknown = 0,
Iso = 1,
Vhd = 2,
Vhdx = 3,
Vhdset = 4
}
public enum VirtualDiskAccessMask
{
None = 0x00000000,
AttachRo = 0x00010000,
AttachRw = 0x00020000,
Detach = 0x00040000,
GetInfo = 0x00080000,
Create = 0x00100000,
Metaops = 0x00200000,
Read = 0x000d0000,
All = 0x003f0000,
Writable = 0x00320000
}
[Flags]
public enum OpenVirtualDiskFlag
{
None = 0x00000000,
NoParents = 0x00000001,
BlankFile = 0x00000002,
BootDrive = 0x00000004,
CachedIo = 0x00000008,
CustomDiffChain = 0x00000010,
ParentCachedIo = 0x00000020,
VhdsetFileOnly = 0x00000040
}
public enum OpenVirtualDiskVersion
{
Unspecified = 0,
Version1 = 1,
Version2 = 2,
Version3 = 3,
}
}
}
Please comment out the line:
[FieldOffset(0)] public GetVirtualDiskInfoChangeTrackingState ChangeTrackingState;
Without that the struct results will be completely screwed up, still trying to figure out why.
Marshal the struct from the function call as an IntPtr. You will need to use Marshal.AllocHGlobal or another similar technique to get a block of unmanaged memory, since the Marshal isn't going to do it for you. You can then load the size member manually, or using Marshal.StructureToPtr.
From there use Marshal.OffsetOf to get the offset to the Union member. Once that's done use Marshal.Read and Marshal.PtrToStringUni to get at the data. For example with the Parent Location information:
IntPtr raw = Marshal.AllocHGlobal(1024);
// This is the GetVirtualDiskInfo from your provided code.
GetVirtualDiskInfo info = new GetVirtualDiskInfo();
info.Version = GetVirtualDiskInfoVersion.ParentLocation;
Marshal.StructureToPtr(info, raw, true);
Class1.Test(raw); // Replace this with your call to the function,
// This is a call to a C++/CLI method I wrote to stuff data
// into the structure.
IntPtr offsetToUnion = Marshal.OffsetOf(typeof(GetVirtualDiskInfo), "Union");
IntPtr data = raw + offsetToUnion.ToInt32();
bool parentResolved = Marshal.ReadInt32(data) != 0;
string parentLocationBuffer = Marshal.PtrToStringUni(data + 4);
Marshal.FreeHGlobal(raw); // Don't forget this!
Here's the method in the C++/CLI that loads the data for testing:
static void Test(IntPtr ptr)
{
GET_VIRTUAL_DISK_INFO* info = (GET_VIRTUAL_DISK_INFO*)ptr.ToPointer();
info->ParentLocation.ParentResolved = TRUE;
memcpy(info->ParentLocation.ParentLocationBuffer, L"123456789", 20);
}
I'm calling the Win32 function EnumJobs (http://msdn.microsoft.com/en-us/library/windows/desktop/dd162625(v=vs.85).aspx) from managed code (C#).
[DllImport("Winspool.drv", SetLastError = true, EntryPoint = "EnumJobsA")]
public static extern bool EnumJobs(
IntPtr hPrinter, // handle to printer object
UInt32 FirstJob, // index of first job
UInt32 NoJobs, // number of jobs to enumerate
UInt32 Level, // information level
IntPtr pJob, // job information buffer
UInt32 cbBuf, // size of job information buffer
out UInt32 pcbNeeded, // bytes received or required
out UInt32 pcReturned // number of jobs received
);
EnumJobs(_printerHandle, 0, 99, 1, IntPtr.Zero, 0, out nBytesNeeded, out pcReturned);
I'm specifying Level 1 to receive a JOB_INFO_1 but the problem I'm having is the above function is returning nBytesNeeded as 240 per struct while the Marshal.SizeOf(typeof(JOB_INFO_1)) is 64 bytes causing a memory exception when I run Marshal.PtrToStructure. Counting the bytes manually for the struct gives 64 so I'm at a bit of a loss as to why I'm receiving the 240 byte structures from function, any insight would be appreciated.
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet=CharSet.Unicode)]
public struct JOB_INFO_1
{
public UInt32 JobId;
public string pPrinterName;
public string pMachineName;
public string pUserName;
public string pDocument;
public string pDatatype;
public string pStatus;
public UInt32 Status;
public UInt32 Priority;
public UInt32 Position;
public UInt32 TotalPages;
public UInt32 PagesPrinted;
public SYSTEMTIME Submitted;
}
The size of 64 is indeed correct for JOB_INFO_1 but if you look closely at the documentation, it talks about an array of structs :
pJob [out]
A pointer to a buffer that receives an array of JOB_INFO_1, JOB_INFO_2, or JOB_INFO_3 structures.
Additionally it is written :
The buffer must be large enough to receive the array of structures and any strings or other data to which the structure members point.
So there are bytes here for extra data beside the structs themselves.
Solution:
Populate the structs, increment pointer for next struct and ignore the remaining bytes.
Complete example:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
namespace WpfApplication3
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// Get handle to a printer
var hPrinter = new IntPtr();
bool open = NativeMethods.OpenPrinterW("Microsoft XPS Document Writer", ref hPrinter, IntPtr.Zero);
Debug.Assert(open);
/* Query for 99 jobs */
const uint firstJob = 0u;
const uint noJobs = 99u;
const uint level = 1u;
// Get byte size required for the function
uint needed;
uint returned;
bool b1 = NativeMethods.EnumJobsW(
hPrinter, firstJob, noJobs, level, IntPtr.Zero, 0, out needed, out returned);
Debug.Assert(!b1);
uint lastError = NativeMethods.GetLastError();
Debug.Assert(lastError == NativeConstants.ERROR_INSUFFICIENT_BUFFER);
// Populate the structs
IntPtr pJob = Marshal.AllocHGlobal((int) needed);
uint bytesCopied;
uint structsCopied;
bool b2 = NativeMethods.EnumJobsW(
hPrinter, firstJob, noJobs, level, pJob, needed, out bytesCopied, out structsCopied);
Debug.Assert(b2);
var jobInfos = new JOB_INFO_1W[structsCopied];
int sizeOf = Marshal.SizeOf(typeof (JOB_INFO_1W));
IntPtr pStruct = pJob;
for (int i = 0; i < structsCopied; i++)
{
var jobInfo_1W = (JOB_INFO_1W) Marshal.PtrToStructure(pStruct, typeof (JOB_INFO_1W));
jobInfos[i] = jobInfo_1W;
pStruct += sizeOf;
}
Marshal.FreeHGlobal(pJob);
// do something with your structs
}
}
public class NativeConstants
{
public const int ERROR_INSUFFICIENT_BUFFER = 122;
}
public partial class NativeMethods
{
[DllImport("kernel32.dll", EntryPoint = "GetLastError")]
public static extern uint GetLastError();
}
public partial class NativeMethods
{
[DllImport("Winspool.drv", EntryPoint = "OpenPrinterW")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenPrinterW([In] [MarshalAs(UnmanagedType.LPWStr)] string pPrinterName,
ref IntPtr phPrinter, [In] IntPtr pDefault);
[DllImport("Winspool.drv", EntryPoint = "EnumJobsW")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumJobsW([In] IntPtr hPrinter, uint FirstJob, uint NoJobs, uint Level, IntPtr pJob,
uint cbBuf, [Out] out uint pcbNeeded, [Out] out uint pcReturned);
}
[StructLayout(LayoutKind.Sequential)]
public struct JOB_INFO_1W
{
public uint JobId;
[MarshalAs(UnmanagedType.LPWStr)] public string pPrinterName;
[MarshalAs(UnmanagedType.LPWStr)] public string pMachineName;
[MarshalAs(UnmanagedType.LPWStr)] public string pUserName;
[MarshalAs(UnmanagedType.LPWStr)] public string pDocument;
[MarshalAs(UnmanagedType.LPWStr)] public string pDatatype;
[MarshalAs(UnmanagedType.LPWStr)] public string pStatus;
public uint Status;
public uint Priority;
public uint Position;
public uint TotalPages;
public uint PagesPrinted;
public SYSTEMTIME Submitted;
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEMTIME
{
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
}
Result:
Job 1:
Job 2 :
EDIT
It seems that you will have to get a little more robust checking than I did, because EnumJobs seems to return true when there are no jobs in queue. In the case of my example the assertion will fail but that won't mean that the code is wrong; just make sure that you have some jobs in the queue for the purpose of testing the function.
I had made the dll injector easy before, but i had Windows 7, I made it in C# and C++, it worked great! but now when i try the same codes in Windows 8, it seems that it doesn't inject the DLL in the right way! :) as the DLL is not working...
(The code which i'm trying is the public one <)
VB.Net Code:
Private TargetProcessHandle As Integer
Private pfnStartAddr As Integer
Private pszLibFileRemote As String
Private TargetBufferSize As Integer
Public Const PROCESS_VM_READ = &H10
Public Const TH32CS_SNAPPROCESS = &H2
Public Const MEM_COMMIT = 4096
Public Const PAGE_READWRITE = 4
Public Const PROCESS_CREATE_THREAD = (&H2)
Public Const PROCESS_VM_OPERATION = (&H8)
Public Const PROCESS_VM_WRITE = (&H20)
Dim DLLFileName As String
Public Declare Function ReadProcessMemory Lib "kernel32" ( _
ByVal hProcess As Integer, _
ByVal lpBaseAddress As Integer, _
ByVal lpBuffer As String, _
ByVal nSize As Integer, _
ByRef lpNumberOfBytesWritten As Integer) As Integer
Public Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" ( _
ByVal lpLibFileName As String) As Integer
Public Declare Function VirtualAllocEx Lib "kernel32" ( _
ByVal hProcess As Integer, _
ByVal lpAddress As Integer, _
ByVal dwSize As Integer, _
ByVal flAllocationType As Integer, _
ByVal flProtect As Integer) As Integer
Public Declare Function WriteProcessMemory Lib "kernel32" ( _
ByVal hProcess As Integer, _
ByVal lpBaseAddress As Integer, _
ByVal lpBuffer As String, _
ByVal nSize As Integer, _
ByRef lpNumberOfBytesWritten As Integer) As Integer
Public Declare Function GetProcAddress Lib "kernel32" ( _
ByVal hModule As Integer, ByVal lpProcName As String) As Integer
Private Declare Function GetModuleHandle Lib "Kernel32" Alias "GetModuleHandleA" ( _
ByVal lpModuleName As String) As Integer
Public Declare Function CreateRemoteThread Lib "kernel32" ( _
ByVal hProcess As Integer, _
ByVal lpThreadAttributes As Integer, _
ByVal dwStackSize As Integer, _
ByVal lpStartAddress As Integer, _
ByVal lpParameter As Integer, _
ByVal dwCreationFlags As Integer, _
ByRef lpThreadId As Integer) As Integer
Public Declare Function OpenProcess Lib "kernel32" ( _
ByVal dwDesiredAccess As Integer, _
ByVal bInheritHandle As Integer, _
ByVal dwProcessId As Integer) As Integer
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Integer
Private Declare Function CloseHandle Lib "kernel32" Alias "CloseHandle" ( _
ByVal hObject As Integer) As Integer
Dim ExeName As String = IO.Path.GetFileNameWithoutExtension(Application.ExecutablePath)
Private Sub Inject()
Try
Timer1.Stop()
Dim TargetProcess As Process() = Process.GetProcessesByName(TextBox1.Text)
TargetProcessHandle = OpenProcess(PROCESS_CREATE_THREAD Or PROCESS_VM_OPERATION Or PROCESS_VM_WRITE, False, TargetProcess(0).Id)
pszLibFileRemote = OpenFileDialog1.FileName
pfnStartAddr = GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA")
TargetBufferSize = 1 + Len(pszLibFileRemote)
Dim Rtn As Integer
Dim LoadLibParamAdr As Integer
LoadLibParamAdr = VirtualAllocEx(TargetProcessHandle, 0, TargetBufferSize, MEM_COMMIT, PAGE_READWRITE)
Rtn = WriteProcessMemory(TargetProcessHandle, LoadLibParamAdr, pszLibFileRemote, TargetBufferSize, 0)
CreateRemoteThread(TargetProcessHandle, 0, 0, pfnStartAddr, LoadLibParamAdr, 0, 0)
CloseHandle(TargetProcessHandle)
Catch ex As Exception
MessageBox.Show("EX:" + ex.ToString)
End Try
End Sub
It was giving an error "System.EntryPointNotFoundException....." but after i changed this:
CloseHandle Lib "kernel32" Alias "CloseHandleA"
TO this:
CloseHandle Lib "kernel32" Alias "CloseHandle"
it doesn't show the error again, but it also doesn't inject it in right way!
For C# :
[DllImport("kernel32")]
public static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
UIntPtr lpStartAddress, // raw Pointer into remote process
IntPtr lpParameter,
uint dwCreationFlags,
out IntPtr lpThreadId
);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(
UInt32 dwDesiredAccess,
Int32 bInheritHandle,
Int32 dwProcessId
);
[DllImport("kernel32.dll")]
public static extern Int32 CloseHandle(
IntPtr hObject
);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool VirtualFreeEx(
IntPtr hProcess,
IntPtr lpAddress,
UIntPtr dwSize,
uint dwFreeType
);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
public static extern UIntPtr GetProcAddress(
IntPtr hModule,
string procName
);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
uint dwSize,
uint flAllocationType,
uint flProtect
);
[DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
string lpBuffer,
UIntPtr nSize,
out IntPtr lpNumberOfBytesWritten
);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(
string lpModuleName
);
[DllImport("kernel32", SetLastError = true, ExactSpelling = true)]
internal static extern Int32 WaitForSingleObject(
IntPtr handle,
Int32 milliseconds
);
public Int32 GetProcessId(String proc)
{
Process[] ProcList;
ProcList = Process.GetProcessesByName(proc);
return ProcList[0].Id;
}
public void InjectDLL(IntPtr hProcess, String strDLLName)
{
IntPtr bytesout;
// Length of string containing the DLL file name +1 byte padding
Int32 LenWrite = strDLLName.Length + 1;
// Allocate memory within the virtual address space of the target process
IntPtr AllocMem = (IntPtr)VirtualAllocEx(hProcess, (IntPtr)null, (uint)LenWrite, 0x1000, 0x40); //allocation pour WriteProcessMemory
// Write DLL file name to allocated memory in target process
WriteProcessMemory(hProcess, AllocMem, strDLLName, (UIntPtr)LenWrite, out bytesout);
// Function pointer "Injector"
UIntPtr Injector = (UIntPtr)GetProcAddress( GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (Injector == null)
{
MessageBox.Show(" Injector Error! \n ");
// return failed
return;
}
// Create thread in target process, and store handle in hThread
IntPtr hThread = (IntPtr)CreateRemoteThread(hProcess, (IntPtr)null, 0, Injector, AllocMem, 0, out bytesout);
// Make sure thread handle is valid
if ( hThread == null )
{
//incorrect thread handle ... return failed
MessageBox.Show(" hThread [ 1 ] Error! \n ");
return;
}
// Time-out is 10 seconds...
int Result = WaitForSingleObject(hThread, 10 * 1000);
// Check whether thread timed out...
if (Result == 0x00000080L || Result == 0x00000102L || Result == 0xFFFFFFFF)
{
/* Thread timed out... */
MessageBox.Show(" hThread [ 2 ] Error! \n ");
// Make sure thread handle is valid before closing... prevents crashes.
if (hThread != null)
{
//Close thread in target process
CloseHandle(hThread);
}
return;
}
// Sleep thread for 1 second
Thread.Sleep(1000);
// Clear up allocated space ( Allocmem )
VirtualFreeEx(hProcess, AllocMem, (UIntPtr)0, 0x8000);
// Make sure thread handle is valid before closing... prevents crashes.
if (hThread != null)
{
//Close thread in target process
CloseHandle(hThread);
}
// return succeeded
return;
}
private void button1_Click(object sender, EventArgs e)
{
String strDLLName = "C:\\test.dll";
String strProcessName = "notepad";
Int32 ProcID = GetProcessId(strProcessName);
if (ProcID >= 0)
{
IntPtr hProcess = (IntPtr)OpenProcess(0x1F0FFF, 1,ProcID);
if (hProcess == null)
{
MessageBox.Show("OpenProcess() Failed!");
return;
}
else
InjectDLL(hProcess, strDLLName);
}
}
It doesn't give me errors, but it also doesn't inject in right way!
Thanks very much! :)
What happens?
If the remote process crashes then it is likely that You should not use GetProcAddress since the remote API address may be different. It can be different for example when the injecting process is hooked using EAT hooking and the remote one isn't, so it does not have anything meaningful at the address You got from local GetProcAddress. It will be also different even when both processes are hooked using EAT hooking, since the hook implementations cause more random or entirely random locations.
I had similar sitation where the crashes occurred under Windows 8, but not in earlier versions.
One solution would be reading EAT table of the remote process and getting the address from there.
Related code follows
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Reflection;
using System.IO;
using System.Threading;
using Microsoft.Win32.SafeHandles;
using System.Runtime.CompilerServices;
using System.Security;
namespace Injection
{
public static class Remote_EAT_Reader
{
public static IntPtr? GetProcessModuleHandle(int processID, string moduleName)
{
IntPtr snapshot = IntPtr.Zero;
try
{
//http://pastebin.com/BzD1jdmH
snapshot = CreateToolhelp32Snapshot(SnapshotFlags.Module | SnapshotFlags.Module32, processID);
MODULEENTRY32 mod = new MODULEENTRY32() { dwSize = MODULEENTRY32.SizeOf };
if (!Module32First(snapshot, ref mod))
return null;
string searchString = moduleName.ToLowerInvariant();
do
{
if (mod.szModule.ToLowerInvariant() == searchString)
return mod.modBaseAddr;
}
while (Module32Next(snapshot, ref mod));
return IntPtr.Zero;
}
finally
{
if (snapshot != IntPtr.Zero)
CloseHandle(snapshot);
}
}
public static IntPtr? GetProcessProcAddress(IntPtr hProcess, int processID, string moduleName, string procName)
{
IntPtr? moduleHandle = GetProcessModuleHandle(processID, moduleName);
if (!moduleHandle.HasValue)
return null;
//code adapted from http://alter.org.ua/en/docs/nt_kernel/procaddr/index3.php
UIntPtr hmodCaller = new UIntPtr(unchecked((ulong)moduleHandle.Value.ToInt64()));
//http://stackoverflow.com/questions/769537/hook-loadlibrary-call-from-managed-code
//parse dos header
UIntPtr dos_header_ptr = hmodCaller;
if (dos_header_ptr == UIntPtr.Zero)
return null;
IMAGE_DOS_HEADER dos_header;
if (!ReadProcessMemory(hProcess, dos_header_ptr, out dos_header, IMAGE_DOS_HEADER.SizeOf, IntPtr.Zero))
return null;
if (dos_header.e_magic != IMAGE_DOS_SIGNATURE)
return null; //not a dos program
IMAGE_DATA_DIRECTORY[] DataDirectory;
if (IntPtr.Size == 4)
{
//parse nt header
UIntPtr nt_header_ptr = new UIntPtr((uint)dos_header.e_lfanew + hmodCaller.ToUInt32());
if (nt_header_ptr == UIntPtr.Zero)
return null;
IMAGE_NT_HEADERS32 nt_header;
if (!ReadProcessMemory(hProcess, nt_header_ptr, out nt_header, IMAGE_NT_HEADERS32.SizeOf, IntPtr.Zero))
return null;
if (nt_header.Signature != IMAGE_NT_SIGNATURE)
return null; //not a windows program
//http://newgre.net/ncodehook
//if (ntHeaders.FileHeader.Characteristics & IMAGE_FILE_DLL)
// throw std::runtime_error("Error while setting image base address: not the image base of an executable");
//optional header (pretty much not optional)
IMAGE_OPTIONAL_HEADER32 optional_header = nt_header.OptionalHeader32;
if (optional_header.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
return null; //no optional header
DataDirectory = optional_header.DataDirectory;
}
else //if (IntPtr.Size == 4)
{
//parse nt header
UIntPtr nt_header_ptr = new UIntPtr((uint)dos_header.e_lfanew + hmodCaller.ToUInt64());
if (nt_header_ptr == UIntPtr.Zero)
return null;
IMAGE_NT_HEADERS64 nt_header;
if (!ReadProcessMemory(hProcess, nt_header_ptr, out nt_header, IMAGE_NT_HEADERS64.SizeOf, IntPtr.Zero))
return null;
if (nt_header.Signature != IMAGE_NT_SIGNATURE)
return null; //not a windows program
//http://newgre.net/ncodehook
//if (ntHeaders.FileHeader.Characteristics & IMAGE_FILE_DLL)
// throw std::runtime_error("Error while setting image base address: not the image base of an executable");
//optional header (pretty much not optional)
IMAGE_OPTIONAL_HEADER64 optional_header = nt_header.OptionalHeader64;
if (optional_header.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
return null; //no optional header
DataDirectory = optional_header.DataDirectory;
} //if (IntPtr.Size == 4)
//http://stackoverflow.com/questions/3430718/thunk-table-in-import-address-table and http://forums.codeguru.com/showthread.php?512610-RESOLVED-api-hooking and especially http://svn.coderepos.org/share/lang/objective-cplusplus/i3/trunk/tmp/dwmedit/ApiHook.cc
DirectoryEntries entryIndex = DirectoryEntries.IMAGE_DIRECTORY_ENTRY_EXPORT;
uint size = DataDirectory[(int)entryIndex].Size;
if (size == 0)
return null; //no import table
uint virtualAddress = DataDirectory[(int)entryIndex].VirtualAddress;
//http://newgre.net/ncodehook
if (virtualAddress == 0)
return null; //no import directory
UIntPtr pExports_ptr = new UIntPtr((ulong)virtualAddress + hmodCaller.ToUInt64());
if (pExports_ptr == UIntPtr.Zero)
return null;
IMAGE_EXPORT_DIRECTORY pExports;
if (!ReadProcessMemory(hProcess, pExports_ptr,
out pExports, IMAGE_EXPORT_DIRECTORY.SizeOf, IntPtr.Zero))
{
return null;
}
UIntPtr functions_ptr = new UIntPtr(hmodCaller.ToUInt64() + (ulong)pExports.AddressOfFunctions);
UIntPtr ordinals_ptr = new UIntPtr(hmodCaller.ToUInt64() + (ulong)pExports.AddressOfNameOrdinals);
UIntPtr names_ptr = new UIntPtr(hmodCaller.ToUInt64() + (ulong)pExports.AddressOfNames);
uint max_name = pExports.NumberOfNames;
uint max_func = pExports.NumberOfFunctions;
uint max_ordinal = max_name;
uint[] functions = new uint[max_func];
if (!ReadProcessMemory(hProcess, functions_ptr, functions, (int)max_func * sizeof(uint), IntPtr.Zero))
{
return null;
}
ushort[] ordinals = new ushort[max_ordinal];
if (!ReadProcessMemory(hProcess, ordinals_ptr, ordinals, (int)max_ordinal * sizeof(ushort), IntPtr.Zero))
{
return null;
}
uint[] names = new uint[max_name];
if (!ReadProcessMemory(hProcess, names_ptr, names, (int)max_name * sizeof(uint), IntPtr.Zero))
{
return null;
}
for (uint i = 0; i < max_ordinal; i++)
{
uint ord = ordinals[i];
if (i >= max_name || ord >= max_func)
{
return null;
}
if (functions[ord] < virtualAddress || functions[ord] >= virtualAddress + size)
{
UIntPtr name_ptr = new UIntPtr(hmodCaller.ToUInt64() + (ulong)names[i]);
if (name_ptr != UIntPtr.Zero)
{
byte[] name_buf = new byte[procName.Length + 1]; //NB! +1 for terminating zero
if (!ReadProcessMemory(hProcess, name_ptr, name_buf, name_buf.Length, IntPtr.Zero))
{
continue;
}
if (name_buf[name_buf.Length - 1] == 0) //check for partial name that does not end with terminating zero
{
string name = Encoding.ASCII.GetString(name_buf, 0, name_buf.Length - 1); //NB! buf length - 1
if (name == procName)
{
var pFunctionAddress1 = new UIntPtr(hmodCaller.ToUInt64() + (ulong)functions[ord]);
return new IntPtr(unchecked((long)pFunctionAddress1.ToUInt64()));
}
}
} //if (name_ptr != UIntPtr.Zero)
} //if (functions[ord] < virtualAddress || functions[ord] >= virtualAddress + size)
} //for (uint i = 0; i < pExports.AddressOfNames; i++)
return null;
} //static IntPtr? GetProcessProcAddress(int procID, string moduleName, string procName)
#region PE Structs
private const uint IMAGE_DOS_SIGNATURE = 0x5A4D; // MZ
private const uint IMAGE_OS2_SIGNATURE = 0x454E; // NE
private const uint IMAGE_OS2_SIGNATURE_LE = 0x454C; // LE
private const uint IMAGE_VXD_SIGNATURE = 0x454C; // LE
private const uint IMAGE_NT_SIGNATURE = 0x00004550; // PE00
private const uint IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b;
private const uint IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b;
private enum DirectoryEntries
{
IMAGE_DIRECTORY_ENTRY_EXPORT = 0, // Export Directory
IMAGE_DIRECTORY_ENTRY_IMPORT = 1, // Import Directory
IMAGE_DIRECTORY_ENTRY_RESOURCE = 2, // Resource Directory
IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3, // Exception Directory
IMAGE_DIRECTORY_ENTRY_SECURITY = 4, // Security Directory
IMAGE_DIRECTORY_ENTRY_BASERELOC = 5, // Base Relocation Table
IMAGE_DIRECTORY_ENTRY_DEBUG = 6, // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7, // (X86 usage)
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7, // Architecture Specific Data
IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8, // RVA of GP
IMAGE_DIRECTORY_ENTRY_TLS = 9, // TLS Directory
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10, // Load Configuration Directory
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11, // Bound Import Directory in headers
IMAGE_DIRECTORY_ENTRY_IAT = 12, // Import Address Table
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13, // Delay Load Import Descriptors
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14, // COM Runtime descriptor
} //private enum DirectoryEntries
//code taken from http://www.sergeyakopov.com/2010/11/reading-pe-format-using-data-marshaling-in-net
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_DOS_HEADER
{
public static int SizeOf = Marshal.SizeOf(typeof(IMAGE_DOS_HEADER));
public UInt16 e_magic;
public UInt16 e_cblp;
public UInt16 e_cp;
public UInt16 e_crlc;
public UInt16 e_cparhdr;
public UInt16 e_minalloc;
public UInt16 e_maxalloc;
public UInt16 e_ss;
public UInt16 e_sp;
public UInt16 e_csum;
public UInt16 e_ip;
public UInt16 e_cs;
public UInt16 e_lfarlc;
public UInt16 e_ovno;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public UInt16[] e_res1;
public UInt16 e_oemid;
public UInt16 e_oeminfo;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public UInt16[] e_res2;
public UInt32 e_lfanew;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_NT_HEADERS32
{
public static int SizeOf = Marshal.SizeOf(typeof(IMAGE_NT_HEADERS32));
public UInt32 Signature;
public IMAGE_FILE_HEADER FileHeader;
public IMAGE_OPTIONAL_HEADER32 OptionalHeader32;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_NT_HEADERS64
{
public static int SizeOf = Marshal.SizeOf(typeof(IMAGE_NT_HEADERS64));
public UInt32 Signature;
public IMAGE_FILE_HEADER FileHeader;
public IMAGE_OPTIONAL_HEADER64 OptionalHeader64;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_FILE_HEADER
{
public UInt16 Machine;
public UInt16 NumberOfSections;
public UInt32 TimeDateStamp;
public UInt32 PointerToSymbolTable;
public UInt32 NumberOfSymbols;
public UInt16 SizeOfOptionalHeader;
public UInt16 Characteristics;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_OPTIONAL_HEADER32
{
public UInt16 Magic;
public Byte MajorLinkerVersion;
public Byte MinorLinkerVersion;
public UInt32 SizeOfCode;
public UInt32 SizeOfInitializedData;
public UInt32 SizeOfUninitializedData;
public UInt32 AddressOfEntryPoint;
public UInt32 BaseOfCode;
public UInt32 BaseOfData;
public UInt32 ImageBase;
public UInt32 SectionAlignment;
public UInt32 FileAlignment;
public UInt16 MajorOperatingSystemVersion;
public UInt16 MinorOperatingSystemVersion;
public UInt16 MajorImageVersion;
public UInt16 MinorImageVersion;
public UInt16 MajorSubsystemVersion;
public UInt16 MinorSubsystemVersion;
public UInt32 Win32VersionValue;
public UInt32 SizeOfImage;
public UInt32 SizeOfHeaders;
public UInt32 CheckSum;
public UInt16 Subsystem;
public UInt16 DllCharacteristics;
public UInt32 SizeOfStackReserve;
public UInt32 SizeOfStackCommit;
public UInt32 SizeOfHeapReserve;
public UInt32 SizeOfHeapCommit;
public UInt32 LoaderFlags;
public UInt32 NumberOfRvaAndSizes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public IMAGE_DATA_DIRECTORY[] DataDirectory;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_OPTIONAL_HEADER64
{
public UInt16 Magic;
public Byte MajorLinkerVersion;
public Byte MinorLinkerVersion;
public UInt32 SizeOfCode;
public UInt32 SizeOfInitializedData;
public UInt32 SizeOfUninitializedData;
public UInt32 AddressOfEntryPoint;
public UInt32 BaseOfCode;
public UInt64 ImageBase;
public UInt32 SectionAlignment;
public UInt32 FileAlignment;
public UInt16 MajorOperatingSystemVersion;
public UInt16 MinorOperatingSystemVersion;
public UInt16 MajorImageVersion;
public UInt16 MinorImageVersion;
public UInt16 MajorSubsystemVersion;
public UInt16 MinorSubsystemVersion;
public UInt32 Win32VersionValue;
public UInt32 SizeOfImage;
public UInt32 SizeOfHeaders;
public UInt32 CheckSum;
public UInt16 Subsystem;
public UInt16 DllCharacteristics;
public UInt64 SizeOfStackReserve;
public UInt64 SizeOfStackCommit;
public UInt64 SizeOfHeapReserve;
public UInt64 SizeOfHeapCommit;
public UInt32 LoaderFlags;
public UInt32 NumberOfRvaAndSizes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public IMAGE_DATA_DIRECTORY[] DataDirectory;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_DATA_DIRECTORY
{
public UInt32 VirtualAddress;
public UInt32 Size;
}
[StructLayout(LayoutKind.Sequential)]
public struct ImgDelayDescr
{
public static uint SizeOf = (uint)Marshal.SizeOf(typeof(ImgDelayDescr));
public uint grAttrs; // attributes
public uint rvaDLLName; // RVA to dll name
public uint rvaHmod; // RVA of module handle
public uint rvaIAT; // RVA of the IAT
public uint rvaINT; // RVA of the INT
public uint rvaBoundIAT; // RVA of the optional bound IAT
public uint rvaUnloadIAT; // RVA of optional copy of original IAT
public uint dwTimeStamp; // 0 if not bound,
// O.W. date/time stamp of DLL bound to (Old BIND)
} //ImgDelayDescr, * PImgDelayDescr;
//http://www.gamedev.net/topic/409936-advanced-c-native-dll-image-import-reading/
[StructLayout(LayoutKind.Explicit)]
public struct IMAGE_IMPORT_DESCRIPTOR
{
public static uint SizeOf = (uint)Marshal.SizeOf(typeof(IMAGE_IMPORT_DESCRIPTOR));
#region union
/// <summary>
/// CSharp doesnt really support unions, but they can be emulated by a field offset 0
/// </summary>
[FieldOffset(0)]
public uint Characteristics; // 0 for terminating null import descriptor
[FieldOffset(0)]
public uint OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
#endregion
[FieldOffset(4)]
public uint TimeDateStamp;
[FieldOffset(8)]
public uint ForwarderChain;
[FieldOffset(12)]
public uint Name;
[FieldOffset(16)]
public uint FirstThunk;
}
//http://pinvoke.net/default.aspx/Structures/IMAGE_EXPORT_DIRECTORY.html
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_EXPORT_DIRECTORY
{
public static uint SizeOf = (uint)Marshal.SizeOf(typeof(IMAGE_EXPORT_DIRECTORY));
public UInt32 Characteristics;
public UInt32 TimeDateStamp;
public UInt16 MajorVersion;
public UInt16 MinorVersion;
public UInt32 Name;
public UInt32 Base;
public UInt32 NumberOfFunctions;
public UInt32 NumberOfNames;
public UInt32 AddressOfFunctions; // RVA from base of image
public UInt32 AddressOfNames; // RVA from base of image
public UInt32 AddressOfNameOrdinals; // RVA from base of image
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_THUNK_DATA
{
public static uint SizeOf = (uint)Marshal.SizeOf(typeof(IMAGE_THUNK_DATA));
public IntPtr ForwarderString; // PBYTE
public IntPtr Function; // PDWORD
public IntPtr Ordinal;
public IntPtr AddressOfData; // PIMAGE_IMPORT_BY_NAME
}
#endregion
[DllImport("kernel32.dll", SetLastError = true)]
static public extern bool CloseHandle(IntPtr hHandle);
[DllImport("kernel32.dll")]
static extern bool Module32First(IntPtr hSnapshot, ref MODULEENTRY32 lpme);
[DllImport("kernel32.dll")]
static extern bool Module32Next(IntPtr hSnapshot, ref MODULEENTRY32 lpme);
[DllImport("kernel32.dll", SetLastError = true)]
static public extern IntPtr CreateToolhelp32Snapshot(SnapshotFlags dwFlags, int th32ProcessID);
public const short INVALID_HANDLE_VALUE = -1;
[Flags]
public enum SnapshotFlags : uint
{
HeapList = 0x00000001,
Process = 0x00000002,
Thread = 0x00000004,
Module = 0x00000008,
Module32 = 0x00000010,
Inherit = 0x80000000,
All = 0x0000001F
}
public struct MODULEENTRY32
{
public static uint SizeOf = (uint)Marshal.SizeOf(typeof(MODULEENTRY32));
//http://pastebin.com/BzD1jdmH
private const int MAX_PATH = 255;
internal uint dwSize;
internal uint th32ModuleID;
internal uint th32ProcessID;
internal uint GlblcntUsage;
internal uint ProccntUsage;
internal IntPtr modBaseAddr;
internal uint modBaseSize;
internal IntPtr hModule;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 1)]
internal string szModule;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 5)]
internal string szExePath;
}
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr baseAddress, [Out] byte[] buffer, int size, out IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr baseAddress, [Out] byte[] buffer, int size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr baseAddress, [Out] byte[] buffer, int size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr baseAddress, [Out] uint[] buffer, int size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr baseAddress, [Out] ushort[] buffer, int size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr baseAddress, out IntPtr buffer, int size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr baseAddress, out IMAGE_DOS_HEADER buffer, int size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr baseAddress, out IMAGE_NT_HEADERS32 buffer, int size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr baseAddress, out IMAGE_NT_HEADERS64 buffer, int size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr baseAddress, out ImgDelayDescr buffer, uint size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr baseAddress, out IMAGE_THUNK_DATA buffer, uint size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr baseAddress, out IMAGE_IMPORT_DESCRIPTOR buffer, uint size, IntPtr numBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, UIntPtr baseAddress, out IMAGE_EXPORT_DIRECTORY buffer, uint size, IntPtr numBytesRead);
}
}
The following is a complete program. It works fine as long as you don't uncomment the '#define BROKEN' at the top. The break is due to a PInvoke failing to marshal a union correctly. The INPUT_RECORD structure in question has a number of substructures that might be used depending on the value in EventType.
What I don't understand is that when I define only the single child structure of KEY_EVENT_RECORD it works with the explicit declaration at offset 4. But when I add the other structures at the same offset the structure's content get's totally hosed.
//UNCOMMENT THIS LINE TO BREAK IT:
//#define BROKEN
using System;
using System.Runtime.InteropServices;
class ConIOBroken
{
static void Main()
{
int nRead = 0;
IntPtr handle = GetStdHandle(-10 /*STD_INPUT_HANDLE*/);
Console.Write("Press the letter: 'a': ");
INPUT_RECORD record = new INPUT_RECORD();
do
{
ReadConsoleInputW(handle, ref record, 1, ref nRead);
} while (record.EventType != 0x0001/*KEY_EVENT*/);
Assert.AreEqual((short)0x0001, record.EventType);
Assert.AreEqual(true, record.KeyEvent.bKeyDown);
Assert.AreEqual(0x00000000, record.KeyEvent.dwControlKeyState & ~0x00000020);//strip num-lock and test
Assert.AreEqual('a', record.KeyEvent.UnicodeChar);
Assert.AreEqual((short)0x0001, record.KeyEvent.wRepeatCount);
Assert.AreEqual((short)0x0041, record.KeyEvent.wVirtualKeyCode);
Assert.AreEqual((short)0x001e, record.KeyEvent.wVirtualScanCode);
}
static class Assert { public static void AreEqual(object x, object y) { if (!x.Equals(y)) throw new ApplicationException(); } }
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ReadConsoleInputW(IntPtr hConsoleInput, ref INPUT_RECORD lpBuffer, int nLength, ref int lpNumberOfEventsRead);
[StructLayout(LayoutKind.Explicit)]
public struct INPUT_RECORD
{
[FieldOffset(0)]
public short EventType;
//union {
[FieldOffset(4)]
public KEY_EVENT_RECORD KeyEvent;
#if BROKEN
[FieldOffset(4)]
public MOUSE_EVENT_RECORD MouseEvent;
[FieldOffset(4)]
public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
[FieldOffset(4)]
public MENU_EVENT_RECORD MenuEvent;
[FieldOffset(4)]
public FOCUS_EVENT_RECORD FocusEvent;
//}
#endif
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_EVENT_RECORD
{
public bool bKeyDown;
public short wRepeatCount;
public short wVirtualKeyCode;
public short wVirtualScanCode;
public char UnicodeChar;
public int dwControlKeyState;
}
[StructLayout(LayoutKind.Sequential)]
public struct MOUSE_EVENT_RECORD
{
public COORD dwMousePosition;
public int dwButtonState;
public int dwControlKeyState;
public int dwEventFlags;
};
[StructLayout(LayoutKind.Sequential)]
public struct WINDOW_BUFFER_SIZE_RECORD
{
public COORD dwSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct MENU_EVENT_RECORD
{
public int dwCommandId;
}
[StructLayout(LayoutKind.Sequential)]
public struct FOCUS_EVENT_RECORD
{
public bool bSetFocus;
}
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public short X;
public short Y;
}
}
UPDATE:
For those worried about the struct declarations themselves:
bool is treated as a 32-bit value
the reason for offset(4) on the data is to allow for the 32-bit structure alignment which prevents the union from beginning at offset 2.
Again, my problem isn't making PInvoke work at all, it's trying to figure out why these additional structures (supposedly at the same offset) are fowling up the data by simply adding them.
I believe it will work if you make bSetFocus and dwCommandId of type uint.
In the future, check out the PInvoke wiki for the proper signatures. It's usually accurate or, at the very least, a good starting point.
//UNCOMMENT THIS LINE TO BREAK IT:
//#define BROKEN
using System;
using System.Runtime.InteropServices;
class ConIOBroken
{
static void Main()
{
int nRead = 0;
IntPtr handle = GetStdHandle(-10 /STD_INPUT_HANDLE/);
Console.Write("Press the letter: 'a': ");
INPUT_RECORD record = new INPUT_RECORD();
do
{
ReadConsoleInputW(handle, ref record, 1, ref nRead);
} while (record.EventType != 0x0001/*KEY_EVENT*/);
Assert.AreEqual((short)0x0001, record.EventType);
Assert.AreEqual(1u, record.KeyEvent.bKeyDown);
Assert.AreEqual(0x00000000, record.KeyEvent.dwControlKeyState & ~0x00000020);//strip num-lock and test
Assert.AreEqual('a', record.KeyEvent.UnicodeChar);
Assert.AreEqual((short)0x0001, record.KeyEvent.wRepeatCount);
Assert.AreEqual((short)0x0041, record.KeyEvent.wVirtualKeyCode);
Assert.AreEqual((short)0x001e, record.KeyEvent.wVirtualScanCode);
return;
}
static class Assert { public static void AreEqual(object x, object y) { if (!x.Equals(y)) throw new ApplicationException(); } }
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ReadConsoleInputW(IntPtr hConsoleInput, ref INPUT_RECORD lpBuffer, int nLength, ref int lpNumberOfEventsRead);
[StructLayout(LayoutKind.Explicit)]
public struct INPUT_RECORD
{
[FieldOffset(0)]
public short EventType;
//union {
[FieldOffset(4)]
public KEY_EVENT_RECORD KeyEvent;
[FieldOffset(4)]
public MOUSE_EVENT_RECORD MouseEvent;
[FieldOffset(4)]
public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
[FieldOffset(4)]
public MENU_EVENT_RECORD MenuEvent;
[FieldOffset(4)]
public FOCUS_EVENT_RECORD FocusEvent;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_EVENT_RECORD
{
public uint bKeyDown;
public short wRepeatCount;
public short wVirtualKeyCode;
public short wVirtualScanCode;
public char UnicodeChar;
public int dwControlKeyState;
}
[StructLayout(LayoutKind.Sequential)]
public struct MOUSE_EVENT_RECORD
{
public COORD dwMousePosition;
public int dwButtonState;
public int dwControlKeyState;
public int dwEventFlags;
};
[StructLayout(LayoutKind.Sequential)]
public struct WINDOW_BUFFER_SIZE_RECORD
{
public COORD dwSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct MENU_EVENT_RECORD
{
public int dwCommandId;
}
[StructLayout(LayoutKind.Sequential)]
public struct FOCUS_EVENT_RECORD
{
public uint bSetFocus;
}
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public short X;
public short Y;
}
}
1) public bool bKeyDown should be Int32 bKeyDown because it's a BOOL (4bytes) in c++
2) public bool bSetFocus should be Int32
Just a thought, what happens if you declare the largest field last? Maybe P/Invoke is copying up to the last field, which ends before earlier fields.
Try adding a manually calculated Size field to the StructLayout attribute, like this:
[StructLayout(LayoutKind.Explicit, Size=...)]
The original code with the bool contained 13 bytes, starting at FieldOffset(4) ...
The MOUSE_EVENT_RECORD starting at the same offset conatianed 16 bytes starting at the same offset.
When you changed the bool (1byte) to an uint(4bytes) you made up the 3 bytes.