COM objects 101
What is COM ?
The Component Object Model is an object-based programming model designed to promote software interoperability; that is, to allow two or more applications or “components” to easily cooperate with one another, even if they were written by different vendors at different times, in different programming languages, or if they are running on different machines running different operating systems.You define a custom COM library by designing the interfaces that your library implements. Consumers of your library can discover and use its features without any knowledge of your library’s deployment and implementation details.
What is the difference between COM and DCOM objects?
COM objects primarily operate at a local level, executing on the client’s machine. In contrast, DCOM (Distributed Component Object Model) functions on the server end. With DCOM, instructions are transmitted to the DCOM object, and execution occurs over the network. In simpler terms, DCOM can be referred to as COM via RPC (Remote Procedure Call)
Object Oriented in COM
Objects
An object is a specific instance of a class. In broader terms, a “class” serves as the blueprint for a set of interconnected data and functionalities designed for a specific shared goal. Typically, this goal involves providing services to external entities, or “clients,” seeking to utilize those services.
Interfaces
Interfaces define only how object should use that interface and what behavior is expected from an object through that interface. Interfaces do not specify any implementation details. Here’s a simple example in C++: An interface called IShape
is defined with a pure virtual function draw()
. An interface serves as a blueprint for classes that implement it but does not provide any implementation itself. Two concrete classes, Circle
and Square
, inherit from the IShape
interface and provide their own implementations of the draw()
function. These classes represent specific shapes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
// Interface defintion
class IShape {
public:
virtual void draw() const = 0; // Pure virtual function
};
// Concrete class implementing the interface
class Circle : public IShape {
public:
void draw() const override {
std::cout << "Drawing a circle." << std::endl;
}
};
// Another concrete class implementing the interface
class Square : public IShape {
public:
void draw() const override {
std::cout << "Drawing a square." << std::endl;
}
};
// Client code using the interface
void drawShape(const IShape& shape) {
shape.draw();
}
int main() {
Circle circle;
Square square;
// Using the drawShape function with different shapes
drawShape(circle);
drawShape(square);
return 0;
}
An interface is different from a class. While a class can create objects, an interface, on its own, cannot be used to create objects because it lacks any implementation. A COM object exposes its features through an interface, which serves as a collection of member functions defining the expected behavior for each function. It is not possible to create a instance of a COM interface independently. Instead, you must instantiate a class that implements the interface to leverage its functionalities. A client never has direct access to the COM object in its entirety. Instead, clients always access the object through clearly defined contracts: “the interfaces that the object supports, and only those interfaces”.
Clients only interact with pointers to interfaces: When a client has access to an object, it has nothing more than a pointer through which it can access the functions in the interface, called simply an interface pointer. The pointer is opaque, meaning that it hides all aspects of internal implementation. You cannot see any details about the object such as its state information, as opposed to C++ object pointers through which a client may directly access the object’s data. In COM, the client can only call functions of the interface to which it has a pointer. But instead of being a restriction, this is what allows COM to provide the efficient binary standard that enables location transparency.
An object can implement multiple interfaces, as shown below.
when the client application want to interact with the object it must use a interface pointer interface.
The client maintains a pointer to the interface which is, in actuality, a pointer to a pointer to an array of pointers to the object’s implementations of the interface member functions the structure is illustrated in Figure below.
By convention the pointer to the interface function table is called the pVtbl pointer. The table itself is generally referred to with the name vtbl for “virtual function table.”
COM Object Components
COM Class : is identified by using a unique 128-bit Class ID (CLSID) that associates a class with a particular deployment in the file system, which for Windows is a DLL or EXE. A CLSID is a GUID, which means that no other class has the same CLSID. ProgID: is a programmer-friendly string representing an underlying CLSID. IID : interface identifier
You might ask How can I obtain a COM object? The registry key HKEY_CLASSES_ROOT\CLSID
exposes all the information needed to enumerate COM objects, including the CLSID and ProgID as shown below . Moreover you can easily parse the com objects easily using oleviewdotnet
In the image below, I am searching for the CLSID of the Shell Automation Service class. This class has multiple interfaces, with two important ones being IUnknown and IClassFactory.
IUnknown Interface
The Component Object Model (COM) defines a standardized interface from which all other interfaces must derive, and this is the IUnknown interface. It has a function called QueryInterface which takes a globally unique identifier (GUID) as a parameter and returns the address of a pointer to an interface. Additionally, it has two more functions used for reference counting: AddRef and Release
QueryInterface
When a client initially obtains access to an object, it receives a single interface pointer. To access other interfaces on the same object, the client uses the member function QueryInterface, present in all COM interfaces. This function facilitates interface negotiation, allowing the client to inquire about the services the object can provide by passing the unique identifier of the desired interface.
IClassFactory interface
The COM objects implement the Factory Design Pattern, which involves creating objects without exposing the creation logic to the client. The objects are referred to using a common interface, this is done using the IClassFactory interface. This interface inherits from the IUnknown interface, enabling it to manage the creation and retrieval of objects in a way that abstracts the underlying details from the client.
How Does COM Works ?
- The client computer requests the remote computer to create an object by its CLSID or PROGID. If the client passes the APPID, the remote computer looks up the CLSID using the PROGID.
- The remote machine checks the APPID and verifies the client has permissions to create the object.
- DCOMLaunch.exe (if an EXE) or DLLHOST.exe (if a DLL) will create an instance of the class the client computer requested.
- Communication is successful! 5. The Client can now access all functions in the class on the remote computer
Fortunately, COM provides APIs to handle the heavy lifting for us. There are two functions: CoGetClassObject, which returns the class factory, and CoCreateInstance, which creates an instance of an object. Note. The CoCreateInstance function internally calls CoGetClassObject itself. It’s just a more convenient function for clients that want to create one object
There are two ways to initialize a COM object:
- The simplest method involves calling the COM function CoCreateInstance. This function creates an object of the given CLSID and returns an interface pointer of the requested type for the client.
- Alternatively, the client can obtain an interface pointer to the “Class Factory” object for a CLSID by calling CoGetClassObject. This class factory supports an interface called IClassFactory, allowing the client to request the manufacturing of an object of its class. At this point, the client has interface pointers for two separate objects—the class factory and an object of that class—each with its own reference counts.
To create an object through the class factory, as shown below in the image:
- Use CoGetClassObject to obtain an interface pointer to the “class factory” object associated with the given CLSID.
- Utilize the IClassFactory interface supported by the class factory to request the manufacturing of an object belonging to its class.
- return interface pointer to the client
Demo
In this section, I will demonstrate how to enumerate COM classes and invoke methods.
Enumerate COM Objects Using Powershell
The provided PowerShell command is using the Get-WmiObject
cmdlet to query information about Component Object Model (COM) settings on a Windows system.
1
Get-WmiObject Win32_COMSetting | ? {$_.progid } | ft ProgId,Caption,InprocServer32
Enumerating COM Object Methods
This command creates an instance of the COM object associated with the ProgID “WScript.Shell.1” using the Activator
class. It does so by calling the CreateInstance
method and passing the type obtained from the ProgID using GetTypeFromProgID
.
1
$com = [activator]::CreateInstance([type]::GetTypeFromProgID(("WScript.Shell.1"))) | gm
Below are the methods exposed by WScript.Shell.1, along with their member types and definitions, which contain the parameters. i have invoked the Exec Method to run the calculator
1
2
$com = [activator]::CreateInstance([type]::GetTypeFromProgID(("WScript.Shell.1")))
$com.Exec("calc.exe")