TLS學習總結

咱們有知道php

Immunity Debugger,OD

調試器,在調試程序時會設斷在OEP(修改第一個字節0xcc)。我在想,使用什麼編程技術,代碼能夠在OEP前被執行。在網上找了些資料,在論壇上看到許多大牛,使用靜態TLS作了好多有趣的事,今天本身也來終結下,,呵呵

1. 什麼是TLS?

TLS是Thread Local Storage(線程局部存儲)的簡稱,是一項解決多線程內部變量使用問題的技術。用於將某些數據和一特定線程關聯起來,即,這些數據爲關聯線程所獨有 (私有)。在多線程編程中, 同一個變量, 若是要讓多個線程共享訪問, 那麼這個變量可使用關鍵字volatile進行聲明; 而若是一個變量不想被多個線程共享訪問, 那麼就應該使用TLS。

2. 如何使用TLS編程?

TLS使用很是簡單, 只要對變量聲明時使用__declspec(thread)修飾就能夠了。例如:

__declspec(thread) int g_nData = 0;


3.多線程中使用TLS的例子(動態)
相關API

Windows TLS的API: TlsAlloc, TlsFree, TlsSetValue, TlsGetValue。(動態TLS)

● DWORD TlsAlloc(); //(若要使用動態T L S,首先必須調用TlsAlloc函數)

這個函數命令系統對進程中的位標誌進行掃描,並找出一個FREE標誌。而後系統將該標誌從FREE改成INUSE,而且TlsAlloc返回位數組中的標誌的索引。DLL(或APP)一般將該索引保存在一個全局變量中,由於它的值是每一個進程而不是每一個線程使用的值。

● BOOL TlsSetValue( //將一個值放入線程的數組中

DWORD dwTlsIndex,

PVOID pvTlsValue);

● PVOID TlsGetValue(DWORD dwTlsIndex); //要從線程的數組中檢索一個值

● BOOL TlsFree(DWORD dwTlsIndex); //當在全部線程中再也不須要保留TLS槽時



例子:
#include <windows.h>
#include "stdio.h"

#define ThreadCound 4 //建立線程個數
DWORD dwTlsIndex;

//全局變量
int iNUM_OF_CALL_COMMON=0;   
int iNUM_OF_CALL_THREAD=0;


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))
                exit(0);
       
        // 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))
                exit(0);
       
        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;
}


void main(void)
{
        DWORD dwThreadId;
        HANDLE hThread[ThreadCound];
        int i;
        //Allocate a TLS index
        if ((dwTlsIndex =TlsAlloc()) == TLS_OUT_OF_INDEXES)
        {
                exit(0);
        }
       
        for (i = 0; i<ThreadCound; i++)
        {
                hThread[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc, NULL, 0, &dwThreadId);
                if(hThread == NULL)
                {
                        exit(0);
                }
        }
        for (i = 0; i < ThreadCound; i++)
        {
                WaitForSingleObject(hThread, 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);

}


以上是動態TLS在多線程中的使用.
而利用靜態線程局部存儲能夠玩弄調試器甚至能夠感染一些pe.
靜態線程局部存儲是在Windows的PE/COFF可執行文件格式中所支持的靜態線程局部存儲。即在PE頭中數據目錄表中的第10項,這一項指向的是Tls目錄,Tls目錄的結構以下:

IMAGE_TLS_DIRECTORY32  STRUCTStartAddressOfRawData  dd  ?EndAddressOfRawData  dd  ?AddressOfIndex    dd  ?AddressOfCallBacks    dd  ?SizeOfZeroFill    dd  ?Characteristics    dd  ?IMAGE_TLS_DIRECTORY32  ENDS比較重要的是該結構的第四項,是指向Tls CallBack的數組,該數組裏面存放了一個或多個TLS回調函數.而TLS回調函數要執行大概經歷如下三個步驟:1.程序在連接時,鏈接器要在PE文 件的目錄表建立TLS目錄2.在建立線程時,PE加載器會從TEB(當前線程環境塊,經過FS寄存器獲取)中獲取指向TLS回調函數數值的指針(TEB偏 移2Ch).3.查看TLS 回調函數數組是否爲空,不爲空時加載器會依次執行回調函數。
靜態TLS代碼;
#include "stdio.h"#include <windows.h>//告訴編譯器在PE文件中要建立TLS目錄#pragma comment(linker, "/INCLUDE:__tls_used")//TLS_CallBack()函數,第二個參數決定了函數在那種狀況下(DllMain函數同樣) /*        DLL_PROCESS_ATTACH:是指新進程建立時,初始化主線程時執行        DLL_PROCESS_DETACH: 是指在進程終止時執行        DLL_THREAD_ATTACH:  是指建立新線程時,可是不包括主線程        DLL_THREAD_DETACH:  是指在全部線程終止時執行,可是一樣不包括主線程*/void go_anit(){        ExitProcess(0);}void __stdcall my_tls_callback(PVOID h, DWORD reason, PVOID pv){        //僅在進程初始化建立主線程執行        if (reason == DLL_PROCESS_ATTACH)        {                //檢查OEP                IMAGE_DOS_HEADER *DosHeader = (IMAGE_DOS_HEADER*)GetModuleHandle(NULL);                IMAGE_NT_HEADERS *NtHeader = (IMAGE_NT_HEADERS*)(((DWORD)DosHeader)+ (DWORD)DosHeader->e_lfanew);                BYTE *ope = (PBYTE)(NtHeader->OptionalHeader.AddressOfEntryPoint + (DWORD)DosHeader);                if (*ope == 0xcc)                {                        go_anit();                }        }}/*        下面建立一個TLS段        ".CRT$XLB"的含義是:".CRT"代表是使用C RunTime機制 $後面的XLB中,        X表示隨機的標識        L表示TLS Callback section        B能夠被換成B到Y的任意一個字母,.        $是給鏈接器的。*/#pragma data_seg(".CRT$XLB")//定義多個TLS_CallBack PIMAGE_TLS_CALLBACK p_Tls_CallBack[] = {my_Tls_CallBack1, my_Tls_CallBack2,my_Tls_CallBack3, 0};PIMAGE_TLS_CALLBACK p_Tls_CallBack = my_tls_callback;#pragma data_seg()void main(){        printf("Hello TLS\n");        getchar();}
這種方法就能真真的玩弄調試器嗎?顯然不是的,首先咱們來看下建立一個進程的過程1.打開將要在該進程中執行的映像文件。
首先操做系統找到執行的Windows映像而後建立一個內存區對象,以便後面將它映射到新的進程地址空間中。
2.建立Windows執行體進程對象。
接下來操做系統調用內部的系統函數NtCreateProcess來建立一個Windwos執行體進程對象。具體步驟是:
(1)創建EPROCESS
(2)建立初始的進程地址空間(3)初始化內核進程塊KPROCESS
(4)結束進程地址空間的建立過程
(5)創建PEB
(6)完成執行體進程對象的建立過程
3.建立初始線程(棧、堆執行環境初始化及執行線程體對象)。
這時候Windows執行體進程對象已經徹底創建完成,但它尚未線程因此沒法執行,接下來系統調用NtCreateThread來建立一個掛起的新線程它就是進程的主線程體。

4.通知Windows子系統新進程建立了(子系統是操做系統的一部分它是一個協助操做系統內核管理用戶態/客戶方的一個子系統具體的進程爲
Csrss.exe)。
接下來操做系統經過客戶態(Kernel32.dll)給Windows子系統(Csrss)發送一個新進程線程建立的數據消息,讓子系統創建本身的進程線程管理塊。當
Csrss接收到該消息時候執行下面的處理:
*複製一份該進程和線程句柄
*設置進程優先級
*分配Csrss進程塊
*把新進程的異常處理端口綁定到Csrss中,這樣當該進程發生異常時,Csrss將會接收到異常消息
*分配和初始化Csrss線程塊
*把線程插入到進程的線程列表中
*把進程插入到Csrss的線程列表中
*顯示進程啓動光標
5.開始執行初始線程(若是建立時候指定了線程的CREATE_SUSPENDED狀態則線程暫時掛起不執行)。
到這裏進程環境已經創建完畢進程中開始建立的主線程到這裏得到執行權開始執行線程
6.在新進程和線程環境中完成地址空間的初始化(好比加載必須的DLL和庫),而後開始到進程入口執行。
到這步實質是調用ldrInitializeThunk來初始化加載器,堆管理器NLS表TLS數組以及臨界區結構,而且加載任何須需要的DLL而且用
DLL_PROCESS_ATTACH功能代碼來調用各DLL入口點,最後當加載器初始化例程返回到用戶模式APC分發器時進程映像開始在用戶模式下執行,而後它調用線程啓動函數開始執行。

操做系統會拋出一個CreateProcessEvent事件,早於Tls_CallBack函數,同時ExceptionEvent事件也是早於 Tls_callback的因此咱們能夠設置調試器第一次斷點「系統斷點」 本實驗我用的Immunity Debugger調試器, OD類似選項->調試選項->事件->設置第一次暫停於 「系統斷點」  單步跟中會發現      對當前進程信息設置
編程

file:///C:/Users/asus/AppData/Local/youdao/ynote/images /C357E1C9D1184437819E2C6A7919ADB8/clipboard.png到此跟進,,,查了好多資料也不知道這個函數是啥 子,,求大牛指導 windows

file:///C:/Users/asus/AppData/Local/youdao/ynote/images /DCE533091FD64FE48C52145B1484E6F6/clipboard.png以後見call跟進,,,到ZwContiune函 數, 繼續執行線程,他的一個參數是CONTEXT指針,找到Context->EIP 數組

file:///C:/Users/asus/AppData/Local/youdao/ynote/images/2D7F6F8AC69F46B293E3ED5CC47F167E/clipboard.png出現OPE地址 多線程

file:///C:/Users/asus/AppData/Local/youdao/ynote/images/000F6F5D807640FB92E5BFC4E562AF28/clipboard.png
跟進call eax
函數

file:///C:/Users/asus/AppData/Local/youdao/ynote/images/98213D4D5ACD48A3A6C4BF957917E3B1/clipboard.pngcall edx 則進入OEP this

file:///C:/Users/asus/AppData/Local/youdao/ynote/images/6A1D98036EB64140BAEF6A1F7B67F792/clipboard.png
spa

file:///C:/Users/asus/AppData/Local/youdao/ynote/images /05EAE8B3357D45CCA440A3082C120B11/clipboard.png這個過程真的還很模糊,不少疑問,不知道進入OEP之 前的那些函數具體的幹了啥,,,,求指導


操作系統

7.png (16.36 KB, 下載次數: 2) 線程

 

7.png

8.png (10.68 KB, 下載次數: 1)

 

8.png
相關文章
相關標籤/搜索