如何实现IAmsiStream以支持在Windows上对流进行恶意软件扫描。

8

当使用 IAmsiStream 来扫描大于约20MB的文件时,使用Windows Defender会失败并显示错误信息: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;
        }

这个测试的完整源代码在这个 GitHub 仓库中。


当内容完全加载到内存中时,您应该处理AMSI_ATTRIBUTE_CONTENT_ADDRESS,请参阅此文档 - weichch
IAmsiStream的实现不应将整个内容加载到内存中,因为它可能太大而无法容纳。目标是流式传输数据。 - Bruno Lopes
2个回答

1

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

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

微软提供的C++示例代码也无法处理超过此大小限制的文件。

Microsoft开发人员文档声明:

AMSI特别设计用于打击“无文件恶意软件”

这就是原因。

根据维基百科,该文件仅存在于计算机内存中。
如果文件大小大于16MB,则可以将其加载到内存中,并通过处理AMSI_ATTRIBUTE_CONTENT_ADDRESS返回缓冲区地址。对于大文件,您可以考虑使用内存映射文件
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;
    ...
}

-1

请使用来自以下链接的 MalwareScanner: https://github.com/NewOrbit/MalwareScan.AMSI

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

附言:官方文档中没有16mb限制,我认为这不是IAmsiStream的限制,IAmsiStream被传递给IAntimalwareProvider,后者是供应商实现,问题可能出在那里;您应该尝试另一个供应商,看看是否会出现相同的错误:


这将整个流复制到一个字节缓冲区中。对于较大的文件,这是不可行的。 - Bruno Lopes
@BrunoLopes 如果我理解正确的话,您想要逐个字节地扫描一个传入的大文件字节段。我已经更新了答案,请查看。 - Danut Radoaica

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