C#中的Windows Defender防病毒扫描[AccessViolation异常]

21

我们正在使用Windows Defender API编写代码,以便从C#对文件进行按需扫描。

        [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)
            { }
        }

这里定义了结构。

    [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;
    }

代码是基于以下文档编写的:

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

我们遇到了这个异常:

尝试读取或写入受保护的内存。这通常表明其他内存已损坏。

位置:

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

可能出了什么问题?结构体的格式是否正确?

附言 - MSDN 上没有关于 MPRESOURCE_CLASS 的信息。

我不确定这行代码是否正确。

 mpResourceInfo.Class = IntPtr.Zero;

更新:

这段代码的快速扫描工作正常:

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

Defender日志记录在事件查看器中[应用程序和服务日志- Microsoft-Windows-Windows Defender / Operational]中,如下:

Windows Defender扫描已启动。
扫描ID:{CDC2AC0D-7648-4313-851C-4D8B7B5EB5CD}
扫描类型:反间谍软件
扫描参数:快速扫描


3
天啊,这些路径是硬编码的!请不要这样做。如果我的引导驱动器不是C驱动器怎么办?如果Windows Defender没有安装在Program Files目录下怎么办? - Cody Gray
@CodyGray - 这只是一个概念验证。但感谢您指出。 - mlg
我看到的第一个错误是MPSCAN_RESOURCES.pResourceList成员声明。它是一个指向数组的指针,而不是UnmanagedType.ByValArray。你必须将其声明为IntPtr并自己进行数组编组。使用Pack=1也非常错误。可能会有更多的错误,这不是一个简单的API。如果您使用C++/CLI来完成此操作,至少可以依赖于mpclient.h头文件,这样您就可以领先一步。 - Hans Passant
@mcNets - 我还没有尝试移动到另一个位置。但是 int result = WDStatus(out pfEnabled); - 这段代码片段可以正确返回Windows Defender的状态。 - mlg
2
最终我放弃了。我们计划使用反恶意软件扫描接口(AMSI)。但是AMSI支持仅适用于Windows 10。我已经编写了一个示例代码,以防有人需要。http://midhunlalg.blogspot.in/2016/12/consume-antimalware-scan-interface-amsi.html - mlg
显示剩余5条评论
4个回答

21

我无法确定问题所在,因此最终选择使用自 Windows 10 开始提供的Antimalware Scan Interface (AMSI)。

我编写了一个C#示例代码,在这里。我发现一个问题是,AMSI需要开启Windows Defender / 任何杀毒软件才能验证传递给API的文件。但是通过MpClient.dll触发扫描即使Defender已关闭也会触发Defender扫描。

另外,请确保您的项目针对x64平台。

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);
}

1
为什么要在这篇文章上投反对票?能说一下为什么要这样做吗?我想在这里提醒大家,AMSI是与防御者进行交互的成功替代方式。 - mlg
@EricHirst - 反过来了。我将这个发表为评论 https://dev59.com/yFgR5IYBdhLWcg3ww_s0#41361625?noredirect=1#comment69762063_40888849 到我的问题本身。Ivan复制了它并作为回答发布 :) - mlg
1
@mlg,我没有抄袭你的答案,因为我实际上没有注意到它。看起来我们在并行地研究同一个问题,并找到了相同的解决方案。无论如何,我会给你点赞并授予悬赏。也许将你已经编写的使用 AMSI 的概念证明代码添加到你的答案中是有意义的。 - Ivan Zhakov
@IvanZhakov 对不起,我误解了。我的错!哦,现在我无法更新评论 :( - mlg
2
你的代码中没有使用AmsiScanBuffer,但如果你使用了,那么这个声明将不起作用。length参数需要声明为uint而不是ulong。在Windows上的C语言中,ULONG是4个字节,但在C#中的ulong是8个字节。这里有一个AmsiScanBuffer的工作示例 - Michael Geary
显示剩余3条评论

3
您可以使用反恶意软件扫描接口检查文件是否包含恶意软件。

反恶意软件扫描接口(AMSI)是一种通用接口标准,允许应用程序和服务与计算机上存在的任何反恶意软件产品集成。它为用户及其数据、应用程序和工作负载提供了增强的恶意软件保护。

该功能从Windows 10开始提供。


已经作为评论添加到问题中了。https://dev59.com/yFgR5IYBdhLWcg3ww_s0#41361625?noredirect=1#comment69762063_40888849 - mlg

3
我一直在搜索问题,并阅读了可能原因之一:

“你经常会看到调试版本和发布版本之间的差异,因为调试版本包含额外的元数据来帮助调试。”

这里:https://social.msdn.microsoft.com/Forums/vstudio/en-US/4f48c152-68cd-45ec-a11e-baa7de7f79c3/attempted-to-read-or-write-protected-memory?forum=csharpgeneral
此外,您还应该检查此答案以“是否可能在.NET中捕获访问冲突异常?”以及在MSDN杂志中解释的进一步细节处理已损坏状态异常
...
根据答案和文章,我会尝试以下方法:
1. 确认所有非托管代码的签名和COM互操作桥是否正确。
2. 将Visual Studio调试器设置为跳过此异常: 工具菜单->选项->调试->常规->取消选中"在模块加载时抑制JIT优化"选项。
3. 使用Try-Catch捕获异常。
(注意:如果您正在使用.Net 4,则在App.config中,在标记内修改runtime以包括legacyCorruptedStateExceptionsPolicy enabled="true",如下所示:)
<runtime>
    <legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>

)

此外,这里,我发现一些 .net framework 版本(最新评论指向其中一个答案的评论中的 4.6.1)存在与此异常相关的错误,解决方案过去是升级框架。此外,在其中一个答案中,我读到了以下内容:
“嗨,有两个可能的原因。 1.我们有未经管理的代码,并从托管代码调用它。这会阻止运行此代码。尝试运行以下命令并重新启动您的计算机 cmd:netsh winsock reset 打开 cmd.exe 并运行命令 "netsh winsock reset catalog" 2.反病毒软件将未经管理的代码视为有害,并限制运行此代码,请禁用反病毒软件,然后检查。”
我想知道这些方法是否有助于解决您的问题。
我真心希望这能帮助到您。
KR,
Juan

谢谢。我会看一下的。 - mlg

1

Windows Defender带有CLI工具'MpCmdRun' - 它不是一个完整的杀毒软件应用程序,而是一个API接口,连接到始终在后台运行的实际Windows Defender。

通过Path.GetTempFileName()保存到临时文件,然后像这样运行扫描

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

即使在运行在应用程序池身份下的ASP.NET(Core)应用程序中,也可以正常工作

我实际上编写了一个小的C#帮助器(40行代码),它可以为您完成所有操作(保存临时文件、运行扫描、清理)

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接