[转]Replace all UUIDs in an ATL COM DLL.

1. Introduction.

1.1 Recently, a friend asked me for advise on a very unusual requirement.

1.2 He needs to replace all UUIDs in a COM DLL with new ones.

1.3 He does not have the source codes to the original COM server so any modifications will have to be done on the binary code.

1.4 To be more accurate, the objective was to replace the UUIDs of COM Types defined in the COM DLL.

1.5 Ever enthusiastic for any opportunity to do unusual spelunking, I helped to research into this.

1.6 Using a sample COM in-proc server written in ATL, I managed to accomplish this and verified the results using a test program.

1.7 In this blog, I aim to demonstrate to the reader how this can be done.

2. The General Outline of the Plan.

2.1 As I advised my friend, performing such a feat is certainly possible but it depends on how the original COM DLL was created :

– What tool was used to create the DLL ? e.g. ATL, VB, C#, etc.
– Was it hand-crafted instead in C++ ?

2.2 Assuming that the COM DLL server was developed using ATL, the process is quite straightforward but involves a number of steps :

  • Replacing the Type Library embedded as a resource inside the DLL.
  • Replacing the registration strings embedded as a resource inside the DLL.
  • Replacing all occurrences of specific GUIDs inside the code of the DLL.
  • Registering the new DLL.

2.3 For the purposes of demonstration, I created a simple COM in-proc server and a test console application that uses the COM Types in the DLL.

2.4 The test program will initially link with the original DLL.

2.5 After we have created a new DLL with all the replacement UUIDs, the test program will link to the new DLL and we shall see that the output of the test program will be the same.

3. A Simple ATL COM DLL.

3.1 The following are the main characteristics of the COM DLL (SimpleATLCOMServer.dll) which we will be working on :

  • It is 32-bit and is written using ATL.
  • It exposes a single coclass named SimpleCOMClass.
  • SimpleCOMClass exposes a single interface ISimpleCOMClass.
  • SimpleCOMClass supports a COM event _ISimpleCOMClassEvents.

3.2 The following is a listing of the IDL :

// SimpleATLCOMServer.idl : IDL source for SimpleATLCOMServer
//

// This file will be processed by the MIDL tool to
// produce the type library (SimpleATLCOMServer.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";

[     object,     uuid(4C1AE3E8-736E-4750-9D08-48CDC33E66FA),     dual,     nonextensible,     pointer_default(unique)
]
interface ISimpleCOMClass : IDispatch{     [id(1)] HRESULT TestMethod01([in] BSTR strParam);
};
[     uuid(8A32BF84-3743-4AE5-A791-623F17C6E804),     version(1.0),
]
library SimpleATLCOMServerLib
{     importlib("stdole2.tlb");     [         uuid(60FD53D5-2D3E-4BCC-96E6-484F8D8A5119)             ]     dispinterface _ISimpleCOMClassEvents     {         properties:         methods:             [id(1)] HRESULT TestEvent01([in] BSTR strParam);     };     [         uuid(68226D2E-8EE4-4B42-9B39-2D4ED9D578DD)             ]     coclass SimpleCOMClass     {         [default] interface ISimpleCOMClass;         [default, source] dispinterface _ISimpleCOMClassEvents;     };
};

3.3 The ISimpleCOMClass interface has a single method TestMethod01() which takes a BSTR as parameter.

3.4 The _ISimpleCOMClassEvents event interface has a single method TestEvent01() which also takes a BSTR parameter.

3.5 The actual implementation for ISimpleCOMClass is a C++ class named CSimpleCOMClass.

3.6 The definition for TestMethod01() is as follows :

STDMETHODIMP CSimpleCOMClass::TestMethod01(BSTR strParam)
{     // TODO: Add your implementation code here     _bstr_t _bst(strParam, true);     LPCTSTR lpszParam = (LPCTSTR)_bst;     MessageBox(NULL, lpszParam, "CSimpleCOMClass", MB_OK);     Fire_TestEvent01(strParam);     return S_OK;
}

It displays the contents of the BSTR parameter and then fires TestEvent01() using the same BSTR parameter to TestMethod01().

3.7 In the sections that follow, we will expound in greater detail the steps that we take to replace all COM UUIDs in the DLL with new ones.

4. Replacing the Type Library embedded as a resource inside the DLL.

4.1 Extracting the type library of the original DLL can be done using OleView. The following is a screenshot of how the IDL will be displayed :

simpleatlcomserver-idl

  • The great thing about OleView is that it is able to save the displayed IDL into an external file.
  • We shall save it as SimpleATLCOMServerNew.IDL.

4.2 Next, open SimpleATLCOMServerNew.IDL and perform the following changes :

  • Generate new UUIDs using GUIDGEN.EXE.
  • Replace the UUID for the SimpleATLCOMServerLib LIBID, _ISimpleCOMClassEvents Event Interface UUID, the SimpleCOMClass coclass CLSID and ISimpleCOMClass interface IID.
  • Change the name of the library from SimpleATLCOMServerLib to SimpleATLCOMServerNewLib.

The following is a sample modified SimpleATLCOMServerNew.IDL :

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: SimpleATLCOMServer.dll

[   uuid(F702C924-7CC9-4E26-BE36-E646222A057E),    version(1.0),   custom(DE77BA64-517C-11D1-A2DA-0000F8773CE9, 134218331),   custom(DE77BA63-517C-11D1-A2DA-0000F8773CE9, 1485538469),   custom(DE77BA65-517C-11D1-A2DA-0000F8773CE9, "Created by MIDL version 8.00.0603 at Sat Jan 28 01:34:25 2017
")

]
library SimpleATLCOMServerNewLib
{     // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}     importlib("stdole2.tlb");     // Forward declare all types defined in this typelib     dispinterface _ISimpleCOMClassEvents;     interface ISimpleCOMClass;     [       uuid(E793B680-C0B2-4FB6-ADDD-7F66A30EF5DB)      ]     dispinterface _ISimpleCOMClassEvents {         properties:         methods:             [id(0x00000001)]             HRESULT TestEvent01([in] BSTR strParam);     };     [       uuid(F7C6EE89-AA69-4BB6-8CA0-D7CC9B6A0473)      ]     coclass SimpleCOMClass {         [default] interface ISimpleCOMClass;         [default, source] dispinterface _ISimpleCOMClassEvents;     };     [       odl,       uuid(78A36043-6DAE-4457-95D8-A4425ECF84DA),       dual,       nonextensible,       oleautomation     ]     interface ISimpleCOMClass : IDispatch {         [id(0x00000001)]         HRESULT TestMethod01([in] BSTR strParam);     };
};

All new replacement UUIDs are displayed in bold. The new name for the library is also in bold.

4.3 Next we need to recompile the new IDL using MIDL.exe :

  • In a command prompt, go to the folder where SimpleATLCOMServerNew.IDL is stored and run the following command :
midl SimpleATLCOMServerNew.IDL /env win32 /W1 /char signed
  • Before running this command, you may need to set the “INCLUDE” path to include new directories (so as to ensure a successful MIDL compilation) :
set INCLUDE=%INCLUDE%;C:Program Files (x86)Windows Kits8.1Includeum;C:Program Files (x86)Windows Kits8.1Includeshared
  • A new type library SimpleATLCOMServerNew.tlb will be produced.

4.4 Then, use Visual Studio to replace the original type library of the DLL with the new modified one :

  • Make a copy of the original SimpleATLCOMServer.dll and named it as SimpleATLCOMServerNew.dll.
  • Run Visual Studio and open SimpleATLCOMServerNew.dll as an executable file.

open-simpleatlcomservernew-dll-as-executable

  • Right-click on “TYPELIB” and select “Add Resource” :

typelib-addresource

  • The “Add Resource” dialog box will appear :

addresourcedlg

  • Select “TYPELIB” and click on the “Import” button.
  • A File Selection dialog box will appear.
  • Search for and select the new type library SimpleATLCOMServerNew.tlb that we have just created.
  • A new “TYPELIB” resource will be added :

typelib-newresource

  • Note well, however, that the resource ID for the new “TYPELIB” resource is one given by Visual Studio.
  • In our example, it is 101. It needs to be renumbered to 1.
  • To do this, remove the “TYPELIB” resource with ID 1 by righ-clicking on it and selecting “Delete” :

typelib-deleteresource

  • Thereafter, select the 101 resource and right-click on it. Then select “Properties”.
  • Under the “Properties” explorer, change the ID to 1 :

typelib-changeresourceid

  • And that’s it. We have put in place a new type library resource within the COM DLL.

5. Replacing the Registration Scripts Embedded as a Resource inside the DLL.

5.1 The registration scripts are the .rgs resources commonly found in ATL projects.

5.2 It contains strings that are used in the COM registration process.

5.3 The objective is to extract the existing .rgs resources, modify them, and then re-insert them into the DLL.

5.4 Extracting the .rgs resource is done once again using Visual Studio by opening up the DLL as an executable file :

  • This time, the .rgs resources are contained in “REGISTRY” :

registry-extractresource

  • Unlike the type library, there could be several .rgs resources contained in “REGISTRY”.
  • However, in our simple example, only one .rgs resource is important (ID 106).
  • Select all relevant ,rgs resources and export them by right-clicking and then selecting “Export” :

registry-exportresource

  • A File-Save dialog box will appear. Save it as SimpleATLCOMServerNew.rgs.
  • SimpleATLCOMServerNew.rgs is a text file. Open it using notepad.exe.
  • The following is the contents :
HKCR
{     NoRemove CLSID     {         ForceRemove {68226D2E-8EE4-4B42-9B39-2D4ED9D578DD} = s 'SimpleCOMClass Class'         {             ForceRemove Programmable             InprocServer32 = s '%MODULE%'             {                 val ThreadingModel = s 'Apartment'             }             TypeLib = s '{8A32BF84-3743-4AE5-A791-623F17C6E804}'             Version = s '1.0'         }     }
}
  • We need to replace the existing UUIDs with new corresponding ones :
HKCR
{     NoRemove CLSID     {         ForceRemove {F7C6EE89-AA69-4BB6-8CA0-D7CC9B6A0473} = s 'SimpleCOMClass Class'         {             ForceRemove Programmable             InprocServer32 = s '%MODULE%'             {                 val ThreadingModel = s 'Apartment'             }             TypeLib = s '{F702C924-7CC9-4E26-BE36-E646222A057E}'             Version = s '1.0'         }     }
}
  • Next, using methods that we have used previously on type library replacement, replace the existing “REGISTRY” resource (ID 106) with the new modified one.

6. Replacing all Occurrences of Specific UUIDs inside the Code of the DLL.

6.1 This is by far the most interesting part of the replacement process.

6.2 To do this, we need to write a program that performs the following :

  • Open the DLL file as a stream of bytes.
  • Scan through the stream of bytes and search for byte patterns that correspond with the original UUIDs.
  • Replace all original UUIDs with the new ones.

6.3 It is important to note that a UUID is in actual fact a binary structure and not a string (see UUID structure).

  • For convenience, UUIDs are often represented as strings in source codes.
  • But they will all resolve to binary structures at runtime.
  • Code like the following :
spISimpleCOMClass->QueryInterface(IID_ISimpleCOMClass, (void**)&pISimpleCOMClass);

requires that IID_ISimpleCOMClass be expressed as a structure like the following :

{0x4c1ae3e8,0x736e,0x4750,{0x9d,0x08,0x48,0xcd,0xc3,0x3e,0x66,0xfa}
  • It is essentially a series of bytes.
  • Hence to replace all occurrences of IID_ISimpleCOMClass in a DLL, our program can simply search for such byte sequences and directly replace them.

6.4 This program is best done in C# due to the rich functionality of the .NET class libraries.

6.5 The following is a listing of the C# program :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace ConsoleGUIDReplacer
{     class Program     {         // Code for the following algorithm taken from :         // Most efficient way to replace one sequence of the bytes with some other sequence         // http://stackoverflow.com/questions/10702514/most-efficient-way-to-replace-one-sequence-of-the-bytes-with-some-other-sequence         private static byte[] Replace(byte[] input, byte[] pattern, byte[] replacement)         {             if (pattern.Length == 0)             {                 return input;             }             List<byte> result = new List<byte>();             int i;             for (i = 0; i <= input.Length - pattern.Length; i++)             {                 bool foundMatch = true;                 for (int j = 0; j < pattern.Length; j++)                 {                     if (input[i + j] != pattern[j])                     {                         foundMatch = false;                         break;                     }                 }                 if (foundMatch)                 {                     result.AddRange(replacement);                     i += pattern.Length - 1;                 }                 else                 {                     result.Add(input[i]);                 }             }             for (; i < input.Length; i++)             {                 result.Add(input[i]);             }             return result.ToArray();         }         static void Main(string[] args)         {             string strCOMServerTargetPath = args[0];             string strCOMServerNewPath = args[1];             Guid guid_search = new Guid(args[2]);             Guid guid_replace = new Guid(args[3]);             byte[] byCOMServerBytesTarget = File.ReadAllBytes(strCOMServerTargetPath);             byte[] byCOMServerBytesNew = Replace(byCOMServerBytesTarget, guid_search.ToByteArray(), guid_replace.ToByteArray());             File.WriteAllBytes(strCOMServerNewPath, byCOMServerBytesNew);         }     }
}
  • Note that the code for the Replace() method is taken directly from the following StackOverflow discussion thread :

Most efficient way to replace one sequence of the bytes with some other sequence.

6.6 The program expects 4 runtime arguments :

  • The path to the original DLL.
  • The path to the a new DLL that will contain the replaced UUID.
  • The original UUID in string form.
  • The replacement UUID in string form.

The following is a sample command line :

ConsoleGUIDReplacer.exe SimpleATLCOMServerNew.dll SimpleATLCOMServerNew.01.dll 4C1AE3E8-736E-4750-9D08-48CDC33E66FA 78A36043-6DAE-4457-95D8-A4425ECF84DA
  • The above command will change all occurrences of the original IID of ISimpleCOMClass with a new one that we have generated.
  • A new DLL SimpleATLCOMServerNew.01.dll will be created with the new IID.
  • We will have to call ConsoleGUIDReplacer.exe 4 times in order to replace all original UUIDs with the new ones.
  • Each time we run ConsoleGUIDReplacer.exe, a new version of the SimpleATLCOMServerNew DLL will be generated.
  • Eventually, we will emerge with a complete SimpleATLCOMServerNew.dll with all original UUIDs replaced with new ones.

6.7 The availability of structures like Guid in .NET goes a long way towards simplifying things :

  • It provides a constructor that takes a UUID in string form (perfect for us).
  • It provides a ToByteArray() method that simplifies the process of expressing the UUID as a series of bytes (again, perfect for us).

7. Registering the new DLL.

7.1 Not to forget : we must also register the new SimpleATLCOMServerNew.dll.

7.2 This is necessary because it is, in the eyes of COM, completely distinct from the original SimpleATLCOMServer.dll.

7.3 Run regsvr32.exe as per normal.

8. Test Program.

8.1 The following is a general game plan for the test :

  • We write a C++ console program that imports the original SimpleATLCOMServer.dll.
  • In the test program, we will create an instance of the SimpleCOMClass coclass.
  • Using its ISimpleCOMClass interface, we call TestMethod01().
  • We will also write an event handler class and receive the TestEvent01() event.
  • We then modify the test program and import the new SimpleATLCOMServerNew.dll.
  • We then re-compile and run the program once again.
  • The same result will be observed.

8.2 The listing for the test program is as follows :

// ConsoleClientApp.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

// Comment in/out the import and using statement according
// to which version of the DLL is to be used.
#import "SimpleATLCOMServer.dll" raw_interfaces_only
using namespace SimpleATLCOMServerLib;

//#import "SimpleATLCOMServerNew.dll" raw_interfaces_only
//using namespace SimpleATLCOMServerNewLib;

#include "TEventHandler.h"
using namespace TEventHandlerNamespace;

class EventHandler;

typedef TEventHandler<EventHandler, ISimpleCOMClass, _ISimpleCOMClassEvents> ISimpleCOMClassEventHandler;

class EventHandler
{
public :     EventHandler(ISimpleCOMClassPtr spISimpleCOMClass)     {         // ***** Create an instance of an object which implements IEventFiringObject. *****         m_spISimpleCOMClass = spISimpleCOMClass;         // ***** Instantiate an IEventFiringObjectEventHandler object. *****         m_pISimpleCOMClassEventHandler = new ISimpleCOMClassEventHandler(*this, m_spISimpleCOMClass, 
         &EventHandler::OnSimpleCOMClassInvoke);     }     ~EventHandler()     {         if (m_pISimpleCOMClassEventHandler)         {             m_pISimpleCOMClassEventHandler->ShutdownConnectionPoint();             m_pISimpleCOMClassEventHandler->Release();             m_pISimpleCOMClassEventHandler = NULL;         }         if (m_spISimpleCOMClass)         {             m_spISimpleCOMClass = NULL;         }     }     ISimpleCOMClassPtr            m_spISimpleCOMClass;     // ***** Declare a pointer to a TEventHandler class which is specially tailored *****     // ***** to receiving events from the _IEventFiringObjectEvents events of an *****     // ***** IEventFiringObject object. *****     ISimpleCOMClassEventHandler* m_pISimpleCOMClassEventHandler;     HRESULT EventHandler::OnSimpleCOMClassInvoke         (             ISimpleCOMClassEventHandler* pEventHandler,             DISPID dispidMember,             REFIID riid,             LCID lcid,             WORD wFlags,             DISPPARAMS* pdispparams,             VARIANT* pvarResult,             EXCEPINFO* pexcepinfo,             UINT* puArgErr             )     {         if (dispidMember == 0x01)  // Event1 event.         {             // 1st param : [in] BSTR strParam.             VARIANT    varValue;                         VariantInit(&varValue);             varValue = (pdispparams->rgvarg)[0];             _bstr_t _bst(V_BSTR(&varValue), true);             char szMessage[256];             sprintf_s(szMessage, sizeof(szMessage), "Event 1 is fired with value : %s.", (LPCTSTR)_bst);             ::MessageBox(NULL, szMessage, "Event", MB_OK);         }         return S_OK;     }
};

void DoTest()
{     ISimpleCOMClassPtr spISimpleCOMClass = NULL;         spISimpleCOMClass.CreateInstance(__uuidof(SimpleCOMClass));     EventHandler event_handler(spISimpleCOMClass);     BSTR bstr = ::SysAllocString(L"Hello World");     if (bstr)     {         spISimpleCOMClass->TestMethod01(bstr);         ::SysFreeString(bstr);         bstr = NULL;     }
}


int main()
{     ::CoInitialize(NULL);     DoTest();     ::CoUninitialize();     return 0;
}

8.3 For the purposes of the test, I have copied the original and new DLLs into the same folder as the project of the test program.

8.4 Notice that when we import SimpleATLCOMServerNew.dll, we use the SimpleATLCOMServerNewLib namespace.

8.5 This is because of what we did in point 4.2 where we changed the name of the library statement in the new IDL from SimpleATLCOMServerLib to SimpleATLCOMServerNewLib.

8.6 For handling COM events, I have created the EventHandler class and have used the TEventHandler class (see Understanding COM Event Handling) to simplify handling IDispatch-based event interfaces.

8.7 When we run the above code, the following will occur :

  • As TestMethod01() is called, the following dialog box will be displayed :

testprogram01

  • Then TestMethod01() will fire the TestEvent01() event which will cause our event handler to display the following dialog box :

testprogram02

8.8 Next, comment out the import of the SimpleATLCOMServer.dll and comment in the import of SimpleATLCOMServerNew.dll :

// Comment in/out the import and using statement according
// to which version of the DLL is to be used.
//#import "SimpleATLCOMServer.dll" raw_interfaces_only
//using namespace SimpleATLCOMServerLib;

#import "SimpleATLCOMServerNew.dll" raw_interfaces_only
using namespace SimpleATLCOMServerNewLib;
  • Re-compile the test program.
  • Run it again.

You will see that the same dialog boxes will be displayed.

8.9 As an additional step that we can take to ensure that these 2 DLLs are truly distinct, we can modify the original SimpleATLCOMServer.dllby changing the code for TestMethod01() as follows :

STDMETHODIMP CSimpleCOMClass::TestMethod01(BSTR strParam)
{     // TODO: Add your implementation code here     _bstr_t _bst(strParam, true);     LPCTSTR lpszParam = (LPCTSTR)_bst;     MessageBox(NULL, lpszParam, "CSimpleCOMClass Original", MB_OK);     Fire_TestEvent01(strParam);     return S_OK;
}

We simply change the dialog box title to “CSimpleCOMClass Original”.

8.10 Thereafter, modify ConsoleClientApp.cpp once again to import SimpleATLCOMServer.dll, compile the test program and run it :

  • This time, when TestMethod01() is called, the following will be displayed :

testprogram-change-original

8.11 Now, once again import SimpleATLCOMServerNew.dll, re-compile and run the program :

  • When TestMethod01() is called, the original dialog box will be displayed :

testprogram01

9. In Summary.

9.1 In summary, we can see that complete replacement of UUIDs in a COM DLL server is possible. However, note well :

  • As mentioned previously, we are merely changing the UUIDs of COM types which have already been implemented in the server.
  • We are not attempting to change the logic of the implementation code.
  • If the implementation code references other CLSIDs, IIDs, LIBIDs or any other UUIDs other than the ones we already know, we do not modify these (in fact, we shouldn’t).

9.2 Note well that, with the exception of COM servers written in a managed language, regardless of whatever tool was used to generate a COM DLL server, the following are common requirements of the DLL :

  • All COM DLLs must have its type library embedded as a resource. This is to ensure that the COM types in the DLL can be referenced by development tools like Visual Studio (e.g. via the #import statement in C++).
  • All COM DLLs (whatever tool was used to create it), must be self-registrable.

Hence at minimum, it should be no problem to at least modify the embedded type libraries contained inside a COM DLL. Modifying the self-registration code will require some further research.

9.3 The techniques for modification of UUIDs in runtime code will also likely be varied.

9.4 I hope to research into these in the future.

10. Source Codes.

10.1 The source codes for this blog can be downloaded from here.

原文地址:https://www.cnblogs.com/czytcn/p/7928173.html