给Source Insight做个外挂系列之三--构建外挂软件的定制代码框架

时间:2022-01-04 15:36:44

上一篇文章介绍了“TabSiPlus”是如何进行代码注入的,本篇将介绍如何构建一个外挂软件最重要的部分,也就是为其扩展功能的定制代码。本文前面提到过,由于windows进程管理的限制,扩展代码必须以动态链接库的形式挂载到被挂程序的进程空间中,使用上一篇介绍的方法已经可以通过创建远程线程的方式启动一个线程,让这个线程加载我们的定制动态链接库,现在就看看这个动态链接库是如何实现的。

首先这是一个动态链接库,因为考虑到扩展功能中有大量的界面操作,所以选择支持MFC,同时,还要提供一个名为“InitFunc”的导出函数,供在被挂程序中启动的远程线程调用,以初始化外挂动态链接库,这个导出函数的原型是这样的:
typedef DWORD (WINAPI *PFN)();
没有参数,但是有一个返回值用于表示初始化是否成功。现在就用Visual C++的向导生成一个支持MFC的动态链接库的框架,并手工添加一个名为“InitFunc”的导出函数,如果你还不清楚怎么做,那么可以停止看本文了,因为本文可能对你毫无用处。
  
    在生成的代码中,MFC对DllMain进行了封装,所以有了一个CxxxApp的类,xxx与你的动态链接库的名字一致,TabSiPlus使用的是CTabSiPlusApp,现在有三个地方需要特别注意,一个是CTabSiPlusApp::InitInstance(),一个是CTabSiPlusApp::ExitInstance(),另一个就是我们的导出函数“InitFunc”。当远程线程中LoadLibrary()调用我们的定制动态链接库时,CTabSiPlusApp::InitInstance()被调用,当FreeLibrary()调用发生时,CTabSiPlusApp::ExitInstance()被调用,当然,伴随而出现的还有两个函数调用,那就是CTabSiPlusApp类的构造函数和析构函数,部分初始化代码也可以放在构造函数中完成,不过并不推荐这样做,因为如果因为构造函数触发异常导致LoadLibrary()失败,那么随后的析构函数也不会被调用,因为构造函数没有完成对象的构造,同时,由于LoadLibrary()失败,使得FreeLibrary()调用分支没有执行,那么导致CTabSiPlusApp::ExitInstance()也没有被调用,这会引起资源释放的异常。

很显然,CTabSiPlusApp::InitInstance()的调用发生在InitFunc函数的调用之前,所以要控制好初始化代码之前的先后关系。CTabSiPlusApp::InitInstance()中布置对资源初始化的代码,而诸如创建文件标签栏窗口,Hook “Source Insight”窗口消息,管理这些消息的代码则可以布置到InitFunc函数中实现。这里需要注意的是由于我们的外挂代码是以动态链接库的形式挂载到“Source Insight”进程中的,所以它没有消息循环,所有的窗口UI系统无法正常工作,解决的办法有两个,一个是在InitFunc函数创建窗口之后人为地添加一个消息循环,关于这一点如何实现可以参考Windows SDK编程的方法;另一个方法就是不要把主要的工作放在InitFunc,而是在InitFunc函数中再创建一个本地线程,把窗口UI这些麻烦的东西放在这个线程中处理,这样就可以利用这个线程的消息循环使窗口UI系统工作起来,这样做还有一个好处,就是InitFunc函数可以立即返回,加载外挂的宿主程序也可以及时得到外挂的加载情况,以便根据情况安排下一次加载动作(就是调用CreateRemoteThread()),同时还可以及时释放在被挂程序中分配的内存。TabSiPlus就是采用的第二种方法,下面就是TabSiPlus的InitFunc函数实现,当然省去了一些代码,主要核心就是一行:

DWORD WINAPI InitFunc()
{
    //其它初始化操作
    g_pTabWndUIThread = (CTabWndUIThread *)AfxBeginThread(RUNTIME_CLASS(CTabWndUIThread),THREAD_PRIORITY_NORMAL,0,0,NULL);
    //其它操作
    
    return (g_pTabWndUIThread != NULL);
}

在CTabWndUIThread类的InitInstance()函数中创建标签栏窗口,hook “Source Insight”中相关窗口的消息:

BOOL CTabWndUIThread::InitInstance()
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());

g_pSiFrameWnd = new CSIFrameWnd(); //CWnd::FromHandle(hDevStudioWnd);
  g_pSiFrameWnd->Attach(hWndSIFrame); //hook SI主窗口

HWND hMDIWnd = g_pSiFrameWnd->GetMDIClientWnd();
    
    //UINT uThressID = GetCurrentThreadId();
  // create the tabs window
  m_pTabbarWnd = new CTabBarsWnd();
  m_pTabbarWnd->Create(CWnd::FromHandle(g_pSiFrameWnd->GetSafeHwnd()), 
        RBS_BANDBORDERS | RBS_AUTOSIZE | RBS_FIXEDORDER | RBS_DBLCLKTOGGLE, 
          WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CBRS_TOP | CBRS_SIZE_FIXED, AFX_IDW_REBAR );

m_pMainWnd = m_pTabbarWnd;//这很重要,否则这个线程就无法退出
    g_pSiFrameWnd->SetTabbarWnd(m_pTabbarWnd->GetSafeHwnd());
    g_MdiChildMng.SetTabbarWnd(m_pTabbarWnd->GetSafeHwnd());

m_pTabbarWnd->SetWindowPos(CWnd::FromHandle(hMDIWnd)->GetWindow(GW_HWNDPREV), 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

g_pSiMDIClientWnd = new CSiMDIWnd();
    g_pSiMDIClientWnd->SetTabbarWnd(m_pTabbarWnd->GetSafeHwnd());
    DebugTracing(gnDbgLevelNormalDebug,_T("MDI Client Attach..."));
    g_pSiMDIClientWnd->Attach(hMDIWnd);
    DebugTracing(gnDbgLevelNormalDebug,_T("MDI Client Enum..."));
    g_pSiMDIClientWnd->EnumMdiChildWnd(g_MdiChildMng,TRUE);
    DebugTracing(gnDbgLevelNormalDebug,_T("MDI Client Enum end (%d)"),g_MdiChildMng.GetChildCount());
    pGlobalActiveSIWindow = g_MdiChildMng.LookupMdiChild(g_pSiMDIClientWnd->MDIGetActive(NULL));
    g_pSiMDIClientWnd->SetManaging(true);

return TRUE;
}

是不是很象标准单文档结构的MFC程序中的CxxxApp::InitInstance()函数?特别是对m_pMainWnd的赋值?对m_pMainWnd赋值其实很重要,否则线程就会直接退出,对m_pMainWnd赋值还有一个好处,就是关闭m_pTabbarWnd窗口就会中止CTabWndUIThread线程,这和标准单文档结构的MFC程序中的结果一样。

CTabWndUIThread::InitInstance()函数中有很多是对“Source Insight”内部窗口进行hook的代码,那么TabSiPlus是如何得到这些窗口的句柄呢,又是如何关联它们之间的消息呢,请看下篇:给Source Insight做个外挂系列之四--分析“Source Insight”

Source Insignt文件标签外挂:TabSiPlus的下载地址:
 http://blog.csdn.net/orbit/