Skip to content

Instantly share code, notes, and snippets.

@peteristhegreat
Last active February 23, 2023 06:49
Show Gist options
  • Save peteristhegreat/0d2d2580bd3fd353b178c1c4da2e455e to your computer and use it in GitHub Desktop.
Save peteristhegreat/0d2d2580bd3fd353b178c1c4da2e455e to your computer and use it in GitHub Desktop.
Accessing COM from Julia

COM Introduction

Unlike standard ccall for C interfaces in Julia, COM or the Component Object Model, mirrors classes in C++ more than straight dll calls.

In C++ or VB there are tons of examples out there. Code Project has several nice examples. Consider looking at the implementation of comtypes.py for complete scripting API info. https://github.com/enthought/comtypes/blob/master/comtypes/__init__.py

Another great intro to COM is found on Code Project:

https://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It

Steps for COM

To handle this idea of using COM, their are multiple steps to interact with a COM class, interface and eventually its function call.

Below are the layers for the simplest call to a COM Interface. Most of these calls interact with "Ole32.dll".

  1. Get GUIDs for Class and Interface
  2. CoInitializeEx
  3. CoCreateInstance
  4. interface->Method
  5. Release(interface)
  6. CoUninitialize

Oh, and CoCreateInstance doesn't work unless you are in the context of a registered dll. (See regsvr32).

Win32 Julia Help

This project showed a bunch of examples for Win32 ccalls: https://github.com/ihnorton/Win32GUIDemo.jl

IClassFactory->CreateInstance instead of CoCreateInstance

So to hack around the need to be registered as a COM enabled server, there are multiple good examples of this on Code Project:

https://www.codeproject.com/Articles/18433/Emulating-CoCreateInstance

https://www.codeproject.com/Tips/1037909/Using-COM-Without-Registration

The underlying code for both of these show drilling into DllGetClassObject and pulling out an instance of the IClassFactory and calling CreateInstance on the instance of the Class Factory interface. Easy right?

I've used techique in the example code.

Now instead of using C++ calling conventions in Julia, ccalls are used. To drill into an interface and to pass in the right REFIID takes some doing but its not too bad.

Handling HRESULT

https://gist.github.com/peteristhegreat/924832e88806b05dafd434fb1e71252c

The best listing of HRESULT errors are here: https://github.com/SecureAuthCorp/impacket/blob/master/impacket/hresult_errors.py

A short list is found here: https://docs.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values

Look up appropriate GUIDs for Classes and Interfaces

Make a small file in Visual Studio C++ that looks like this:

#include <windows.h>
#include <winnt.h>
#include <stdio.h>
#include <Wincrypt.h>
#include <objbase.h>
#include <combaseapi.h>
#include <wtypesbase.h>
#include "windows.h"
#include "winnls.h"
#include "shobjidl.h"
#include "objbase.h"
#include "objidl.h"
#include "certadm.h"
#include "shlguid.h"
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "Ole32.lib")
#pragma comment(lib, "Certidl.lib")

#include <atlbase.h>
#include <iostream>
#include <Certcli.h>
#include <Certsrv.h>
#include <Unknwn.h>

REFIID r = IID_ICertAdmin2;
REFIID r = IID_IClassFactory;
REFIID r = IID_ICertAdmin;
REFIID r = CLSID_CCertAdmin;

And right click on one of the IID_XXXXX variables and select Go to definition to find the GUIDs associated with it. Googling works to some extent, but MSDN doesn't publish all the GUIDs nicely.

# This is a mostly working example tested against Julia 1.0.5 October 2019
const _crypt32 = "Crypt32.dll"
const _ole32 = "Ole32.dll"
HANDLE = Ptr{Cvoid}
HCRYPTPROV = HANDLE
HCRYPTPROV_LEGACY = HCRYPTPROV
HCERTSTORE = HANDLE
LPWSTR = Cwstring #Ptr{Cushort} # Cwstring, Ptr{UInt16}
LPCWSTR = Cwstring #Ptr{Cushort} # Cwstring, Ptr{UInt16}
LPCOLESTR = Cwstring
LPSTR = Cstring #Ptr{UInt8} # Cstring?
BOOL = Cint
BYTE = Cuchar #Cint #UInt8 # Cint
DWORD = Culong
struct _GUID
Data::UInt128
# unsigned long Data1;
# unsigned short Data2;
# unsigned short Data3;
# Data4:: # unsigned char Data4[8];
end
LPCLSID = Ptr{UInt128} # Ptr{_GUID}
BSTR = Cwstring # technically a pointer to the first character of the string
HRESULT = Int32
LPVOID = Ptr{Cvoid}
REFCLSID = Ptr{UInt128} #Array{UInt8,1} # Ptr{UInt128}???? # UInt128 # _GUID
LPUNKNOWN = HANDLE
REFIID = Ptr{UInt128} #Array{UInt8,1} # UInt128 # _GUID
mutable struct IClassFactory
QueryInterface::Ptr
AddRef::Ptr
Release::Ptr
CreateInstance::Ptr
LockServer::Ptr
end
# dllname not used, because requires constant in ccall
function MyCoCreateInstance(dllname, rclsid::REFCLSID, pUnkOuter::LPUNKNOWN, riid::REFIID, ppv) #::Ptr{LPVOID})
hr = 0x80040152; #REGDB_E_KEYMISSING;
IID_IClassFactory = Vector{UInt128}(undef, 1) # convert(UInt128, 0)
hr = ccall((:CLSIDFromString, _ole32), HRESULT, (LPCOLESTR, LPCLSID),
"{00000001-0000-0000-C000-000000000046}", pointer(IID_IClassFactory)) # Unknwn.h
# @show IID_IClassFactory
pIFactory = Vector{Ptr{Ptr{Ptr{Cvoid}}}}(undef, 1)
# @show pIFactory
hr = ccall((:DllGetClassObject, _ole32), HRESULT, (REFCLSID, REFIID, Ptr{LPVOID}),
rclsid, pointer(IID_IClassFactory), pointer(pIFactory))
hr < 0 && error("Failed DllGetClassObject pIFactory: HRESULT 0x$(string(reinterpret(UInt32, hr),base=16))")
# The interface is stored in Unknwn.h as a pointer to a Vtbl object, so there are multiple layers
# to unwrap to get to the function calls.
# See lines 455 to 492 in C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\Unknwn.h
# The fourth element in the Interface is CreateInstance
IFactory_this = unsafe_load(pIFactory[])
CreateInstance = unsafe_load(IFactory_this, 4)
Release = unsafe_load(IFactory_this, 3)
# hr = ccall(CreateInstance, HRESULT, (LPVOID, LPUNKNOWN, REFIID, LPVOID), IFactory_this, pUnkOuter, riid, ppv)
# HRESULT CreateInstance(
# [in] IUnknown *pUnkOuter, # C_NULL
# [in] REFIID riid, # pointer(IID_ICertAdmin)
# [out] void **ppvObject # pCertAdmin
# );
hr = ccall(CreateInstance, HRESULT, (LPVOID, LPUNKNOWN, REFIID, LPVOID), IFactory_this, pUnkOuter, riid, ppv)
hr < 0 && error("Failed IFactory::CreateInstance ppv: HRESULT 0x$(string(reinterpret(UInt32, hr),base=16))")
# pIFactory->Release();
# untested
hr = ccall(Release, HRESULT, ())
hr < 0 && error("Failed IFactory::Release, HRESULT 0x$(string(reinterpret(UInt32, hr),base=16))")
return (hr, ppv);
end
function test()
hr = ccall((:CoInitializeEx, _ole32), HRESULT, (LPVOID, DWORD),
C_NULL, COINIT_APARTMENTTHREADED)
hr < 0 && error("Failed CoInitializeEx: HRESULT 0x$(string(reinterpret(UInt32, hr),base=16))")
CLSID_CCertAdmin = Vector{UInt128}(undef, 1) #Vector{UInt8}(undef, 16) # convert(UInt128, 0)
hr = ccall((:CLSIDFromString, _ole32), HRESULT, (LPCOLESTR, LPCLSID),
"{37eabaf0-7fb6-11d0-8817-00a0c903b83c}", pointer(CLSID_CCertAdmin))
@show CLSID_CCertAdmin
IID_ICertAdmin = Vector{UInt128}(undef, 1)
hr = ccall((:CLSIDFromString, _ole32), HRESULT, (LPCOLESTR, LPCLSID),
"{34df6950-7fb6-11d0-8817-00a0c903b83c}", pointer(IID_ICertAdmin))
IID_ICertAdmin2 = Vector{UInt128}(undef, 1)
hr = ccall((:CLSIDFromString, _ole32), HRESULT, (LPCOLESTR, LPCLSID),
"{f7c3ac41-b8ce-4fb4-aa58-3d1dc0e36b39}", pointer(IID_ICertAdmin2))
@show IID_ICertAdmin
@show IID_ICertAdmin2
global pCertAdmin = Vector{Ptr{Ptr{Cvoid}}}(undef, 1)
if false
hr = ccall((:CoCreateInstance, _ole32), HRESULT,
(REFCLSID, LPUNKNOWN, DWORD, REFIID, LPVOID),
CLSID_CCertAdmin, C_NULL, CLSCTX_INPROC_SERVER, IID_ICertAdmin2, pCertAdmin )
# fails with NOT_REGISTERED
else
hr = MyCoCreateInstance(_ole32, pointer(CLSID_CCertAdmin), C_NULL, pointer(IID_ICertAdmin2), pCertAdmin)
end
@show pCertAdmin
hr < 0 && error("Failed CoCreateInstance pCertAdmin: HRESULT 0x$(string(reinterpret(UInt32, hr),base=16))")
# fails with NO_INTERFACE
# Untested
certAdm = unsafe_wrap(Vector{Ptr{Cvoid}}, pCertAdmin[], (10, ) )
# Untested
hr = ccall((:CoUninitialize, _ole32), HRESULT, ())
hr < 0 && error("Failed CoUninitializeEx: HRESULT 0x$(string(reinterpret(UInt32, hr),base=16))")
end
EXTERN_C const IID IID_IClassFactory;
#if defined(__cplusplus) && !defined(CINTERFACE)
MIDL_INTERFACE("00000001-0000-0000-C000-000000000046")
IClassFactory : public IUnknown
{
public:
virtual /* [local] */ HRESULT STDMETHODCALLTYPE CreateInstance(
/* [annotation][unique][in] */
_In_opt_ IUnknown *pUnkOuter,
/* [annotation][in] */
_In_ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject) = 0;
virtual /* [local] */ HRESULT STDMETHODCALLTYPE LockServer(
/* [in] */ BOOL fLock) = 0;
};
#else /* C style interface */
typedef struct IClassFactoryVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
__RPC__in IClassFactory * This,
/* [in] */ __RPC__in REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
__RPC__in IClassFactory * This);
ULONG ( STDMETHODCALLTYPE *Release )(
__RPC__in IClassFactory * This);
/* [local] */ HRESULT ( STDMETHODCALLTYPE *CreateInstance )(
IClassFactory * This,
/* [annotation][unique][in] */
_In_opt_ IUnknown *pUnkOuter,
/* [annotation][in] */
_In_ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
/* [local] */ HRESULT ( STDMETHODCALLTYPE *LockServer )(
IClassFactory * This,
/* [in] */ BOOL fLock);
END_INTERFACE
} IClassFactoryVtbl;
interface IClassFactory
{
CONST_VTBL struct IClassFactoryVtbl *lpVtbl;
};
#ifdef COBJMACROS
#define IClassFactory_QueryInterface(This,riid,ppvObject) \
( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )
#define IClassFactory_AddRef(This) \
( (This)->lpVtbl -> AddRef(This) )
#define IClassFactory_Release(This) \
( (This)->lpVtbl -> Release(This) )
#define IClassFactory_CreateInstance(This,pUnkOuter,riid,ppvObject) \
( (This)->lpVtbl -> CreateInstance(This,pUnkOuter,riid,ppvObject) )
#define IClassFactory_LockServer(This,fLock) \
( (This)->lpVtbl -> LockServer(This,fLock) )
#endif /* COBJMACROS */
#endif /* C style interface */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment