microsoft / CsWin32

A source generator to add a user-defined set of Win32 P/Invoke methods and supporting types to a C# project.
MIT License
1.99k stars 84 forks source link

EntryPointNotFoundException - wintrust.dll #1079

Closed joeloff closed 8 months ago

joeloff commented 8 months ago

Actual behavior

Created a .NET 7.0 console application that calls into WinVerifyTrust. I run into the following exception

System.EntryPointNotFoundException: Unable to find an entry point named 'WinVerifyTrust' in DLL 'WINTRUST.dll'

I created a second console application without CsWin32 with the following:

        private static void Main(string[] args)
        {
            WinVerifyTrust(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
        }

        [DllImport("wintrust.dll", SetLastError = true)]
        [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
        public static extern uint WinVerifyTrust(IntPtr hWnd, IntPtr pgActionID, IntPtr pWinTrustData);

This obviously still fails because of the null pointers, but the external method is successfully resolved. I also ran dumpbin /exports wintrust.dll just to make sure.

The only thing I can think of is that perhaps I messed up the data structures and somehow overwrite the function pointer returned when the library is loaded.

Expected behavior

Expected the API call to succeed.

Repro steps

  1. NativeMethods.txt content:

    WinVerifyTrust
    WINTRUST_DATA
    WINTRUST_ACTION*
    HWND
  2. NativeMethods.json content (if present):

  3. Any of your own code that should be shared?

using Windows.Win32.Foundation;
using Windows.Win32.Security.WinTrust;
using static Windows.Win32.PInvoke;

namespace WinTrust
{

    public class Program
    {
        public static unsafe bool IsAuthentiCodeSigned(string path)
        {
            WINTRUST_FILE_INFO* fi = stackalloc WINTRUST_FILE_INFO[1];
            WINTRUST_DATA* td = stackalloc WINTRUST_DATA[1];

            fixed (char* p = Path.GetFullPath(path))
            {
                fi[0].pcwszFilePath = p;
                fi[0].cbStruct = (uint)sizeof(WINTRUST_FILE_INFO);
                fi[0].hFile = (HANDLE)IntPtr.Zero;
                fi[0].pgKnownSubject = null;
            }

            Guid policyGuid = WINTRUST_ACTION_GENERIC_VERIFY_V2;

            td[0].cbStruct = (uint)sizeof(WINTRUST_DATA);
            td[0].pPolicyCallbackData = null;
            td[0].pSIPClientData = null;
            td[0].dwUIChoice = WINTRUST_DATA_UICHOICE.WTD_UI_NONE;
            td[0].fdwRevocationChecks = WINTRUST_DATA_REVOCATION_CHECKS.WTD_REVOKE_NONE;
            td[0].dwUnionChoice = WINTRUST_DATA_UNION_CHOICE.WTD_CHOICE_FILE;
            td[0].dwStateAction = WINTRUST_DATA_STATE_ACTION.WTD_STATEACTION_VERIFY;
            td[0].hWVTStateData = (HANDLE)IntPtr.Zero;
            td[0].pwszURLReference = null;
            td[0].dwUIContext = 0;
            td[0].Anonymous.pFile = fi;

            int lstatus = WinVerifyTrust((HWND)IntPtr.Zero, ref policyGuid, td);

            return lstatus == 0;
        }

        private static void Main(string[] args)
        {
            Console.WriteLine(IsAuthentiCodeSigned(args[0]));
        }
    }
}

Context

AArnott commented 8 months ago

Thanks for your report. When I try with 0.3.49-beta, to generate only WinVerifyTrust and use this code:

using static Windows.Win32.PInvoke;

unsafe
{
    WinVerifyTrust(default, default, default);
}

I get an AV, which as you say is expected given the null pointers. I don't get an EntryPointNotFoundException. When I compare your non-CsWin32 simple repro to what CsWin32 minimally generates, it looks equivalent.

So then I took your full repro and it succeeded too. But it's only lucky, because you have at least one bug, in that your only pin Path.GetFullPath(path) long enough to set a pointer, but that pointer can become invalid as soon as you exit the fixed block, which is before the native code uses the pointer.

joeloff commented 8 months ago

Oh man, that never registered with me, I'll fix that up right away.

The bug persists though in my original project.

I created a 3rd project, which only had the minimal API and calling it with default parameters work just fine, no entry point issue. I'm sure this is going to be something silly - this has to be corrupted memory or something getting overwritten. I'll keep the issue open for now and update with anything I discover.

joeloff commented 8 months ago

Closing this. In my haste, I decided to name my console app wintrust, so it created a wintrust.dll which is not the same as wintrust.dll in system32, which explains why my random consoleapp123 works just fine.