Click here to Skip to main content
15,881,881 members
Articles / Programming Languages / C++

Accessing an API through a closed source C++ DLL

Rate me:
Please Sign up or sign in to vote.
4.88/5 (19 votes)
21 Jan 2013CPOL6 min read 36.7K   524   53   9
Making calls on a C++ DLL without source code or header files, even when the DLL depends on C run-time libraries

Introduction

This article describes a useful technique for using a closed source C++ DLL, loaded at run-time, to access an API for a popular consumer peripheral. It’s assumed the developer does not have access to import libraries or source code.

Background

This is a technical follow-up to my Continuum blog post describing how I automated a simple software task to enable an automatic pan-and-zoom feature of a consumer webcam that didn't provide an API. We needed to do this without disturbing the user experience, so standard program automation tools like AutoIt were off the table.

Throughout the process, I realized some of the pitfalls in making calls on C++ DLLs and identified workarounds. The inspiration for this article was a blog post from Recx Ltd, Working with C++ DLL Exports without Source or Headers. An example project is provided, but our specific case relied on a proprietary DLL that could not be included due to licensing.

API Monitor is an invaluable tool for monitoring the activity of API calls. It has more comprehensive features for Windows API calls, but it can also be used to view calls to external DLLs. You can spawn processes
from within the tool or attach to a running process. The state of the call stack can be seen before and after API calls are made, return values are visible, and it even provides breakpoint functionality.

With API Monitor, I spawned an instance of the web cam controller application, which allowed me to see which DLLs were being loaded. Monitoring from process execution (as opposed to attaching to running process) can be important when trying to see initialization behavior.

The module dependency view revealed a large list of DLLs. Most of them were Windows system DLLs or related to the Qt framework. One in particular stood out for what we needed: CameraControls_Core.dll. I set up the API Monitor to log all calls to this DLL and this was the relevant output:

LWS::CameraControls::CameraControls_Core::Init ( ... )
LWS::CameraControls::CameraControls_Core::GetCurrentVideoDevice ( ... )
LWS::CameraControls::CameraControls_Core::SetCurrentVideoDevice ( ... )
LWS::CameraControls::CameraControls_Core::GetFaceTracking ( ... ) 
Monitoring the API activity live, I checked and unchecked the facial recognition box. I noticed calls to SetFaceTracking () were being made. A quick look at the call stack revealed Boolean values being sent as parameters to the method. I used the Microsoft dumpbin utility on CameraControls_Core.dll to view the exported method list. It was quickly evident that I was dealing with a C++ DLL (due to the use of name decoration).
The method names were like so:
1 0 00001366 ??0CameraControls_Core@CameraControls@LWS@@QAE@XZ
2 1 000015C3 ??1CameraControls_Core@CameraControls@LWS@@UAE@XZ
3 2 00038740 ??_7CameraControls_Core@CameraControls@LWS@@6B@   

I ran the output of the dumpbin tool into Microsoft’s undname utility to undecorate the names.

This provided a more coherent output:

C++
1    0 00001366 public: __thiscall LWS::CameraControls::CameraControls_Core::CameraControls_Core(void)
2    1 000015C3 public: virtual __thiscall LWS::CameraControls::CameraControls_Core::~CameraControls_Core(void)
3    2 00038740 const LWS::CameraControls::CameraControls_Core::`vftable'
...
40   27 000012E9 public: bool __thiscall LWS::CameraControls::CameraControls_Core::Init(class QString)  
...
86   55 000016D1 public: long __thiscall LWS::CameraControls::CameraControls_Core::SetFaceTracking(unsigned long,long) 

It was around this point in time that I stumbled on the above mentioned Recx Ltd article. I realized that our particular DLL would present a few challenges that made a simple script-kiddie application of this technique impossible. I moved a copy of the DLL into my application’s working directory and attempted to write some code to load it dynamically.

C++
static HINSTANCE CameraControlDllHandle = NULL;
CameraControlDllHandle = LoadLibrary(L"CameraControls_Core.dll"); 

Stepping through the code revealed the first dependency library of many: Qt4Gui4. As the errors presented themselves, I copied the dependencies into the working directory. It turns out ten additional DLLs were required. I ran through the code one last time, and got an error about MSVCR90.DLL being missing.

Image 1

Placing this DLL in the directory results in an error about the C-runtime being loaded improperly.

Image 2

MSVCR90.DLL is the C-runtime library for Visual C++ 2008. I tried re-building my project to run with this runtime, so it would use the same runtime as the DLL, but it didn't mitigate the problem. As it turns out, Microsoft introduced a new form of system-wide DLL management in Windows 98 for allowing conflicting DLLs to exist simultaneously in memory (Wikipedia DLL Hell). DLL’s complying with this standard have a manifest file that is imported into the DLL to inform a calling process which dependencies need to be loaded.

To overcome the C run-time dependency, I needed to create a manifest file pointing to the run-time library and embed it into my CameraControls_Core.dll. This MSDN article outlines how to use Microsoft’s MT.EXE utility to embed a manifest file into an already built executable or DLL.

The manifest file I needed to use for MSVCR90.DLL is as follows:

XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="Microsoft.VC90.DebugCRT" 
        version="9.0.21022.8" processorArchitecture="x86" 
        publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
    </dependentAssembly>
  </dependency>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="Microsoft.VC90.CRT" 
        version="9.0.21022.8" processorArchitecture="x86" 
        publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
    </dependentAssembly>
  </dependency>
</assembly>

After this step, the library was loading into memory without exceptions! Now, I just needed to verify method calls would work. Early attempts at calling essentially any of the methods were throwing exceptions. I realized this was because some critical init function was probably not being called. The reader should read through Working with C++ DLL Exports without Source or Headers first.

I’ll jump straight to the working solution and explain it:
C++
 #include <QString>
//------------------------------------------------------------------------------
// Function Pointer Typedefs
//------------------------------------------------------------------------------
typedef long (__thiscall *_SetFaceTracking)(DWORD *pThis, unsigned long, long boolValue);
typedef long (__thiscall *_GetFaceTracking)(DWORD *pThis, unsigned long, long *boolValue);
typedef void (__thiscall *_CameraControls_Core)(DWORD *pThis);
typedef bool (__thiscall *_Init)(DWORD *pThis, class QString param1);
typedef long (__thiscall *_GetCurrentVideoDevice)(DWORD *pThis, unsigned long *param);
typedef long (__thiscall *_SetCurrentVideoDevice)(DWORD *pThis, unsigned long param);
//--------------------------------------------------------------------------------------------
// Static Variables
//--------------------------------------------------------------------------------------------
static BOOL            freeResult;
static _SetFaceTracking    CameraSetFaceTracking;
static _GetFaceTracking     CameraGetFaceTracking;
static DWORD            dwFakeObject[512];
static HINSTANCE        CameraControlDllHandle = NULL;
static unsigned long    currentVideoDevice = 0;
PRINCEDLL_API HRESULT APIENTRY InitLibrary()
{
    long retValue = 0;
    QString inputString = "";
 
    CameraControlDllHandle = LoadLibrary(L"CameraControls_Core.dll");
    
    if (CameraControlDllHandle == NULL)
        return E_FAIL;
    _CameraControls_Core CameraImportedConstructor = (_CameraControls_Core) GetProcAddress(
      CameraControlDllHandle,"??0CameraControls_Core@CameraControls@LWS@@QAE@XZ");
    if (CameraImportedConstructor == NULL)
        return E_FAIL;
    CameraSetFaceTracking = (_SetFaceTracking) GetProcAddress (CameraControlDllHandle,
      "?SetFaceTracking@CameraControls_Core@CameraControls@LWS@@QAEJKJ@Z");
    if (CameraSetFaceTracking == NULL)
        return E_FAIL;
    CameraGetFaceTracking = (_GetFaceTracking) GetProcAddress (CameraControlDllHandle, 
      "?GetFaceTracking@CameraControls_Core@CameraControls@LWS@@QAEJKPAJ@Z");
    if (CameraGetFaceTracking == NULL)
        return E_FAIL;
    _Init CameraInitializer = (_Init) GetProcAddress (CameraControlDllHandle, 
      "?Init@CameraControls_Core@CameraControls@LWS@@QAE_NVQString@@@Z");
    if (CameraInitializer == NULL)
        return E_FAIL;
    _SetCurrentVideoDevice CameraSetCurrentVideoDevice = (_SetCurrentVideoDevice) GetProcAddress (
      CameraControlDllHandle, "?SetCurrentVideoDevice@CameraControls_Core@CameraControls@LWS@@QAEJK@Z");
    if (CameraSetCurrentVideoDevice == NULL)
        return E_FAIL;
    _GetCurrentVideoDevice CameraGetCurrentVidDevice = (_GetCurrentVideoDevice) GetProcAddress (
      CameraControlDllHandle, "?GetCurrentVideoDevice@CameraControls_Core@CameraControls@LWS@@QAEJPAK@Z");
    if (CameraGetCurrentVidDevice == NULL)
        return E_FAIL;
    memset (dwFakeObject, 0x00, 512);
    CameraImportedConstructor(dwFakeObject);
    retValue = CameraInitializer(dwFakeObject, inputString);
    retValue = CameraGetCurrentVidDevice (dwFakeObject, &currentVideoDevice);
    retValue = CameraSetCurrentVideoDevice (dwFakeObject, currentVideoDevice);
    return S_OK;
} 
A function or method's calling convention determines how information is passed and returned from the caller to the callee. The calling convention our DLL used differed from those presented in the above
article. Their example used __cdecl, which I just learned is now a standard calling convention for all functions on Win x64 systems ( this was done to eliminate the problem of so many complicated calling conventions).

In __cdecl, all the parameters are passed on the call stack. The object instance this pointer is passed last. Our DLL used the __thiscall convention. __thiscall, it turns out, informs the callee that the this pointer is being passed via the ECX register (not on the call stack). The __thiscall keyword tells the compiler that the first parameter should be placed in the ECX register. Technically, the first parameter should be a pointer to the object instance.

Since our DLLs Init() function requires a QString as a parameter, I needed to determine the version of Qt being used by the target DLL, build Qt, and statically link it to my DLL. Fortunately, passing an empty QString to the Init function completed initialization without errors and allowed the other methods to be called. The dwFakeObject array is required because C++ instance methods expect to be passed a reference to the object they are designed to work on. We reserve an area in memory and treat this as a
reference to a dummy object that is passed to the methods within the DLL.

Points of Interest

Hopefully this will prove insightful to someone going through a similar situation with attempting to use third party DLLs. Without access to source for the definitions of the underlying interface and data
types, the problem can be more or less complex than this example. However, it serves to illustrate that there isn’t always a general purpose solution for accessing DLL functions and some cases will require an ad-hoc approach.

Topics touched on:

  • Embedded DLL Manifest files
  • Viewing export functions and making calls from C++ DLLs
  • APIMonitor as a tool for spying on API calls
  • Calling conventions

History

17 Jan 2013 - Initial publication.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Continuum LLC
United States United States
I am an embedded software engineer with almost 10 years of experience in the industry. I currently work at Continuum Advanced Systems, a global design innovation consultancy specializing in consumer and medical products. My specializations and interests cover embedded systems, mobile application development, and various web software technologies.

Comments and Discussions

 
SuggestionLooking for some deep insight resources Pin
anhdung8817-Feb-15 1:17
anhdung8817-Feb-15 1:17 
SuggestionGood article! And one suggestion. Pin
Shawn-USA5-Aug-13 14:07
Shawn-USA5-Aug-13 14:07 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA16-Feb-13 19:20
professionalȘtefan-Mihai MOGA16-Feb-13 19:20 
GeneralVersion Dependency Pin
Sharjith21-Jan-13 7:10
professionalSharjith21-Jan-13 7:10 
GeneralRe: Version Dependency Pin
Robert Bermani25-Jan-13 3:08
Robert Bermani25-Jan-13 3:08 
QuestionAPI Monitor Pin
alexquisi18-Jan-13 16:51
alexquisi18-Jan-13 16:51 
AnswerRe: API Monitor Pin
Robert Bermani21-Jan-13 2:13
Robert Bermani21-Jan-13 2:13 
QuestionAre you sure that the author of the DLL allows reverse-engineering? Pin
Philippe Mori17-Jan-13 12:55
Philippe Mori17-Jan-13 12:55 
Although you are able to control the camera that way, the author of the DLL probably don't want you to do that. Most commercial licenses explicitly disallow reverse-engineering.

If it is for personal use then they might not bother but if it is used for commercial applications that you develop, you should make an agreement with the original vendor and in such case why not have them provide you the required header and library.
Philippe Mori

AnswerRe: Are you sure that the author of the DLL allows reverse-engineering? Pin
Robert Bermani17-Jan-13 13:08
Robert Bermani17-Jan-13 13:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.