前面已经提到,不是所有的程序都能够“容忍”外部注入的代码,我之所以觉得“Source Insight”可以挂一下,是因为“Source Insight”使用的是标准的Windows MDI(多文档界面)窗口,窗口之前的消息流向简单且遵循Windows标准机制。于是一个月以后,“Source Insight”的文件标签外挂:TabSiPlus就诞生了,在研究“Source Insight”和编写“TabSiPlus”期间积累了一些经验,留在自己的脑子中只会慢慢遗忘,现在把它们整理成文字和大家一起共享。
首先介绍一下“TabSiPlus”,它的主要功能就是给“Source Insight”添加一个文件切换标签栏,这个切换标签栏对于使用“Source Insight”编写代码的人有很大的帮助,先看一些它都给“Source Insight”带来了哪些变化:
代码窗口下面多了一个文件标签栏,菜单也变样了,还加上了几个图标,其实菜单的底色和文字颜色都是可以改变的,文件标签栏的颜色也是可以改变的,看看:
除此之外,还添加了C/C++文件翻转的功能,这个可是VA的常用功能,相信大家都不陌生,这个C/C++文件翻转功能继承了“Tabbar for Visual C++”插件的多目录、多扩展名搜索功能:
从现在开始,我就通过一系列文章介绍“TabSiPlus”是怎样一步一步的做出来的,也包括对“Source Insight”的研究过程,本篇主要介绍如何找到“Source Insight”。这是一个很重要的问题,如果不能从系统中找到正在运行的“Source Insight”,那么外挂就无从挂起了。查找系统中运行的“Source Insight”程序有很多种方法,可以遍历系统中的所有进程,然后看看有没有insight3.exe,并得到这个进程的句柄;也可以通过窗口枚举,找到有“Source Insight”标志的主窗口,并获得这个主窗口的句柄。当然还有其他的方法,这里就不一一介绍了,“TabSiPlus”采用窗口枚举的方法,因为“Source Insight”的主窗口的类名是固定的且标题栏文字很有规律,在任何情况下都有“Source Insight”字样,便于匹配,其实主要的原因是窗口枚举方法简单。
使用Spy++工具研究“Source Insight”的主窗口,发现其窗口的类名是“si_Frame”,这是一个好兆头,如果一个窗口的类名是类似于“Afx:400000:0:10011:10:0”就麻烦了,这是MFC主框架窗口类的典型名字,里面的那些数字是诸如进程地址,窗口图标句柄,鼠标光标句柄格式化成的一个字符串,它是可变的,在某个系统上是一个结果,在另一个系统上可能是另一个结果。再来看看“Source Insight”主窗口的标题文字,发现无论什么情况都包含一个“Source Insight”子串,这对于我们确定这个窗口是否是“Source Insight”主窗口可以起到一个辅助判断的作用。枚举窗口使用EnumWindows() API,这个API使用一个回调函数,以下是回调函数的原型:
BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam)
下面是“TabSiPlus”中EnumWindowsProc()回调函数的实现:
BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam)
{
BOOL bSuccess = TRUE;
if(hwnd != NULL && IsSourceInsightFrameWnd(hwnd))
{
if(lParam)
{
HWND *pHwnd = (HWND *)lParam;
*pHwnd = hwnd;
bSuccess = FALSE;//已经找到一个Source Insight窗口,退出枚举
}
}
return bSuccess;
}
这个函数利用lParam参数将窗口句柄传递出来,它一次只处理一个“Source Insight”窗口,如果系统中有多个“Source Insight”运行,就会有多个“Source Insight”主窗口,这由外部机制驱动枚举函数进行多次枚举,保证对所有的“Source Insight”进行处理。你可能已经看出来这样存在重复发现的问题了,是的,存在这样的问题,不过“TabSiPlus”采用一个巧妙的方法解决了这个问题。“TabSiPlus”在Hook一个“Source Insight”窗口之后就在窗口标题栏添加一个“ with TabSiPlus”的标志,这样就可以区分窗口是否已经处理过了。下面就是判断一个窗口是否是“Source Insight”窗口的IsSourceInsightFrameWnd()函数:
LPCTSTR lpszSourceInsight = _T("Source Insight");
LPCTSTR lpszSiFrameWndClass = _T("si_Frame");
LPCTSTR lpszTextMark = _T(" with TabSiPlus");
BOOL IsSourceInsightFrameWnd(HWND hWnd)
{
TCHAR szClassName[128],szTitle[256];
int nRtn = GetClassName(hWnd,szClassName,128);
if(nRtn == 0)
return FALSE;
nRtn = GetWindowText(hWnd,szTitle,256);
if(nRtn == 0)
return FALSE;
//类名是si_Frame,并且窗口标题又含有Source Insight,可以基本判定是一个Source Insignt窗口
if((lstrcmp(lpszSiFrameWndClass,szClassName) == 0) && (StrStr(szTitle,lpszSourceInsight) != NULL))
{
if(StrStr(szTitle,lpszTextMark) != NULL)//有这个mark说明已经Hook过了,不要再骚扰source insignt窗口了
return FALSE;
return TRUE;
}
return FALSE;
}
下面是找到一个“Source Insight”窗口的调度函数,每次调用一次调度函数可以查询到一个没有被Hook过的“Source Insight”:
HWND FindSourceInsightFrameWindow()
{
HWND hSiFrmWnd = NULL;
BOOL bRtn = ::EnumWindows(EnumWindowsProc,(LPARAM)&hSiFrmWnd);
if(!bRtn && hSiFrmWnd != NULL)
return hSiFrmWnd;
else
return NULL;
}
最后是查找“Source Insight”窗口并将指定的动态连接库挂到“Source Insight”进程中的函数:
//一次试图查找并Hook一个Source Insighe窗口
BOOL FindAndHookSourceInsightWindow(LPCTSTR lpszHookDll)
{
BOOL bSuccess = FALSE;
if(lpszHookDll)
{
HWND hSiFrmWnd = FindSourceInsightFrameWindow();
if(hSiFrmWnd != NULL)
{
bSuccess = HookSourceInsightWindow(hSiFrmWnd,lpszHookDll);
}
}
return bSuccess;
}
这个函数中调用了一个重要的函数:HookSourceInsightWindow(),这个函数负责将我们的代码注入到“Source Insight”进程中,这涉及到代码远程注入的很多细节,关于代码注入方法将在下一篇:《给Source Insight做个外挂系列之二--将本地代码注入到Source Insight进程》中介绍,本篇到此结束。
Source Insignt文件标签外挂:TabSiPlus的下载地址:
点击下载