Inspired by a post on DirectShow Development Forum, it is sometimes useful to find out how certain application is using DirectShow to capture live video and/or audio, or play certain file etc. Filter graph topology, details about media types on the pins, used interfaces and call order. There is quite an easy step for the developer to spy over running filter graphs system wide by connecting Component Object Model (COM) “Treat As” feature with filter graph remoting through Running Object Table and connecting to remote process graphs using GraphEdit utility.

Who could imagine Windows Media Player to build such a filter graph for a video and audio Windows Media file? SRS Wow DMO, equalizer, analyzer and stuff?

There are many tricks to catch the created filter graph but the most legit is the mentioned above “Treat As” feature to emulate certain COM class identifier (CLSID) through another coclass. COM API offers CoTreatAsClass and CoGetTreatAsClass functions to manage the emulation. To emulate DirectShow’s CLSID_FilterGraph through my CSpy class, I am adding the following code to the default COM registration process for the utility DLL:

static HRESULT WINAPI UpdateRegistry(BOOL bRegister) throw()
{
    _Z2(atlTraceRegistrar, 2, _T("bRegister %d\n"), bRegister);
    _ATLTRY
    {
        CLSID TreatAsClassIdentifier;
        HRESULT nCoGetTreatAsClassResult = CoGetTreatAsClass(CLSID_FilterGraph, &TreatAsClassIdentifier);
        __C(nCoGetTreatAsClassResult);
        __D(!bRegister || nCoGetTreatAsClassResult != S_OK || TreatAsClassIdentifier == GetObjectCLSID(), E_UNNAMED);
        if(!bRegister && nCoGetTreatAsClassResult == S_OK)
            __C(CoTreatAsClass(CLSID_FilterGraph, CLSID_NULL));
        _A(_pAtlModule);
        UpdateRegistryFromResource<CSpy>(bRegister);
        if(bRegister)
            __C(CoTreatAsClass(CLSID_FilterGraph, GetObjectCLSID()));
    }
    _ATLCATCH(Exception)
    {
        _C(Exception);
    }
    return S_OK;
}

The idea is that the emulation is enabled in case it was not enabled before or it was already enabled through our coclass. Every time an application cocreates an instance of Filter Graph Manager (FGM), CSpy class will be created instead.

Still as we have no plans to reinvent one of the most important DirectShow objects, we have to embed the original FGM insde to forward the calls to. The proper place in the ATL C++ code is FinalConstruct and what we actually do there is instantiating an aggregated FGM from the original in-process server. The safest would be to query registry for the original host, but we know it’s hosted by quartz.dll and it seems to be safe to hardcode:

HINSTANCE hModule = CoLoadLibrary(_T("quartz.dll"), TRUE);
_ATLTRY
{
    typedef HRESULT (WINAPI *DLLGETCLASSOBJECT)(REFCLSID, REFIID, VOID**);
    DLLGETCLASSOBJECT DllGetClassObject = (DLLGETCLASSOBJECT) GetProcAddress(hModule, "DllGetClassObject");
    __E(DllGetClassObject);
    CComPtr<IClassFactory> pClassFactory;
    __C(DllGetClassObject(CLSID_FilterGraph, __uuidof(IClassFactory), (VOID**) &pClassFactory));
    _A(pClassFactory);
    CComPtr<IUnknown> pUnknown;
    __C(pClassFactory->CreateInstance(GetControllingUnknown(), __uuidof(IUnknown), (VOID**) &pUnknown));
    CComQIPtr<IFilterGraph2> pFilterGraph2 = pUnknown;
    __D(pFilterGraph2, E_NOINTERFACE);

What is remained at the point is to implement IFilterGraph2 (along with inherited IGraphBuilder and IFilterGraph) and forward all incoming calls to the inner aggregated object. It would also be helpful to trace calls to a log file C:\FilterGraphSpy.log:

2009-01-28 22:07:51	4456	5656	dllmain.h(29): CFilterGraphSpyModule::CFilterGraphSpyModule: this 0x01cd327c
2009-01-28 22:07:51	4456	5656	Spy.h(75): CSpy::CSpy: this 0x01ce297c
2009-01-28 22:07:51	4456	5656	Spy.h(132): CSpy::AddFilter: pszName "C:\Documents and Settings\John Doe\Desktop\Melissa Cherry Interview.wmv"
2009-01-28 22:07:51	4456	5656	Spy.h(142): CSpy::EnumFilters: ...
2009-01-28 22:07:51	4456	5656	Spy.h(132): CSpy::AddFilter: pszName "ffdshow Audio Decoder"
2009-01-28 22:07:51	4456	5656	Spy.h(152): CSpy::ConnectDirect: ...
2009-01-28 22:07:51	4456	5656	Spy.h(137): CSpy::RemoveFilter: ...
2009-01-28 22:07:51	4456	5656	Spy.h(132): CSpy::AddFilter: pszName "WMAudio Decoder DMO"
2009-01-28 22:07:51	4456	5656	Spy.h(152): CSpy::ConnectDirect: ...
2009-01-28 22:07:51	4456	5656	Spy.h(142): CSpy::EnumFilters: ...

And additionally, the filter graph is immediately published on the ROT and is accessible with GraphEdit. Feel free to spy over graphs system wide.

A partial Visual C++ .NET 2008 source code is available from SVN, release binary included.

To install Filter Graph Spy:

  • download the binary DLL from version control repository (DirectShowSpy.dllWin32, x64)
  • COM register the DLL on target system, for example from command line using regsvr32 utility “regsvr32 DirectShowSpy.dll” (administrative privilege elevation required on Vista)
  • on some operating systems (MS Vista in particular) it is also required to have SDK’s proppage.dll available in the system, see Vista related post for more details

To uninstall:

  • use “regsvr32 /u” utility to unregister the DLL, close all DirectShow based applications or reboot OS, and delete the file; see also a related thread on MSDN Forums

Tags: , , , , , , ,