Statistics

Members: 1927
News: 293
Web Links: 1
Visitors: 3933178

Who's Online

Damn Vulnerable LinuxDamn Vulnerable Linux (DVL) is a Linux-based (modified Damn Small Linux) tool for IT-Security & IT-Anti- Security and Attack & Defense. [CLICK HERE FOR MORE INFOS! ]

Featured Conference Video

T16-Recon2006-Joe_Stewart-OllyBonE.gif OllyBone - Semi-Automatic Unpacking on IA-32. View the conference video here!
Home arrow About/Disclaimer
COM in Assembly Part II
User Rating: / 0
PoorBest 
Written by Bill Tyler   


My previous atricle described how to use COM objects in your assembly language programs. It described only how to call COM methods, but not how to create your own COM objects. This article will describe how to do that.

This article will describe implementing COM Objects, using MASM syntax. TASM or NASM assemblers will not be considered, however the methods can be easily applied to any assembler.

This article will also not describe some of the more advanced features of COM such as reuse, threading, servers/clients, and so on. These will presented in future articles.

COM Interfaces Review

An interface definition specifies the interface's methods, their return types, the number and types of their parameters, and what the methods must do. Here is a sample interface definition:

IInterface struct

lpVtbl dd ?
IInterface ends

IInterfaceVtbl struct

; IUnknown methods

    STDMETHOD       QueryInterface, :DWORD, :DWORD, :DWORD
STDMETHOD       AddRef, :DWORD
STDMETHOD       Release, :DWORD
; IInterface methods
STDMETHOD       Method1, :DWORD
STDMETHOD       Method2, :DWORD

IInterfaceVtbl ends

STDMETHOD is used to simplify the interface declaration, and is defined as:

STDMETHOD MACRO name, argl :VARARG

LOCAL @tmp_a
LOCAL @tmp_b
@tmp_a TYPEDEF PROTO argl
@tmp_b TYPEDEF PTR @tmp_a
name @tmp_b ?
ENDM

This macro is used to greatly simplify interface declarations, and so that the MASM invoke syntax can be used. (Macro originally by Ewald :)

Access to the interface's methods occurs through a pointer. This pointer points to a table of function pointers, called a vtable. Here is a sample method call:

mov     eax, [lpif]                            ; lpif is the interface pointer
mov     eax, [eax]                             ; get the address of the vtable

invoke (IInterfaceVtbl [eax]).Method1, [lpif] ; indirect call to the function - or -
invoke [eax][IInterfaceVtbl.Method2], [lpif] ; alternate notation

Two different styles of addressing the members are shown. Both notations produce equivalent code, so the method used is a matter of personal preference.

All interfaces must inherit from the IUnknown interface. This means that the first 3 methods of the vtable must be QueryInterface, AddRef, and Release. The purpose and implementation of these methods will be discussed later.

GUIDS


A GUID is a Globally Unique ID. A GUID is a 16-byte number, that is unique to an interface. COM uses GUID's to identify different interfaces from one another. Using this method prevents name clashing as well as version clashing. To get a GUID, you use a generator utility that is included with most win32 development packages.

A GUID is represented by the following structure:

GUID STRUCT

Data1 dd ?
Data2 dw ?
Data3 dw ?
Data4 db 8 dup(?)
GUID ENDS

A GUID is then defined in the data section: MyGUID GUID <3F2504E0h, 4f89h, 11D3h, <9Ah, 0C3h, 0h, 0h, 0E8h, 2Ch, 3h, 1h>>

Once a GUID is assigned to an interface and published, no furthur changes to the interface definition are allowed. Note, that this does mean that the interface implementation may not change, only the definition. For changes to the interface definition, a new GUID must be assigned.

COM Objects


A COM object is simply an implementation of an interface. Implementation details are not covered by the COM standard, so we are free to implement our objects as we choose, so long as they satisfy all the requirements of the interface definition.

A typical object will contain pointers to the various interfaces it supports, a reference count, and any other data that the object needs. Here is a sample object definition, implemented as a structure:

Object struct

    interface   IInterface  <?>     ; pointer to an IInterface
nRefCount   dd          ?       ; reference count
nValue      dd          ?       ; private object data

Object ends

We also have to define the vtable's we are going to be using. These tables must be static, and cannot change during run-time. Each member of the vtable is a pointer to a method. Following is a method for defining the vtable.

@@IInterface segment dword
vtblIInterface:

    dd      offset IInterface@QueryInterface
dd      offset IInterface@AddRef
dd      offset IInterface@Release
dd      offset IInterface@GetValue
dd      offset IInterface@SetValue

@@IInterface ends

Reference Counting


COM object manage their lifetimes through reference counting. Each object maintains a reference count that keeps track of how many instances of the interface pointer have been created. The object is required to keep a counter that supports 2^32 instances, meaning the reference count must be a DWORD.

When the reference count drops to zero, the object is no longer in use, and it destroys itself. The 2 IUnknown methods AddRef and Release handle the reference counting for a COM object.

QueryInterface


The QueryInterface method is used by a COM object to determine if the object supports a given interface, and then if supported, to get the interface pointer. There are 3 rules to implementing the QueryInterface method:
  1. Objects must have an identity - a call to QueryInterface must always return the same pointer value.
  2. The set of interfaces of an object must never change - for example, if a call to QueryInterface with on IID succeeds once, it must succeed always. Likewise, if it fails once, it must fail always.
  3. It must be possible to successfully query an interface of an object from any other interface.

QueryInterface returns a pointer to a specified interface on an object to which a client currently holds an interface pointer. This function must call the AddRef method on the pointer it returns.

Following are the QueryInterface parameters:

pif : [in] a pointer to the calling interface riid : [in] pointer to the IID of the interface being queried ppv : [out] pointer to the pointer of the interface that is to be set.

If the interface is not supported, the pointed to value is set to 0

QueryInterface returns the following:

S_OK if the interface is supported
E_NOINTERFACE if not supported

Here is a simple assembly implementation of QueryInterface:

IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD

; The following compares the requested IID with the available ones. ; In this case, because IInterface inherits from IUnknown, the IInterface ; interface is prefixed with the IUnknown methods, and these 2 interfaces ; share the same interface pointer. invoke IsEqualGUID, [riid], addr IID_IInterface

    or      eax,eax
jnz     @1
invoke  IsEqualGUID, [riid], addr IID_IUnknown
or      eax,eax
jnz     @1
jmp     @NoInterface

@1:

; GETOBJECTPOINTER is a macro that will put the object pointer into eax, ; when given the name of the object, the name of the interface, and the ; interface pointer.
GETOBJECTPOINTER Object, interface, pif

; now get the pointer to the requested interface lea eax, (Object ptr [eax]).interface

; set *ppv with this interface pointer

    mov     ebx, [ppv]
mov     dword ptr [ebx], eax

; increment the reference count by calling AddRef GETOBJECTPOINTER Object, interface, pif mov eax, (Object ptr [eax]).interface invoke (IInterfaceVtbl ptr [eax]).AddRef, pif

; return S_OK

    mov     eax, S_OK
jmp     return

@NoInterface:

; interface not supported, so set *ppv to zero

    mov     eax, [ppv]
mov     dword ptr [eax], 0

; return E_NOINTERFACE
mov eax, E_NOINTERFACE

return
ret IInterface@QueryInterface endp

AddRef


The AddRef method is used to increment the reference count for an interface of an object. It should be called for every new copy of an interface pointer to an object.

AddRef takes no parameters, other than the interface pointer required for all methods. AddRef should return the new reference count. However, this value is to be used by callers only for testing purposes, as it may be unstable in certain situations.

Following is a simple implementation of the AddRef method:

IInterface@AddRef proc pif:DWORD

GETOBJECTPOINTER Object, interface, pif ; increment the reference count
inc [(Object ptr [eax]).nRefCount] ; now return the count
mov eax, [(Object ptr [eax]).nRefCount] ret
IInterface@AddRef endp

Release


Release decrements the reference count for the calling interface on a object. If the reference count on the object is decrememnted to 0, then the object is freed from memory. This function should be called when you no longer need to use an interface pointer

Like AddRef, Release takes only one parameter - the interface pointer. It also returns the current value of the reference count, which, similarly, is to be used for testing purposess only

Here is a simple implementation of Release:

IInterface@Release proc pif:DWORD

GETOBJECTPOINTER Object, interface, pif

; decrement the reference count
dec [(Object ptr [eax]).nRefCount]

; check to see if the reference count is zero. If it is, then destroy ; the object.

    mov     eax, [(Object ptr [eax]).nRefCount]
or      eax, eax
jnz     @1

; free the object: here we have assumed the object was allocated with ; LocalAlloc and with LMEM_FIXED option GETOBJECTPOINTER Object, interface, pif invoke LocalFree, eax
@1:

ret
IInterface@Release endp

Creating a COM object


Creating an object consists basically of allocating the memory for the object, and then initializing its data members. Typically, the vtable pointer is initialized and the reference count is zeroed. QueryInterface could then be called to get the interface pointer.

Other methods exist for creating objects, such as using CoCreateInstance, and using class factories. These methods will not be discussed, and may be a topic for a future article.

COM implementatiion sample application


Here follows a sample implementation and usage of a COM object. It shows how to create the object, call its methods, then free it. It would probably be very educational to assemble this and run it through a debugger. This and other examples can be found at {http://asm.tsx.org.}

.386
.model flat,stdcall

include windows.inc
include kernel32.inc
include user32.inc

includelib kernel32.lib
includelib user32.lib
includelib uuid.lib

;----------------------------------------------------------------------------- ; Macro to simply interface declarations ; Borrowed from Ewald, {http://here.is/diamond/} STDMETHOD MACRO name, argl :VARARG
LOCAL @tmp_a
LOCAL @tmp_b
@tmp_a TYPEDEF PROTO argl
@tmp_b TYPEDEF PTR @tmp_a
name @tmp_b ?
ENDM

; Macro that takes an interface pointer and returns the implementation ; pointer in eax
GETOBJECTPOINTER MACRO Object, Interface, pif

mov eax, pif
IF (Object.Interface)

sub eax, Object.Interface
ENDIF
ENDM

;----------------------------------------------------------------------------- IInterface@QueryInterface proto :DWORD, :DWORD, :DWORD

IInterface@AddRef           proto :DWORD
IInterface@Release          proto :DWORD
IInterface@Get              proto :DWORD
IInterface@Set              proto :DWORD, :DWORD
CreateObject                proto :DWORD
IsEqualGUID                 proto :DWORD, :DWORD
externdef                   IID_IUnknown:GUID

;----------------------------------------------------------------------------- ; declare the interface prototype
IInterface struct

lpVtbl dd ?
IInterface ends

IInterfaceVtbl struct

; IUnknown methods

    STDMETHOD       QueryInterface, pif:DWORD, riid:DWORD, ppv:DWORD
STDMETHOD       AddRef, pif:DWORD
STDMETHOD       Release, pif:DWORD
; IInterface methods
STDMETHOD       GetValue, pif:DWORD
STDMETHOD       SetValue, pif:DWORD, val:DWORD

IInterfaceVtbl ends

; declare the object structure
Object struct

; interface object
interface IInterface <?>

; object data

    nRefCount   dd          ?
nValue      dd          ?

Object ends

;----------------------------------------------------------------------------- .data
; define the vtable
@@IInterface segment dword
vtblIInterface:

    dd      offset IInterface@QueryInterface
dd      offset IInterface@AddRef
dd      offset IInterface@Release
dd      offset IInterface@GetValue
dd      offset IInterface@SetValue

@@IInterface ends

; define the interface's IID
; {CF2504E0-4F89-11d3-9AC3-0000E82C0301} IID_IInterface GUID <0cf2504e0h, 04f89h, 011d3h, <09ah, 0c3h, 00h, 00h,

0e8h, 02ch, 03h, 01h>>

;----------------------------------------------------------------------------- .code
start:
StartProc proc

LOCAL pif:DWORD ; interface pointer

; create the object
invoke CreateObject, addr [pif]

    or      eax,eax
js      exit

; call the SetValue method

    mov     eax, [pif]
mov     eax, [eax]

invoke (IInterfaceVtbl ptr [eax]).SetValue, [pif], 12345h

; call the GetValue method

    mov     eax, [pif]
mov     eax, [eax]

invoke (IInterfaceVtbl ptr [eax]).GetValue, [pif]

; release the object

    mov     eax, [pif]
mov     eax, [eax]

invoke (IInterfaceVtbl ptr [eax]).Release, [pif]

exit
ret StartProc endp

;----------------------------------------------------------------------------- IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD

invoke IsEqualGUID, [riid], addr IID_IInterface test eax,eax
jnz @F
invoke IsEqualGUID, [riid], addr IID_IUnknown test eax,eax

    jnz     @F
jmp     @Error

@@:

GETOBJECTPOINTER Object, interface, pif lea eax, (Object ptr [eax]).interface

; set *ppv

    mov     ebx, [ppv]
mov     dword ptr [ebx], eax

; increment the reference count
GETOBJECTPOINTER Object, interface, pif mov eax, (Object ptr [eax]).interface invoke (IInterfaceVtbl ptr [eax]).AddRef, [pif]

; return S_OK

    mov     eax, S_OK
jmp     return

@Error:

; error, interface not supported

    mov     eax, [ppv]
mov     dword ptr [eax], 0
mov     eax, E_NOINTERFACE
return
ret IInterface@QueryInterface endp

IInterface@AddRef proc pif:DWORD

GETOBJECTPOINTER Object, interface, pif

    inc     [(Object ptr [eax]).nRefCount]
mov     eax, [(Object ptr [eax]).nRefCount]

ret
IInterface@AddRef endp

IInterface@Release proc pif:DWORD

GETOBJECTPOINTER Object, interface, pif

    dec     [(Object ptr [eax]).nRefCount]
mov     eax, [(Object ptr [eax]).nRefCount]
or      eax, eax
jnz     @1
; free object
mov     eax, [pif]
mov     eax, [eax]

invoke LocalFree, eax
@1:

ret
IInterface@Release endp

IInterface@GetValue proc pif:DWORD

GETOBJECTPOINTER Object, interface, pif mov eax, (Object ptr [eax]).nValue ret
IInterface@GetValue endp

IInterface@SetValue proc uses ebx pif:DWORD, val:DWORD

GETOBJECTPOINTER Object, interface, pif

    mov     ebx, eax
mov     eax, [val]
mov     (Object ptr [ebx]).nValue, eax

ret
IInterface@SetValue endp

;----------------------------------------------------------------------------- CreateObject proc uses ebx ecx pobj:DWORD

; set *ppv to 0

    mov     eax, pobj
mov     dword ptr [eax], 0

; allocate object
invoke LocalAlloc, LMEM_FIXED, sizeof Object

    or      eax, eax
jnz     @1
; alloc failed, so return
mov     eax, E_OUTOFMEMORY
jmp     return

@1:

    mov     ebx, eax
mov     (Object ptr [ebx]).interface.lpVtbl, offset vtblIInterface
mov     (Object ptr [ebx]).nRefCount, 0
mov     (Object ptr [ebx]).nValue, 0

; Query the interface

    lea     ecx, (Object ptr [ebx]).interface
mov     eax, (Object ptr [ebx]).interface.lpVtbl
invoke  (IInterfaceVtbl ptr [eax]).QueryInterface,
ecx,
addr IID_IInterface,
[pobj]
cmp     eax, S_OK
je      return

; error in QueryInterface, so free memory push eax
invoke LocalFree, ebx
pop eax

return
ret CreateObject endp

;----------------------------------------------------------------------------- IsEqualGUID proc rguid1:DWORD, rguid2:DWORD

cld

    mov     esi, [rguid1]
mov     edi, [rguid2]
mov     ecx, sizeof GUID / 4
repe    cmpsd
xor     eax, eax
or      ecx, ecx

setz al
ret
IsEqualGUID endp

end start

Conclusion


We have (hopefully) seen how to implement a COM object. We can see that it is a bit messy to do, and adds quite some overhead to our programs. However, it can also add great flexibility and power to our programs.

Remember that COM defines only interfaces, and implementation is left to the programmer. This article presents only one possible implementation. This is not the only method, nor is it the best one. The reader should feel free to experiment with other methods.

Copyright (C) 1999 Bill Tyler ({ This e-mail address is being protected from spam bots, you need JavaScript enabled to view it })