问题描述
有用户反馈,他的电脑在打开TXT文本文档,notepad.exe会报“无法定位序数345于动态链接库comctl32.dll上”;
经过初步了解,当他的电脑在运行公司产品进行管理的时候,就会出现这个错误;关闭公司产品的管理功能,就不会报错了。
分析过程:
1.先定位是什么影响导致出现了现象
经过初步定位,应该是和产品注入了DLL来监控文本文档的操作,引发了用户产生了这条报错信息。
找到了注入相关的代码,是这段了。
BOOL MyCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE pThreadProc, LPVOID pRemoteBuf)
{
HANDLE hThread = NULL;
FARPROC pFunc = NULL;
if (IsVistaOrLater()) // Vista, 7, Server2008
{
pFunc = GetProcAddress(GetModuleHandle("ntdll.dl"), "NtCreateThreadEx");
if (pFunc == NULL)
{
printf("MyCreateRemoteThread() : GetProcAddress(/" NtCreateThreadEx / ") 调用失败!错误代码: [%d]/n",
GetLastError());
return FALSE;
}
((PFNTCREATETHREADEX)pFunc)(&hThread,
0x1FFFFF,
NULL,
hProcess,
pThreadProc,
pRemoteBuf,
FALSE,
NULL,
NULL,
NULL,
NULL);
if (hThread == NULL)
{
printf("MyCreateRemoteThread() : NtCreateThreadEx() 调用失败!错误代码: [%d]/n", GetLastError());
return FALSE;
}
}
else // 2000, XP, Server2003
{
hThread = CreateRemoteThread(hProcess,
NULL,
0,
pThreadProc,
pRemoteBuf,
0,
NULL);
if (hThread == NULL)
{
printf("MyCreateRemoteThread() : CreateRemoteThread() 调用失败!错误代码: [%d]/n", GetLastError());
return FALSE;
}
}
if (WAIT_FAILED == WaitForSingleObject(hThread, INFINITE))
{
printf("MyCreateRemoteThread() : WaitForSingleObject() 调用失败!错误代码: [%d]/n", GetLastError());
return FALSE;
}
return TRUE;
}
经过替换测试分析,发现
当使用从ntdll.dll的导出函数NtCreateThreadEx进行注入的时候,就会出现问题;
当使用CreateRemoteThread的时候就没事;
NtCreateThreadEx是一个微软未公开的函数,CreateRemoteThread是公开的。
那么问题来了,这段为啥要用一个未公开函数NtCreateThreadEx来进行注入呢?
查看了一下这段代码的提交记录,14年的,额……这都过去八年了,谁能记得啊???
2.确定问题代码来源及意图
既然有这个用法,那我们来查查这个用法什么时候开始的。
度娘是2000年成立的,使用度娘的高级搜索,关键词就是NtCreateThreadEx,设置时间从2000年开始一年一年往后搜索
然后在搜索结果里,找了2011年的这个文章
https://blog.csdn.net/wangningyu/article/details/6456607/
看了下这里面的代码段,嗯~~产品里注入这块当时谁写的?这就是从网上抄的呀,基本就没怎么改。
好吧,代码哪里来的目前清楚了,下面问题是用NtCreateThreadEx的目的是什么?
看了看这篇文章,也查了查相关的资料。
文章和资料里说,从vista之后,由于windows加入的session隔离,不同session的进程是无法完成DLL注入操作的。从而导致使用CreateRemoteThread进行注入时,并不是所有的进程都会注入,因此采用使用CreateRemoteThread的底层调用函数NtCreateThreadEx来突破这个限制。
3.了解产品注入机制实现
再看了下产品代码;产品主进程以服务启动,启动时session是0;当需要注入进程时,获取了当前explorer.exe进程的session,并以该session启动了注入进程A.EXE,由A.EXE将监控用DLL注入到notepad.exe中。
解决方案:
两个思路来解决这个问题
思路一、使用WaitForInputIdle
WaitForInputIdle的用法说明:
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-waitforinputidle
保持现有的注入逻辑不动,使用WaitForInputIdle来等待被注入进程的初始化完成,待其初始化完成后,再将我们的DLL进行注入。
WaitForInputIdle之前,需要调用OpenProcess
hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessId );
然后搞个循环,反复WaitForInputIdle就行了。
之所以弄个循环,是为了做一个超时退出的机制;当然如果你说没必要做超时,那就直接WaitForInputIdle就哦了;
思路二、使用不同session去注入
去掉使用NtCreateThreadEx,统一采用CreateRemoteThread的方式来进行注入;
那么如何解决session隔离的问题?我们可以通过获取被注入进程的session来进行对应的注入;
产品主进程是session=0的,那么当被注入进程的session是0时,我们可以采用主进程的session来启动注入进程A.EXE,此时A.EXE就可以成功注入到session是0的进程中了;
如果被注入进程的session不是0,则采用类似现有的逻辑,不使用explorer.exe的session,而是使用被注入进程的session来启动注入进程A.EXE,就可以注入到目标进程中了。
20221102记录
产品最终采用了思路一来解决此问题
不是说思路二不对,是考虑到产品已经平稳运行了四五年了,在维保合同接近尾声的情况下,以稳定为主,因此采用思路一,在原有逻辑上打补丁,而不是贸然更换新方法。