I am having issues passing strings to a certain function in my external DLL. I would post an actual code snippet but it's kind of messy and might be hard to read. The following snippets are what my personal code boils down to.
C# File (UNICODE)
[DllImport("InjectDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern ulong FindProgramProcessId(string procName);
[DllImport("InjectDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern bool InjectIntoProcess(ulong procId, string Dll);
string ProcName = "random_test_game.exe";
string DllPath = #"C:\ProgramData\Hack\File.dll";
ulong procId = FindProgramProcessId(ProcName);
bool Injected = InjectIntoProcess(procId, DllPath);
C++ File (ANSI)
DllExport DWORD FindProgramProcessId(const char* procName)
{
...
}
DllExport bool InjectIntoProcess(DWORD procId, const char* Dll)
{
if (Dll == nullptr)
{
MessageBox(NULL, "DLL", "EMPTY", MB_OK);
return false;
}
...
}
C++ Header File
#pragma once
#include <Windows.h>
#include <TlHelp32.h>
#include <string>
#ifdef EXPORT
#define DllExport __declspec(dllexport)
#else
#define DllExport __declspec(dllimport)
#endif
extern "C" DllExport DWORD FindProgramProcessId(const char* procName);
extern "C" DllExport bool InjectIntoProcess(DWORD procId, const char* Dll);
Referencing the snippets, the issue arising is that FindProgramProcessId will successfully pass a string, no problemo, but InjectIntoProcess will show const char* Dll as nullptr according to the "extra" code I put in that method.
Notice, I have tried passing an IntPtr in place of string and using Marshal.StringToHGlobalAnsi, and I still get the Dll == nullptr issue. It's breaking my code. More of the same info can be found here at my GuidedHacking thread.
The Win32 DWORD is a 32 bit integer, but the C# ulong is a 64 bit integer. The confusion stems from the fact that DWORD is an alias for unsigned long, but the C++ long isn't necessarily 64 bits (in fact, in MSVC, it's 32 bits; unsigned long long is the 64 bit unsigned integer).
Since you're using the cdecl calling convention, the caller is responsible for cleaning the stack (so no crash occurs), and arguments are passed right-to-left (so Dll ends up pointing somewhere in the middle of the value passed to procId, which probably contains zeroes). Or at least that's my guess, since we're in undefined behavior territory here.
You should declare the return value of FindProgramProcessId and the procId parameter of InjectIntoProcess as uint instead.
Related
The project I am working on has a case where I have to read and display hardware info, for which they have a function written in a C++ DLL, and I am writing a C# stub to PInvoke it. I am fairly new to PInvoke, and hold beginner status in C# and C++ in general, as I mostly work in the Java space.
The issue here is that I am getting an exception, I feel like there is some marshaling issue going on here, which is causing this exception, but please correct me if I am wrong. Can you please help me spot the issue?
Also, I did try combining multiple MarshalAs options and different data types. In addition to the complication as it is, the function takes in a reference to a struct as an argument, and the struct itself has a nested struct within. Also, it returns a long as a flag I believe, when called with no arguments returns 0 with no exception, which is interesting.
===========================================================================================================
Part of sample hardware operation C++ dll header.
===========================================================================================================
#define APOINTER *
typedef unsigned char BYTETYPE;
typedef BYTETYPE CHARTYPE;
typedef unsigned long int ULONGTYPE;
typedef HDINFO APOINTER PTR_HDINFO;
typedef struct VERSION {
BYTETYPE major;
BYTETYPE minor;
}VERSION;
typedef VERSION APOINTER PTR_VERSION;
typedef struct HDINFO {
VERSION hardwareVersion;
CHARTYPE hardwareManufactureID[32]; /* blank padded */
ULONGTYPE hardwareflags; /* must be zero */
CHARTYPE hardwareDesc[32]; /* blank padded */
VERSION apiVersion;
}HDINFO;
typedef HDINFO APOINTER PTR_HDINFO;
extern "C" __declspec(dllexport) ULONGTYPE GetHardwareInfo(PTR_HDINFO pSoInfo);
===========================================================================================================
Sample C# code for PInvoke. Throws Exception
===========================================================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace ConsoleHardwareApp
{
[StructLayout(LayoutKind.Sequential, Size =1)]
struct VERSION
{
byte majorVersion;
byte minorVerison;
}
[StructLayout(LayoutKind.Sequential, Size =1)]
struct HDINFO
{
VERSION hardwareVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
string hardwareManufactureID;
int hardwareflags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
string hardwareDesc;
VERSION apiVersion;
}
class Program
{
[DllImport("sampleHardwareOp.dll")]
public static extern int GetHardwareInfo(ref IntPtr hdInfoPtr);
static void Main(string[] args)
{
HDINFO hdInfo = new HDINFO();
IntPtr hdInfoPtr = new IntPtr();
hdInfoPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(HDINFO)));
Marshal.StructureToPtr<HDINFO>(hdInfo, hdInfoPtr, false);
int rv = 1;
rv = GetHardwareInfo(ref hdInfoPtr); //Exception thrown here
Console.WriteLine(rv);
Console.ReadKey();
}
}
}
The exception:
ConsoleHardwareApp.Program::GetHardwareInfo' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
Without concise details about which C++ compiler was used to make the DLL, what size its long data type is (may be 32bit or 64bit), what alignment settings are used, if any structure padding is present, etc, then a translation to C# is difficult. However, your C# code should probably look something more like the following instead:
namespace ConsoleHardwareApp
{
[StructLayout(LayoutKind.Sequential/*, Pack=4 or 8*/)]
struct VERSION
{
byte major;
byte minor;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi/*, Pack=4 or 8*/)]
struct HDINFO
{
VERSION hardwareVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
string hardwareManufactureID;
uint hardwareflags; // or ulong, depending on the C++ compiler used
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
string hardwareDesc;
VERSION apiVersion;
}
class Program
{
[DllImport("sampleHardwareOp.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern uint/*or: ulong*/ GetHardwareInfo(ref HDINFO pSoInfo);
static void Main(string[] args)
{
HDINFO hdInfo = new HDINFO();
uint rv = GetHardwareInfo(ref hdInfo);
Console.WriteLine(rv);
Console.ReadKey();
}
}
}
The CallingConvention in particular is very important. The default calling convention in C++ is usually __cdecl (but can be changed in compiler settings), however in C# the default CallingConvention in DllImport is CallingConvention.Winapi, which is __stdcall on Windows. A calling convention mismatch can easily corrupt the call stack, causing the error you are seeing.
I am wanting to call a C DLL function from C#. The function is described in the C include header file as:
#ifdef _WIN32
#ifdef CMGO_BUILD_DLL
#define CMGO_DLL_API __declspec(dllexport)
#else
#define CMGO_DLL_API __declspec(dllimport)
#endif
#define CMGO_API_CC __stdcall
#else
#define CMGO_DLL_API
#define CMGO_API_CC
#endif
typedef void* CMGO_DATACHANNEL_HANDLE;
CMGO_DLL_API int CMGO_API_CC cmgo_create_datachannel(const char* host, unsigned short port, CMGO_DATACHANNEL_HANDLE* handle);
A sample C file showing how to use the cmgo_create_datachanell DLL call to create the channel has this example usage:
int main(int argc, char** argv)
{
int st;
char address[] = "127.0.0.1";
unsigned int port = 9800;
CMGO_DATACHANNEL_HANDLE handle = NULL;
struct CMGO_DATACHANNEL_READ_RESULT result;
memset(&result, 0, sizeof(result));
printf("Creating DataChannel [%s,%u] ...\n", address, port);
st = cmgo_create_datachannel(address, port, &handle);
In C# I have written the following:
const string CarmenSDKDLL = "cmgocapi.dll";
[DllImport(CarmenSDKDLL, CallingConvention= CallingConvention.Cdecl)]
static extern int cmgo_create_datachannel(string host, ushort port, IntPtr handle);
static void Main(string[] args)
{
const string ANPRServer = "127.0.0.1";
ushort port = 9800;
IntPtr iHandle = new IntPtr(0);
int iResult = cmgo_create_datachannel(ANPRServer, port, iHandle);
}
When I run the C# code I get an exception
System.AccessViolationException HResult=0x80004003 Message=Attempted to read or write protected memory.
Can anyone pointer me in the right direction and explain what I have written incorrectly in the C# code?
The main problem with your code is you're passing the handle incorrectly - it is in fact a void**, not a void*; meaning, it's an out IntPtr, not an IntPtr
Change the calling code as follows:
const string CarmenSDKDLL = "cmgocapi.dll";
[DllImport(CarmenSDKDLL, CallingConvention = CallingConvention.StdCall)]
extern
static int cmgo_create_datachannel([MarshalAs(UnmanagedType.LPStr)] string host,
ushort port,
out IntPtr handle);
There are a few corrections in the above declaration:
Calling convention - you need to match the declared calling convention, which is __stdcall, not __cdecl
String type - C# strings are Unicode, not ANSI. You need to specify correct marshalling type.
out parameter - Notice the declaration typedef void* CMGO_DATACHANNEL_HANDLE; - it's a void*; however, the function takes a pointer to that type - which is now a void**.
The access violation exception you are getting is specifically due to the last bullet point - the C function is trying to write to an invalid memory location.
Calling code:
const string ANPRServer = "127.0.0.1";
ushort port = 9800;
int errorCode = cmgo_create_datachannel(ANPRServer, port, out var handle);
if(errorCode == 0)
{
// handle is a valid pointer of type IntPtr
}
In general, the errors that happen in the C functions called via P/Invoke may differ quite a bit from the exceptions you see in C# calling code. I recommend looking into Marshal.GetLastWin32Error() documentation and detailed example to improve the error handling in working with P/Invoke.
The handle argument is obviously used to return a handle - you need to declare and call it with ref. Other than that, string arguments generally need some care - for example, by default, C# marshals strings as Unicode, and it seems that the DLL expects ANSI strings. So my guess at the correct P/Invoke signature is
[DllImport(CarmenSDKDLL, CallingConvention= CallingConvention.Cdecl)]
static extern int cmgo_create_datachannel([MarshalAs(UnmanagedType.LPStr)] string host, ushort port, ref IntPtr handle);
We need to import native c## methods in c#, but we don't know how to marshal the code correctly. Especially we don't know which datatypes to use.
Are the used datatypes in the dll import correct?
VDLL32 LS_STATUS_CODE VMSWINAPI MyMethodToImport(
unsigned char LSFAR *server_name, /* IN */
VLSservInfo LSFAR *srv_info, /* out */
unsigned char LSFAR *unused1, /*reserved*/
unsigned long LSFAR *unused2 /*reserved*/
);
For C# we used the following:
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "MyMethodToImport", CharSet = CharSet.Ansi)]
public static extern int MyMethodToImport(ref VLSservInfo servInfo, IntPtr serverName);
The current result is empty, so serverName is not interpreted correctly inside the native dll
I expect a not empty ServInfo object, but the actual output is empty.
This is my C# code:
[DllImport("Tomb.dll")]
public static extern unsafe uint InjectManualMap(ulong pId, [MarshalAs(UnmanagedType.LPCStr)]string dllPath);
and for whatever reason when I attempt to use my C++ code:
extern "C" DllExport unsigned int StdCall InjectManualMap(unsigned long pid, const char* dllPath) {
ManualMapInjector* mmp = new ManualMapInjector;
std::string dll(dllPath);
unsigned int kk = mmp->Inject(pid, dll);
delete mmp;
return kk;
}
the const char* dllPath is always a bad pointer (0x000000000).
I'm not sure what's happening here, because all other solutions point to using either a StringBuilder (tested it, did the same thing), or using MarshalAs which is something the current code I have posted does.
The issue is that C# long is 64 bits wide, but C++ long, on Windows, is 32 bits wide. The pinvoke should be:
[DllImport("Tomb.dll")]
public static extern uint InjectManualMap(uint pId, string dllPath);
Note that I also removed the unsafe directive and the MarshalAs attribute which are not needed.
I have the following C++ function exported in a DLL:
extern "C" __declspec(dllexport) bool GetResolutionArray(int32_t adapterIndex, int32_t outputIndex, uint32_t arrayLength, Resolution outResolutionArr[]) {
memcpy_s(
outResolutionArr,
sizeof(Resolution) * arrayLength,
RENDER_COMPONENT.GetResolutionArray(adapterIndex, outputIndex),
RENDER_COMPONENT.GetOutput(adapterIndex, outputIndex).NumResolutions * sizeof(Resolution)
);
return true;
}
And, the matching extern function declaration in C#:
[DllImport(InteropUtils.RUNTIME_DLL, EntryPoint = "GetResolutionArray", CallingConvention = CallingConvention.Cdecl)]
internal static extern bool _GetResolutionArray(int adapterIndex, int outputIndex, uint resolutionArrayLength, [MarshalAs(UnmanagedType.LPArray), In, Out] ref Resolution[] resolutions);
However, when I attempt to use this function as below, the program crashes with a FatalExecutionEngineError (indicating I corrupted something somewhere I guess) (error code 0xc0000005, i.e. access violation):
Resolution[] resolutions = new Resolution[outOutputDesc.NumResolutions];
if (!_GetResolutionArray(outAdapterDesc.AdapterIndex, outOutputDesc.OutputIndex, (uint) resolutions.Length, ref resolutions)) {
EnginePipeline.TerminateWithError("Internal engine call failed: _GetResolutionArray");
}
I strongly suspect that my call to memcpy_s is causing the access violation, but I can't see how or why, and I therefore reason that perhaps the marshalling is going wrong somewhere.
Thank you.
The array parameter is declared incorrectly. A C# array is already a reference and so you don't need the ref. Declare it like this:
[DllImport(InteropUtils.RUNTIME_DLL, EntryPoint = "GetResolutionArray",
CallingConvention = CallingConvention.Cdecl)]
internal static extern bool _GetResolutionArray(int adapterIndex,
int outputIndex, uint resolutionArrayLength, Resolution[] resolutions);