【问题标题】:How to implement IAmsiStream to support running a malware scan on windows on a stream如何实现 IAmsiStream 以支持在流上的 Windows 上运行恶意软件扫描
【发布时间】:2021-12-17 23:22:32
【问题描述】:

当实现 IAmsiStream 以使用 Windows Defender 执行扫描时,对于大于 ~20MB 的文件,它会以 Value does not fall within the expected range. 失败。

这个实现缺少什么?

    public class AmsiStream : IAmsiStream
    {
        private readonly Stream _input;
        private readonly string _name;
        private static readonly byte[] _nullPtr = new byte[Marshal.SizeOf(IntPtr.Zero)];

        public AmsiStream(Stream input, string name)
        {
            _input = input ?? throw new ArgumentNullException(nameof(input));
            _name = name ?? throw new ArgumentNullException(nameof(name));
        }

        public int GetAttribute(AMSI_ATTRIBUTE attribute, int dataSize, byte[] data, out int retData)
        {
            const int E_NOTIMPL = unchecked((int)0x80004001);
            const int E_NOT_SUFFICIENT_BUFFER = unchecked((int)0x8007007A);

            byte[] bytes = { };
            int retValue = 0;

            switch (attribute)
            {

                case AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_APP_NAME:
                    bytes = Encoding.Unicode.GetBytes("TestAmsi" + "\0");
                    break;
                case AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_CONTENT_NAME:
                    bytes = Encoding.Unicode.GetBytes(_name + "\0");
                    break;
                case AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_CONTENT_SIZE:
                    bytes = BitConverter.GetBytes((ulong)_input.Length);
                    break;
                case AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_SESSION:
                    bytes = _nullPtr;
                    break;
                case AMSI_ATTRIBUTE.AMSI_ATTRIBUTE_CONTENT_ADDRESS:
                    retValue = E_NOTIMPL;
                    break;
                default:
                    retValue = E_NOTIMPL;
                    break;
            }

            retData = 0;
            if (retValue == 0)
            {
                retData = bytes.Length;
                if (dataSize < bytes.Length)
                    return E_NOT_SUFFICIENT_BUFFER;

                Array.Copy(bytes, data, bytes.Length);
            }

            return retValue;

        }

        public int Read(long position, int size, byte[] buffer, out int readSize)
        {
            _input.Seek(position, SeekOrigin.Begin);
            readSize = _input.Read(buffer, 0, size);
            return 0;
        }
    }

一个测试用例是:

        [Fact]
        public void TestWithLargeFile()
        {
            const long k = 1024;
            const long m = 1024 * k;
            const long fileSize = 21 * m;
            var c = new Random(42);
            var fileBuffer = new byte[fileSize];

            c.NextBytes(fileBuffer);
            Equal(AMSI_RESULT.AMSI_RESULT_CLEAN, ScanInternal(new MemoryStream(fileBuffer), "test.file.txt"));
        }

        private AMSI_RESULT ScanInternal(Stream streamToCheck, string fileName)
        {
            var scanner = (IAntiMalware)new CAntiMalware();
            var stream = new AmsiStream(streamToCheck, fileName);
            var result = scanner.Scan(stream, out AMSI_RESULT scanResult, out IAntiMalwareProvider _);

            if (result != (ulong)HResult.S_OK)
            {
                throw new InvalidOperationException($"Malware scan returned not OK: {result}");
            }

            return scanResult;
        }

此测试的完整源代码this github repo

【问题讨论】:

  • 当内容完全加载到内存中时,您应该处理AMSI_ATTRIBUTE_CONTENT_ADDRESS,请参阅此docs
  • IAmsiStream 实现不应将整个内容加载到内存中,因为它可能不适合。目标是流式传输数据。

标签: c# windows-defender antimalware


【解决方案1】:

改为使用来自:https://github.com/NewOrbit/MalwareScan.AMSI 的 MalwareScanner

var scanner = new MalwareScanner("MyApplications Viruscanner"); var result = scanner.HasVirus(stream, filename);

或用于逐字节扫描传入的大文件字节,请参阅:

WindowsCOMAntiMalware:https://github.com/bsmg/BeatSaber-IPA-Reloaded/blob/master/IPA.Loader/AntiMalware/_HideInNet3/WindowsCOMAntiMalware.cs

具有 2 个 IAmsiStream 实现:

AmsiMemoryStream:https://github.com/bsmg/BeatSaber-IPA-Reloaded/blob/master/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/AmsiMemoryStream.cs AmsiFileStream:https://github.com/bsmg/BeatSaber-IPA-Reloaded/blob/master/IPA.Loader/AntiMalware/_HideInNet3/ComAPI/AmsiFileStream.cs

P.S.:在官方文档中找不到 16mb 的限制,我不认为是 IAmsiStream 限制,IAmsiStream 被传递给 IAntimalwareProvider 这是一个供应商实现,也许问题就在那里;您应该尝试其他供应商,看看您是否遇到相同的错误:

【讨论】:

【解决方案2】:

我认为您不能将大文件流式传输到 Windows Defender 提供程序。根据这个github thread,平台似乎实施了一个限制,如果您不处理GetAttribute 中的AMSI_ATTRIBUTE_CONTENT_ADDRESS,则Read 方法中传递的最大缓冲区大小为16MB。 Read 方法的目的不是流式传输,而是流式传输

请求读取一个充满内容的缓冲区。

Microsoft 提供的sample code (C++) 也无法处理大于此大小限制的文件。

Microsoft developer docs 声明:

AMSI 专门用于对抗“无文件恶意软件”

哪个

仅作为基于计算机内存的工件存在

根据Wikipedia

对我来说,如果文件大小大于 16MB,您可以将其加载到内存中并通过处理 AMSI_ATTRIBUTE_CONTENT_ADDRESS 返回缓冲区地址。对于大文件,您可以考虑使用memory mapped file

public unsafe int GetAttribute(AMSI_ATTRIBUTE attribute, int dataSize,
    [Out]byte[] data, [Out]out int retData)
{
    ...
    case AMSI_ATTRIBUTE_CONTENT_ADDRESS:

        retData = sizeof(IntPtr);

        if (dataSize < retData)
        {
            return E_NOT_SUFFICIENT_BUFFER;
        }

        // This is an example
        // Better to open in constructor and close in Dispose
        var mappedFile = MemoryMappedFile.CreateFromFile(_name, FileMode.Open);
        var mappedFileAccessor = mappedFile.CreateViewAccessor(0, fileSize);

        byte* fileContent = null;
        mappedFileAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref fileContent);

        fixed (byte* pData = data)
        {
            *(IntPtr*) pData = (IntPtr)fileContent;
        }

        return 0;
    ...
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-06-30
    • 1970-01-01
    • 2016-12-09
    • 1970-01-01
    • 2019-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多