您的位置首页百科知识

以SDK重建DBWIN

以SDK重建DBWIN

的有关信息介绍如下:

以SDK重建DBWIN

著名的MFC四大天王之一侯俊杰这样说:“没有DBWIN,Trace唱什么独角戏,Trace的输出只有在VC的调试窗口才能看到,我们只不过想去游乐场玩玩,Microsoft却要我们扛一支155加农炮!”

从前,win16时代,Trace输出到一个叫DBWIN的窗口,而现在它没了!!写程序的生命变得有点不堪。《深入浅出MFC 第二版》中的附录4展现了某位大师以MFC重建DBWIN的做法(这就是网上流传的Tracewin),但这个程序只对MFC程序有效(没办法,它是针对MFC的),在这篇文章中,我将以SDK重建DBWIN。

要知道,Trace宏实际上就是调用了OutputDebugStringW或OutputDebugStringA(取决于是否使用Unicode字符集),而我们要重建DBWIN,就要拦截这两个函数!用我们自己的函数覆盖这两个函数!

但是,要拦截这两个函数并不简单,Win32中每个进程都有自己的地址空间,为了拦截这两个函数的调用,我们得侵入远程进程的地址空间!!

为了达到目的,我们得使用动态链接库(DLL)技术,我们可以写一个DLL,让其他进程加载这个DLL,这时,DLL已经在其他进程的地址空间里了,我们的DLL就可以做想做的事了。

道理说完了,开始实战!记住,如果不掌握上述的任一技术,都应先弄懂。

首先,我们先写一个主程序,用作输出窗口,我也做了一个,由于网上有个软件叫DebugView(功能非常强大),所以我也写了一个DebugView(功能相比弱多了,只是权宜设计)。

如图,这是一个对话框程序,由1个ComboBox,4个Button,1个Edit等组成。

给程序先使用ToolHelp函数,获得一个进程快照,并加入到ComboBox的列表中,用户根据列表中的信息,输入进程ID,程序会尝试打开一个进程并通过远程注入DLL技术注入DLL!

当然,为了DLL能找到我们的主程序窗口,窗口类得自定义,而不能使用默认的对话框类。我们可以在对话框属性中改变对话框窗口类。并把默认对话框类的信息复制到我们的窗口类,最后注册我们的窗口类。以下是实现函数:

VOID RegisterDlgClass(LPTSTR szClassName)

{

//注册自定义窗口类

WNDCLASS wc;

GetClassInfo(hInst/*程序当前实例*/,_T("#32770")/*默认对话框类名*/,&wc);

wc.lpszClassName=szClassName;

RegisterClass(&wc);

}

以下是DLL注入的实现代码:

单击“加载”后程序调用以下函数:

VOID LoadInstance(HWND hDlg)

{

TCHAR sz[MAX_PATH];

sz='\0';

DWORD ProcessId=GetDlgItemInt(hDlg,IDC_PROCESS_ID,NULL,FALSE);

dwProcessId=ProcessId;

HANDLE Thread=(HANDLE)_beginthreadex(NULL,0,MyCreateRemoteThread,hDlg,0,0);

if(Thread==NULL) {

AddText(GetDlgItem(hDlg,IDC_LIST1),_T("错误: %u"),GetLastError());

}

}

该函数尝试打开一个进程,并新建了一个线程来实现真正操作。以下线程函数代码:

UINT CALLBACK MyCreateRemoteThread(LPVOID pParam)

{

HWND hDlg=(HWND)pParam;

HANDLE hProcess=OpenProcess(PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION,

FALSE,dwProcessId);

if(hProcess==NULL) {

AddText(GetDlgItem(hDlg,IDC_LIST1),_T("错误: %u"),GetLastError());

return FALSE;

}

LPTHREAD_START_ROUTINE pfn=(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("kernel32")),"LoadLibraryW");

/*获取DLL绝对路径*/

TCHAR sz[MAX_PATH]=_T("");

GetModuleFileName(NULL,sz,_countof(sz));

int cbSize=_countof(sz);

for(LPTSTR p=&sz[cbSize];*p!=TEXT('\\');p--,cbSize--) ;

StringCchPrintf(sz,cbSize+2,sz);//截断

StringCchPrintf(sz,_countof(sz),_T("%s%s"),sz,_T("DebugViewDLL.dll"));

/*获取DLL绝对路径*/

PVOID pData=VirtualAllocEx(hProcess,NULL,_countof(sz)*sizeof(TCHAR),MEM_COMMIT,PAGE_READWRITE);

WriteProcessMemory(hProcess,pData,sz,_countof(sz)*sizeof(TCHAR),NULL);

HANDLE newThread=CreateRemoteThread(hProcess,NULL,0,pfn,pData,0,NULL);

if(newThread==NULL) {

AddText(GetDlgItem(hDlg,IDC_LIST1),_T("错误: %u"),GetLastError());

}

WaitForSingleObject(newThread,INFINITE);//等待载入完成

VirtualFreeEx(hProcess,pData,0,MEM_RELEASE);

EnableWindow(GetDlgItem(hDlg,IDC_WATCH),TRUE);

return TRUE;

}

注意上述使用了DLL的绝对路径,这是必要的,因为DLL是通过远程线程来注入的,这时如果DLL不在远程进程的当前目录或系统目录,DLL就不会被找到。

注入DLL后,剩下的事就是DLL的了。DLL的任务是覆盖原始API函数。

我采用了修改文件导入段来覆盖函数,具体代码请查看本文最后的资源。

这时,当程序调用OutputDebugString,系统会调用我们的MyOutputDebugString(W或A)。我们应该实验这两个函数。

函数实现:

VOID CALLBACK MyOutputDebugStringW(LPCWSTR sz)

{

int cbSize=0;

for(LPWSTR p=(LPWSTR)sz;*p!='\0';p++,cbSize+=sizeof(WCHAR)) ;

COPYDATASTRUCT cs={0};

cs.cbData=cbSize;

cs.lpData=(PVOID)sz;

SendMessage(hWnd,WM_COPYDATA,0,(LPARAM)&cs);

}

VOID CALLBACK MyOutputDebugStringA(LPCSTR sz)

{

int cbSize=MultiByteToWideChar(0,0,sz,-1,0,0);

int newSize=cbSize*sizeof(WCHAR);

LPWSTR p=new WCHAR[newSize];

MultiByteToWideChar(0,0,sz,-1,p,newSize);

MyOutputDebugStringW(p);

delete []p;

}

注意 MyOutputDebugStringA 没有把代码再写一遍,而是把字符转成Unicode,再调用MyOutputDebugStringW,这在API中是极为普遍的。

当我们的MyOutputDebugString函数被调用,我们得把字符丢给我们的主程序。这是用WM_COPYDATA很容易实现,具体看最后的资源。

这个程序只对它加载的程序有效,有什么方法来做一个系统级的DBWIN呢?

其实在看完上述例子中,这并不难。只要利用SetWindowHookEx来让每个程序都载入我们的DLL就行了。

本程序使用VS2012编写。

文件大小:5.1M,已高度压缩!中间文件(*.obj等)已全部删除。

下载链接:http://pan.baidu.com/s/1pKzPHof

SHA1:808416A44BF953A39CC24F0C399EBAD57ABE0368

MD-5:658329999B0A1F4C6711E780A54705CC