VitoPlantamura.com logo - Click here to go to the site home page... Click here to go to the site home page... Click here to go to the Blog page... Click here to go to the archives page... Click here to go to the about page...
"extremely impressive work"
- Mark Russinovich, www.sysinternals.com
(referring to my BugChecker)
my face...
Homepage of Vito Plantamura (@), Windows Researcher, VPC Technologies SRL CEO. [user=Guest] - updated: August 08, 2007
 ..:: COM NDR Inspector v.1.00 (with source code) ::..
NDR INSPECTOR v.1.00

NDR Inspector is an unique tool by Vito Plantamura .COM that allows to peek at the format of COM interfaces even in the case when a Type Library is not available.


NDR Inspector looking at the NDR-derived interface format of the interface IMapGen4x3Matrix (from the automation model of the Vito Plantamura's MapGen CSG editor)...

INTRODUCTION

Some time ago, in a reverse engineering work I was doing for discovering the reason of a very nasty crash in an application I was debugging, I needed a way to know the schemas of some COM interfaces that the application in question was calling for making out what was happening. Infact the COM object that the application was calling was generating an access violation exception and the source code of both the application and the server component was not available to me for reference. Obviously the Type Libraries of the aforementioned interfaces were not packaged inside/along with the component binaries, so an other method had to be devised to resolve the problem. Obviously I could disassemble the application exactly in the positions in code where the calls to the component were being made or, in the case of the component, try to guess the positions of the entry points of the methods and then run the disassembler over them. As you can guess, this is a very hard work to do: in most cases, you have to debug the application itself in assembly mode (disassembling it offline is not enough) and then try to guess what the code in question is doing in order to make an idea of its real purpose... I was not prepared to all this pain (and consider that I love debugging and disassembling works of others), so I came out with an idea that could resolve or at least simplify my problem.

I remembered that the DCOM marshaling engine transmits parameter data in what is known as NDR format. After some research and several tests, I noticed that when you compile an IDL source with the MIDL tool, the resulting proxy/stub code that is automatically generated may contain some interesting bytecode data that is intended to be passed as parameter to the well-known-to-debug-people NdrClientCall2 function. As it turned out reading the MSDN, you can compile your IDL source files specifying exactly to the compiler the format and working of the resulting proxy/stub function code: infact, the MIDL tool provides two methods for marshaling data: fully-interpreted and mixed-mode. The two methods differ a lot both in terms of approach to data marshaling and in terms of memory footprint of the resulting P/S modules. Basically what differs is really the presence or not and the format of the aforemetioned bytecode data: doing a simple test compiling a simple IDL source, I noticed that applying the old mixed-mode approach, MIDL was generating a 3Mb source file for the proxy code and that the bytecode data in question (although present in that source file) was pretty poor in format and richness for our purposes (it was in what the documentation calls the -Oi format). Infact, in this case, the stub code of the interface methods marshals the parameter data "online", i.e. by the means of actual source code that on a case by case basis handles the job of getting the parameters, preparing the buffers, converting the data and so on... The -Oi data present in this code can still come in handy for guessing the format of the related interfaces, but, after a quick investigation in internet and after several tests with MIDL I realized that the vast majority of the P/S modules registered on registries of today machines was compiled with the -Oif MIDL parameter: this flag instructs MIDL to use the fully-interpreted method in the generated P/S code. This means that the format of all the interface methods is represented in a special bytecode (in what the documentation calls the -Oif format) and no code is generated (in most cases) for the stub methods. In all cases the P/S code will call the NdrClientCall2 function simply specifying the offset in this bytecode table of the method being called and the NDR engine will do the rest (handling the buffers, converting the parameters data etc.). As you can guess, an all-purposes engine like this requires data in a very rich format: getting this data and trying to interpret it will surely come in handy in my reverse engineering work... !

GETTING "-OIF" DATA FROM P/S MODULES

After realizing the potentialities of the NDR bytecode data, I was faced with an other serious problem: how to get a pointer to the correct record in the bytecode table for a given interface method? I thought to various solutions to this problem, but the easiest one can be deduced looking at the source files that the MIDL compiler generates. Infact it turned out that MIDL prepares and fills in code an interesting structure (of type "ProxyFileInfo") that seems to be the starting point for getting pointers to other data structures that give out a wealth of information on the proxy and the stub code itself.


Since we are searching for the byte pointers to the NDR bytecode records of our interface methods, we have to follow the pointers in the various structs as in the graph above. Infact the pStubVtblList pointer in the ProxyFileInfo structure will point to an array of CInterfaceStubVtbl: each item in this collection refers to a single interface in the P/S module. From this point on, it is only a matter of following the indicated pointers for knowing the pointer to the global format string table and to the array of method_index-to-format_string_table_offset values for the specified interface.

The next problem, however, is how to obtain a pointer to the ProxyFileInfo data structure itself, our starting point in knowing the secrets of our proxy/stubs: from a quick research, you can discover that a good part of the P/S modules that are registered in your system export a function with name "GetProxyDllInfo". The prototype of this function can be extracted with ease from the Platform SDK header files relative to the RPC PROXY APIs:

void RPC_ENTRY GetProxyDllInfo(
    const ProxyFileInfo*** pInfo,
    const CLSID ** pId
);

This function returns a pointer to a pointer to the ProxyFileInfo structure and in the second output parameter tipically a pointer to the well-known CLSID_PSFactoryBuffer clsid identifier. However, further researching with utilities such as DEPENDS, can lead you to an unpleasant discovery: a very not-small portion of the P/S dlls registered in the registry of a typical computer, doesn't come with this function exported. For resolving this problem, I was forced to think to an other solution for obtaining this pointer. After further research in the Platform SDK header files, I discovered that this same pointer is also handled internally by the well-known DllGetClassObject function: this function needs this pointer in order to call the low-level NdrDllGetClassObject function in order to process the application requests for class objects that tipically come from calls to the CoGetClassObject function (note however that the mechanism described here for returning a class object interface pointer to the user in result of a CoGetClassObject call is the "standard" one that MIDL implements in all its automatically generated stub files: a custom implementation can override this behaviour and use other means for returning this same information). Then the idea that I have implemented in the NDR Inspector is to force a call to the DllGetClassObject function and then to intercept all the calls that are made in that same thread (in the scope of that function call) to the low-level function NdrDllGetClassObject. This has been proved to lead to the expected results.

This is the implementation of the class that takes care of this nasty task:

//
// NDR class.
//
 
DETOUR_TRAMPOLINE( HRESULT RPC_ENTRY Trampoline_NdrDllGetClassObject( IN REFCLSID rclsid, IN REFIID riid, OUT void **ppv, IN const ProxyFileInfo **pProxyFileList, IN const CLSID *pclsid, IN CStdPSFactoryBuffer* pPSFactoryBuffer ), NdrDllGetClassObject )
HRESULT RPC_ENTRY Hooked_NdrDllGetClassObject( IN REFCLSID rclsid, IN REFIID riid, OUT void **ppv, IN const ProxyFileInfo **pProxyFileList, IN const CLSID *pclsid, IN CStdPSFactoryBuffer* pPSFactoryBuffer );
 
typedef HRESULT ( STDAPICALLTYPE * PFNDllGetClassObject ) ( REFCLSID rclsid, REFIID riid, LPVOID * ppv );
typedef void ( RPC_ENTRY * PFNGetProxyDllInfo )( const ProxyFileInfo*** pInfo, const CLSID ** pId );
 
typedef std::vector< ProxyFileInfo* >        VPProxyFileInfos;
 
struct SClsIdModName
{
      CLSID                         clsid;
      charstring                    modname;
      HMODULE                             hmod;
      CHAR                          szPSDllName[ MAX_PATH ];
      PFNDllGetClassObject      pfnDllGetClassObject;
      PFNGetProxyDllInfo            pfnGetProxyDllInfo;
 
      BOOL                          getclsCalled;
      VPProxyFileInfos        vpPFIs;
};
typedef std::vector< SClsIdModName >         VClsIdModName;
 
class CProxyFileInfoRetriever
{
public:
 
      //
      // Construction.
      //
 
      CProxyFileInfoRetriever ()
      {
            if ( pINSTANCE )
                  throw;
            else
                  pINSTANCE = this;
 
            ::InitializeCriticalSection( & csGetCs );
 
            ::DetourFunctionWithTrampoline( (PBYTE) Trampoline_NdrDllGetClassObject, (PBYTE) Hooked_NdrDllGetClassObject );
      }
 
      virtual ~CProxyFileInfoRetriever ()
      {
            ::DetourRemove( (PBYTE) Trampoline_NdrDllGetClassObject, (PBYTE) Hooked_NdrDllGetClassObject );
 
            ::DeleteCriticalSection( & csGetCs );
 
            for ( int i=0; i<vModNames.size(); i ++ )
                  ::FreeLibrary( vModNames[ i ].hmod );
 
            pINSTANCE = NULL;
      }
 
      //
      // Methods.
      //
 
      class CGetSync
      {
      public:
            CGetSync ()
            {
                  ::EnterCriticalSection( & CProxyFileInfoRetriever::pINSTANCE->csGetCs );
            }
            virtual ~CGetSync ()
            {
                  ::LeaveCriticalSection( & CProxyFileInfoRetriever::pINSTANCE->csGetCs );
            }
      };
 
      BOOL Get( IN REFIID riid, OUT VPProxyFileInfos& out, OUT OPTIONAL charstring* pcsPSDllName = NULL, OUT OPTIONAL CLSID* pclsidPS = NULL )
      {
            // init params.
 
            out.clear ();
            if ( pcsPSDllName )
                  * pcsPSDllName = "";
            if ( pclsidPS )
                  ::memset( pclsidPS, 0, sizeof( CLSID ) );
 
            // get ndr data.
 
            CGetSync                sync;
            // // // // // // // // // // // // // // // // // // // // // // // // // // //
            {
                  BOOL              retval = FALSE;
 
                  // Get the CLSID of the Proxy/Stub module.
 
                  CLSID       psclsid;
                  ::memset( & psclsid, 0, sizeof( psclsid ) );
 
                  HRESULT           hr;
 
                  hr = ::CoGetPSClsid( riid, & psclsid );
                  if ( SUCCEEDED( hr ) )
                  {
                        // Check out whether this CLSID was already encountered.
 
                        int               i;
 
                        SClsIdModName*          pMOD = NULL;
 
                        for ( i=0; i<vModNames.size(); i ++ )
                              if ( ::IsEqualCLSID( psclsid, vModNames[ i ].clsid ) )
                              {
                                    pMOD = & vModNames[ i ];
                                    break;
                              }
 
                        // If not, get the module name from the Registry.
 
                        if ( pMOD == NULL )
                        {
                              LPOLESTR          clsidOleStr = NULL;
                              if ( ::StringFromCLSID( psclsid, & clsidOleStr ) == S_OK )
                              {
                                    charstring        kname =
                                          "CLSID\\" + ::WideStringToCharString( widestring( clsidOleStr ) ) + "\\InprocServer32";
 
                                    LONG              res;
                                    HKEY              hkey;
 
                                    res = ::RegOpenKey( HKEY_CLASSES_ROOT, kname.c_str(), & hkey );
                                    if ( res == ERROR_SUCCESS )
                                    {
                                          DWORD       dwType = 0;
                                          CHAR        path[ MAX_PATH ] = "";
                                          DWORD       dwLen = sizeof( path );
 
                                          res = ::RegQueryValueEx( hkey, NULL, NULL, & dwType, (LPBYTE) path, & dwLen );
 
                                          if ( res == ERROR_SUCCESS && dwType == REG_EXPAND_SZ )
                                          {
                                                CHAR        szExp[ MAX_PATH ] = "";
                                                if ( ::ExpandEnvironmentStrings( path, szExp, sizeof( szExp ) ) == 0 )
                                                {
                                                      res = ERROR_BAD_FORMAT;
                                                }
                                                else
                                                {
                                                      ::strcpy( path, szExp );
                                                      dwType = REG_SZ;
                                                }
                                          }
 
                                          if ( res == ERROR_SUCCESS && dwType == REG_SZ )
                                          {
                                                // Load the module.
 
                                                HMODULE                 hmod = ::LoadLibrary( path );
                                                if ( hmod )
                                                {
                                                      PFNDllGetClassObject      pfnDllGetClassObject =
                                                            (PFNDllGetClassObject) ::GetProcAddress( hmod, "DllGetClassObject" );
                                                      PFNGetProxyDllInfo            pfnGetProxyDllInfo =
                                                            (PFNGetProxyDllInfo) ::GetProcAddress( hmod, "GetProxyDllInfo" );
 
                                                      if ( pfnDllGetClassObject == NULL )
                                                      {
                                                            ::FreeLibrary( hmod );
                                                      }
                                                      else
                                                      {
                                                            SClsIdModName           item;
                                                            item.clsid = psclsid;
                                                            item.modname = path;
                                                            item.hmod = hmod;
                                                            ::strcpy( item.szPSDllName, path );
                                                            item.pfnDllGetClassObject = pfnDllGetClassObject;
                                                            item.pfnGetProxyDllInfo = pfnGetProxyDllInfo;
                                                            item.getclsCalled = FALSE;
 
                                                            vModNames.push_back( item );
                                                            pMOD = & vModNames[ vModNames.size() - 1 ];
                                                      }
                                                }
                                          }
 
                                          ::RegCloseKey( hkey );
                                    }
 
                                    ::CoTaskMemFree( clsidOleStr );
                              }
                        }
 
                        // If the module was found, call the GetClassObject function, with an arbitrary CLSID.
 
                        if ( pMOD )
                        {
                              if ( pMOD->getclsCalled == FALSE )
                              {
                                    pCURRMOD = pMOD;
                                    pCURRTHREAD = ::GetCurrentThreadId();
 
                                          // call the export function, if present.
 
                                          if ( pMOD->pfnGetProxyDllInfo )
                                          {
                                                const ProxyFileInfo**   ppPFI = NULL;
                                                const CLSID*                  pCLSID = NULL;
                                                pMOD->pfnGetProxyDllInfo( & ppPFI, & pCLSID );
                                                if ( ppPFI && * ppPFI )
                                                      pMOD->vpPFIs.push_back( const_cast<ProxyFileInfo*>( * ppPFI ) );
                                          }
 
                                          // try making a fake call to DllGetClassObject: this will trigger our NDR low-level hook.
 
                                          IClassFactory*          pFactory = NULL;
                                          if ( SUCCEEDED( pMOD->pfnDllGetClassObject( pMOD->clsid, IID_IClassFactory, (LPVOID*) & pFactory ) ) )
                                                pFactory->lpVtbl->Release ( pFactory ); // should never happen !
 
                                    pCURRMOD = NULL;
                                    pCURRTHREAD = 0;
 
                                    pMOD->getclsCalled = TRUE;
                              }
 
                              if ( pMOD->vpPFIs.size() )
                              {
                                    out = pMOD->vpPFIs;
                                    retval = TRUE;
                              }
 
                              if ( pcsPSDllName )
                                    * pcsPSDllName = pMOD->szPSDllName;
 
                              if ( pclsidPS )
                                    * pclsidPS = pMOD->clsid;
                        }
                  }
 
                  return retval;
            }
            // // // // // // // // // // // // // // // // // // // // // // // // // // //
      }
 
      //
      // Instance Pointer.
      //
 
      static CProxyFileInfoRetriever*            pINSTANCE;
 
protected:
 
      //
      // State.
      //
 
      CRITICAL_SECTION              csGetCs;
 
      VClsIdModName                       vModNames;
 
      static SClsIdModName*          pCURRMOD;
      static DWORD                   pCURRTHREAD;
 
      //
      // Implementation.
      //
 
      VOID RPC_ENTRY NdrDllGetClassObject( IN REFCLSID rclsid, IN REFIID riid, OUT void **ppv, IN const ProxyFileInfo **pProxyFileList, IN const CLSID *pclsid, IN CStdPSFactoryBuffer* pPSFactoryBuffer )
      {
            if ( pCURRMOD && pCURRTHREAD == ::GetCurrentThreadId() && pProxyFileList )
            {
                  ProxyFileInfo*          pfi = * (ProxyFileInfo**) pProxyFileList;
                  if ( pfi )
                  {
                        BOOL              found = FALSE;
 
                        for( int i=0; i<pCURRMOD->vpPFIs.size(); i ++ )
                              if ( pCURRMOD->vpPFIs[ i ] == pfi )
                              {
                                    found = TRUE;
                                    break;
                              }
 
                        if ( found == FALSE )
                              pCURRMOD->vpPFIs.push_back( pfi );
                  }
            }
      }
 
      //
      // Friends.
      //
 
      friend
            HRESULT RPC_ENTRY Hooked_NdrDllGetClassObject( IN REFCLSID rclsid, IN REFIID riid, OUT void **ppv, IN const ProxyFileInfo **pProxyFileList, IN const CLSID *pclsid, IN CStdPSFactoryBuffer* pPSFactoryBuffer );
      friend
            class CGetSync;
};
 
SClsIdModName*                            CProxyFileInfoRetriever::pCURRMOD = NULL;
DWORD                                     CProxyFileInfoRetriever::pCURRTHREAD = 0;
CProxyFileInfoRetriever*            CProxyFileInfoRetriever::pINSTANCE = NULL;
 
HRESULT RPC_ENTRY Hooked_NdrDllGetClassObject( IN REFCLSID rclsid, IN REFIID riid, OUT void **ppv, IN const ProxyFileInfo **pProxyFileList, IN const CLSID *pclsid, IN CStdPSFactoryBuffer* pPSFactoryBuffer )
{
      if ( CProxyFileInfoRetriever::pINSTANCE )
            CProxyFileInfoRetriever::pINSTANCE->NdrDllGetClassObject( rclsid, riid, ppv, pProxyFileList, pclsid, pPSFactoryBuffer );
 
      return Trampoline_NdrDllGetClassObject( rclsid, riid, ppv, pProxyFileList, pclsid, pPSFactoryBuffer );
}
 

LIMITATIONS

There are two main limitations that affect the use of NDR Inspector at its current release:

     The NDR parser that is included in this version is rather elementary. I have NOT written a full NDR parser because the few times I really required this application, its basic NDR dump was detailed enough to come in handy in those occasions. Furthermore I suspect that writing a full-featured NDR parser and dumper can require a very large amount of time, effort and research (the MSDN documentation is far from being complete): this may easily turn out as wasted time because the level of dump of the current release will suffice in most situations.
     Note that for unknown reasons that will be cleared by further research some interfaces that are registered in the registry as remotable via a proxy/stub module, sometimes are not "published" in the structures that are included in the ProxyFileInfo tree. I will try to do experiments forcing marshaling of parameter data on those interfaces in order to understand whether the NDR data for those interfaces exists and perhaps, if required, to think to an other system to obtain that data.

USEFUL LINKS

/Os MIDL flag from MSDN site: link.
/Oi MIDL flag from MSDN site: link.
RPC NDR Format Strings from MSDN site: link.

DOWNLOAD

Download NDR Inspector v.1.00 (binaries only) from here (24KB).
Download NDR Inspector v.1.00 (plus source code) from here (140KB).

 Quotes
"Among the Windows experts I know personally, no one can beat Vito Plantamura."
- Francesco Balena, Code Architects SRL

"Your NDIS Monitor application, is amongst the most impressive networking code I have seen on the .Net framework."
- Ben Hakim.
 Photos
Various images from italian conferences and events (keep the mouse on a thumbnail for a short description):
Me at the Microsoft/HP/Intel organized Route64 event in Milan in May 2005, explaining how COM+ behaves on 64-bit Microsoft operating systems. I was there with the friends of Code Architects.
Me at the Microsoft Security Roadshow event in Bari in April 2006, explaining how the logon process works in Windows NT. There were 250 attendees.
Microsoft Security Roadshow 2006 in Treviso. This is an image of the huge 700-seats conference room.
Me at the Microsoft Security Roadshow 2006 in Treviso. This is a moment of the 3-hours session.
 Site login
NOTE: Actually the login feature is used only for administrative and content management purposes.
Username

Password

Everything here (code, binaries, text, graphics, design, html) is © 2010 Vito Plantamura and VPC Technologies SRL (VATID: IT06203700965).
If you download something (compilable or not) from the site, you should read the license policy file.
If you want to contact me via email, write at this address.