Windows Defender Antivirus scan from C# [AccessViolation exception]
Asked Answered
C

4

21

We are writing a code to do on-demand scan of a file from C# using Windows Defender APIs.

        [DllImport(@"C:\Program Files\Windows Defender\MpClient.dll")]
        public static extern int WDStatus(out bool pfEnabled);

        [DllImport(@"C:\Program Files\Windows Defender\MpClient.dll")]
        public static extern int MpManagerOpen(uint dwReserved, out IntPtr phMpHandle);

        [DllImport(@"C:\Program Files\Windows Defender\MpClient.dll")]
        public static extern int MpScanStart(IntPtr hMpHandle, uint ScanType, uint dwScanOptions, IntPtr pScanResources, IntPtr pCallbackInfo, out IntPtr phScanHandle);

        [DllImport(@"C:\Program Files\Windows Defender\MpClient.dll")]
        public static extern int MpHandleClose(IntPtr hMpHandle);

        private void DoDefenderScan_Click(object sender, EventArgs e)
        {
            try
            {
                bool pfEnabled;
                int result = WDStatus(out pfEnabled); //Returns the defender status - It's working properly.
                ErrorHandler.ThrowOnFailure(result, VSConstants.S_OK);

                IntPtr phMpHandle;
                uint dwReserved = 0;

                IntPtr phScanHandle;

                MpManagerOpen(dwReserved, out phMpHandle); //Opens Defender and returns the handle in phMpHandle. 

                tagMPRESOURCE_INFO mpResourceInfo = new tagMPRESOURCE_INFO();
                mpResourceInfo.Path = "eicar.com";
                mpResourceInfo.Scheme = "file";
                mpResourceInfo.Class = IntPtr.Zero;

                tagMPRESOURCE_INFO[] pResourceList = new tagMPRESOURCE_INFO[1];
                pResourceList.SetValue(mpResourceInfo, 0);

                tagMPSCAN_RESOURCES scanResource = new tagMPSCAN_RESOURCES();
                scanResource.dwResourceCount = 1;
                scanResource.pResourceList = pResourceList;
                IntPtr resourcePointer = StructToPtr(scanResource);

                result = MpScanStart(phMpHandle, 3, 0, resourcePointer, IntPtr.Zero, out phScanHandle); **//Getting Access violation exception here**.

                MpHandleClose(phMpHandle);
                MpHandleClose(phScanHandle);
                Marshal.FreeHGlobal(resourcePointer);
            }
            catch (Exception)
            { }
        }

And the structure is defined here.

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct tagMPSCAN_RESOURCES
    {
        public uint dwResourceCount;

        [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1)]
        public tagMPRESOURCE_INFO[] pResourceList;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct tagMPRESOURCE_INFO
    {
        [MarshalAs(UnmanagedType.LPWStr)]
        public String Scheme;

        [MarshalAs(UnmanagedType.LPWStr)]
        public String Path;

         public IntPtr Class;
    }

    public class MPRESOURCE_CLASS
    {
        public uint Value;
    }

    private static IntPtr StructToPtr(object obj)
    {
        var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(obj));
        Marshal.StructureToPtr(obj, ptr, false);
        return ptr;
    }

The code is written based on the documentation available at

https://msdn.microsoft.com/en-us/library/vs/alm/dn920144(v=vs.85).aspx

We are getting this exception

Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

at

result = MpScanStart(phMpHandle, 3, 0, resourcePointer, IntPtr.Zero, out phScanHandle); **//Getting Access violation exception here**.

What could be the problem? Is the format of struct is correct?

P.S - No information about MPRESOURCE_CLASS is available in msdn.

I'm not sure, whether this line of code is correct.

 mpResourceInfo.Class = IntPtr.Zero;

Update:

Quick scan is working fine with this code:

result = MpScanStart(phMpHandle, 1, 0, IntPtr.Zero, IntPtr.Zero, out phScanHandle);

Defender logs in the event viewer [ Applications and Services Logs-Microsoft-Windows-Windows Defender/Operational ] as

Windows Defender scan has started.
Scan ID:{CDC2AC0D-7648-4313-851C-4D8B7B5EB5CD}
Scan Type:AntiSpyware
Scan Parameters:Quick Scan

Crescentic answered 30/11, 2016 at 12:51 Comment(10)
Holy Hardcoded Paths, Batman! Please don't do this. What if my boot drive isn't drive C? And what if Windows Defender isn't installed in Program Files?Doby
@CodyGray - It's a POC. But thanks for pointing out.Crescentic
First bug I see is the MPSCAN_RESOURCES.pResourceList member declaration. It is a pointer to an array, not a UnmanagedType.ByValArray. You have to declare it as IntPtr and marshal the array yourself. Using Pack=1 is also very wrong. There might be more bugs, it is not an easy api. You'll be ahead by using C++/CLI to do this, at least you can rely on the mpclient.h header file.Hodge
@HansPassant Thanks!. I'll look into it.Crescentic
It is possible this .dll has been blocked by Windows Defender, just to avoid a bad use of it? Have you tried to move it to another location?Mayotte
@mcNets - I haven't tried moving to another location. But int result = WDStatus(out pfEnabled); - This code snippet is properly returning the status of Windows Defender.Crescentic
Finally I gave up on this. We are planning to use Antimalware Scan Interface(AMSI). But AMSI support is only available in Windows 10. I have written a sample code, in case anybody need it. midhunlalg.blogspot.in/2016/12/…Crescentic
mpclient.h is nowhere to be found, so even if we have this function work (I do), there is no API to query the result (MpScanResult is exported from the dll, but not documented at all), @ivanzhakov, why the bounty? what do you want exactly?Castiglione
I hope someone would find mpclient.h somewhere :) But I also decided to use Antimalware Scan Interface (AMSI).Liber
@mcNets - I don't think that is the problem. I'm able to trigger a quick scan with referring to default Defender DLL location. To trigger a quick scan change MPSCAN_TYPE ScanType to 1. result = MpScanStart(phMpHandle, 1, 0, IntPtr.Zero, IntPtr.Zero, out phScanHandle);Crescentic
C
21

I couldn't identify the problem here. So I ended up with Antimalware Scan Interface (AMSI) available starting from Windows 10.

I have written a sample C# code here.
One thing I found is AMSI requires Windows defender/any antivirus to be turned on to verify the file passed to API. But triggering a scan through MpClient.dllwill trigger a defender scan even if defender is turned off.

Also ensure your project targets x64 platform.

public enum AMSI_RESULT
    {
        AMSI_RESULT_CLEAN = 0,
        AMSI_RESULT_NOT_DETECTED = 1,
        AMSI_RESULT_DETECTED = 32768
    }

[DllImport("Amsi.dll", EntryPoint = "AmsiInitialize", CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiInitialize([MarshalAs(UnmanagedType.LPWStr)]string appName, out IntPtr amsiContext);

[DllImport("Amsi.dll", EntryPoint = "AmsiUninitialize", CallingConvention = CallingConvention.StdCall)]
public static extern void AmsiUninitialize(IntPtr amsiContext);

[DllImport("Amsi.dll", EntryPoint = "AmsiOpenSession", CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiOpenSession(IntPtr amsiContext, out IntPtr session);

[DllImport("Amsi.dll", EntryPoint = "AmsiCloseSession", CallingConvention = CallingConvention.StdCall)]
public static extern void AmsiCloseSession(IntPtr amsiContext, IntPtr session);

[DllImport("Amsi.dll", EntryPoint = "AmsiScanString", CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiScanString(IntPtr amsiContext, [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)]string @string, [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)]string contentName, IntPtr session, out AMSI_RESULT result);
[DllImport("Amsi.dll", EntryPoint = "AmsiScanBuffer", CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiScanBuffer(IntPtr amsiContext, [In] [MarshalAs(UnmanagedType.LPArray)] byte[] buffer, uint length, [In()] [MarshalAs(UnmanagedType.LPWStr)] string contentName, IntPtr session, out AMSI_RESULT result);

//This method apparently exists on MSDN but not in AMSI.dll (version 4.9.10586.0)
[DllImport("Amsi.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern bool AmsiResultIsMalware(AMSI_RESULT result);

private void CallAntimalwareScanInterface()
{
    IntPtr amsiContext;
    IntPtr session;
    AMSI_RESULT result = 0;
    int returnValue;

    returnValue = AmsiInitialize("VirusScanAPI", out amsiContext); //appName is the name of the application consuming the Amsi.dll. Here my project name is VirusScanAPI.   
    returnValue = AmsiOpenSession(amsiContext, out session);
    returnValue = AmsiScanString(amsiContext, @"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*", "EICAR", session, out result); //I've used EICAR test string.   
    AmsiCloseSession(amsiContext, session);
    AmsiUninitialize(amsiContext);
}
Crescentic answered 28/12, 2016 at 11:55 Comment(8)
Why down vote on this post. Care to tell, why it's down voted? I wanted to tell here, AMSI is a successful alternative way to communicate to defender.Crescentic
@EricHirst - It's other way around. I posted this as a comment #40889349 to my question itself. Ivan copied that and posted as answer :)Crescentic
@Crescentic I didn't copied you answer, because I didn't noticed it actually. It seems we were investigating the same problem in parallel and found the same solution. Anyway I'll upvote your response and award bounty to you. It probably make sense to add proof of concept code to use AMSI that your already wrote to your answer.Liber
@IvanZhakov I'm sorry on that. I thought it other way. My bad!. Oh, I can't update the comment now :(Crescentic
No problem. Btw I've used COM API for AMSI in my tests. It's better because doesn't require linking with amsi.lib. It also possible to give IAmsiStream interface instead of loading whole file to check to memory.Liber
@Crescentic - Sorry -- I was confused as well. I'll delete my previous comment.Harlie
You're not using AmsiScanBuffer in your code, but if you did, that declaration would not work. The length parameter needs to be declared as a uint, not a ulong. In C on Windows, a ULONG is 4 bytes, but a C# ulong is 8 bytes. Here is a working example of AmsiScanBuffer.Hedges
I couldn't find a way to get detection details from AMSI interface, it just provides whether detection found or not. Is there a workaround to get detection details from AMSI interface calling windows defender?Totipalmate
D
3

I've been searching about problem and I've read this as one of the possible causes:

"You often see differences between debug and release builds because debug builds contain extra metadata to assist in debugging."

here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/4f48c152-68cd-45ec-a11e-baa7de7f79c3/attempted-to-read-or-write-protected-memory?forum=csharpgeneral

Also you should check this answer to "Is it possible to catch an access violation exception in .NET?" and the further details that are explained in the article Handling Corrupted State Exceptions in MSDN magazine
...

So, according to that answers and articles I'd try:

1st Double check signatures and COM interop thunks for all unmanaged code to verify that they're correct.

2nd Set Visual Studio Debugger to bypass this exception: Tools menu ->Options -> Debugging -> General -> Uncheck this option "Suppress JIT optimization on module load"

3rd Try-Catch the exception

(note: if you are using .Net 4 then in App.config, within the tag modify runtime to include legacyCorruptedStateExceptionsPolicy enabled="true"like:

<runtime>
    <legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>

)

In addition, here, I've found that some .net framework versions (latest comment point to 4.6.1 in one of the answer's comments) has a bug related with this exception and the solution, in the past, has been upgrading the framework. Also, in the one of that answers I've read:

Hi There are two possible reasons.

1.We have un-managed code and we are calling it from managed code. that is preventing to run this code. try running these commands and restart your pc

cmd: netsh winsock reset

open cmd.exe and run command "netsh winsock reset catalog" 2.Anti-virus is considering un-managed code as harmful and restricting to run this code disable anti-virus and then check

I'd like to know if some of these approaches helps you to solve your issue.

I really hope this helps.

KR,

Juan

Daysidayspring answered 22/12, 2016 at 14:49 Comment(1)
Thanks. I will see that.Crescentic
L
3

You may use Antimalware Scan Interface to check file for malware.

The Antimalware Scan Interface (AMSI) is a generic interface standard that allows applications and services to integrate with any antimalware product present on a machine. It provides enhanced malware protection for users and their data, applications, and workloads.

It's available starting from Windows 10.

Liber answered 23/12, 2016 at 13:37 Comment(1)
Was already added as comment to the question. #40889349Crescentic
M
1

Windows Defender comes with CLI tool 'MpCmdRun' - it's not a full-sized antivirus app, but an API interface to the actual Windows Defender that's always (?) running in background.

Saving to a temporary file via Path.GetTempFileName() and then running a scan like this

MpCmdRun.exe -Scan -ScanType 3 -File "c:\path\to\temp\file" -DisableRemediation

works fine even in an ASP.NET (Core) app, that runs under app-pool identity

I've actually written a small (40 lines of code) C# helper that does everything for you (saves temp file, runs a scan, cleans up)

https://github.com/jitbit/WinDefender/blob/main/WinDefender.cs

Manufactory answered 9/6, 2022 at 9:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.