程序內執行緒入口的探究
由於要寫個檢測功能,對於這塊進行了下探究。
執行緒的常規啟動有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