Virus scan with AMSI classes

Heiko 1,291 Reputation points
2023-05-07T10:52:06.75+00:00

Hello,

in my WPF desktop bridge app I want to scan single files for viruses. On my PC Windows Defender Antivirus is activated. I'm trying to use the AMSI class implementations of the Microsft sample from Github. These implemetations contain the following methods:

HRESULT BaseGetAttribute(
	_In_ AMSI_ATTRIBUTE attribute,
	_In_ ULONG bufferSize,
	_Out_writes_bytes_to_(bufferSize, *actualSize) PBYTE buffer,
	_Out_ ULONG* actualSize)
{
	if (actualSize == nullptr || (buffer == nullptr && bufferSize > 0)) {
	    return E_INVALIDARG;
	}
	*actualSize = 0;

	switch (attribute)
	{
	case AMSI_ATTRIBUTE_CONTENT_SIZE:
	    return CopyAttribute(&m_contentSize, sizeof(m_contentSize), bufferSize, buffer, actualSize);

	case AMSI_ATTRIBUTE_CONTENT_NAME:
	    return CopyAttribute(m_contentName, (wcslen(m_contentName) + 1) * sizeof(WCHAR), bufferSize, buffer, actualSize);

	case AMSI_ATTRIBUTE_APP_NAME:
	    return CopyAttribute(AppName, sizeof(AppName), bufferSize, buffer, actualSize);

	case AMSI_ATTRIBUTE_SESSION:
	    constexpr HAMSISESSION session = nullptr; // no session for file stream
	    return CopyAttribute(&session, sizeof(session), bufferSize, buffer, actualSize);
	}
	return E_NOTIMPL; // unsupport attribute
}

STDMETHOD(Read)(
	_In_ ULONGLONG position,
	_In_ ULONG size,
	_Out_writes_bytes_to_(size, *readSize) PBYTE buffer,
	_Out_ ULONG* readSize)
{
	OVERLAPPED o = {};
	o.Offset = LODWORD(position);
	o.OffsetHigh = HIDWORD(position);

	if (!ReadFile(m_fileHandle.Get(), buffer, size, readSize, &o))
	{
		HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
		wprintf(L"ReadFile failed with 0x%x\n", hr);
		return hr;
	}
	return S_OK;
}

If I scan a file with small size, BaseGetAttribute() is called several times. At last with attribute AMSI_ATTRIBUTE_CONTENT_ADDRESS. Then I return E_NOTIMPL or I can fill the 'buffer' with zeros and return S_OK. It doesn't matter. Read() is called and the scan (m_antimalware->Scan()) will be finished with S_OK.

But if I have a larger file, for example an exe file with 49 MB, BaseGetAttribute() is called with attribute AMSI_ATTRIBUTE_CONTENT_ADDRESS and then it doesn't matter whether I return E_NOTIMPL or fill the 'buffer' with zeros and return S_OK, the method m_antimalware->Scan() returns immediately with E_INVALIDARG and the Read() method is never called.

Where lies the problem? What else could I return for attribute AMSI_ATTRIBUTE_CONTENT_ADDRESS, or is the implementation of the AMSI classes in Windows Defender Antivirus incorrect?

Developer technologies | Windows Presentation Foundation
Windows development | Windows API - Win32
Developer technologies | C++
Developer technologies | C#
{count} votes

2 answers

Sort by: Most helpful
  1. Heiko 1,291 Reputation points
    2023-05-10T10:08:16.9433333+00:00

    I want to scan only files in my app. The user can choose any file, e.g. from the download directory. Such a file can be large, and it can be any compressed file, e.g. zip, rar, etc. The virus scanner can scan the contents of such a compressed file for viruses. With the AmsiScanBuffer() function this would not be possible if only a part of the large file was passed to the function, because then the split parts of a compressed file would no longer be consistent compressed data that the AmsiScanBuffer() function could decompress.

    If it is not a compressed file, you would probably have to read the file overlapping, since the virus pattern can theoretically be above the split position. So, for example, you would have to read from offset 0 to 1000 and check for viruses, then read and check from offset 900 to 1900, then from 1800 to 2800, and so on. Here you would need to know how large a virus pattern can be. Reading overlapping can be done, but what if it is a compressed file, in a format I don't know, but the virus scanner could extract it and check its contents?

    I saw the AmsiScanBuffer() function, but then had the thoughts I just described and thought to myself, you need a function that can stream, and then I came across the IAmsiStream interface and its sample implementation from Microsoft, only to find after much testing that the virus scanner supports the IAmsiStream interface, but not streaming.

    I think that AmsiScanBuffer(), AmsiScanString() and IAmsiStream are not primarily intended for scanning files, but for data that is in RAM and that an app has received from some stream. Large files, especially compressed files, can probably only be read and checked efficiently by the virus scanner itself. Such a feature, which is supported by all scanners, is missing to me. On the other hand, a virus scanner automatically checks every file when it is created. However, this does not apply to the contents of a compressed file. Here, the use of IAmsiStream would be justified, since it detects and checks compressed data.

    2 people found this answer helpful.
    0 comments No comments

  2. Pedro Gil 35 Reputation points
    2023-05-07T14:27:33.09+00:00
    1 person found this answer helpful.

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.