using System; using System.IO; using System.Runtime.InteropServices; using Vanara.Extensions; using Vanara.InteropServices; namespace Vanara.PInvoke { public static partial class AMSI { /// Represents a stream to be scanned. // 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 { /// Returns a requested attribute from the stream. /// Specifies the type of attribute to be returned. See Remarks. /// The size of the output buffer, data, in bytes. /// Buffer to receive the requested attribute. data must be set to its size in bytes. /// /// The number of bytes returned in data. If this method returns E_NOT_SUFFICIENT_BUFFER, retData contains /// the number of bytes required. /// /// /// This method can return one of these values. /// /// /// Return code /// Description /// /// /// S_OK /// Success. /// /// /// E_NOTIMPL /// The attribute is not supported. /// /// /// E_NOT_SUFFICIENT_BUFFER /// /// The size of the output buffer, as indicated by data, is not large enough. retData contains the number of bytes required. /// /// /// /// E_INVALIDARG /// One or more argument is invalid. /// /// /// E_NOT_VALID_STATE /// The object is not initialized. /// /// /// /// /// Depending on the attribute requested in attribute, the following data should be copied to data: /// /// /// attribute /// data /// /// /// AMSI_ATTRIBUTE_APP_NAME /// The name, version, or GUID string of the calling application, copied from a LPWSTR. /// /// /// AMSI_ATTRIBUTE_CONTENT_NAME /// The filename, URL, unique script ID, or similar of the content, copied from a LPWSTR. /// /// /// AMSI_ATTRIBUTE_CONTENT_SIZE /// The size of the input, as a ULONGLONG. /// /// /// AMSI_ATTRIBUTE_CONTENT_ADDRESS /// The memory address if the content is fully loaded into memory. /// /// /// AMSI_ATTRIBUTE_SESSION /// /// Session is used to associate different scan calls, such as if the contents to be scanned belong to the same original script. /// Return nullptr if the content is self-contained. /// /// /// /// // 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); /// Requests a buffer-full of content to be read. /// The zero-based index into the content at which the read is to begin. /// The number of bytes to read from the content. /// Buffer into which the content is to be read. /// The number of bytes read into buffer. /// /// This method can return one of these values. /// /// /// Return code /// Description /// /// /// S_OK /// Success. /// /// /// E_INVALIDARG /// One or more argument is invalid. /// /// /// E_NOT_VALID_STATE /// The object is not initialized. /// /// /// // 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); } /// Represents the antimalware product. // 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 { /// Scan a stream of content. /// The IAmsiStream stream to be scanned. /// The result of the scan. See AMSI_RESULT. /// The IAntimalwareProvider provider of the antimalware product. /// /// This method can return one of these values. /// /// /// Return code /// Description /// /// /// S_OK /// Success. /// /// /// E_INVALIDARG /// One or more argument is invalid. /// /// /// E_NOT_VALID_STATE /// The object is not initialized. /// /// /// // 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); /// Closes the session. /// /// Type: ULONGLONG /// The id/handle of the session to close. /// /// None // https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalware-closesession void CloseSession( [in] ULONGLONG // session ); [PreserveSig] void CloseSession(ulong session); } /// Represents the antimalware product. // 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 { /// Scan a stream of content. /// The IAmsiStream stream to be scanned. /// The result of the scan. See AMSI_RESULT. /// The IAntimalwareProvider provider of the antimalware product. /// /// This method can return one of these values. /// /// /// Return code /// Description /// /// /// S_OK /// Success. /// /// /// E_INVALIDARG /// One or more argument is invalid. /// /// /// E_NOT_VALID_STATE /// The object is not initialized. /// /// /// // 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); /// Closes the session. /// /// Type: ULONGLONG /// The id/handle of the session to close. /// /// None // 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); /// /// Sends to the antimalware product a notification of an arbitrary operation. The notification doesn't imply the request of an /// antivirus scan. Rather, IAntimalware2::Notify 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. /// /// /// Type: PVOID /// The buffer that contains the notification data. /// /// /// Type: ULONG /// The length, in bytes, of the data to be read from buffer. /// /// /// Type: LPCWSTR /// The filename, URL, unique script ID, or similar of the content being scanned. /// /// /// Type: LPCWSTR /// The name of the application sending the AMSI notification. /// /// /// Type: AMSI_RESULT* /// The result of the scan. /// /// /// This method can return one of these values. /// /// /// Return code /// Description /// /// /// S_OK /// Success. /// /// /// E_INVALIDARG /// One or more arguments is invalid. /// /// /// E_NOT_VALID_STATE /// The object isn't initialized. /// /// /// // 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); } /// Represents the provider of the antimalware product. /// /// /// 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. /// /// /// Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\AMSI\FeatureBits /// /// /// /// Value /// Behavior /// /// /// 0x1 /// The signing check is disabled. This is the default behavior. You can also use this value, temporarily, while testing. /// /// /// 0x2 /// The check for Authenticode signing is enabled. /// /// /// Deleting the registry value altogether behaves as if the value 0x1 were present. /// /// Note /// As a provider, you must use the /// /ac /// 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 /// /kp /// option. If the SignTool returns no error, then your binary is properly signed. /// /// /// /// Important /// /// 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. /// /// /// /// 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. /// /// /// 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: <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Name="Microsoft-Windows-CodeIntegrity" Guid="{4ee76bd8-3cf4-44a0-a0ac-3937643e37a3}" /> <EventID>3040</EventID> <Version>0</Version> <Level>5</Level> <Task>14</Task> <Opcode>1</Opcode> <Keywords>0x4000000000000000</Keywords> <TimeCreated SystemTime="YYYY-MM-DDT02:26:48.875954700Z" /> <EventRecordID>7</EventRecordID> <Correlation /> <Execution ProcessID="4972" ThreadID="7752" ProcessorID="1" KernelTime="14" UserTime="2" /> <Channel>Microsoft-Windows-CodeIntegrity/Verbose</Channel> <Computer>[COMPUTER_NAME]</Computer> <Security UserID="[USER_SID]" /> </System> <EventData> <Data Name="FileNameLength">40</Data> <Data Name="FileNameBuffer">[PATH_AND_FILENAME]</Data> </EventData> </Event> /// /// /// 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: <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> <System> <Provider Name="Microsoft-Windows-CodeIntegrity" Guid="{4ee76bd8-3cf4-44a0-a0ac-3937643e37a3}" /> <EventID>3041</EventID> <Version>2</Version> <Level>5</Level> <Task>14</Task> <Opcode>2</Opcode> <Keywords>0x4000000000000000</Keywords> <TimeCreated SystemTime="YYYY-MM-DDT02:26:48.875964700Z" /> <EventRecordID>8</EventRecordID> <Correlation /> <Execution ProcessID="4972" ThreadID="7752" ProcessorID="1" KernelTime="14" UserTime="2" /> <Channel>Microsoft-Windows-CodeIntegrity/Verbose</Channel> <Computer>[COMPUTER_NAME]</Computer> <Security UserID="[USER_SID]" /> </System> <EventData> <Data Name="Status">0xc0000225</Data> <Data Name="CachedFlags">0x0</Data> <Data Name="CacheSource">0</Data> <Data Name="CachedPolicy">0</Data> </EventData> </Event> /// /// // 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 { /// Scan a stream of content. /// The IAmsiStream stream to be scanned. /// The result of the scan. See AMSI_RESULT. /// /// This method can return one of these values. /// /// /// Return code /// Description /// /// /// S_OK /// Success. /// /// /// E_INVALIDARG /// One or more argument is invalid. /// /// /// E_NOT_VALID_STATE /// The object is not initialized. /// /// /// // 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); /// Closes the session. /// /// Type: ULONGLONG /// The id/handle of the session to close. /// /// None // https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iantimalwareprovider-closesession void CloseSession( [in] // ULONGLONG session ); [PreserveSig] void CloseSession(ulong session); /// The name of the antimalware provider to be displayed. /// A pointer to a LPWSTR that contains the display name. /// /// This method can return one of these values. /// /// /// Return code /// Description /// /// /// S_OK /// Success. /// /// /// E_INVALIDARG /// The argument is invalid. /// /// /// E_NOT_VALID_STATE /// The object is not initialized. /// /// /// // 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); } /// Represents the provider of the antimalware product. /// See Remarks in the IAntimalwareProvider interface topic. // 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 { /// Scan a stream of content. /// The IAmsiStream stream to be scanned. /// The result of the scan. See AMSI_RESULT. /// /// This method can return one of these values. /// /// /// Return code /// Description /// /// /// S_OK /// Success. /// /// /// E_INVALIDARG /// One or more argument is invalid. /// /// /// E_NOT_VALID_STATE /// The object is not initialized. /// /// /// // 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); /// Closes the session. /// /// Type: ULONGLONG /// The id/handle of the session to close. /// /// None // 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); /// The name of the antimalware provider to be displayed. /// A pointer to a LPWSTR that contains the display name. /// /// This method can return one of these values. /// /// /// Return code /// Description /// /// /// S_OK /// Success. /// /// /// E_INVALIDARG /// The argument is invalid. /// /// /// E_NOT_VALID_STATE /// The object is not initialized. /// /// /// // 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); /// /// Sends to the antimalware provider a notification of an arbitrary operation. The notification doesn't imply the request of an /// antivirus scan. Rather, IAntimalwareProvider2::Notify 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. /// /// /// Type: PVOID /// The buffer that contains the notification data. /// /// /// Type: ULONG /// The length, in bytes, of the data to be read from buffer. /// /// /// Type: LPCWSTR /// The filename, URL, unique script ID, or similar of the content being scanned. /// /// /// Type: LPCWSTR /// The name of the application sending the AMSI notification. /// /// /// Type: AMSI_RESULT* /// The result of the scan. /// /// /// This method can return one of these values. /// /// /// Return code /// Description /// /// /// S_OK /// Success. /// /// /// E_INVALIDARG /// One or more arguments is invalid. /// /// /// E_NOT_VALID_STATE /// The object isn't initialized. /// /// /// // 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); } /// Memory based stream that implements IAmsiStream. /// /// public class AmsiStream : NativeMemoryStream, IAmsiStream { private static readonly HRESULT E_INSUFF_BUFFER = HRESULT.HRESULT_FROM_WIN32(Win32Error.ERROR_INSUFFICIENT_BUFFER); /// Initializes a new instance of the class. public AmsiStream() : base() { } /// Initializes a new instance of the class with an initial capacity. /// The capacity. public AmsiStream(int capacity) : base(capacity) { } /// Initializes a new instance of the class and inserts the contents of a buffer. /// The buffer to copy. /// if set to , the stream is read-write; if , it is read-only. public AmsiStream(byte[] buffer, bool writable) : base(new SafeCoTaskMemHandle(buffer), access: writable ? FileAccess.ReadWrite : FileAccess.Read) { } /// Initializes a new instance of the class with file information. /// The file information. /// if set to , the stream is read-write; if , it is read-only. public AmsiStream(FileInfo file, bool writable) : this(file is null ? null : File.ReadAllBytes(file.FullName), writable) => ContentName = file.FullName; /// Initializes a new instance of the class. /// The memory allocator used to create and extend the native memory. /// if set to , the stream is read-write; if , it is read-only. public AmsiStream(SafeAllocatedMemoryHandle mem, bool writable) : this(mem.GetBytes(), writable) { } /// Gets or sets the name, version, or GUID string of the calling application. public string AppName { get; set; } /// Gets or sets the filename, URL, unique script ID, or similar of the content. public string ContentName { get; set; } /// /// 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. /// 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; } } /// CLSID_Antimalware [ComImport, Guid("fdb00e52-a214-4aa1-8fba-4357bb0072ec"), ClassInterface(ClassInterfaceType.None)] public class CAntimalware { } } }