Added Antimalware assembly (AMSI) with supporting unit test and wrapper class in Vanara.SystemServices, AntimalwareScan.

pull/279/head
dahall 2022-02-10 11:16:09 -07:00
parent dd2e46ae75
commit 57c2216e4c
8 changed files with 1320 additions and 1 deletions

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<ProjectExtensions>
<SupportedDlls>amsi.dll</SupportedDlls>
</ProjectExtensions>
<PropertyGroup>
<Description>PInvoke API (methods, structures and constants) imported from Windows Antimalware Scan Interface (AMSI.dll).</Description>
<AssemblyTitle>$(AssemblyName)</AssemblyTitle>
<TargetFrameworks>net462;net48;net5.0;net6.0;netstandard2.0;netcoreapp3.1</TargetFrameworks>
<AssemblyName>Vanara.PInvoke.AMSI</AssemblyName>
<PackageId>$(AssemblyName)</PackageId>
<PackageTags>pinvoke;vanara;net-extensions;interop;amsi;antimalware scan interface</PackageTags>
<PackageReleaseNotes/>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\Vanara.Core.csproj" />
<ProjectReference Include="..\Shared\Vanara.PInvoke.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,626 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Vanara.Extensions;
using Vanara.InteropServices;
namespace Vanara.PInvoke
{
public static partial class AMSI
{
/// <summary>Represents a stream to be scanned.</summary>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nn-amsi-iamsistream
[PInvokeData("amsi.h", MSDNShortId = "NN:amsi.IAmsiStream")]
[ComImport, Guid("3e47f2e5-81d4-4d3b-897f-545096770373"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAmsiStream
{
/// <summary>Returns a requested attribute from the stream.</summary>
/// <param name="attribute">Specifies the type of attribute to be returned. See Remarks.</param>
/// <param name="dataSize">The size of the output buffer, <c>data</c>, in bytes.</param>
/// <param name="data">Buffer to receive the requested attribute. <c>data</c> must be set to its size in bytes.</param>
/// <param name="retData">
/// The number of bytes returned in <c>data</c>. If this method returns <c>E_NOT_SUFFICIENT_BUFFER</c>, <c>retData</c> contains
/// the number of bytes required.
/// </param>
/// <returns>
/// <para>This method can return one of these values.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term><c>S_OK</c></term>
/// <term>Success.</term>
/// </item>
/// <item>
/// <term><c>E_NOTIMPL</c></term>
/// <term>The attribute is not supported.</term>
/// </item>
/// <item>
/// <term><c>E_NOT_SUFFICIENT_BUFFER</c></term>
/// <term>
/// The size of the output buffer, as indicated by <c>data</c>, is not large enough. <c>retData</c> contains the number of bytes required.
/// </term>
/// </item>
/// <item>
/// <term><c>E_INVALIDARG</c></term>
/// <term>One or more argument is invalid.</term>
/// </item>
/// <item>
/// <term><c>E_NOT_VALID_STATE</c></term>
/// <term>The object is not initialized.</term>
/// </item>
/// </list>
/// </returns>
/// <remarks>
/// <para>Depending on the attribute requested in <c>attribute</c>, the following data should be copied to <c>data</c>:</para>
/// <list type="table">
/// <listheader>
/// <term><c>attribute</c></term>
/// <term><c>data</c></term>
/// </listheader>
/// <item>
/// <term><c>AMSI_ATTRIBUTE_APP_NAME</c></term>
/// <term>The name, version, or GUID string of the calling application, copied from a <c>LPWSTR</c>.</term>
/// </item>
/// <item>
/// <term><c>AMSI_ATTRIBUTE_CONTENT_NAME</c></term>
/// <term>The filename, URL, unique script ID, or similar of the content, copied from a <c>LPWSTR</c>.</term>
/// </item>
/// <item>
/// <term><c>AMSI_ATTRIBUTE_CONTENT_SIZE</c></term>
/// <term>The size of the input, as a <c>ULONGLONG</c>.</term>
/// </item>
/// <item>
/// <term><c>AMSI_ATTRIBUTE_CONTENT_ADDRESS</c></term>
/// <term>The memory address if the content is fully loaded into memory.</term>
/// </item>
/// <item>
/// <term><c>AMSI_ATTRIBUTE_SESSION</c></term>
/// <term>
/// Session is used to associate different scan calls, such as if the contents to be scanned belong to the same original script.
/// Return <c>nullptr</c> if the content is self-contained.
/// </term>
/// </item>
/// </list>
/// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iamsistream-getattribute HRESULT GetAttribute( [in]
// AMSI_ATTRIBUTE attribute, [in] ULONG dataSize, [out] unsigned char *data, [out] ULONG *retData );
[PreserveSig]
HRESULT GetAttribute(AMSI_ATTRIBUTE attribute, uint dataSize, [Out] IntPtr data, out uint retData);
/// <summary>Requests a buffer-full of content to be read.</summary>
/// <param name="position">The zero-based index into the content at which the read is to begin.</param>
/// <param name="size">The number of bytes to read from the content.</param>
/// <param name="buffer">Buffer into which the content is to be read.</param>
/// <param name="readSize">The number of bytes read into <c>buffer</c>.</param>
/// <returns>
/// <para>This method can return one of these values.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term><c>S_OK</c></term>
/// <term>Success.</term>
/// </item>
/// <item>
/// <term><c>E_INVALIDARG</c></term>
/// <term>One or more argument is invalid.</term>
/// </item>
/// <item>
/// <term><c>E_NOT_VALID_STATE</c></term>
/// <term>The object is not initialized.</term>
/// </item>
/// </list>
/// </returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iamsistream-read HRESULT Read( [in] ULONGLONG position, [in]
// ULONG size, [out] unsigned char *buffer, [out] ULONG *readSize );
[PreserveSig]
HRESULT Read(ulong position, uint size, [Out] IntPtr buffer, out uint readSize);
}
/// <summary>Represents the antimalware product.</summary>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nn-amsi-iantimalware
[PInvokeData("amsi.h", MSDNShortId = "NN:amsi.IAntimalware")]
[ComImport, Guid("82d29c2e-f062-44e6-b5c9-3d9a2f24a2df"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), CoClass(typeof(CAntimalware))]
public interface IAntimalware
{
/// <summary>Scan a stream of content.</summary>
/// <param name="stream">The IAmsiStream stream to be scanned.</param>
/// <param name="result">The result of the scan. See AMSI_RESULT.</param>
/// <param name="provider">The IAntimalwareProvider provider of the antimalware product.</param>
/// <returns>
/// <para>This method can return one of these values.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term><c>S_OK</c></term>
/// <term>Success.</term>
/// </item>
/// <item>
/// <term><c>E_INVALIDARG</c></term>
/// <term>One or more argument is invalid.</term>
/// </item>
/// <item>
/// <term><c>E_NOT_VALID_STATE</c></term>
/// <term>The object is not initialized.</term>
/// </item>
/// </list>
/// </returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalware-scan HRESULT Scan( [in] IAmsiStream *stream,
// [out] AMSI_RESULT *result, [out] IAntimalwareProvider **provider );
[PreserveSig]
HRESULT Scan([In] IAmsiStream stream, out AMSI_RESULT result, out IAntimalwareProvider provider);
/// <summary>Closes the session.</summary>
/// <param name="session">
/// <para>Type: ULONGLONG</para>
/// <para>The id/handle of the session to close.</para>
/// </param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalware-closesession void CloseSession( [in] ULONGLONG
// session );
[PreserveSig]
void CloseSession(ulong session);
}
/// <summary>Represents the antimalware product.</summary>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nn-amsi-iantimalware2
[PInvokeData("amsi.h", MSDNShortId = "NN:amsi.IAntimalware2")]
[ComImport, Guid("301035b5-2d42-4f56-8c65-2dcaa7fb3cdc"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), CoClass(typeof(CAntimalware))]
public interface IAntimalware2 : IAntimalware
{
/// <summary>Scan a stream of content.</summary>
/// <param name="stream">The IAmsiStream stream to be scanned.</param>
/// <param name="result">The result of the scan. See AMSI_RESULT.</param>
/// <param name="provider">The IAntimalwareProvider provider of the antimalware product.</param>
/// <returns>
/// <para>This method can return one of these values.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term><c>S_OK</c></term>
/// <term>Success.</term>
/// </item>
/// <item>
/// <term><c>E_INVALIDARG</c></term>
/// <term>One or more argument is invalid.</term>
/// </item>
/// <item>
/// <term><c>E_NOT_VALID_STATE</c></term>
/// <term>The object is not initialized.</term>
/// </item>
/// </list>
/// </returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalware-scan HRESULT Scan( [in] IAmsiStream *stream,
// [out] AMSI_RESULT *result, [out] IAntimalwareProvider **provider );
[PreserveSig]
new HRESULT Scan([In] IAmsiStream stream, out AMSI_RESULT result, out IAntimalwareProvider provider);
/// <summary>Closes the session.</summary>
/// <param name="session">
/// <para>Type: ULONGLONG</para>
/// <para>The id/handle of the session to close.</para>
/// </param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalware-closesession void CloseSession( [in] ULONGLONG
// session );
[PreserveSig]
new void CloseSession(ulong session);
/// <summary>
/// Sends to the antimalware product a notification of an arbitrary operation. The notification doesn't imply the request of an
/// antivirus scan. Rather, <c>IAntimalware2::Notify</c> is designed to provide a quick and lightweight mechanism to communicate
/// to the antimalware product that an event has taken place. In general, the antimalware product should process the
/// notification, and return to the caller as quickly as possible.
/// </summary>
/// <param name="buffer">
/// <para>Type: <c>PVOID</c></para>
/// <para>The buffer that contains the notification data.</para>
/// </param>
/// <param name="length">
/// <para>Type: <c>ULONG</c></para>
/// <para>The length, in bytes, of the data to be read from buffer.</para>
/// </param>
/// <param name="contentName">
/// <para>Type: <c>LPCWSTR</c></para>
/// <para>The filename, URL, unique script ID, or similar of the content being scanned.</para>
/// </param>
/// <param name="appName">
/// <para>Type: <c>LPCWSTR</c></para>
/// <para>The name of the application sending the AMSI notification.</para>
/// </param>
/// <param name="pResult">
/// <para>Type: <c>AMSI_RESULT*</c></para>
/// <para>The result of the scan.</para>
/// </param>
/// <returns>
/// <para>This method can return one of these values.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term>S_OK</term>
/// <term>Success.</term>
/// </item>
/// <item>
/// <term>E_INVALIDARG</term>
/// <term>One or more arguments is invalid.</term>
/// </item>
/// <item>
/// <term>E_NOT_VALID_STATE</term>
/// <term>The object isn't initialized.</term>
/// </item>
/// </list>
/// </returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalware2-notify HRESULT Notify( PVOID buffer, ULONG
// length, LPCWSTR contentName, LPCWSTR appName, AMSI_RESULT *pResult );
[PreserveSig]
HRESULT Notify([In] IntPtr buffer, uint length, [MarshalAs(UnmanagedType.LPWStr)] string contentName,
[MarshalAs(UnmanagedType.LPWStr)] string appName, out AMSI_RESULT pResult);
}
/// <summary>Represents the provider of the antimalware product.</summary>
/// <remarks>
/// <para>
/// As of Windows 10, version 1903, Windows has added a way to enable Authenticode signing checks for providers. The feature is
/// disabled by default, for both 32-bit and 64-bit processes. If you are creating a provider for test purposes, then you can enable
/// or disable sign checks by setting the following Windows Registry value appropriately. The value is a DWORD.
/// </para>
/// <para>
/// <code>Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\AMSI\FeatureBits</code>
/// </para>
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <term>Behavior</term>
/// </listheader>
/// <item>
/// <term>0x1</term>
/// <term>The signing check is disabled. This is the default behavior. You can also use this value, temporarily, while testing.</term>
/// </item>
/// <item>
/// <term>0x2</term>
/// <term>The check for Authenticode signing is enabled.</term>
/// </item>
/// </list>
/// <para>Deleting the registry value altogether behaves as if the value 0x1 were present.</para>
/// <para>
/// <para>Note</para>
/// <para>As a provider, you must use the
/// <code>/ac</code>
/// switch (with the SignTool) to cross-sign with an Authenticode certificate. Once you've signed your binary, you can then verify it
/// by using the SignTool and the
/// <code>/kp</code>
/// option. If the SignTool returns no error, then your binary is properly signed.
/// </para>
/// </para>
/// <para>
/// <para>Important</para>
/// <para>
/// Even though the Windows Registry value is not protected by the operating system, your computer's antivirus provider might protect
/// the value, thus making it write-protected.
/// </para>
/// </para>
/// <para>
/// To check whether or not your provider is loading, you can view code integrity events. Be sure to enable verbose logging of code
/// integrity diagnostic events. The event IDs to look for are 3040 and 3041. Here are some examples.
/// </para>
/// <para>
/// <code>Log Name: Microsoft-Windows-CodeIntegrity/Verbose Source: Microsoft-Windows-CodeIntegrity Date: M/DD/YYYY H:MM:SS PM Event ID: 3040 Task Category: (14) Level: Verbose Keywords: User: [DOMAIN_NAME]\Administrator Computer: [COMPUTER_NAME] Description: Code Integrity started retrieving the cached data of [PATH_AND_FILENAME] file. Event Xml: &lt;Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"&gt; &lt;System&gt; &lt;Provider Name="Microsoft-Windows-CodeIntegrity" Guid="{4ee76bd8-3cf4-44a0-a0ac-3937643e37a3}" /&gt; &lt;EventID&gt;3040&lt;/EventID&gt; &lt;Version&gt;0&lt;/Version&gt; &lt;Level&gt;5&lt;/Level&gt; &lt;Task&gt;14&lt;/Task&gt; &lt;Opcode&gt;1&lt;/Opcode&gt; &lt;Keywords&gt;0x4000000000000000&lt;/Keywords&gt; &lt;TimeCreated SystemTime="YYYY-MM-DDT02:26:48.875954700Z" /&gt; &lt;EventRecordID&gt;7&lt;/EventRecordID&gt; &lt;Correlation /&gt; &lt;Execution ProcessID="4972" ThreadID="7752" ProcessorID="1" KernelTime="14" UserTime="2" /&gt; &lt;Channel&gt;Microsoft-Windows-CodeIntegrity/Verbose&lt;/Channel&gt; &lt;Computer&gt;[COMPUTER_NAME]&lt;/Computer&gt; &lt;Security UserID="[USER_SID]" /&gt; &lt;/System&gt; &lt;EventData&gt; &lt;Data Name="FileNameLength"&gt;40&lt;/Data&gt; &lt;Data Name="FileNameBuffer"&gt;[PATH_AND_FILENAME]&lt;/Data&gt; &lt;/EventData&gt; &lt;/Event&gt;</code>
/// </para>
/// <para>
/// <code>Log Name: Microsoft-Windows-CodeIntegrity/Verbose Source: Microsoft-Windows-CodeIntegrity Date: M/DD/YYYY H:MM:SS PM Event ID: 3041 Task Category: (14) Level: Verbose Keywords: User: [DOMAIN_NAME]\Administrator Computer: [COMPUTER_NAME] Description: Code Integrity completed retrieval of file cache. Status 0xC0000225. Event Xml: &lt;Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"&gt; &lt;System&gt; &lt;Provider Name="Microsoft-Windows-CodeIntegrity" Guid="{4ee76bd8-3cf4-44a0-a0ac-3937643e37a3}" /&gt; &lt;EventID&gt;3041&lt;/EventID&gt; &lt;Version&gt;2&lt;/Version&gt; &lt;Level&gt;5&lt;/Level&gt; &lt;Task&gt;14&lt;/Task&gt; &lt;Opcode&gt;2&lt;/Opcode&gt; &lt;Keywords&gt;0x4000000000000000&lt;/Keywords&gt; &lt;TimeCreated SystemTime="YYYY-MM-DDT02:26:48.875964700Z" /&gt; &lt;EventRecordID&gt;8&lt;/EventRecordID&gt; &lt;Correlation /&gt; &lt;Execution ProcessID="4972" ThreadID="7752" ProcessorID="1" KernelTime="14" UserTime="2" /&gt; &lt;Channel&gt;Microsoft-Windows-CodeIntegrity/Verbose&lt;/Channel&gt; &lt;Computer&gt;[COMPUTER_NAME]&lt;/Computer&gt; &lt;Security UserID="[USER_SID]" /&gt; &lt;/System&gt; &lt;EventData&gt; &lt;Data Name="Status"&gt;0xc0000225&lt;/Data&gt; &lt;Data Name="CachedFlags"&gt;0x0&lt;/Data&gt; &lt;Data Name="CacheSource"&gt;0&lt;/Data&gt; &lt;Data Name="CachedPolicy"&gt;0&lt;/Data&gt; &lt;/EventData&gt; &lt;/Event&gt;</code>
/// </para>
/// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nn-amsi-iantimalwareprovider
[PInvokeData("amsi.h", MSDNShortId = "NN:amsi.IAntimalwareProvider")]
[ComImport, Guid("b2cabfe3-fe04-42b1-a5df-08d483d4d125"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAntimalwareProvider
{
/// <summary>Scan a stream of content.</summary>
/// <param name="stream">The IAmsiStream stream to be scanned.</param>
/// <param name="result">The result of the scan. See AMSI_RESULT.</param>
/// <returns>
/// <para>This method can return one of these values.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term><c>S_OK</c></term>
/// <term>Success.</term>
/// </item>
/// <item>
/// <term><c>E_INVALIDARG</c></term>
/// <term>One or more argument is invalid.</term>
/// </item>
/// <item>
/// <term><c>E_NOT_VALID_STATE</c></term>
/// <term>The object is not initialized.</term>
/// </item>
/// </list>
/// </returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalwareprovider-scan HRESULT Scan( [in] IAmsiStream
// *stream, [out] AMSI_RESULT *result );
[PreserveSig]
HRESULT Scan(IAmsiStream stream, out AMSI_RESULT result);
/// <summary>Closes the session.</summary>
/// <param name="session">
/// <para>Type: ULONGLONG</para>
/// <para>The id/handle of the session to close.</para>
/// </param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalwareprovider-closesession void CloseSession( [in]
// ULONGLONG session );
[PreserveSig]
void CloseSession(ulong session);
/// <summary>The name of the antimalware provider to be displayed.</summary>
/// <param name="displayName">A pointer to a <c>LPWSTR</c> that contains the display name.</param>
/// <returns>
/// <para>This method can return one of these values.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term><c>S_OK</c></term>
/// <term>Success.</term>
/// </item>
/// <item>
/// <term><c>E_INVALIDARG</c></term>
/// <term>The argument is invalid.</term>
/// </item>
/// <item>
/// <term><c>E_NOT_VALID_STATE</c></term>
/// <term>The object is not initialized.</term>
/// </item>
/// </list>
/// </returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalwareprovider-displayname HRESULT DisplayName( [out]
// LPWSTR *displayName );
[PreserveSig]
HRESULT DisplayName([MarshalAs(UnmanagedType.LPWStr)] out string displayName);
}
/// <summary>Represents the provider of the antimalware product.</summary>
/// <remarks>See <c>Remarks</c> in the IAntimalwareProvider interface topic.</remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nn-amsi-iantimalwareprovider2
[PInvokeData("amsi.h", MSDNShortId = "NN:amsi.IAntimalwareProvider2")]
[ComImport, Guid("7c1e6570-3f73-4e0f-8ad4-98b94cd3290f"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAntimalwareProvider2 : IAntimalwareProvider
{
/// <summary>Scan a stream of content.</summary>
/// <param name="stream">The IAmsiStream stream to be scanned.</param>
/// <param name="result">The result of the scan. See AMSI_RESULT.</param>
/// <returns>
/// <para>This method can return one of these values.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term><c>S_OK</c></term>
/// <term>Success.</term>
/// </item>
/// <item>
/// <term><c>E_INVALIDARG</c></term>
/// <term>One or more argument is invalid.</term>
/// </item>
/// <item>
/// <term><c>E_NOT_VALID_STATE</c></term>
/// <term>The object is not initialized.</term>
/// </item>
/// </list>
/// </returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalwareprovider-scan HRESULT Scan( [in] IAmsiStream
// *stream, [out] AMSI_RESULT *result );
[PreserveSig]
new HRESULT Scan(IAmsiStream stream, out AMSI_RESULT result);
/// <summary>Closes the session.</summary>
/// <param name="session">
/// <para>Type: ULONGLONG</para>
/// <para>The id/handle of the session to close.</para>
/// </param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalwareprovider-closesession void CloseSession( [in]
// ULONGLONG session );
[PreserveSig]
new void CloseSession(ulong session);
/// <summary>The name of the antimalware provider to be displayed.</summary>
/// <param name="displayName">A pointer to a <c>LPWSTR</c> that contains the display name.</param>
/// <returns>
/// <para>This method can return one of these values.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term><c>S_OK</c></term>
/// <term>Success.</term>
/// </item>
/// <item>
/// <term><c>E_INVALIDARG</c></term>
/// <term>The argument is invalid.</term>
/// </item>
/// <item>
/// <term><c>E_NOT_VALID_STATE</c></term>
/// <term>The object is not initialized.</term>
/// </item>
/// </list>
/// </returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalwareprovider-displayname HRESULT DisplayName( [out]
// LPWSTR *displayName );
[PreserveSig]
new HRESULT DisplayName([MarshalAs(UnmanagedType.LPWStr)] out string displayName);
/// <summary>
/// Sends to the antimalware provider a notification of an arbitrary operation. The notification doesn't imply the request of an
/// antivirus scan. Rather, <c>IAntimalwareProvider2::Notify</c> is designed to provide a quick and lightweight mechanism to
/// communicate to the antimalware provider that an event has taken place. In general, the antimalware provider should process
/// the notification, and return to the caller as quickly as possible.
/// </summary>
/// <param name="buffer">
/// <para>Type: <c>PVOID</c></para>
/// <para>The buffer that contains the notification data.</para>
/// </param>
/// <param name="length">
/// <para>Type: <c>ULONG</c></para>
/// <para>The length, in bytes, of the data to be read from buffer.</para>
/// </param>
/// <param name="contentName">
/// <para>Type: <c>LPCWSTR</c></para>
/// <para>The filename, URL, unique script ID, or similar of the content being scanned.</para>
/// </param>
/// <param name="appName">
/// <para>Type: <c>LPCWSTR</c></para>
/// <para>The name of the application sending the AMSI notification.</para>
/// </param>
/// <param name="pResult">
/// <para>Type: <c>AMSI_RESULT*</c></para>
/// <para>The result of the scan.</para>
/// </param>
/// <returns>
/// <para>This method can return one of these values.</para>
/// <list type="table">
/// <listheader>
/// <term>Return code</term>
/// <term>Description</term>
/// </listheader>
/// <item>
/// <term>S_OK</term>
/// <term>Success.</term>
/// </item>
/// <item>
/// <term>E_INVALIDARG</term>
/// <term>One or more arguments is invalid.</term>
/// </item>
/// <item>
/// <term>E_NOT_VALID_STATE</term>
/// <term>The object isn't initialized.</term>
/// </item>
/// </list>
/// </returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalwareprovider2-notify HRESULT Notify( PVOID buffer,
// ULONG length, LPCWSTR contentName, LPCWSTR appName, AMSI_RESULT *pResult );
[PreserveSig]
HRESULT Notify(IntPtr buffer, uint length, [MarshalAs(UnmanagedType.LPWStr)] string contentName,
[MarshalAs(UnmanagedType.LPWStr)] string appName, out AMSI_RESULT pResult);
}
/// <summary>Memory based stream that implements IAmsiStream.</summary>
/// <seealso cref="System.IO.MemoryStream"/>
/// <seealso cref="Vanara.PInvoke.AMSI.IAmsiStream"/>
public class AmsiStream : NativeMemoryStream, IAmsiStream
{
private static readonly HRESULT E_INSUFF_BUFFER = HRESULT.HRESULT_FROM_WIN32(Win32Error.ERROR_INSUFFICIENT_BUFFER);
/// <summary>Initializes a new instance of the <see cref="AmsiStream"/> class.</summary>
public AmsiStream() : base()
{
}
/// <summary>Initializes a new instance of the <see cref="AmsiStream"/> class with an initial capacity.</summary>
/// <param name="capacity">The capacity.</param>
public AmsiStream(int capacity) : base(capacity)
{
}
/// <summary>Initializes a new instance of the <see cref="AmsiStream"/> class and inserts the contents of a buffer.</summary>
/// <param name="buffer">The buffer to copy.</param>
/// <param name="writable">if set to <see langword="true"/>, the stream is read-write; if <see langword="false"/>, it is read-only.</param>
public AmsiStream(byte[] buffer, bool writable) : base(new SafeCoTaskMemHandle(buffer), access: writable ? FileAccess.ReadWrite : FileAccess.Read)
{
}
/// <summary>Initializes a new instance of the <see cref="AmsiStream"/> class with file information.</summary>
/// <param name="file">The file information.</param>
/// <param name="writable">if set to <see langword="true"/>, the stream is read-write; if <see langword="false"/>, it is read-only.</param>
public AmsiStream(FileInfo file, bool writable) : this(file is null ? null : File.ReadAllBytes(file.FullName), writable) => ContentName = file.FullName;
/// <summary>Initializes a new instance of the <see cref="AmsiStream"/> class.</summary>
/// <param name="mem">The memory allocator used to create and extend the native memory.</param>
/// <param name="writable">if set to <see langword="true"/>, the stream is read-write; if <see langword="false"/>, it is read-only.</param>
public AmsiStream(SafeAllocatedMemoryHandle mem, bool writable) : this(mem.GetBytes(), writable)
{
}
/// <summary>Gets or sets the name, version, or GUID string of the calling application.</summary>
public string AppName { get; set; }
/// <summary>Gets or sets the filename, URL, unique script ID, or similar of the content.</summary>
public string ContentName { get; set; }
/// <summary>
/// Gets or sets the session is used to associate different scan calls, such as if the contents to be scanned belong to the
/// sample original script.
/// </summary>
public IntPtr Session { get; set; }
HRESULT IAmsiStream.GetAttribute(AMSI_ATTRIBUTE attribute, uint dataSize, IntPtr data, out uint retData)
{
byte[] bytes = attribute switch
{
AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_APP_NAME => StringHelper.GetBytes(AppName, true, CharSet.Unicode),
AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_CONTENT_NAME => StringHelper.GetBytes(ContentName, true, CharSet.Unicode),
AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_CONTENT_SIZE => BitConverter.GetBytes((ulong)Length),
AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_CONTENT_ADDRESS => IntPtr.Size == 8 ? BitConverter.GetBytes(Pointer.ToInt64()) : BitConverter.GetBytes(Pointer.ToInt32()),
AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_SESSION => IntPtr.Size == 8 ? BitConverter.GetBytes(Session.ToInt64()) : BitConverter.GetBytes(Session.ToInt32()),
_ => null,
};
if (bytes is not null)
{
retData = (uint)bytes.Length;
if (bytes.Length > dataSize)
return E_INSUFF_BUFFER;
Marshal.Copy(bytes, 0, data, bytes.Length);
return HRESULT.S_OK;
}
retData = 0;
return HRESULT.E_NOTIMPL;
}
HRESULT IAmsiStream.Read(ulong position, uint size, IntPtr buffer, out uint readSize)
{
readSize = 0;
if (buffer == IntPtr.Zero || (long)position > Length)
{
return HRESULT.E_INVALIDARG;
}
long bytesToRead = Math.Min(Length - (long)position, size);
if (bytesToRead > 0)
{
Pointer.Offset((long)position).CopyTo(buffer, bytesToRead);
}
readSize = (uint)bytesToRead;
return HRESULT.S_OK;
}
}
/// <summary>CLSID_Antimalware</summary>
[ComImport, Guid("fdb00e52-a214-4aa1-8fba-4357bb0072ec"), ClassInterface(ClassInterfaceType.None)]
public class CAntimalware { }
}
}

393
PInvoke/AMSI/amsi.cs Normal file
View File

@ -0,0 +1,393 @@
using System;
using System.Runtime.InteropServices;
namespace Vanara.PInvoke
{
/// <summary>Items from the AMSI.dll.</summary>
public static partial class AMSI
{
private const string Lib_Amsi = "amsi.dll";
/// <summary>The <c>AMSI_ATTRIBUTE</c> enumeration specifies the types of attributes that can be requested by IAmsiStream::GetAttribute.</summary>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/ne-amsi-amsi_attribute typedef enum AMSI_ATTRIBUTE {
// AMSI_ATTRIBUTE_APP_NAME, AMSI_ATTRIBUTE_CONTENT_NAME, AMSI_ATTRIBUTE_CONTENT_SIZE, AMSI_ATTRIBUTE_CONTENT_ADDRESS,
// AMSI_ATTRIBUTE_SESSION, AMSI_ATTRIBUTE_REDIRECT_CHAIN_SIZE, AMSI_ATTRIBUTE_REDIRECT_CHAIN_ADDRESS, AMSI_ATTRIBUTE_ALL_SIZE,
// AMSI_ATTRIBUTE_ALL_ADDRESS, AMSI_ATTRIBUTE_QUIET } ;
[PInvokeData("amsi.h", MSDNShortId = "NE:amsi.AMSI_ATTRIBUTE")]
public enum AMSI_ATTRIBUTE
{
/// <summary>
/// Return the name, version, or GUID string of the calling application, copied from a <c>LPWSTR</c>.
/// </summary>
AMSI_ATTRIBUTE_APP_NAME = 0,
/// <summary>
/// Return the filename, URL, unique script ID, or similar of the content, copied from a <c>LPWSTR</c>.
/// </summary>
AMSI_ATTRIBUTE_CONTENT_NAME,
/// <summary>
/// Return the size of the input, as a <c>ULONGLONG</c>.
/// </summary>
AMSI_ATTRIBUTE_CONTENT_SIZE,
/// <summary>Return the memory address if the content is fully loaded into memory.</summary>
AMSI_ATTRIBUTE_CONTENT_ADDRESS,
/// <summary>
/// Session is used to associate different scan calls, such as if the contents to be scanned belong to the sample original
/// script. Return a <c>PVOID</c> to the next portion of the content to be scanned. Return NULL if the content is self-contained.
/// </summary>
AMSI_ATTRIBUTE_SESSION,
/// <summary/>
AMSI_ATTRIBUTE_REDIRECT_CHAIN_SIZE,
/// <summary/>
AMSI_ATTRIBUTE_REDIRECT_CHAIN_ADDRESS,
/// <summary/>
AMSI_ATTRIBUTE_ALL_SIZE,
/// <summary/>
AMSI_ATTRIBUTE_ALL_ADDRESS,
/// <summary/>
AMSI_ATTRIBUTE_QUIET,
}
/// <summary>The <c>AMSI_RESULT</c> enumeration specifies the types of results returned by scans.</summary>
/// <remarks>
/// <para>
/// The antimalware provider may return a result between 1 and 32767, inclusive, as an estimated risk level. The larger the result,
/// the riskier it is to continue with the content. These values are provider specific, and may indicate a malware family or ID.
/// </para>
/// <para>
/// Results within the range of <c>AMSI_RESULT_BLOCKED_BY_ADMIN_START</c> and <c>AMSI_RESULT_BLOCKED_BY_ADMIN_END</c> values
/// (inclusive) are officially blocked by the admin specified policy. In these cases, the script in question will be blocked from
/// executing. The range is large to accommodate future additions in functionality.
/// </para>
/// <para>
/// Any return result equal to or larger than 32768 is considered malware, and the content should be blocked. An app should use
/// AmsiResultIsMalware to determine if this is the case.
/// </para>
/// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/ne-amsi-amsi_result typedef enum AMSI_RESULT { AMSI_RESULT_CLEAN,
// AMSI_RESULT_NOT_DETECTED, AMSI_RESULT_BLOCKED_BY_ADMIN_START, AMSI_RESULT_BLOCKED_BY_ADMIN_END, AMSI_RESULT_DETECTED } ;
[PInvokeData("amsi.h", MSDNShortId = "NE:amsi.AMSI_RESULT")]
public enum AMSI_RESULT : uint
{
/// <summary>Known good. No detection found, and the result is likely not going to change after a future definition update.</summary>
AMSI_RESULT_CLEAN = 0,
/// <summary>No detection found, but the result might change after a future definition update.</summary>
AMSI_RESULT_NOT_DETECTED = 1,
/// <summary>Administrator policy blocked this content on this machine (beginning of range).</summary>
AMSI_RESULT_BLOCKED_BY_ADMIN_START = 0x4000,
/// <summary>Administrator policy blocked this content on this machine (end of range).</summary>
AMSI_RESULT_BLOCKED_BY_ADMIN_END = 0x4fff,
/// <summary>Detection found. The content is considered malware and should be blocked.</summary>
AMSI_RESULT_DETECTED = 32768,
}
/// <summary>Close a session that was opened by AmsiOpenSession.</summary>
/// <param name="amsiContext">The handle of type HAMSICONTEXT that was initially received from AmsiInitialize.</param>
/// <param name="amsiSession">The handle of type HAMSISESSION that was initially received from AmsiOpenSession.</param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiclosesession void AmsiCloseSession( [in] HAMSICONTEXT
// amsiContext, [in] HAMSISESSION amsiSession );
[DllImport(Lib_Amsi, SetLastError = false, ExactSpelling = true)]
[PInvokeData("amsi.h", MSDNShortId = "NF:amsi.AmsiCloseSession")]
public static extern void AmsiCloseSession([In] HAMSICONTEXT amsiContext, [In] HAMSISESSION amsiSession);
/// <summary>Initialize the AMSI API.</summary>
/// <param name="appName">The name, version, or GUID string of the app calling the AMSI API.</param>
/// <param name="amsiContext">A handle of type HAMSICONTEXT that must be passed to all subsequent calls to the AMSI API.</param>
/// <returns>If this function succeeds, it returns <c>S_OK</c>. Otherwise, it returns an <c>HRESULT</c> error code.</returns>
/// <remarks>When the app is finished with the AMSI API it must call AmsiUninitialize.</remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiinitialize HRESULT AmsiInitialize( [in] LPCWSTR appName, [out]
// HAMSICONTEXT *amsiContext );
[DllImport(Lib_Amsi, SetLastError = false, ExactSpelling = true)]
[PInvokeData("amsi.h", MSDNShortId = "NF:amsi.AmsiInitialize")]
public static extern HRESULT AmsiInitialize([MarshalAs(UnmanagedType.LPWStr)] string appName, out SafeHAMSICONTEXT amsiContext);
/// <summary>
/// Sends to the antimalware provider a notification of an arbitrary operation. The notification doesn't imply the request of an
/// antivirus scan. Rather, <c>IAntimalwareProvider2::Notify</c> is designed to provide a quick and lightweight mechanism to
/// communicate to the antimalware provider that an event has taken place. In general, the antimalware provider should process the
/// notification, and return to the caller as quickly as possible.
/// </summary>
/// <param name="amsiContext">
/// <para>Type: _In_ <c>HAMSICONTEXT</c></para>
/// <para>The handle (of type <c>HAMSICONTEXT</c>) that was initially received from AmsiInitialize.</para>
/// </param>
/// <param name="buffer">
/// <para>Type: _In_reads_bytes_(length) <c>PVOID</c></para>
/// <para>The buffer that contains the notification data.</para>
/// </param>
/// <param name="length">
/// <para>Type: _In_ <c>ULONG</c></para>
/// <para>The length, in bytes, of the data to be read from buffer.</para>
/// </param>
/// <param name="contentName">
/// <para>Type: _In_opt_ <c>LPCWSTR</c></para>
/// <para>The filename, URL, unique script ID, or similar of the content being scanned.</para>
/// </param>
/// <param name="result">
/// <para>Type: _Out_ <c>AMSI_RESULT*</c></para>
/// <para>The result of the scan.</para>
/// <para>You should use AmsiResultIsMalware to determine whether the content should be blocked.</para>
/// </param>
/// <returns>If this function succeeds, it returns <c>S_OK</c>. Otherwise, it returns an <c>HRESULT</c> error code.</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsinotifyoperation HRESULT AmsiNotifyOperation( HAMSICONTEXT
// amsiContext, PVOID buffer, ULONG length, LPCWSTR contentName, AMSI_RESULT *result );
[DllImport(Lib_Amsi, SetLastError = false, ExactSpelling = true)]
[PInvokeData("amsi.h", MSDNShortId = "NF:amsi.AmsiNotifyOperation")]
public static extern HRESULT AmsiNotifyOperation([In] HAMSICONTEXT amsiContext, [In] IntPtr buffer, [In] uint length,
[Optional, MarshalAs(UnmanagedType.LPWStr)] string contentName, out AMSI_RESULT result);
/// <summary>Opens a session within which multiple scan requests can be correlated.</summary>
/// <param name="amsiContext">The handle of type HAMSICONTEXT that was initially received from AmsiInitialize.</param>
/// <param name="amsiSession">
/// A handle of type HAMSISESSION that must be passed to all subsequent calls to the AMSI API within the session.
/// </param>
/// <returns>If this function succeeds, it returns <c>S_OK</c>. Otherwise, it returns an <c>HRESULT</c> error code.</returns>
/// <remarks>When the app is finished with the session it must call AmsiCloseSession.</remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiopensession HRESULT AmsiOpenSession( [in] HAMSICONTEXT
// amsiContext, [out] HAMSISESSION *amsiSession );
public static HRESULT AmsiOpenSession([In] HAMSICONTEXT amsiContext, out SafeHAMSISESSION amsiSession)
{
HRESULT hr = AmsiOpenSessionInternal(amsiContext, out HAMSISESSION h);
amsiSession = hr.Succeeded ? new SafeHAMSISESSION((IntPtr)h, true) : new SafeHAMSISESSION(IntPtr.Zero, false);
return hr;
}
/// <summary>Determines if the result of a scan indicates that the content should be blocked.</summary>
/// <param name="r">The AMSI_RESULT returned by AmsiScanBuffer or AmsiScanString.</param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiresultismalware void AmsiResultIsMalware( [in] r );
[PInvokeData("amsi.h", MSDNShortId = "NF:amsi.AmsiResultIsMalware")]
public static bool AmsiResultIsMalware(AMSI_RESULT r) => r >= AMSI_RESULT.AMSI_RESULT_DETECTED;
/// <summary>Scans a buffer-full of content for malware.</summary>
/// <param name="amsiContext">The handle of type HAMSICONTEXT that was initially received from AmsiInitialize.</param>
/// <param name="buffer">The buffer from which to read the data to be scanned.</param>
/// <param name="length">The length, in bytes, of the data to be read from <c>buffer</c>.</param>
/// <param name="contentName">The filename, URL, unique script ID, or similar of the content being scanned.</param>
/// <param name="amsiSession">
/// If multiple scan requests are to be correlated within a session, set <c>session</c> to the handle of type HAMSISESSION that was
/// initially received from AmsiOpenSession. Otherwise, set <c>session</c> to <c>nullptr</c>.
/// </param>
/// <param name="result">
/// <para>The result of the scan. See AMSI_RESULT.</para>
/// <para>An app should use AmsiResultIsMalware to determine whether the content should be blocked.</para>
/// </param>
/// <returns>If this function succeeds, it returns <c>S_OK</c>. Otherwise, it returns an <c>HRESULT</c> error code.</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiscanbuffer HRESULT AmsiScanBuffer( [in] HAMSICONTEXT
// amsiContext, [in] PVOID buffer, [in] ULONG length, [in] LPCWSTR contentName, [in, optional] HAMSISESSION amsiSession, [out]
// AMSI_RESULT *result );
[DllImport(Lib_Amsi, SetLastError = false, ExactSpelling = true)]
[PInvokeData("amsi.h", MSDNShortId = "NF:amsi.AmsiScanBuffer")]
public static extern HRESULT AmsiScanBuffer([In] HAMSICONTEXT amsiContext, [In] IntPtr buffer, uint length,
[Optional, MarshalAs(UnmanagedType.LPWStr)] string contentName, [In, Optional] HAMSISESSION amsiSession, out AMSI_RESULT result);
/// <summary>Scans a string for malware.</summary>
/// <param name="amsiContext">The handle of type HAMSICONTEXT that was initially received from AmsiInitialize.</param>
/// <param name="str">The string to be scanned.</param>
/// <param name="contentName">The filename, URL, unique script ID, or similar of the content being scanned.</param>
/// <param name="amsiSession">
/// If multiple scan requests are to be correlated within a session, set <c>session</c> to the handle of type HAMSISESSION that was
/// initially received from AmsiOpenSession. Otherwise, set <c>session</c> to <c>nullptr</c>.
/// </param>
/// <param name="result">
/// <para>The result of the scan. See AMSI_RESULT.</para>
/// <para>An app should use AmsiResultIsMalware to determine whether the content should be blocked.</para>
/// </param>
/// <returns>If this function succeeds, it returns <c>S_OK</c>. Otherwise, it returns an <c>HRESULT</c> error code.</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiscanstring HRESULT AmsiScanString( [in] HAMSICONTEXT
// amsiContext, [in] LPCWSTR string, [in] LPCWSTR contentName, [in, optional] HAMSISESSION amsiSession, [out] AMSI_RESULT *result );
[DllImport(Lib_Amsi, SetLastError = false, ExactSpelling = true)]
[PInvokeData("amsi.h", MSDNShortId = "NF:amsi.AmsiScanString")]
public static extern HRESULT AmsiScanString(HAMSICONTEXT amsiContext, [MarshalAs(UnmanagedType.LPWStr)] string str,
[Optional, MarshalAs(UnmanagedType.LPWStr)] string contentName, [In, Optional] HAMSISESSION amsiSession, out AMSI_RESULT result);
/// <summary>Remove the instance of the AMSI API that was originally opened by AmsiInitialize.</summary>
/// <param name="amsiContext">The handle of type HAMSICONTEXT that was initially received from AmsiInitialize.</param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiuninitialize void AmsiUninitialize( [in] HAMSICONTEXT
// amsiContext );
[DllImport(Lib_Amsi, SetLastError = false, ExactSpelling = true)]
[PInvokeData("amsi.h", MSDNShortId = "NF:amsi.AmsiUninitialize")]
public static extern void AmsiUninitialize(HAMSICONTEXT amsiContext);
[DllImport(Lib_Amsi, SetLastError = false, EntryPoint = "AmsiOpenSession")]
private static extern HRESULT AmsiOpenSessionInternal([In] HAMSICONTEXT amsiContext, out HAMSISESSION amsiSession);
/// <summary>Provides a handle to an AMSI context.</summary>
[StructLayout(LayoutKind.Sequential)]
public struct HAMSICONTEXT : IHandle
{
private readonly IntPtr handle;
/// <summary>Initializes a new instance of the <see cref="HAMSICONTEXT"/> struct.</summary>
/// <param name="preexistingHandle">An <see cref="IntPtr"/> object that represents the pre-existing handle to use.</param>
public HAMSICONTEXT(IntPtr preexistingHandle) => handle = preexistingHandle;
/// <summary>Returns an invalid handle by instantiating a <see cref="HAMSICONTEXT"/> object with <see cref="IntPtr.Zero"/>.</summary>
public static HAMSICONTEXT NULL { get; } = default;
/// <summary>Gets a value indicating whether this instance is a null handle.</summary>
public bool IsNull => handle == IntPtr.Zero;
/// <summary>Performs an explicit conversion from <see cref="HAMSICONTEXT"/> to <see cref="IntPtr"/>.</summary>
/// <param name="h">The handle.</param>
/// <returns>The result of the conversion.</returns>
public static explicit operator IntPtr(HAMSICONTEXT h) => h.handle;
/// <summary>Performs an implicit conversion from <see cref="IntPtr"/> to <see cref="HAMSICONTEXT"/>.</summary>
/// <param name="h">The pointer to a handle.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator HAMSICONTEXT(IntPtr h) => new(h);
/// <summary>Implements the operator !=.</summary>
/// <param name="h1">The first handle.</param>
/// <param name="h2">The second handle.</param>
/// <returns>The result of the operator.</returns>
public static bool operator !=(HAMSICONTEXT h1, HAMSICONTEXT h2) => h1.handle != h2.handle;
/// <summary>Implements the operator ==.</summary>
/// <param name="h1">The first handle.</param>
/// <param name="h2">The second handle.</param>
/// <returns>The result of the operator.</returns>
public static bool operator ==(HAMSICONTEXT h1, HAMSICONTEXT h2) => h1.handle == h2.handle;
/// <inheritdoc/>
public override bool Equals(object obj) => (obj is IHandle h && handle == h.DangerousGetHandle()) || (obj is IntPtr p && handle == p);
/// <inheritdoc/>
public override int GetHashCode() => handle.GetHashCode();
/// <inheritdoc/>
public IntPtr DangerousGetHandle() => handle;
}
/// <summary>Provides a handle to an AMSI session.</summary>
[StructLayout(LayoutKind.Sequential)]
public struct HAMSISESSION : IHandle
{
private readonly IntPtr handle;
/// <summary>Initializes a new instance of the <see cref="HAMSISESSION"/> struct.</summary>
/// <param name="preexistingHandle">An <see cref="IntPtr"/> object that represents the pre-existing handle to use.</param>
public HAMSISESSION(IntPtr preexistingHandle) => handle = preexistingHandle;
/// <summary>Returns an invalid handle by instantiating a <see cref="HAMSISESSION"/> object with <see cref="IntPtr.Zero"/>.</summary>
public static HAMSISESSION NULL { get; } = default;
/// <summary>Gets a value indicating whether this instance is a null handle.</summary>
public bool IsNull => handle == IntPtr.Zero;
/// <summary>Performs an explicit conversion from <see cref="HAMSISESSION"/> to <see cref="IntPtr"/>.</summary>
/// <param name="h">The handle.</param>
/// <returns>The result of the conversion.</returns>
public static explicit operator IntPtr(HAMSISESSION h) => h.handle;
/// <summary>Performs an implicit conversion from <see cref="IntPtr"/> to <see cref="HAMSISESSION"/>.</summary>
/// <param name="h">The pointer to a handle.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator HAMSISESSION(IntPtr h) => new(h);
/// <summary>Implements the operator !=.</summary>
/// <param name="h1">The first handle.</param>
/// <param name="h2">The second handle.</param>
/// <returns>The result of the operator.</returns>
public static bool operator !=(HAMSISESSION h1, HAMSISESSION h2) => h1.handle != h2.handle;
/// <summary>Implements the operator ==.</summary>
/// <param name="h1">The first handle.</param>
/// <param name="h2">The second handle.</param>
/// <returns>The result of the operator.</returns>
public static bool operator ==(HAMSISESSION h1, HAMSISESSION h2) => h1.handle == h2.handle;
/// <inheritdoc/>
public override bool Equals(object obj) => (obj is IHandle h && handle == h.DangerousGetHandle()) || (obj is IntPtr p && handle == p);
/// <inheritdoc/>
public override int GetHashCode() => handle.GetHashCode();
/// <inheritdoc/>
public IntPtr DangerousGetHandle() => handle;
}
/// <summary>Provides a <see cref="SafeHandle"/> for <see cref="HAMSICONTEXT"/> that is disposed using <see cref="AmsiUninitialize"/>.</summary>
public class SafeHAMSICONTEXT : SafeHANDLE
{
/// <summary>Initializes a new instance of the <see cref="SafeHAMSICONTEXT"/> class and assigns an existing handle.</summary>
/// <param name="preexistingHandle">An <see cref="IntPtr"/> object that represents the pre-existing handle to use.</param>
/// <param name="ownsHandle">
/// <see langword="true"/> to reliably release the handle during the finalization phase; otherwise, <see langword="false"/> (not recommended).
/// </param>
public SafeHAMSICONTEXT(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { }
/// <summary>Initializes a new instance of the <see cref="SafeHAMSICONTEXT"/> class.</summary>
private SafeHAMSICONTEXT() : base() { }
/// <summary>Performs an implicit conversion from <see cref="SafeHAMSICONTEXT"/> to <see cref="HAMSICONTEXT"/>.</summary>
/// <param name="h">The safe handle instance.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator HAMSICONTEXT(SafeHAMSICONTEXT h) => h.handle;
/// <inheritdoc/>
protected override bool InternalReleaseHandle() { AmsiUninitialize(handle); return true; }
}
/// <summary>Provides a <see cref="SafeHandle"/> for <see cref="HAMSISESSION"/> that is disposed using <see cref="AmsiCloseSession"/>.</summary>
public class SafeHAMSISESSION : SafeHANDLE
{
private SafeHAMSICONTEXT ctx;
/// <summary>Initializes a new instance of the <see cref="SafeHAMSISESSION"/> class and assigns an existing handle.</summary>
/// <param name="preexistingHandle">An <see cref="IntPtr"/> object that represents the pre-existing handle to use.</param>
/// <param name="ownsHandle">
/// <see langword="true"/> to reliably release the handle during the finalization phase; otherwise, <see langword="false"/> (not recommended).
/// </param>
public SafeHAMSISESSION(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { }
/// <summary>Initializes a new instance of the <see cref="SafeHAMSISESSION"/> class.</summary>
/// <param name="context">The context.</param>
public SafeHAMSISESSION(HAMSICONTEXT context) : base() => Open(Context = context);
/// <summary>Initializes a new instance of the <see cref="SafeHAMSISESSION"/> class.</summary>
/// <param name="appName">The name, version, or GUID string of the app calling the AMSI API.</param>
public SafeHAMSISESSION(string appName) : base()
{
AmsiInitialize(appName, out SafeHAMSICONTEXT hc).ThrowIfFailed();
Open(ctx = hc);
}
/// <summary>Initializes a new instance of the <see cref="SafeHAMSISESSION"/> class.</summary>
private SafeHAMSISESSION() : base() { }
/// <summary>Gets or sets the handle of type HAMSICONTEXT that was initially received from AmsiInitialize.</summary>
/// <value>The context handle.</value>
public HAMSICONTEXT Context { get => ctx; set => ctx = new SafeHAMSICONTEXT((IntPtr)value, false); }
/// <summary>Performs an implicit conversion from <see cref="SafeHAMSISESSION"/> to <see cref="HAMSISESSION"/>.</summary>
/// <param name="h">The safe handle instance.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator HAMSISESSION(SafeHAMSISESSION h) => h.handle;
/// <inheritdoc/>
protected override bool InternalReleaseHandle() { AmsiCloseSession(Context, handle); ctx?.Dispose(); return true; }
private void Open(HAMSICONTEXT context)
{
AmsiOpenSessionInternal(context, out HAMSISESSION h).ThrowIfFailed();
SetHandle((IntPtr)h);
}
}
}
}

View File

@ -0,0 +1,116 @@
using System;
using System.IO;
using Vanara.InteropServices;
using static Vanara.PInvoke.AMSI;
namespace Vanara.PInvoke.Diagnostics
{
/// <summary>The <c>ScanResult</c> enumeration specifies the types of results returned by scans.</summary>
public enum ScanResult : uint
{
/// <summary>Known good. No detection found, and the result is likely not going to change after a future definition update.</summary>
Clean = AMSI_RESULT.AMSI_RESULT_CLEAN,
/// <summary>No detection found, but the result might change after a future definition update.</summary>
NotDetected = AMSI_RESULT.AMSI_RESULT_NOT_DETECTED,
/// <summary>A threat level less than the max was found, so there is a potential that the content is considered malware.</summary>
PotentialDetected = AMSI_RESULT.AMSI_RESULT_NOT_DETECTED + 1,
/// <summary>Detection found. The content is considered malware and should be blocked.</summary>
Detected = AMSI_RESULT.AMSI_RESULT_DETECTED,
}
/// <summary>Provides scanning of strings and buffers to detect malware using either the system provider or a custom provider.</summary>
public static class AntimalwareScan
{
private static SafeHAMSICONTEXT hCtx;
/// <summary>
/// Gets or sets the provider to use for Antimalware scans. If <see langword="null"/>, the system default provider is used.
/// </summary>
/// <value>The Antimalware scan provider.</value>
public static IAntimalwareProvider Provider { get; set; }
/// <summary>Scans a buffer-full of content for malware.</summary>
/// <param name="buffer">The buffer from which to read the data to be scanned.</param>
/// <param name="contentName">The filename, URL, unique script ID, or similar of the content being scanned.</param>
/// <returns>The result of the scan.</returns>
public static ScanResult Scan(byte[] buffer, string contentName = null)
{
unsafe
{
fixed (byte* bufferPtr = buffer)
{
return Scan((IntPtr)bufferPtr, (uint)buffer.Length, contentName);
}
}
}
/// <summary>Scans a buffer-full of content for malware.</summary>
/// <param name="buffer">The buffer from which to read the data to be scanned.</param>
/// <param name="bufferLen">The length, in bytes, of the data to be read from <c>buffer</c>.</param>
/// <param name="contentName">The filename, URL, unique script ID, or similar of the content being scanned.</param>
/// <returns>The result of the scan.</returns>
public static ScanResult Scan(IntPtr buffer, uint bufferLen, string contentName = null)
{
AMSI_RESULT result;
if (Provider is null)
{
EnsureContext();
using SafeHAMSISESSION session = new(hCtx);
AmsiScanBuffer(session.Context, buffer, bufferLen, contentName, session, out result).ThrowIfFailed();
return result.Convert();
}
else
{
using AmsiStream stream = new AmsiStream(new SafeCoTaskMemHandle(buffer, bufferLen, false), false);
Provider.Scan(stream, out result).ThrowIfFailed();
}
return result.Convert();
}
/// <summary>Scans a string for malware.</summary>
/// <param name="str">The string to be scanned.</param>
/// <param name="contentName">The filename, URL, unique script ID, or similar of the content being scanned.</param>
/// <returns>The result of the scan.</returns>
public static ScanResult Scan(string str, string contentName = null)
{
AMSI_RESULT result;
if (Provider is null)
{
EnsureContext();
using SafeHAMSISESSION session = new(hCtx);
AmsiScanString(session.Context, str, contentName, session, out result).ThrowIfFailed();
return result.Convert();
}
else
{
using AmsiStream stream = new AmsiStream(new SafeCoTaskMemString(str), false);
Provider.Scan(stream, out result).ThrowIfFailed();
}
return result.Convert();
}
/// <summary>Scans a file for malware.</summary>
/// <param name="file">The file from which to read the data to be scanned.</param>
/// <returns>The result of the scan.</returns>
public static ScanResult Scan(FileInfo file) => Scan(File.ReadAllBytes(file.FullName), file.FullName);
private static ScanResult Convert(this AMSI_RESULT result) => result switch
{
AMSI_RESULT.AMSI_RESULT_CLEAN => ScanResult.Clean,
AMSI_RESULT.AMSI_RESULT_NOT_DETECTED => ScanResult.NotDetected,
>= AMSI_RESULT.AMSI_RESULT_DETECTED => ScanResult.Detected,
_ => ScanResult.PotentialDetected,
};
private static void EnsureContext()
{
if (hCtx is null || hCtx.IsInvalid)
{
AmsiInitialize(Guid.NewGuid().ToString(), out hCtx).ThrowIfFailed();
}
}
}
}

View File

@ -26,6 +26,7 @@ BatteryStatus, EnergySaverStatus, JobLimit, NetworkInterfaceAccessType, NetworkI
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" Condition=" $(TargetFramework.StartsWith('netstandard')) Or $(TargetFramework.StartsWith('netcore')) Or $(TargetFramework.StartsWith('net5')) " />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PInvoke\AMSI\Vanara.PInvoke.AMSI.csproj" />
<ProjectReference Include="..\PInvoke\IpHlpApi\Vanara.PInvoke.IpHlpApi.csproj" />
<ProjectReference Include="..\PInvoke\Kernel32\Vanara.PInvoke.Kernel32.csproj" />
<ProjectReference Include="..\PInvoke\Mpr\Vanara.PInvoke.Mpr.csproj" />

View File

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>UnitTest.PInvoke.AMSI</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\PInvoke\AMSI\Vanara.PInvoke.AMSI.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,116 @@
using NUnit.Framework;
using NUnit.Framework.Internal;
using System;
using System.IO;
using System.Runtime.InteropServices;
using Vanara.InteropServices;
using static Vanara.PInvoke.AMSI;
namespace Vanara.PInvoke.Tests
{
[TestFixture]
public class AMSITests
{
[OneTimeSetUp]
public void _Setup()
{
}
[OneTimeTearDown]
public void _TearDown()
{
}
[Test]
public void AmsiScanStringTest()
{
Assert.That(AmsiInitialize(Guid.NewGuid().ToString(), out var hctx), ResultIs.Successful);
Assert.That(AmsiOpenSession(hctx, out var hsess), ResultIs.Successful);
var fn = TestCaseSources.LogFile;
Assert.That(AmsiScanString(hctx, File.ReadAllText(fn), fn, hsess, out var ret), ResultIs.Successful);
Assert.IsFalse(AmsiResultIsMalware(ret));
}
[Test]
public void AmsiScanStringTest2()
{
SafeHAMSISESSION hsess;
using (hsess = new SafeHAMSISESSION(Guid.NewGuid().ToString()))
{
Assert.That(hsess, ResultIs.ValidHandle);
var fn = TestCaseSources.LogFile;
Assert.That(AmsiScanString(hsess.Context, File.ReadAllText(fn), fn, hsess, out var ret), ResultIs.Successful);
Assert.IsFalse(AmsiResultIsMalware(ret));
}
Assert.That(hsess, Is.Not.Null);
Assert.That(hsess.Context, ResultIs.Not.ValidHandle);
Assert.That(hsess, ResultIs.Not.ValidHandle);
}
[Test]
public void AmsiNotifyOperationTest()
{
using var hsess = new SafeHAMSISESSION(Guid.NewGuid().ToString());
var fn = TestCaseSources.BmpFile;
using var fs = File.OpenRead(fn);
using var mem = new NativeMemoryStream();
fs.CopyTo(mem);
Assert.That(AmsiNotifyOperation(hsess.Context, mem.Pointer, (uint)mem.Length, fn, out var ret), ResultIs.Successful);
Assert.IsFalse(AmsiResultIsMalware(ret));
Assert.That(AmsiScanBuffer(hsess.Context, mem.Pointer, (uint)mem.Length, fn, hsess, out ret), ResultIs.Successful);
Assert.IsFalse(AmsiResultIsMalware(ret));
}
[Test]
public void AmsiStreamTest()
{
var fn = TestCaseSources.BmpFile;
var app = "MyTestApp";
AmsiStream str = null;
Assert.That(() => str = new(new FileInfo(fn), false) { AppName = app }, Throws.Nothing);
var istr = str as IAmsiStream;
using var mem = new SafeCoTaskMemHandle(2048);
Assert.That(istr.GetAttribute(AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_APP_NAME, mem.Size, mem, out var sz), ResultIs.Successful);
Assert.That(mem.ToString((int)sz * 2), Is.EqualTo(app));
Assert.That(istr.GetAttribute(AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_CONTENT_NAME, mem.Size, mem, out sz), ResultIs.Successful);
Assert.That(mem.ToString((int)sz * 2), Is.EqualTo(fn));
Assert.That(istr.GetAttribute(AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_CONTENT_SIZE, mem.Size, mem, out sz), ResultIs.Successful);
Assert.That(sz, Is.EqualTo(sizeof(ulong)));
Assert.That(mem.ToStructure<ulong>(), Is.EqualTo((ulong)str.Length));
Assert.That(istr.GetAttribute(AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_CONTENT_ADDRESS, mem.Size, mem, out sz), ResultIs.Successful);
Assert.That(sz, Is.EqualTo(IntPtr.Size));
Assert.That(mem.ToStructure<IntPtr>(), Is.EqualTo(str.Pointer));
Assert.That(istr.GetAttribute(AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_SESSION, mem.Size, mem, out sz), ResultIs.Successful);
Assert.That(sz, Is.EqualTo(IntPtr.Size));
Assert.That(mem.ToStructure<IntPtr>(), Is.EqualTo(IntPtr.Zero));
}
[Test]
public void InterfaceTest()
{
var fn = TestCaseSources.BmpFile;
var app = "MyTestApp";
AmsiStream str = null;
Assert.That(() => str = new(new FileInfo(fn), false) { AppName = app }, Throws.Nothing);
IAntimalware2 iam = new();
Assert.That(iam.Scan(str, out var res, out var prov), ResultIs.Successful);
TestContext.WriteLine(res);
Assert.That(prov.DisplayName(out var pname), ResultIs.Successful);
TestContext.WriteLine(pname);
using var mem = new SafeCoTaskMemHandle(20);
mem.Fill(78);
Assert.That(iam.Notify(mem, mem.Size, fn, app, out var res2), ResultIs.Successful);
TestContext.WriteLine(res2);
}
}
}

View File

@ -358,7 +358,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Imm32", "UnitTests\PInvoke\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vanara.PInvoke.Usp10", "PInvoke\Usp10\Vanara.PInvoke.Usp10.csproj", "{A793953F-5D22-4384-9AD4-A7355DCED03D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Usp10", "UnitTests\PInvoke\Usp10\Usp10.csproj", "{E21DCE50-E36E-41B3-BE57-D908ACBE88E5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Usp10", "UnitTests\PInvoke\Usp10\Usp10.csproj", "{E21DCE50-E36E-41B3-BE57-D908ACBE88E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vanara.PInvoke.AMSI", "PInvoke\AMSI\Vanara.PInvoke.AMSI.csproj", "{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AMSI", "UnitTests\PInvoke\AMSI\AMSI.csproj", "{496DFA71-4C35-49AE-95C1-4B802DB91615}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -2251,6 +2255,40 @@ Global
{E21DCE50-E36E-41B3-BE57-D908ACBE88E5}.Release|x64.Build.0 = Release|Any CPU
{E21DCE50-E36E-41B3-BE57-D908ACBE88E5}.Release|x86.ActiveCfg = Release|Any CPU
{E21DCE50-E36E-41B3-BE57-D908ACBE88E5}.Release|x86.Build.0 = Release|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Debug|x64.ActiveCfg = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Debug|x64.Build.0 = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Debug|x86.ActiveCfg = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Debug|x86.Build.0 = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.DebugNoTests|Any CPU.ActiveCfg = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.DebugNoTests|Any CPU.Build.0 = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.DebugNoTests|x64.ActiveCfg = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.DebugNoTests|x64.Build.0 = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.DebugNoTests|x86.ActiveCfg = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.DebugNoTests|x86.Build.0 = Debug|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Release|Any CPU.Build.0 = Release|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Release|x64.ActiveCfg = Release|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Release|x64.Build.0 = Release|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Release|x86.ActiveCfg = Release|Any CPU
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD}.Release|x86.Build.0 = Release|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Debug|Any CPU.Build.0 = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Debug|x64.ActiveCfg = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Debug|x64.Build.0 = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Debug|x86.ActiveCfg = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Debug|x86.Build.0 = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.DebugNoTests|Any CPU.ActiveCfg = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.DebugNoTests|x64.ActiveCfg = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.DebugNoTests|x64.Build.0 = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.DebugNoTests|x86.ActiveCfg = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.DebugNoTests|x86.Build.0 = Debug|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Release|Any CPU.ActiveCfg = Release|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Release|x64.ActiveCfg = Release|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Release|x64.Build.0 = Release|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Release|x86.ActiveCfg = Release|Any CPU
{496DFA71-4C35-49AE-95C1-4B802DB91615}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2407,6 +2445,8 @@ Global
{8D556821-EEA4-4D58-948B-9347EB33E085} = {385CAD2D-0A5E-4F80-927B-D5499D126B90}
{A793953F-5D22-4384-9AD4-A7355DCED03D} = {212ABBD0-B724-4CFA-9D6D-E3891547FA90}
{E21DCE50-E36E-41B3-BE57-D908ACBE88E5} = {385CAD2D-0A5E-4F80-927B-D5499D126B90}
{129B31E3-1247-4D73-AA45-52D0B2B4C6FD} = {212ABBD0-B724-4CFA-9D6D-E3891547FA90}
{496DFA71-4C35-49AE-95C1-4B802DB91615} = {385CAD2D-0A5E-4F80-927B-D5499D126B90}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {543FAC75-2AF1-4EF1-9609-B242B63FEED4}