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
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.