Search code examples
c#pinvoke

How to fix Ntdll.dll APPCRASH with Hardware ID Extractor?


I am using the Hardware ID Extractor library (written in Delphi) from https://soft.tahionic.com/download-hdd_id/ with the purpose of generating unique system fingerprints.

The library is really good and unlike anything else I have seen on the market, but the main issue with it is that it's unstable when running with .NET applications, meaning that it sometimes works, other times it works for a few function calls then the main application crashes, or most of the time the application instantly crashes when a dll function is being called.

As the developer of the library pointed out (in the last support e-mail that I have received), the fault is with ntdll.dll, as I have seen that for myself: enter image description here

Following is a link to a demo project I have created with the purpose of testing the dll functions (so to make sure that nothing else interferes, the demo app does that and only that- it calls the dll functions). http://www.mediafire.com/download/1jws7zh9218v88a/HardwareIdExtractDllTest.zip The archive contains the Visual Studio 2013 project with source code and a compiled demo application which looks like this: enter image description here

The list of functions contained by the dll can be found here: https://soft.tahionic.com/download-hdd_id/hardware%20id%20programming%20source%20code/exported%20functions%20for%20non-Delphi.html

If anyone has the knowledge and is willing to test the demo project/application to make tests or personal opinions in regard to the issue, and then share a possible solution with me, I would be grateful.

Please let me know if there's anything I can do to further assist in solving this issue if you think there's anything that can be done about it.

EDIT: This is how I am declaring the dll functions

    [DllImport("HardwareIDExtractorC.dll")]
    private static extern bool EnterKey(int key);

    [DllImport("HardwareIDExtractorC.dll")]
    private static extern bool IsCPUIDAvailable();

    [DllImport("HardwareIDExtractorC.dll")]
    private static extern int GetCPUCount();

    [DllImport("HardwareIDExtractorC.dll")]
    private static extern byte CoreNumber2CoreMask(byte cpuCore);

    [DllImport("HardwareIDExtractorC.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "GetCPUID")]
    [return: MarshalAs(UnmanagedType.LPStr)]
    private static extern string GetCPUID(byte cpuCore);

    [DllImport("HardwareIDExtractorC.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "GetCpuIdNow")]
    [return: MarshalAs(UnmanagedType.LPStr)]
    private static extern string GetCpuIdNow(); 

    [DllImport("HardwareIDExtractorC.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "GetIDESerialNumber")]
    [return: MarshalAs(UnmanagedType.LPStr)]
    private static extern string GetIDESerialNumber(byte driveNumber);

Solution

  • The functions that fail are the ones that return string. Such a p/invoke marshals the return value as a pointer to null terminated character array, and then calls CoTaskMemFree on the raw pointer returned by the unmanaged code.

    With probability close to 1 that string was not allocated on the COM heap and so the error is likely in the C# p/invoke declarations. That an access violation then arises in ntdll is quite plausible.

    So, to make progress you need to fix the p/invoke calls. I cannot tell you how to do so because you have not shown the unmanaged function declarations. Or, more importantly, how the memory is allocated.


    There are some clues at the documentation

    procedure ReleaseMemory (P: PAnsiChar); stdcall;
    

    I think this tells us that the strings returned by the DLL must be deallocated by the DLL, by calling this function. Presumably because they were allocated by the DLL's heap allocator.

    So, use IntPtr for the return type of the functions that return text. Call Marshal.PtrToStringAnsi to convert to a C# string. And then pass the pointer to ReleaseMemory.

    For example:

    [DllImport(DllPath, CallingConvention = CallingConvention.StdCall)]
    private static extern IntPtr GetCPUID(ushort CoreMask);
    
    [DllImport(DllPath, CallingConvention = CallingConvention.StdCall)]
    private static extern void ReleaseMemory(IntPtr P);
    
    ....
    
    IntPtr ptr = GetCPUID(CoreMask);
    string cpuid = Marshal.PtrToStringAnsi(ptr);
    ReleaseMemory(ptr);