IEInternals Blog IE8 FAQ
Add-ons Tweaks Web Development Add-on Development Troubleshooting Contact 

IOfficeAntiVirus interface in C#

Registered handlers of the IOfficeAntiVirus interface are called by Chrome, Internet Explorer, Edge (and some other clients) when a file is downloaded.  We can leverage this interface to display the hashes of the downloaded file for user review.

FileHashes Screenshot ancient IE  FileHashes Screenshot modern Chrome 

Sample Source

NOTE: The regasm call below will probably call the 32bit version of REGASM. You may need to explicitly change this to:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe ShowHash.dll
/*
This sample shows how to implement the IOfficeAntiVirus interface in C#

The resulting object, once registered, will calculate the file hash (SHA256/Sha1/MD5) for each file downloaded by Chrome, Edge, and Internet Explorer.

In order to successfully register this object, we'll use REGASM to register the assembly, then run a registry script to add the IOfficeAntiVirus
component category, and we'll delete the "Discardable\PostSetup" cache of known IOfficeAntiVirus implementations so that the next call to the IOfficeAntiVirus
providers will find our new object.

Eric Lawrence
14 November 2007; Updated 5 June 2019
[email protected]

//--------------------------------
AddIOAVCat.reg
//--------------------------------
    Windows Registry Editor Version 5.00

    [-HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Discardable\PostSetup\Component Categories\{56FFCC30-D398-11D0-B2AE-00A0C908FA49}]

    [HKEY_CLASSES_ROOT\CLSID\{00000000-0002-4746-931C-36014B146679}\Implemented Categories\{56FFCC30-D398-11D0-B2AE-00A0C908FA49}]
//--------------------------------

//--------------------------------
go.bat
//--------------------------------
    sn -k IOAV.snk
    csc /target:library /keyfile:IOAV.snk showhash.cs
    REM C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe ShowHash.dll
    regasm ShowHash.dll
    gacutil -i ShowHash.dll
    AddIOAVCat.reg
//--------------------------------
*/

// References
// SEE THIS LINK FOR UPDATED SOURCE: https://gist.github.com/ericlaw1979/ff937f81a663d6930be720afca92aa3a
// IOfficeAntiVirus Reference: http://msdn2.microsoft.com/en-us/library/ms537369.aspx 
// Test case for Code Download: http://www.alternatiff.com/install/00/atifxf2a.html 
// Info on MarshalAs: http://msdn2.microsoft.com/en-us/library/aa288468(VS.71).aspx 
// ComInterop Tutorials: http://msdn2.microsoft.com/en-us/library/aa645712(VS.71).aspx 
// Info on building COM servers in .NET: http://www.codeproject.com/useritems/BuildCOMServersInDotNet.asp 
// Marshalling unmanaged types: http://msdn2.microsoft.com/en-us/library/system.runtime.interopservices.unmanagedtype(VS.71).aspx 
// Shell Extension for showing file hash: http://www.beeblebrox.org/hashtab/ 
// http://www.hexacorn.com/blog/2018/08/04/beyond-good-ol-run-key-part-83/ 


using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Reflection;
using System.Windows.Forms;
using System.Runtime.InteropServices;

[assembly: AssemblyVersion("1.0.1.6")]
namespace IOAVShowHash
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct MSOAVINFO 
    {
            public int cbsize;
            
            [MarshalAs(UnmanagedType.U4)]
            public uint uFlags;
            public IntPtr hwnd;
            
            // Technically, not quite right, since this could point at a structured storage pointer
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pwzFullPath;
            
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pwzHostname;
            
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pwzOrigURL;
    }

    [Guid("56FFCC30-D398-11d0-B2AE-00A0C908FA49"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IOfficeAntiVirus
    {
       // Specify PreserveSig to ensure that non-OK HRESULT is handed back to the caller (instead of throwing an exception)
       [PreserveSig]
       uint Scan(IntPtr ptr);
    }

    [Guid("00000000-0002-4746-931C-36014B146679")]
    public class ShowHash : IOfficeAntiVirus
    {        
        private string GetFileInfo(string sFilename){
            byte[] arrHash;
            
            FileStream fs = new FileStream(sFilename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);

            StringBuilder sbHash = new StringBuilder();
            sbHash.Append("SHA256:\t");

            SHA256 shaM = new SHA256Managed();
            arrHash = shaM.ComputeHash(fs);

            for (int i = 0; i < arrHash.Length; i++)
            {
                sbHash.Append(arrHash[i].ToString("X2"));
            }

            fs.Seek(0, SeekOrigin.Begin);
            SHA1 sha = new SHA1CryptoServiceProvider();
            arrHash = sha.ComputeHash(fs);

            sbHash.Append("\nSHA1:\t");
            for (int i = 0; i < arrHash.Length; i++)
            {
                sbHash.Append(arrHash[i].ToString("X2"));
            }

            fs.Seek(0, SeekOrigin.Begin);
            MD5 md5Hasher = MD5.Create();
            arrHash = md5Hasher.ComputeHash(fs);
       

            sbHash.Append("\nMD5:\t");
            for (int i = 0; i < arrHash.Length; i++)
            {
                sbHash.Append(arrHash[i].ToString("X2"));
            }

            sbHash.AppendFormat("\nSize:\t{0:N0}", fs.Length);
            fs.Close();

            return sbHash.ToString();
        }

        public uint Scan(IntPtr ptr)
        {
            bool bReportVirus = false;
            try
            {
                MSOAVINFO oInfo = (MSOAVINFO)Marshal.PtrToStructure(ptr, typeof(MSOAVINFO));

                if ((null == oInfo.pwzFullPath) || !File.Exists(oInfo.pwzFullPath))
                {
                    return 0;
                }

                StringBuilder sbInfo = new StringBuilder();
                sbInfo.Append(GetFileInfo(oInfo.pwzFullPath) + "\n\n");

                /*
                sbInfo.Append(((oInfo.uFlags & 1) > 0) ? "Path:\tyes\n" : "Path:\tno\n");
                sbInfo.Append(((oInfo.uFlags & 2) > 0) ? "Readonly:\tyes\n" : "Readonly:\tno\n");
                sbInfo.Append(((oInfo.uFlags & 4) > 0) ? "Installed:\tyes\n" : "Installed:\tno\n");
                sbInfo.Append(((oInfo.uFlags & 8) > 0) ? "Download:\tyes\n" : "Download:\tno\n");

                sbInfo.Append("HWND: " + oInfo.hwnd.ToString() + "\n");
                sbInfo.Append("pwzHostname: " + oInfo.pwzHostname + "\n");  // "Urlmon" for ActiveX installs, "SHDOCVW" otherwise
                */

                sbInfo.Append("Path:\t" + oInfo.pwzFullPath + "\n");
                sbInfo.Append("URL:\t" + oInfo.pwzOrigURL+ "\n");
                sbInfo.Append("\n(Hit CTRL+C to copy...)");
                //sbInfo.Append("\nIndicate that this file contains a virus?");
                //bReportVirus = (DialogResult.Yes == MessageBox.Show(sbInfo.ToString(), "File Hashes", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2));
                MessageBox.Show(sbInfo.ToString(), "File Hashes", MessageBoxButtons.OK, MessageBoxIcon.Information);

            }
            catch (Exception eX)
            {
                MessageBox.Show(eX.ToString(), "Error");
            }
            /*
                0 = S_OK The file was scanned, and no viruses were found. 
                1 = S_FALSE The file was infected, and the virus was cleaned from the file. 
                0x80004005 = E_FAIL The file was infected, but the virus could not be cleaned from the file. 
                ERROR_FILE_NOT_FOUND The file was not found. You must embed ERROR_FILE_NOT_FOUND in an HRESULT before you return it.  
            */
            return (bReportVirus) ? 0x80004005 : 0;
      }
   }
}

Feedback?

Please, send me an e-mail.

©2019 Eric Lawrence