由于要写个检测功能,对于这块进行了下探究。
线程的常规启动有2种:
1、Createthread方式 ,纯win api的方法
2、_beginthreadex方式,这是VC中安全使用线程的方法
下面给出DEMO代码:
#include <Windows.h>
#include <process.h>
unsigned int __stdcall DemoThread1(PVOID p){
while (1)
{
printf("Thread1\r\n");
Sleep(1000);
}
}
unsigned int __stdcall DemoThread2(PVOID p){
while (1)
{
printf("Thread2\r\n");
Sleep(1000);
}
}
unsigned int __stdcall DemoThread3(PVOID p){
while (1)
{
printf("Thread3\r\n");
Sleep(1000);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
//方式1
printf("Thread1 Address = 0x%X\r\n",(DWORD)DemoThread1);
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)DemoThread1,NULL,0,NULL);
//方式2
printf("Thread2 Address = 0x%X\r\n",(DWORD)DemoThread2);
_beginthreadex(NULL,0,DemoThread2,NULL,0,NULL);
printf("Thread3 Address = 0x%X\r\n",(DWORD)DemoThread3);
_beginthreadex(NULL,0,DemoThread3,NULL,0,NULL);
system("pause");
return 0;
}
代码比较简单,有3个线程函数,1使用API的方式启动,2和3使用_beginthreadex方式启动。
代码执行后可以得到:
不难发现,Thread1的线程入口是可以对应上的(因为使用了API直接启动),但是另外2根线程后面显示的模块却是MSVCR80.DLL,并且线程入口也不对,这是因为使用了封装过的线程类启动而导致的,这里我们要把这个真实的入口枚举出来。
通过对_beginthreadex进行逆向分析:
可以看到将真实的线程入口和参数放入结构后,最终还是使用了Createthread但是启动的函数却是 0x6F8B29E1,比对下刚才的线程入口就可以对应上了,接下来分析下这个函数,我们只要知道这个函数从哪里读这个线程参数结构就行了。
这里可以知道参数结构是从 fls_getvalue内获取,翻阅了下这个call内执行。可以确定是跟线程内TLS有关,所以分析后最终得到结果就是
_get_tlsindex获取序号 --->TLS内获取Flsgetvalue(KERNEL32.DLL的导出函数)回调,执行Flsgetvalue后可以获取到参数结构,分析了下Flsgetvalue
可以看到有个 FS:[18]这个是取TEB,所以可以得到 [TEB + 0xFB4] + 4 * TLSINDEX + 8,这里的TLSINDEX就是_get_tlsindex得到的。
结构拿到后 +0x54就是入口,+0x58就是参数。
好了分析完了,总结下:
1、先要获取线程的TEB
2、使用_get_tlsindex获取到TLSINDEX
3、使用[TEB + 0xFB4] + 4 * TLSINDEX + 8
4、得到真实线程入口
ps:作者系统是win7环境,后来切换XP发现不存在 flsgetvalue这个导出函数,所以得知这个导出函数是win7才有的。但是不要紧,xp下这个结构是放在tls的数组内因此第3步在xp下是 TEB + 0xE10 + 4 * TLSINDEX
这里给出一段最终封装的代码,缺少的函数自己封装了,仅供参考:
DWORD GetThreadEntryByThreadId(DWORD dwThreadId)
{
THREAD_BASIC_INFORMATION ThreadBasicInfo = {0};
if (PCHunter::Instance().QueryThreadBaseInfo(dwThreadId,&ThreadBasicInfo)){
if (ThreadBasicInfo.TebBaseAddress != NULL){
DWORD *pTlsSlots = (DWORD*)((DWORD)ThreadBasicInfo.TebBaseAddress + TEB_TLSSLOTS);
PVOID pEntry = PCHunter::Instance().QueryThreadEntryByThreadId(dwThreadId);
if (pEntry != NULL){
char szModuleName[MAX_PATH] = {0};
GetModuleNameByMemoryAddress((DWORD)pEntry,szModuleName);
//判断是不是库模块;
if (StrStrI(szModuleName,"msvcr")){
HMODULE hModule = GetModuleHandle(szModuleName);
if (hModule != NULL){
FGetTlsIndex pGetTlsIndex = (FGetTlsIndex)GetProcAddress(hModule,"__get_tlsindex");
if (pGetTlsIndex!=NULL){
DWORD dwIdx = pGetTlsIndex();
//AdminPrintf("TlsIndex:%d\r\n",pGetTlsIndex());
DWORD dwAddress = 0;
DWORD SafeThunkCall = 0;
//区分下是否存在 FlsGetValue 函数;
bool IsNewKernel32 = false;
HMODULE hKer = GetModuleHandle("kernel32.dll");
if (hKer != NULL){
if (GetProcAddress(hKer,"FlsGetValue") != NULL){
IsNewKernel32 = true;
}
}
if (IsNewKernel32){
SafeThunkCall = *(DWORD*)((DWORD)ThreadBasicInfo.TebBaseAddress + TEB_SAFE_THUNK_CALL);
dwAddress = *(DWORD*)(SafeThunkCall + dwIdx * 4 + 8);
}else{
SafeThunkCall = (DWORD)ThreadBasicInfo.TebBaseAddress + TEB_TLSSLOTS;
dwAddress = *(DWORD*)(SafeThunkCall + dwIdx * 4);
}
if ((dwAddress != 0) && (dwAddress != 0xFFFFFFFF)) {
pEntry = (PVOID)*(DWORD*)(dwAddress + 0x54);
if (pEntry!=NULL){
GetModuleNameByMemoryAddress((DWORD)pEntry,szModuleName);
}
}
}
}
}
return (DWORD)pEntry;
}
}
}
return 0;
}
这里说明1个TEB的知识
TEB +0xE10 是线程 TLS的地方,TLS是个32位指针数组长度为 64
TEB +0xFB4 是线程 SafeThunkCall 类型为UCHAR