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