Introduction
A recent project where security is important and the use of COM BSTR strings is required was the impulse for developing the secure wrapper framework. Security coding rules mandate that:
- Sensitive data should be left in plain text in memory for as short a time as possible
- When a variable goes out of scope its contents should be overwritten with a particular pattern immediately
- When sensitive data is passed over COM it must be encrypted
As you know, there are two main Microsoft-supplied wrapper classes for the
BSTR
data type:
CComBSTR
and
_bstr_t
. They both have their good and bad points and developers on the project use them both. Rather than write a third wrapper I decided to build on these two well-known, reliable classes, giving them the additional security features we required.
Features Required
I came up with the following list of requirements to satisfy the requirements of the project and to make development simple and as similar to using the regular wrappers as possible:
- Data should be kept hidden in memory
- Data should be revealed in memory only when required
- Data should be monitored for how long it has been revealed
- Data should be positively wiped when no longer needed
- Data for COM transmission should be encrypted and decrypted
- New wrappers should be able to interact with existing wrappers
- Performance should not be noticeably affected
- The new features should be as automatic as possible
Hiding the Data
Secure data in memory is obfuscated so that hackers using a memory scan or memory dump to file cannot easily see it. The rule that is followed is that the data is normally hidden, being revealed only when needed and then for as short a time as possible. This rule is implemented in the methods of the
BSTR
wrappers by making sure that data is hidden unless the method itself calls for exposure � such as copying out to a
BSTR
.
I chose to hide the data using an efficient obfuscation technique based on a rolling exclusive OR. This is functionality is held in an Obfuscator class which can be altered independently of other code if a more robust method is required. Note that this feature is different from the encryption required for communication.
Revealing the Data
Data should be revealed, that is available in clear text in memory, for as short a time as possible. You reveal the data by using one of the extractor or casting functions, for instance:
Secure::_bstr_t
strData(aBSTR,true);
const char *szData;
.
.
.
szData = (const char *)strData;
.
.
.
strData.HideData();
This reveals the data since
szData
is a pointer straight into an underlying member variable and so must be in clear text. Hiding the data will have the effect of also hiding the data pointed to by
szData
. Clearly, care must be taken not to hide the data too early in situations like this.
Exposure Monitoring
Data is revealed or exposed when it is available in clear text in memory, that is when it is not obfuscated. The role of exposure monitoring is to detect instances of secure data having been exposed for longer than a programmable time and to do something about them.
There are some obvious limitations on exposure monitoring. For instance if you take a copy of the data to an insecure area when it is exposed then hide the original data quickly, exposure monitoring will not detect this yet there is still an exposed copy of the data in memory. You should deal with this as described in the next section.
To monitor overexposed data I chose to implement a separate thread and a simple map of currently exposed data. The methods that reveal and hide secure data keep the map updated. You can start and stop declaratively or programmatically by wrapper type. The abstract base classes, described later, implement this thread.
When data has been exposed for too long, you can get the thread to call your callback routine or simply to re-hide the data.
Your callback routine should have the following signature:
typedef void (*pfnExposedDataCallback)(const DataHolder * pData);
Wiping Data
When data goes out of scope or is deleted it is positively overwritten with a defined pattern. A single non-class
Wipe
function writes the pattern FE EE FE EE� to memory. Destructors of data holding classes call this function which is also available to be used on insecure copies of secure data immediately after the data is no longer needed.
Encrypting for COM Transmission
I chose the strong AES symmetric encryption using 128 bit keys and a randomly generated initialization vector. The encrypted data includes the length of the original data and a SHA1 hash of the data. The data is encrypted first with a key available to all software components then encrypted again with a Diffie-Hellman ephemeral key negotiated between the two COM partners. I use base64 encoding of the entire blob to make the encrypted data easily transmissible via COM as a string.
This may be the subject of a separate KB. Because of issues of performance, security and key management these features are kept separate from the wrapper classes and are not presented here.
Existing Wrappers
I took a hard look at the implementation of the existing wrapper classes to determine what changes to make.
Of the two wrappers _bstr_t
is more complicated, having a contained class, Data_t
. Data_t
is reference-counted and can hold the data for several _bstr_t
objects having the same value.
For instance consider the following code fragment:
_bstr_t strA(TEXT(�secret�);
_bstr_t strB(strA);
_bstr_t strC;
strC = strB;
The above code results in
three_bstr_t
objects but only
one Data_t
object, which is shared between the three
_bstr_t
s. I decided to keep the contained class but to de-implement
Data_t
sharing because of problems that would arise with clashes of data being revealed in one
_bstr_t
object but hidden in another when they shared a common
Data_t
object. The
CComBSTR
wrapper is a much more straightforward, thinner class, holding the data itself.
Since the new wrappers classes mimic the original builtin classes I decided to keep the same names but to place them, along with the rest of the implementation, in the Secure
namespace. Interoperability is built in to the new wrappers. For instance a Secure _bstr_t
and an original one can be constructed from each other, be compared to each other and be set to each other.
Abstract Base Classes
To implement the required features I created two abstract base classes which contain new shared functionality and mandate a few extra pure virtual methods on the new wrapper classes. These abstract base classes are
DataWrapper
and
DataHolder
.
DataWrapper
is the externally used wrapper class (like
_bstr_t
) and
DataHolder
holds the data (like
Data_t
).
DataWrapper
This class is inherited by the new versions of
_bstr_t
and
CComBSTR
since they are the wrappers. It implements additional required functionality associated with controlling exposure monitoring and mandates a method to return the DataWrapper�s
DataHolder
object.
DataHolder
This class is inherited by the new versions of
Data_t
and
CCOMBSTR
since that is where the data is held. It implements the bulk of the functionality associated with data hiding and revelation as well as exposure monitoring. It mandates methods to determine which internal data is to be hidden and revealed.
Overall Class Structure
So in outline we have the following class structure:
Namespace Secure
{
inline void Wipe(void * pData, size_t size);
class Obfuscator { };
template<class T>class Checker{};
class DataHolder{};
class DataWrapper{};
class _bstr_t : public DataWrapper
{
class Data_t : public DataHolder{};
};
class CComBSTR : public DataWrapper, public DataHolder{};
};
Let�s look at each of the classes in a bit more detail.
Checker
Checker is a convenience class used to declaratively start and stop exposure monitoring.
template<class T
class Checker
{
public:
inline Checker<T>(bool bStart = true,
pfnExposedDataCallback callback = NULL);
inline ~Checker<T>();
};
You can control exposure monitoring simply by declaring an object of
Checker
:
Secure::Checker<CComBSTR> BSTRMonitor(true, MyCallBack);
When the object goes out of scope, the exposure monitoring thread stops.
DataHolder
DataHolder
is the largest class containing methods to handle data hiding and revelation as well as exposure monitoring.
The structure EXPOSEDATA
helps the exposure monitoring thread, its controlling methods and the Hide
and Reveal
methods. The contained ExposeMap
is a standard template library map holding details of currently revealed data. The Reveal
method adds to the map and the Hide
method removes entries from the map.
class DataHolder
{
public:
typedef std::map<const DataHolder *,_timeb>ExposeMap;
typedef struct EXPOSEDATA
{
ExposeMap Map;
CRITICAL_SECTION Crit
HANDLE hThread;
HANDLE hWake;
bool bThreadActive;
unsigned long lWaitTime;
double dblMaxExposeTime;
pfnExposedDataCallback callback;
};
DataHolder()
inline void Hide() const;
inline void Reveal() const;
bool IsHidden() const;
inline static void StartExposeThread(pfnExposedDataCallback callback,
double maxExposeTime, unsigned long waitTime);
inline static void CloseExposeThread();
inline void Obfuscate(void * pData, size_t size, unsigned char seed);
inline void Deobfuscate(void * pData, size_t size, unsigned char seed);
protected:
virtual void HideMe() const = 0;
virtual void RevealMe() const = 0;
void SeedAndHide() const;
mutable bool m_hidden;
mutable unsigned char m_seed;
Obfuscator *m_obfuscator;
private:
inline static EXPOSEDATA * EMap();
inline static DWORD WINAPI ExposeThread(LPVOID param);
};
Inheriting classes must implement the
HideMe
and
RevealMe
methods because only they know where the data is.
The implementation for Data_t
is very small, m_wstr
is the unicode string and m_str
the ASCII string.
inline void _bstr_t::Data_t::HideMe() const
{
Obfuscate(m_wstr, ::SysStringByteLen(m_wstr),m_seed);
Obfuscate(m_str, ::SysStringLen(m_wstr), m_seed);
}
inline void _bstr_t::Data_t::RevealMe() const
{
Deobfuscate(m_wstr, ::SysStringByteLen(m_wstr), m_seed);
Deobfuscate(m_str, ::SysStringLen(m_wstr), m_seed);
}
and for
CComBSTR
there is only an optional unicode string.
inline void CComBSTR::HideMe() const
{
if (m_str != NULL)
{
Obfuscate(m_str, ::SysStringByteLen(m_str),m_seed);
}
}
inline void CComBSTR::RevealMe() const
{
if (m_str != NULL)
{
Deobfuscate(m_str, ::SysStringByteLen(m_str),m_seed);
}
}
DataWrapper
DataWrapper implements control methods for exposure monitoring and mandates inheriting classes with a single pure virtual method,
Data
, concerning the location of the
DataHolder
data object.
class DataWrapper
{
public:
virtual const DataHolder * Data() const=0;
void HideData() const;
void RevealData() const;
virtual bool IsDataHidden() const;
inline static StartExposeChecking(pfnExposedDataCallback callback,
double maxExposeTime = MAXEXPOSETIME,
unsigned long waitTime = EXPOSEHEARTBEATMILLISECONDS);
inline static void StopExposeChecking();
};
We require the
Data
method to be implemented by the derived class because the base class does not know how the derived class will reference the
DataHolder
class.
Again, the implementation of the method is quite small.
For _bstr_t
the DataHolder
class is held in class variable m_Data
:
inline const DataHolder * _bstr_t::Data() const
{
return m_Data;
}
and for
CComBSTR
, which also implements
DataHolder
itself:
inline const DataHolder *CComBSTR::Data() const
{
return this;
}
Obfuscator
Obfuscator contains the code to hide and reveal in-memory data. I currently use a rolling, seeded exclusive OR technique that is very fast. It is not a very strong encryption but acceptable for protecting in-memory, transient data. A stronger method may be included later.
class Obfuscator
{
public:
void Obfuscate (void * pData, size_t size, unsigned char seed) const;
void Deobfuscate (void * pData, size_t size, unsigned char seed) const;
private:
void Xor(void * pData, size_t size, unsigned char seed) const
};
Changes to Existing Wrappers
Many of methods need changing in small but similar ways. Almost all of the changes are associated with data hiding. I followed these rules:
- All constructors and assignment methods hide the data on receipt.
- The data remains hidden, apart for brief internal revelation/hiding changes, until it is required externally
- When a method operates on another wrapper object it leaves that object in the same hidden state as it found it.
For instance the comparison operator !:
inline bool _bstr_t::operator!() const throw()
{
return (m_Data != NULL) ? !m_Data->GetWString() : true;
}
becomes
inline bool _bstr_t::operator!() const throw()
{
bool Rtn;
bool bHidden = IsDataHidden();
RevealData();
Rtn = (m_Data != NULL) ? !m_Data->GetWString() : true;
if (bHidden)
HideData();
}
The changes here are to determine if the data is currently hidden, then reveal it. After the main work of determining the return value the data is hidden if it was hidden at the start of the method.
Another example in the contained Data_t
class of a constructor:
inline _bstr_t::Data_t::Data_t(const char* s) throw(_com_error)
: m_str(NULL), m_RefCount(1)
{
m_wstr = _com_util::ConvertStringToBSTR(s);
if (m_wstr == NULL && s != NULL)
{
_com_issue_error(E_OUTOFMEMORY);
}
}
becomes
inline _bstr_t::Data_t::Data_t(const char* s) throw(_com_error)
: m_str(NULL), m_RefCount(1)
{
m_wstr = _com_util::ConvertStringToBSTR(s);
if (m_wstr == NULL && s != NULL)
{
_com_issue_error(E_OUTOFMEMORY);
}
SeedAndHide();
}
At the end of the method, the DataHolder�s
SeedAndHide
method is called to calculate a seed value for obfuscation and to hide the data.
Secure Wrappers in Use
After extracting the data you can copy the data elsewhere then call the
HideData
method or let the exposure monitoring thread deal with it. You may also choose to use the
Secure::Wipe
method to wipe other areas of memory containing sensitive data that is no longer required:
STDMETHODIMP CLASS:Method(BSTR bstrInput,�)
{
Secure::_bstr_t strSensitiveData(bstrInput);
const char * pExtract = (const char *)strSensitiveData;
size_t uiSize = strlen(pExtract) + 1;
char * pCopy = new char[uiSize];
strcpy(pCopy, pExtract);
strSensitiveData.HideData();
.
.
.
Secure::Wipe(pCopy, uiSize);
delete[] pCopy;
.
.
.
}
For situations where a whole C++ file or project requires mainly secure wrappers you can default to secure wrappers but to continue using the original wrappers using the
_ibstr_t
original wrapper. This example also shows the use of the exposure monitoring callback function.
#define SECURE_BSTR_T
#include �SecureBstrT.h�
void MyCallback(const DataHolder * pData)
{
const char * szData = (const char *)(*pData);
.
.
.
pData->Hide();
}
Secure::Checker<_bstr_t> BstrCheck(true, MyCallback);
HRESULT CDemo::Method(BSTR bstrInput, BSTR * pbstrOutput)
{
_bstr_t strSensitiveData(bstrInput);
_ibstrt strNormalData;
.
.
.
strNormalData = strSensitiveData;
.
.
.
}
Summary
An effective, simple to use, extensible security-enhanced class framework has been developed.