以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