Windows antivirus API in .NET, and a COM interop crash course

Like I said in my previous post, I already coded something cool in my framework, and here it is: .NET code to call the Windows antivirus API. The best use case scenario I can think of is when retrieving and then redistributing file content from an untrusted data source, especially a web upload form. In cases like this, content might slip through the realtime protection of most antivirus products, and an API like the one I created is the only solution.

The API

I learned of the API’s existence when I saw Firefox scan my downloads. Luckily it’s open source, and here is the source code for download scanning. Then it was easy to find official documentation on MSDN, too. This API is called IAttachmentExecute. Apparently it’s not an antivirus API per se, it’s rather something that once was intended to be called by e-mail clients when they save an attachment. But luckily most if not all antivirus products subscribe to this interface so in the absence of a real antivirus API, this is the next best thing to use.

COM interop

If I were into C/C++, I’d skip this part, but in .NET, there were a few challenges, even though COM interop is supposed to be as easy as a pie and I’ve used it before. I’m not a COM expert, so my choice of words might be inaccurate in the following paragraphs, but I’ll tell you my findings anyway.

First of all, this API doesn’t come with a type library (it contains a compiled interface description), and Visual Studio offers you to add a reference to COM interfaces with type libraries only. So much for being easy. There is an ShObjIdl.idl file in the Windows SDK, I converted it to a type library with midl.exe, but tlbimp.exe couldn’t import it. Then I stripped down the idl file to only contain what I needed, now tlbimp.exe worked, but I still couldn’t add a reference to the tlb directly because of an unspecified error, even though the add reference dialog contains a *.tlb filter. I didn’t want to include the manually tlbimp‘d dll in my project, so I coded the interface myself, here’s how it looks like:

[Guid("73DB1241-1E85-4581-8E4F-A81E1D0F8C57"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAttachmentExecute
{
    void SetClientTitle(string pszTitle);
    // and so on
}

But you can’t instantiate it, there’s no CLSID entry in the registry with that GUID, and here’s the next piece of COM wisdom: there are COM service classes, which you can instantiate, and COM interfaces, which you can query on COM instances, and you can only make calls to the classes through interfaces. A service class has its own name and GUID, and some other properties, but let’s skip that for now. Apparently, the class that provides the above interface is called AttachmentServices. See, it’s a service, not an interface. In native code, you would call CoCreateInstance, and now we know why it has a CLSID and also an interface identifier parameter. In managed code, however, the most straightforward way is this:

var type = Type.GetTypeFromCLSID(new Guid("4125DD96-E03A-4103-8F70-E0597D803B9C"));
var svc = (IAttachmentExecute)Activator.CreateInstance(type);

The first line gets some kind of proxy type that can instantiate the object based on it’s CLSID,  the second line instantiates it and the cast is translated to querying the interface. However, if you take a look at COM interop assemblies in Reflector, you can’t find code like this. There’s a lot of magic going on in those assemblies, but the magic we need is the ComImport attribute, which allows us to instantiate COM classes like managed classes. The result:

[ComImport, Guid("4125DD96-E03A-4103-8F70-E0597D803B9C")]
public class AttachmentServices { }

This class doesn’t need any members, you can just create it, cast it to IAttachmentServices and it works. As far as I can tell, it doesn’t make a difference if I apply ComImport on the interface, too, but then I decided to do it anyway to make the code cleaner.

COM apartment support

And the last challenge: it doesn’t always work, in certain contexts the interface cast throws an InvalidCastException (0×80004002, E_NOINTERFACE, I’ve encountered this before). The problem lies within COM apartments. For the purposes of this post, we don’t need to understand them, let’s assume that they’re just an obstacle. Some COM services can be called from STA threads, some from MTA threads, and some from both (as I mentioned above, it’s another property of a COM service).

This particular interface works on STA threads only. I haven’t found generic solutions to deal with this problem in .NET COM clients, and the problem doesn’t exist in .NET COM services because they always support both apartments, so I came up with my own solution. I created a COMInvoke function which can execute code in an arbitrary apartment state, and it creates a new thread if the current one is not suitable. You can find the code in COMUtils.cs.

My code

I decided to clearly separate low level Windows imports and higher level functions built on top of them. The former went to ShObjIdl.cs in the Internal namespace: the name suggests that it’s use is not preferred, but it’s still public in case anyone needs it (I’ve been frustrated a lot in the past by non-public Microsoft code, and I’m sure I’m not the only one). The latter can be found in VirusUtils.cs.

A lot of effort and research has gone into VirusUtils. The problem is that the behavior of IAttachmentExecute is not clearly defined. Some antivirus products indicate a virus with an exception, some others don’t. Some products delete the file immediately, some others just deny access to it, and the behavior also varies with different settings in the same product. I ended up with a function which calls the COM API and also checks if the file is deleted or access to it is denied after the scanning. So this function should reliably tell if a file contains a virus or not.

I have access to Windows Defender, Microsoft Security Essentials and ESET NOD32 Antivirus. I tested them rigorously using the awesome EICAR test file and documented the behaviors in the source code. My function worked with all of them, with multiple settings, too, so I’m confident in publishing it and I suggest it for general use by anyone. Also, if you can give me detailed test results with other antivirus products and would like your name and work to appear in the source code, don’t hesitate to contact me!

10 thoughts on “Windows antivirus API in .NET, and a COM interop crash course

  1. Renato Ciuffo

    Hello,
    I have been trying to use IAttachmentExecute in a project, and have been extremelly frustrated by the lack of documentation. Once I came across your blog, I thought “finally! someone that can help!”.

    I was able to compile your framework in VS2012 with no issues, however, I cannot compile VirusUtils.cs, ShObjIdl.cs, and COMUtils.cs into any projects. I was wondering if you had a working project that I could build, rather then just the 3 source files.

    This post has been the most helpful post on this subject that I have come across so far! Keep it up!

  2. David Roberts

    Hi

    looking at the code, the scan calls the Save method of the IAttachmentExecute COM interface, which will save the file (which already has a file path!)

    What’s the difference between this and just saving the file anyway, as that would invoke the virus scanner as well? In fact, if the file being Saved has a path its already been virus scanned!

    1. Joco Post author

      I think that the file won’t be saved again if it is already in the temp folder, but MSDN is not entirely clear about that. The point of calling Save is to make sure that the virus scanner is called. You might want to turn off realtime scanning and only scan specific files, that’s when it comes handy. I cannot guarantee that this is a bulletproof or recommended solution, and this interface may not have been invented to be used like this, but it seems to be a general way to invoke virus scanners.

  3. Angus Seymour

    Hey,

    I’ve got this compiling in Windows 8.1, but when I run the unit test for the EICAR file (either by using the string provided, or as a standalone .com file), it doesn’t seem to scan the file- it all runs through smoothly as if it were a valid file. I’ve tried it with both Sophos and Avast running, and get the same result. However, when I try it on a Windows 7 machine with Sophos running, it seems to work! I’ll check with Windows Defender running as well to make sure it’s not just those two sets of AV software that have the problem.

    I’m wondering if anyone has got this running in Windows 8+ (especially an x64 environment), or if it’s just me?

    As you mentioned that Firefox uses this, I did try downloading the EICAR file, and found the scan was immediately run, so it does seem to work in some cases.

  4. Philipp ksz

    BROKEN -> no source Code

    403. That’s an error.
    Your client does not have permission to get URL /p/joco-library/source/browse/trunk/Source/FejesJoco.Framework.Windows/VirusUtils.cs from this server. That’s all we know

Leave a Reply