对于可扩充架构的软件,都存在着很多的插件。这样相对于主模块,插件的工作就是不安全的。为了防止插件的不安全性影响到主模块甚至全局,很多框架都采取了好的方式隔离插件的执行,如.NET中的执行域(AppDomain)。对于那些采用Exe/DLL架构的原始代码,DLL的运行相对于EXE来说就是不安全性的。为了最大限度的隔离DLL的运行,防止其对宿主EXE造成影响。需要做以下几点:
首先需要在主模块中采用结构化异常处理防止EXE对DLL导出函数的调用出现异常。这类问题一般比较容易解决。
其次,主模块和插件之间的通信必须有所限制——主模块和插件都不应该假定对方和自己存在于一个地址空间,也就是说不直接使用对方的地址。
最后,主模块需要安装未处理异常过滤器,对于常规错误,定位错误的位置,然后采取必要的修复措施,甚至终结主模块。
第二点的目的是为了让第三点能够完好的工作。当未处理异常过滤器获取到异常时,首先定位异常的所在模块,然后检测哪些线程正位于此模块,接着终结所有位于此模块的线程,最后卸载此模块(也许会重新载入)。由于第一点已经解决了边界处的异常,第二点又屏蔽了非边界的模块调用,这样一个线程只要此时位于此模块,就肯定是此模块中的代码创建的;同样,如果一个线程当前不再此模块中,也不可能通过非边界进入此模块,通过边界进入的第一点也会保证其一致性。因此终结了这些线程以后,就可以安全的卸载此模块。
下面说说完成这些工作的核心点——获取指定进程所有线程的当前执行模块。首先可以通过CreateToolhelp32Snapshot获取线程快照,然后根据线程信息确定其是否属于指定线程。获取到线程后,可以通过GetThreadContext获取线程当前的上下文,根据上下文中的Eip就可以获取当前调用帧的代码地址。使用VirtualQuery可以获取此地址所述的模块句柄,然后GetModuleFileName就可以获取到此模块的名称了。这里还有一点需要说明,当你获取模块名时,返回的可能是系统模块的模块名(大部分情况下都是的),因而需要根据栈帧信息回溯到上一个函数地址,一直到函数地址位于非系统模块中。函数调用过程中,EBP保存了当前栈帧的基址,并且被压入下一栈帧的首地址,因而利用EBP就可以遍历整个函数调用,而函数调用地址就保存在上一栈帧的末地址上。
下面是一个获取当前进程所有线程的当前执行模块信息的函数:
VOID GetThreadMsg(CHAR lpszBuffer[], UINT32 iBufferSize)
{
CHAR lpszTemp[MAX_PATH] = {0};
DWORD dwProcessId = GetCurrentProcessId();
CHAR lpszCurDirectory[MAX_PATH] = "C://Windows";
UINT32 iCurDirectoryLen = (UINT32)(INT64)strlen(lpszCurDirectory)-1;
sprintf_s(lpszBuffer, iBufferSize, "当前进程: %d, 线程信息如下:", dwProcessId);
HANDLE hThreadSnap;
THREADENTRY32 th32;
if ((hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessId)) != NULL
&& hThreadSnap != INVALID_HANDLE_VALUE)
{
th32.dwSize = sizeof(THREADENTRY32);
if (Thread32First(hThreadSnap, &th32))
{
do
{
if (th32.th32OwnerProcessID == dwProcessId && th32.th32ThreadID != GetCurrentThreadId())
{
sprintf_s(lpszTemp, MAX_PATH, "/r/nId: %8x", th32.th32ThreadID);
strcat_s(lpszBuffer, iBufferSize, lpszTemp);
HANDLE hThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_TERMINATE
, FALSE, th32.th32ThreadID);
CONTEXT context = {0};
context.ContextFlags = CONTEXT_CONTROL;
if(hThread != NULL && hThread != INVALID_HANDLE_VALUE)
{
SuspendThread(hThread);
BOOL bOK = GetThreadContext(hThread, &context);
ResumeThread(hThread);
if(bOK)
{
DWORD dwAddr = context.Eip;
DWORD dwEbp = context.Ebp;
do
{
MEMORY_BASIC_INFORMATION mbi = {0};
if(VirtualQuery((LPCVOID)(INT64)dwAddr, &mbi, sizeof(mbi)))
{
UINT_PTR h_module = (UINT_PTR)mbi.AllocationBase;
GetModuleFileNameA((HMODULE)h_module, lpszTemp, MAX_PATH);
if(_strnicmp(lpszTemp, lpszCurDirectory, iCurDirectoryLen))
{
strcat_s(lpszBuffer, iBufferSize, " CurFrameBase: ");
sprintf_s(lpszBuffer+strlen(lpszBuffer), iBufferSize-strlen(lpszBuffer), "%8x", dwAddr);
strcat_s(lpszBuffer, iBufferSize, " CurModuleName: ");
strcat_s(lpszBuffer, iBufferSize, lpszTemp);
break;
}
}
if((dwEbp = *(PDWORD)(INT64)dwEbp) == 0)
{
break;
}
dwAddr = ((PDWORD)(INT64)dwEbp)[1];
} while (TRUE);
}
CloseHandle(hThread);
}
}
} while(Thread32Next(hThreadSnap, &th32));
}
CloseHandle(hThreadSnap);
}
}