Monday, 6 February 2012

Working with C++ DLL Exports without Source or Headers

We had a small problem that we had to overcome last week during an assessment. This blog entry is going to show the mistakes we made along the way and how it was finally solved.

We had acquired a 64 bit DLL which exported a C++ class and associated methods that contained some secret sauce (reversing was infeasible in the allotted time). We've put together the following example DLL to demonstrate:

#define DLLCPP_API __declspec(dllexport)  
  class DLLCPP_API CDLLCPP {  
  private:  
       bool bInit;  
  public:  
       CDLLCPP();  
       ~CDLLCPP();  
       int SuperSecretSauce(char *strString);  
  };  

As we didn't have the code, library or headers we ran dumpbin.exe /exports against the compiled DLL to reveal the exported names (note: the code project solution to dynamic C++ DLL loading needs headers):
 Microsoft (R) COFF/PE Dumper Version 10.00.40219.01  
 Copyright (C) Microsoft Corporation. All rights reserved.  
 Dump of file DLLCPP.dll  
 File Type: DLL  
  Section contains the following exports for DLLCPP.dll  
   00000000 characteristics  
   4F2E3F6C time date stamp Sun Feb 05 08:35:56 2012  
     0.00 version  
       1 ordinal base  
       4 number of functions  
       4 number of names  
   ordinal hint RVA   name  
      1  0 00001010 ??0CDLLCPP@@QEAA@XZ  
      2  1 00001020 ??1CDLLCPP@@QEAA@XZ  
      3  2 00001000 ??4CDLLCPP@@QEAAAEAV0@AEBV0@@Z  
      4  3 00001030 ?SuperSecretSauce@CDLLCPP@@QEAAHPEAD@Z  
  Summary  
     1000 .data  
     1000 .pdata  
     1000 .rdata  
     1000 .reloc  
     1000 .rsrc  
     1000 .text  
We wanted to call the SuperSecretSauce method, so ran undname.exe on the export (note: the DLL is 64bit in this example as well hence the __ptr64):
 C:\>undname ?SuperSecretSauce@CDLLCPP@@QEAAHPEAD@Z  
 Microsoft (R) C++ Name Undecorator  
 Copyright (C) Microsoft Corporation. All rights reserved.  
 Undecoration of :- "?SuperSecretSauce@CDLLCPP@@QEAAHPEAD@Z"  
 is :- "public: int __cdecl CDLLCPP::SuperSecretSauce(char * __ptr64) __ptr64"  
Pro Tip: You can run undname.exe against the output of dumpbin.exe /exports:
  • dumpbin /exports DLLCPP.dll > out.txt
  • undname < out.txt
We end  up with (our comments are in blue text):
   // The constructor  
   1  0 00001010 public: __cdecl CDLLCPP::CDLLCPP(void) __ptr64  
   // The deconstructor  
   2  1 00001020 public: __cdecl CDLLCPP::~CDLLCPP(void) __ptr64  
   // The class  
   3  2 00001000 public: class CDLLCPP & __ptr64 __cdecl CDLLCPP::operator=(class CDLLCPP const & __ptr64) __ptr64  
   // The SuperSecretSauce Method  
   4  3 00001030 public: int __cdecl CDLLCPP::SuperSecretSauce(char * __ptr64) __ptr64  
Pro Tip 2: There is also the the UnDecorateSymbolName function in DbgHelp

We put together some code to try and call the method like so:

#include "stdafx.h"  
  #include <Windows.h>  
  #include <strsafe.h>  
  #include <Dbghelp.h>  
  typedef void (__cdecl* _ExampleSecretSauce)(char *);  
  int _tmain(int argc, _TCHAR* argv[])  
  {  
       fprintf(stdout,"[i] Recx - C++ class DLL example\n");  
       HMODULE modDLL = LoadLibrary(L".\\DLLCPP.dll");  
       if (modDLL==NULL)  
       {  
            fprintf(stderr,"[!] Could not load .\\DLLCPP.dll");            
            return 1;  
       }  
       _ExampleSecretSauce OurImportedSecretSauce  = (_ExampleSecretSauce)GetProcAddress(modDLL,"?SuperSecretSauce@CDLLCPP@@QEAAHPEAD@Z");  
       if(OurImportedSecretSauce==NULL){  
            fprintf(stderr,"[!] Could not get address of the secret sauce method");            
            return 1;  
       }  
       OurImportedSecretSauce("Hello Secret Sauce");  
       return 0;  
  }  
But when we run this code we get the following response from the SuperSecretSauce method if were lucky enough that it doesn't crash:
[i] Recx - C++ class DLL example
[!] Our constructor has not been called!
The error above is due a check we've put in the demo method, in the real world all manner of unpredictable outcomes could occur. At which point its worth pointing out when you normally instantiate a method from a C++ class the code would be something like this:
#include "stdafx.h"  
  #include "DLLCPP.h"  
  #pragma comment(lib,".\\Release\\dllcpp.lib")  
  int _tmain(int argc, _TCHAR* argv[])  
  {  
       CDLLCPP *cppDLL = new CDLLCPP();  
       cppDLL->SuperSecretSauce("Hello");  
       return 0;  
  }  
So we modified the code to first import and call the constructor.
#include "stdafx.h"  
  #include <Windows.h>  
  #include <strsafe.h>  
  #include <Dbghelp.h>  
  typedef void (__cdecl* _ExampleConstruct)();  
  typedef void (__cdecl* _ExampleSecretSauce)(char *);  
  int _tmain(int argc, _TCHAR* argv[])  
  {  
       fprintf(stdout,"[i] Recx - C++ class DLL example\n");  
       HMODULE modDLL = LoadLibrary(L".\\DLLCPP.dll");  
       if (modDLL==NULL)  
       {  
            fprintf(stderr,"[!] Could not load .\\DLLCPP.dll");            
            return 1;  
       }  
       _ExampleConstruct OurImportedConstructor = (_ExampleConstruct)GetProcAddress(modDLL,"??0CDLLCPP@@QEAA@XZ");  
       if(OurImportedConstructor==NULL){  
            fprintf(stderr,"[!] Could not get address of the constructor");            
            return 1;  
       }  
       _ExampleSecretSauce OurImportedSecretSauce  = (_ExampleSecretSauce)GetProcAddress(modDLL,"?SuperSecretSauce@CDLLCPP@@QEAAHPEAD@Z");  
       if(OurImportedSecretSauce==NULL){  
            fprintf(stderr,"[!] Could not get address of the secret sauce method");            
            return 1;  
       }  
       OurImportedConstructor();  
       OurImportedSecretSauce("Hello Secret Sauce");  
       return 0;  
  }  
When we ran this code it just crashed with an access violation in the constructor . This should have been obvious in hindsight. If we look at the disassembly for a legitimate call on 64 bit to new() and the constructor in the demo above we see the following (our comments in blue text):
  000000013FF71000 sub   rsp,38h    
  000000013FF71004 mov   qword ptr [rsp+20h],0FFFFFFFFFFFFFFFEh    
    CDLLCPP *cppDLL = new CDLLCPP();   
  000000013FF7100D mov   ecx,1  
  000000013FF71012 call  qword ptr [__imp_operator new (13FF72148h)]    
  // Microsoft x64 calling convention says the ret comes back in RAX (pointer)  
  000000013FF71018 mov   qword ptr [rsp+50h],rax    
  000000013FF7101D test  rax,rax    
  000000013FF71020 je   wmain+2Ch (13FF7102Ch)    
  // The first argument - the pointer to the object - is passed in RCX - to the constructor  
  000000013FF71022 mov   rcx,rax    
  000000013FF71025 call  qword ptr [__imp_CDLLCPP::CDLLCPP (13FF72008h)]    
  000000013FF7102B nop    
    cppDLL->SuperSecretSauce("Hello");   
  000000013FF7102C lea   rdx,[string "Hello" (13FF721D0h)]    
  // Again passing a pointer to the object in RCX from the original RAX result from new  
  000000013FF71033 mov   rcx,rax    
  000000013FF71036 call  qword ptr [__imp_CDLLCPP::SuperSecretSauce (13FF72000h)]   
Also there was a likely hint in the dumpbin out (in red text):
   // The constructor  
   1  0 00001010 public: __cdecl CDLLCPP::CDLLCPP(void) __ptr64  
   // The deconstructor  
   2  1 00001020 public: __cdecl CDLLCPP::~CDLLCPP(void) __ptr64  
   // The class  
   3  2 00001000 public: class CDLLCPP & __ptr64 __cdecl CDLLCPP::operator=(class CDLLCPP const & __ptr64) __ptr64  
   // The SuperSecretSauce Method  
   4  3 00001030 public: int __cdecl CDLLCPP::SuperSecretSauce(char * __ptr64) __ptr64  
All of the exports, after the function definition, listed an extra 64bit pointer. While we don't know for sure (as the output of undname isn't fully documented), but we think this is likely an indicator of the need to pass the object pointer. So the reason for the crash was that there was no pointer to an object being passed to the constructor or the SuperSecretSauce method. The way we solved this problem was to supply an extra parameter at the start to the function typedef for any method in that class. This would ensure that the pointer to our object is passed in the RCX register (note: there is only one calling convention on Windows 64bit):
typedef void (__cdecl* _ExampleConstruct)(char *);  
  typedef void (__cdecl* _ExampleSecretSauce)(char *, char *);  
We then allocate a block of memory big enough to hold it (an exercise for the reader to work out how large it needs to be), memset and pass it to the constructor and other methods.
char vFakeObject[4096];  
      memset(vFakeObject,0x00,4096);  
      OurImportedConstructor(vFakeObject);  
      OurImportedSecretSauce(vFakeObject,"Hello Secret Sauce");  
So we end up with code that looks like this:
#include "stdafx.h"  
  #include <Windows.h>  
  #include <strsafe.h>  
  #include <Dbghelp.h>  
  typedef void (__cdecl* _ExampleConstruct)(char *);  
  typedef void (__cdecl* _ExampleSecretSauce)(char *, char *);  
  int _tmain(int argc, _TCHAR* argv[])  
  {  
       fprintf(stdout,"[i] Recx - C++ class DLL example\n");  
       HMODULE modDLL = LoadLibrary(L".\\DLLCPP.dll");  
       if (modDLL==NULL)  
       {  
            fprintf(stderr,"[!] Could not load .\\DLLCPP.dll");            
            return 1;  
       }  
       _ExampleConstruct OurImportedConstructor = (_ExampleConstruct)GetProcAddress(modDLL,"??0CDLLCPP@@QEAA@XZ");  
       if(OurImportedConstructor==NULL){  
            fprintf(stderr,"[!] Could not get address of the constructor");            
            return 1;  
       }  
       _ExampleSecretSauce OurImportedSecretSauce  = (_ExampleSecretSauce)GetProcAddress(modDLL,"?SuperSecretSauce@CDLLCPP@@QEAAHPEAD@Z");  
       if(OurImportedSecretSauce==NULL){  
            fprintf(stderr,"[!] Could not get address of the secret sauce method");            
            return 1;  
       }  
       char vFakeObject[4096];  
       memset(vFakeObject,0x00,4096);  
       OurImportedConstructor(vFakeObject);  
       OurImportedSecretSauce(vFakeObject,"Hello Secret Sauce");  
       return 0;  
  }  
And... drum role... voilĂ :
[i] Recx - C++ class DLL example
[!] You sent the secret sauce Hello Secret Sauce
Hopefully this will save you an hour or two of reading plus trial and error. All of the code for the above examples can be downloaded from here. The projects included in the download archive are:
  • DLLCPP - The C++ DLL.
  • LoaderAttempt1 - the example where we don't call the constructor.
  • LoaderAttempt2 - the example where we don't pass an object.
  • Loader - the working example.
The project as-is will only work for the 64bit build as the undecorated function names will need changing for the 32bit version of the DLL.

2 comments:

  1. I ran across a similar situation recently. Luckily I was on ARM where the "this" pointer is passed in exactly the same way as the first argument of a normal function; this simplified the work a little (otherwise I might have had to use some inline assembly to set things up, or alternately make a dummy class to mimic the one I was calling). My approach was simple -- 1) preallocate the space for "this", I just made a big buffer that I knew was oversized. 2) use dlopen/dlsym to get pointers to the methods that I wanted to call, 3) call the methods as if they were functions, passing the address of my "this" pointer as the first argument.
    I actually skipped calling the constructor because I knew that the constructor didnt do anything important other than zeroing out the structure. Also I skipped building any method table because I knew that there were no virtuals called in the functions I cared about. All of these shortcuts made my work a lot easier, but none of them would be very difficult to overcome if I had to. The resulting program was only about 30 or 40 lines of code and didnt require any special linking (since I was dynamically loading the library).

    ReplyDelete
  2. Excellent solution. This solved a similar problem of mine. Linked here by fxam on stackoverflow. And yeah, I made a 1 MB space for "this", I can probably trim that down.

    ReplyDelete