轉(zhuǎn)載自:
http://bbs.pediy.com/showthread.php?p=442958
TLS簡(jiǎn)介
1. 什么是TLS?
TLS是Thread Local Storage(線程局部存儲(chǔ))的簡(jiǎn)稱,是一項(xiàng)解決多線程內(nèi)部變量使用問(wèn)題的技術(shù)。用于將某些數(shù)據(jù)和一特定線程關(guān)聯(lián)起來(lái),即,這些數(shù)據(jù)為關(guān)聯(lián)線程所獨(dú)有(私有)。在多線程編程中, 同一個(gè)變量, 如果要讓多個(gè)線程共享訪問(wèn), 那么這個(gè)變量可以使用關(guān)鍵字volatile進(jìn)行聲明; 而如果一個(gè)變量不想被多個(gè)線程共享訪問(wèn), 那么就應(yīng)該使用TLS。
2. 如何使用TLS編程?
TLS使用非常簡(jiǎn)單, 只要對(duì)變量聲明時(shí)使用__declspec(thread)修飾就可以了。例如:
_declspec(thread) int g_nData = 0;
3. 一個(gè)使用TLS的例子
//--------------------------------------------------------------------------------------------------------
#include <windows.h>
#include <stdio.h>
#define THREADCOUNT 4
DWORD dwTlsIndex;
int iNUM_OF_CALL_COMMON=0;
int iNUM_OF_CALL_THREAD=0;
VOID ErrorExit(LPSTR);
VOID CommonFunc(VOID)


{
LPVOID lpvData;
// Retrieve a data pointer for the current thread.
iNUM_OF_CALL_COMMON++;
lpvData = TlsGetValue(dwTlsIndex);
if ((lpvData == 0) && (GetLastError() != ERROR_SUCCESS))
ErrorExit("TlsGetValue error");
// Use the data stored for the current thread.
printf("common: thread %d: lpvData=%lx\n",
GetCurrentThreadId(), lpvData);
Sleep(5000);
}
DWORD WINAPI ThreadFunc(VOID)


{
LPVOID lpvData;
// Initialize the TLS index for this thread.
iNUM_OF_CALL_THREAD++;
lpvData = (LPVOID) LocalAlloc(LPTR, 256);
if (! TlsSetValue(dwTlsIndex, lpvData))
ErrorExit("TlsSetValue error");
printf("thread %d: lpvData=%lx\n", GetCurrentThreadId(), lpvData);
CommonFunc();
// Release the dynamic memory before the thread returns.
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != 0)
LocalFree((HLOCAL) lpvData);
return 0;
}
int main(VOID)


{
DWORD IDThread;
HANDLE hThread[THREADCOUNT];
int i;
// Allocate a TLS index.
if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)
ErrorExit("TlsAlloc failed");
// Create multiple threads.
for (i = 0; i < THREADCOUNT; i++)

{
hThread[i] = CreateThread(NULL, // default security attributes
0, // use default stack size
(LPTHREAD_START_ROUTINE) ThreadFunc, // thread function
NULL, // no thread function argument
0, // use default creation flags
&IDThread); // returns thread identifier
// Check the return value for success.
if (hThread[i] == NULL)
ErrorExit("CreateThread error\n");
}
// printf("All threads were created.\n");
for (i = 0; i < THREADCOUNT; i++)
WaitForSingleObject(hThread[i], INFINITE);
TlsFree(dwTlsIndex);
printf("The nums of thread is: %d\n",iNUM_OF_CALL_THREAD);
printf("The nums of call is: %d\n",iNUM_OF_CALL_COMMON);
return 0;
}
VOID ErrorExit (LPSTR lpszMessage)


{
fprintf(stderr, "%s\n", lpszMessage);
ExitProcess(0);
}
//--------------------------------------------------------------------------------------------------------

4. T L S的內(nèi)部數(shù)據(jù)結(jié)構(gòu)
1.jpg
圖1 用于管理T L S的內(nèi)部數(shù)據(jù)結(jié)構(gòu)
每個(gè)標(biāo)志均可設(shè)置為FREE或者INUSE,表示TLS槽( s l o t )是否正在使用。Microsoft保證至少TLS_MINIMUM_AVAILABLE位標(biāo)志是可供使用的。
5. 相關(guān)API
Windows TLS的API: TlsAlloc, TlsFree, TlsSetValue, TlsGetValue。
● DWORD TlsAlloc(); //(若要使用動(dòng)態(tài)T L S,首先必須調(diào)用TlsAlloc函數(shù))
這個(gè)函數(shù)命令系統(tǒng)對(duì)進(jìn)程中的位標(biāo)志進(jìn)行掃描,并找出一個(gè)FREE標(biāo)志。然后系統(tǒng)將該標(biāo)志從FREE改為INUSE,并且TlsAlloc返回位數(shù)組中的標(biāo)志的索引。DLL(或APP)通常將該索引保存在一個(gè)全局變量中,因?yàn)樗闹凳敲總€(gè)進(jìn)程而不是每個(gè)線程使用的值。
● BOOL TlsSetValue( //將一個(gè)值放入線程的數(shù)組中
DWORD dwTlsIndex,
PVOID pvTlsValue);
● PVOID TlsGetValue(DWORD dwTlsIndex); //要從線程的數(shù)組中檢索一個(gè)值
● BOOL TlsFree(DWORD dwTlsIndex); //當(dāng)在所有線程中不再需要保留TLS槽時(shí)
參考資料:Jeffrey Richter《《Programming Applications for Microsoft Windows (4th Ed.)》》Chapter 21
6. TLS目錄
7.JPGTLS Callback Functions這是線程建立和退出時(shí)的回調(diào)函數(shù), 包括主線程和其他線程.AddressOfCallBacks 是指向函數(shù)指針數(shù)組的指針, 以 0 結(jié)束.

typedef struct _TEB
{
NT_TIB Tib;
PVOID EnvironmentPointer;
CLIENT_ID Cid;
PVOID ActiveRpcInfo;
PVOID ThreadLocalStoragePointer; ; 2ch
PPEB Peb; ; 30h
ULONG LastErrorValue; ; 34h
…}

TLS目錄 #define IMAGE_DIRECTORY_ENTRY_TLS 9 (第十個(gè)目錄)
Tls隱藏的入口點(diǎn)利用
就是利用Address of Callbacks字段,寫入要執(zhí)行的代碼的地址就可以了. 測(cè)試對(duì)象:test.exe
程序類型:delphi編寫
PeEditor檢測(cè)Tls信息(文件偏移59400H處)如下:
2.jpg TLS信息
------------------------------------------
目錄表: 0045D000
TLS數(shù)據(jù): 0045D010
索引變量: 004570A0
調(diào)用返回地址: 0045E010 (調(diào)用返回地址)
------------------------------------------
我們打算讓系統(tǒng)在0045E010處執(zhí)行我們的代碼,就需要在這個(gè)地方對(duì)應(yīng)的文件偏移處寫入我們的代碼。
觀察節(jié)表信息
VA=0045E010,按照文件不需要重定位去計(jì)算則RVA=0005E010
觀察節(jié)表信息,這個(gè)值處于.rdata節(jié)內(nèi)。
.rdata節(jié)起始RVA=0005E000,起始Offset為00059400
則callback地址對(duì)應(yīng)的Offset=(0005E010 - 0005E000)+ 00059400 =00059410
注意在這里(00059410H)應(yīng)該寫入一個(gè)函數(shù)地址。
打造一個(gè)簡(jiǎn)單的代碼:
;----------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
.code
start:
jmp @F
db 'Run thru Tls entry point.',0
@@:
mov eax,$
sub eax,26
call @delta
@delta:
pop ebp
sub ebp,offset @delta
add eax,ebp
invoke MessageBox,NULL,eax,NULL,0
ret
end start
;----------------------------------------------------------------

編譯生成可執(zhí)行文件,這只是一個(gè)簡(jiǎn)單的彈出對(duì)話框的程序。
3.jpg 打開(kāi)16進(jìn)制工具,載入msgbox.exe,復(fù)制代碼:
4.jpg 注意00402000H處存放的是MessageBox的入口地址,我把它固化下來(lái),動(dòng)態(tài)跟蹤發(fā)現(xiàn)是8A05D577H。(當(dāng)然在編程時(shí)可以動(dòng)態(tài)搜尋我們需要的API,為簡(jiǎn)單起見(jiàn)這里Hard-code一下。)
打開(kāi)16進(jìn)制工具,載入藝術(shù)字體2.exe,定位到00059410。
5.jpg 我們把代碼復(fù)制在偏移00059410處,對(duì)應(yīng)的VA=0045E030H,則把0045E030存入偏移00059410。
還要注意最后的 MessageBox的入口地址我們使用硬編碼的方式,我們把這個(gè)地址寫入在00059480處了。保存修改,然后運(yùn)行程序。程序首先彈出兩個(gè)對(duì)話框(一個(gè)是TLS模板,一個(gè)是主線程創(chuàng)建),結(jié)束程序運(yùn)行還會(huì)彈出一個(gè)對(duì)話框,不過(guò)這個(gè)對(duì)話框太丑了:
6.jpg 說(shuō)明:我在看一本介紹病毒知識(shí)的書籍時(shí)看到作者提及到這么一句話,大概意思是說(shuō)他們公司某人發(fā)現(xiàn)了這個(gè)秘密,一直沒(méi)有公布,不過(guò)后來(lái)由于rgb利用此技術(shù)制造了病毒,也就無(wú)所謂秘密可言了。由于Tls入口要比OEP先執(zhí)行,所以在加殼與脫殼中都有利用的價(jià)值。