views:

58

answers:

0

I am facing a issue of 'weirdness' in relation to using .NET's run-time to inject a DLL into WordPad.

The objective is simple, to be able to obtain the EM_STREAMOUT, pass the data back to the .NET C# code via way of WM_COPYDATASTRUCT message in which the .NET form intercepts and spits out the message into the console. For DLL injection to be do-able from the .NET landscape, I used Syringe DLL Injector, which is written in C#, to perform the actual injection, furthermore, I have used this managed Windows API to do the work for me to retrieve the handles.

I have verified all handles that are legitimate and am stumped with this problem, sometimes it works - well, data does get passed back to .NET no problem but it crashes WordPad, and other times it does not work and still crashes Wordpad.

Consider this code that is a simple DLL that requires to be injected into WordPad's process, in order to obtain the EM_STREAMOUT message, via way of the function which is exported called RtfNotifyStreamOut.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winuser.h>
#include <richedit.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>

#ifdef __cplusplus
#extern "C" {
#endif

typedef struct _INIT_STRUCT {
    HWND rtfHandle;
    HWND calldProcHandle;
    HANDLE thisProcess;
} INIT_STRUCT, *PINIT_STRUCT;
static HWND _HWND_Rtf;
static HWND _HWND_Parent;
static HANDLE _hModule;
static HANDLE _hProc;

static void __cdecl odprintf(const char *format, ...);
static unsigned long GetWindowThreadId(HWND targetWin);
static DWORD CALLBACK EditStreamCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb);
//
__declspec(dllexport) void RtfNotifyStart(PVOID message);
__declspec(dllexport) void RtfNotifyStreamOut(void);
__declspec(dllexport) void RtfDummy(void);
//
#ifdef __cplusplus
}
#endif

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH){
        _hModule = hModule;
        DisableThreadLibraryCalls( hModule );
        odprintf("W00tness!");
    }
    return TRUE;
}

static void __cdecl odprintf(const char *format, ...)
{
    char buf[4096], *p = buf;
    va_list args;
    int n;

    va_start(args, format);
    n = _vsnprintf(p, sizeof buf - 3, format, args); // buf-3 is room for CR/LF/NUL
    va_end(args);

    p += (n < 0) ? sizeof buf - 3 : n;

    while ( p > buf  &&  isspace(p[-1]) )
            *--p = '\0';

    *p++ = '\r';
    *p++ = '\n';
    *p   = '\0';

    OutputDebugString(buf);
}


static DWORD CALLBACK EditStreamCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
    int rvPostMsg = 0;
    COPYDATASTRUCT cds;
    //
    odprintf("We are in the Callback!!!!");
    odprintf("[C] - Len = %d", cb);
    if (cb > 0){
        ZeroMemory(&cds, sizeof(COPYDATASTRUCT));
        cds.dwData = dwCookie;
        cds.cbData = cb;
        cds.lpData = pbBuff;
        *pcb = cb; 
        odprintf("About to Fire Event to Outside Process's Handle with WM_COPYDATA message");
        SendMessage(_HWND_Parent, WM_COPYDATA, (WPARAM)_HWND_Parent, (LPARAM)(LPVOID)&cds);
    }
    return 0;
}

__declspec(dllexport) void RtfNotifyStart(PVOID message){
    PINIT_STRUCT messageStruct = (PINIT_STRUCT)message;
    //
    _HWND_Parent = messageStruct->calldProcHandle;
    _HWND_Rtf = messageStruct->rtfHandle;
    _hProc = messageStruct->thisProcess;
    //
    odprintf("[C] - rtfHandle = %d; parentHandle = %d; ourProcess = %d", _HWND_Rtf, _HWND_Parent, _hProc);
}

__declspec(dllexport) void RtfNotifyStreamOut(void)
{
    EDITSTREAM _editStream;
    int rvNotifyMsg = 0;
    //
    ZeroMemory(&_editStream, sizeof(EDITSTREAM));
    _editStream.dwCookie = (DWORD_PTR)_HWND_Parent;
    _editStream.pfnCallback = EditStreamCallBack;
    odprintf("Value of callback is %d", _editStream.pfnCallback);
    //
    //PostMessage(_HWND_Rtf, EM_STREAMOUT, (WPARAM)SF_RTF, (LPARAM)&_editStream);
    rvNotifyMsg = PostMessage(_HWND_Rtf, EM_STREAMOUT, (WPARAM)SF_RTF, (LPARAM)&_editStream);
    if (rvNotifyMsg == 0) odprintf("RtfNotifyStreamOut - PostMessage Failed...");
    else{
        odprintf("RtfNotifyStreamOut - Fired Event to RTF's Handle");
        if (_editStream.dwError == (DWORD)0) odprintf("No errors so far so good....");
        else odprintf("Whoops!");
    }
}

__declspec(dllexport) void RtfDummy(void){
    odprintf("Dummy!");
}

Here's the basis of the code that does the interception of WM_COPYDATASTRUCT

protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case Win32API.WM_COPYDATA:
                System.Diagnostics.Debug.WriteLine("Got the message!");
                //
                Win32API.COPYDATASTRUCT cds;
                cds = (Win32API.COPYDATASTRUCT)m.GetLParam(typeof(Win32API.COPYDATASTRUCT));
                System.Diagnostics.Debug.WriteLine(string.Format("Len = {0}", cds.cbData));
                if (cds.cbData > 0)
                {
                    byte[] data = new byte[cds.cbData];
                    Marshal.Copy(cds.lpData, data, 0, cds.cbData);
                    Encoding unicodeStr = Encoding.ASCII;
                    char[] myString = unicodeStr.GetChars(data);
                    string returnText = new string(myString);
                    System.Diagnostics.Debug.WriteLine(string.Format("Data = {0}", returnText));
                }
                m.Result = new IntPtr(1);
                break;
        }
        base.WndProc(ref m);
    }

The injection hook is initiated by a form with two buttons - 'Inject' and 'Grab', upon pressing the 'Inject' button, this is the code that uses the Injector API

private IntPtr GetTarget(string sTitle2Hunt)
        {
            ManagedWinapi.Windows.SystemWindow desktop = new SystemWindow(IntPtr.Zero);
            SystemWindow[] swList = desktop.AllDescendantWindows;
            SystemWindow targetHunt = null;
            foreach (SystemWindow sw in swList)
            {
                if (sw.Title.IndexOf(sTitle2Hunt) > -1)
                {
                    targetHunt = sw;
                    break;
                }
            }
            if (targetHunt != null)
            {
                return targetHunt.HWnd;
            }
            return IntPtr.Zero;
        }

private void btnInject_Click(object sender, EventArgs e)
        {
            string rtfClass = "RICHEDIT";
            IntPtr ptr = GetTarget("WordPad");
            if (ptr.ToInt32() != 0)
            {
                this._rtfWinProc = new SystemWindow(ptr);
                SystemWindow[] swList = this._rtfWinProc.AllChildWindows;
                foreach (SystemWindow sWin in swList)
                {
                    System.Diagnostics.Debug.WriteLine("Class
= " + sWin.ClassName);
                    if (sWin.ClassName.ToUpper().IndexOf(rtfClass)
> -1)
                    {
                        this._rtfPtr = sWin.HWnd;
                        break;
                    }
                }
            }
            if (this._rtfPtr.ToInt32() > 0)
            {
                this._thisPtr = this.Handle;
                this._inj = new Injector(this._rtfWinProc.Process, true);
                if (this._inj != null)
                {
                    MessageStruct messageData = new MessageStruct() { rtfHandle = this._rtfPtr.ToInt32(), thisProcHandle = this._thisPtr.ToInt32(), calleeProcessId = this._rtfWinProc.Process.Id };
                    System.Diagnostics.Debug.WriteLine(string.Format("[C#]
- rtfHandle = {0}; parentHandle = {1}; injectedProcess = {2}", messageData.rtfHandle, messageData.thisProcHandle, messageData.calleeProcessId));
                    this._inj.InjectLibrary(this._sFullPath);
                    this._inj.CallExport<MessageStruct>(this._sFullPath, "RtfNotifyStart", messageData);
                    this.btnInject.Enabled = false;
                }
            }
        }

The 'Grab' event that initiates the grabbing of the data via EM_STREAMOUT is this code

private void btnGrab_Click(object sender, EventArgs e)
        {
            if (this._inj != null)
            {
                this._inj.CallExport(this._sFullPath, "RtfNotifyStreamOut");
            }
        }

I have read this article on Codeproject, and have turned off /Gz switch when compiling the DLL...

My questions is this:

Why is the behaviour sporadic, and yet crashes the WordPad Process...? Is it DEP that is randomizing the launch of the WordPad process, or is it the .NET runtime....