The .NET framework provides a Marshal class from its Runtime.InteropServices namespace which helps interfacing native and unmanaged data with managed code. The easy path for most of these cases is to simply use unsafe blocks and cast a pointer, but you end up losing references to allocated structures, leaking memory and likely leaving some funny exploitable condition in your unmanaged code bridge. Those pesky dangling pointers...
The function below calls an internal method to retrieve the list of loaded
kernel modules from userland. It depends on NtQuerySystemInformation()
and requires a heap-allocated structure array. Interfacing this with a C# managed
class will require another exported function to call HeapFree() and
release the allocated memory.
Using such an approach is certainly not recommended but it will cut you some hassle:
extern "C" __declspec(dllexport) PSYSTEM_MODULE_INFORMATION GetKernelModules(void)
{
HANDLE tmpHeap = GetProcessHeap();
PSYSTEM_MODULE_INFORMATION modList = NULL;
LoadFunctionPointers();
_getSysModules(&modList, tmpHeap);
return modList;
}
extern "C" __declspec(dllexport) void MyFreeHeap(LPVOID ptrToFree)
{
HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, ptrToFree);
}
On the C# side, we will be using a Marshal structure declaration in order to be
able to use the PtrToStructure method, which allows us to copy memory from
unmanaged space into our managed class, and then we can release whatever memory
was allocated for the native API.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 2)]
public struct SYSTEM_MODULE_INFORMATION
{
[MarshalAs(UnmanagedType.U4)] public UInt32 Reserved1;
[MarshalAs(UnmanagedType.U4)] public UInt32 Reserved2;
...
}
int curOffset = (int)(i * Marshal.SizeOf(Modules[i]));
IntPtr curPtr = new IntPtr(ModulesListPtr.ToInt32() + curOffset);
Modules[i] = (SYSTEM_MODULE_INFORMATION) Marshal.PtrToStructure(curPtr,
typeof(SYSTEM_MODULE_INFORMATION));
[DllImport("mylib.dll", CharSet = CharSet.Unicode)]
private extern static void MyFreeHeap(IntPtr ptr);
Depending on your target platform, you might want to adjust CharSet
since Unicode is the default on NT based systems (that is, all modern versions of
Windows, excluding 9x/ME if you consider them modern... although in terms of
security, it seems like Windows 98 is safer, after all, most malware doesn't work
on it anymore). Packing is also important, since it means how your structure is
actually stored on memory. Values of 1-2 are safe, just verify the alignment of
the variables within the structure you are trying to use.
Some suggestions:
- In your bridge DLL, don't resort to typical pointer overwrites to return or store information to the calling function. Using GCHandles to deal with such functions will cause a great deal of stupid exceptions about incorrect references in the internal GC object tracking. Let's say that .NET GC does not like to release handle with wrong pinned object addresses. Save you some debugging time, and in the meanwhile, do proper unmanaged programming.
- Your bridge DLL should allocate heap memory and set or return a pointer
to that location. Then your managed class should call a
pInvokeor similar method that itself works with HeapFree within your DLL bridge library. You could also useMarshal.AllocHCGlobal - Use a
GCHandlewhen you need to write data from your unmanaged code directly, and remember to release it once you are done with it. But never overwrite the address of managed object or you will end up hitting an invalid free whenever the GC attempts to release your now corrupted object. And that might happen in a manner that makes debugging a pain in the ass. Better go clubbing than waste your time debugging that.
Leave a comment