1. 程式人生 > >一種基於TLS的高級反調試技術

一種基於TLS的高級反調試技術

use 工作 main 時間 nal [] ack array 及其

盜版行為日益猖獗,嚴重影響到軟件開發者和開發商的知識產權及利益,反盜版技術的重要性也越來越引起人們的重視。在反盜版技術中,起最大作用的當屬反調試技術。然而傳統的反調試技術都存在一個弱點:他們都在程序真正開始執行之後才采取反調試手段。實際上在反調試代碼被執行前,調試器有大量的時間來影響程序的執行,甚至可以在程序入口處插入斷點命令來調試程序。對於使用C/C++語言編譯的程序來說,問題通常會更嚴重,在執行到main()函數之前,會執行C/C++編譯器插入的很大一段代碼,這也給調試器帶來影響程序執行的機會。

本文討論一種利用Windows提供的線程訪問互斥機制,來實現一種在程序入口之前就執行反調試代碼的技術。技術本身不會影響程序的執行,但能有效地防止調試器的調試。

1 TLS技術簡介

TLS全稱為Thread Local Storage,是Windows為解決一個進程中多個線程同時訪問全局變量而提供的機制。TLS可以簡單地由操作系統代為完成整個互斥過程,也可以由用戶自己編寫控制信號量的函數。當進程中的線程訪問預先制定的內存空間時,操作系統會調用系統默認的或用戶自定義的信號量函數,保證數據的完整性與正確性[1]

1.1 TLS回調函數

當用戶選擇使用自己編寫的信號量函數時,在應用程序初始化階段,系統將要調用一個由用戶編寫的初始化函數以完成信號量的初始化以及其他的一些初始化工作。此調用必須在程序真正開始執行到入口點之前就完成,以保證程序執行的正確性。

TLS回調函數具有如下的函數原型:

void NTAPI TlsCallBackFunction(PVOID Handle, DWORD Reason, PVOID Reserve);

1.2 TLS的數據結構

Windows的可執行文件為PE格式,在PE格式中,專門為TLS數據開辟了一段空間,具體位置為IMAGE_NT_HEADERS.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]。其中DataDirectory的元素具有如下結構:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, 
*PIMAGE_DATA_DIRECTORY;

對於TLS的DataDirectory元素,VirtualAddress成員指向一個結構體,結構體中定義了訪問需要互斥的內存地址、TLS回調函數地址以及其他一些信息[2]

2 具體實現及原理

充分利用TLS回調函數在程序入口點之前就能獲得程序控制權的特性,在TLS回調函數中進行反調試操作比傳統的反調試技術有更好的效果。

2.1 在程序中使用TLS

Microsoft提供的VC編譯器都支持直接在程序中使用TLS,下文都將使用VC進行操作。

要在程序中使用TLS,必須為TLS數據單獨建一個數據段,用相關數據填充此段,並通知鏈接器為TLS數據在PE文件頭中添加數據。為此,需要在程序源文件中添加如下代碼[3]

#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma data_seg(".CRT$XLB")
       PIMAGE_TLS_CALLBACK TlsCallBackArray[] = {
TlsCallBackFunction1,
TlsCallBackFunction2,
......
NULL
};
#pragma data_seg()

其中TlsCallBackArray數組中保存了所有的TLS回調函數指針。值得指出的是,數組必須以NULL指針結束,且數組中的每一個回調函數在程序初始化時都會被調用,程序員可按需要添加。但程序員不應當假設操作系統已何種順序調用回調函數。如此則要求在TLS回調函數中進行反調試操作需要一定的獨立性。

2.2 回調函數的具體實現

2.2.1 檢測調試器在程序入口插入的斷點

PE可執行文件在初始化階段,PE文件都會被完整地加載進入內存。通過分析PE文件頭來獲取程序的入口點,並對入口點的前一個或多個字節進行判斷,以阻止調試器在程序入口點下斷。在下面代碼中,使用GetModuleHandle(NULL)來獲得應用程序的加載基址;也可以模擬GetModuleHandle()手動讀取程序的PEB來得到。但為了程序的通用性,這裏仍然使用GetModuleHandle()。獲得程序加載基址後,將其強制類轉換為相關指針,並進行計算,最終得到程序的入口點在內存中的具體地址[3]

IMAGE_DOS_HEADER *dos_head=(IMAGE_DOS_HEADER *)GetModuleHandle(NULL);
PIMAGE_NT_HEADERS32 nt_head=(PIMAGE_NT_HEADERS32)((DWORD)dos_head+(DWORD)dos_head->e_lfanew);
BYTE*OEP=(BYTE*)(nt_head->OptionalHeader.AddressOfEntryPoint+(DWORD)dos_head);
下面的代碼則通過掃描程序入口點的20字節,判斷其中有無調試斷點,如有,則退出進程。
for(unsigned long index=0;index<20;index++){
If(OEP[index]==0xcc){
ExitProcess(0);
}
}

需要指出的是,在TLS回調函數執行時,VC運行庫msvcrt.dll,mfc.dll等並未載入,不能使用C庫的函數。如果有需要使用,應該使用LoadLibrary()函數載入相應的庫並使用GetProcAddress()獲得函數地址。但此類操作可能會導致調試器的相關事件觸發,不建議進行此類操作。

2.2.2 使調試器窗口無效

此處使用FindWindow()查找指定的窗口,並使用SetWindowsLong()將其超類化為不可用,具體代碼見下:

HWND hd_od=FindWindow("ollydbg",NULL);

SetWindowLong(hd_od,GWL_STYLE,WS_DISABLED);

.........處理其他類型的調試器

這裏需要說明的是,此類方法僅僅對ring3調試器才起作用。如SoftICE,WinDebug之類的內核調試器,因為根本不存在窗口,此種方法對其無效。

2.2.3 為程序執行所必需的元素進行初始化或分配空間

這個操作的目的是為了防止盜版者通過直接將TLS數據清除的方法來避開反調試。當程序正常運行所需要的內存空間或數據必需經過TLS回調函數初始化時,盜版者不可以將程序的TLS數據清除。因為那樣做帶來的後果是程序運行根本不正常。

另外,如果這種初始化或分配空間的操作分散在各個TLS回調函數中完成,效果會更好。

2.2.4 堵塞輸入

此功能主要是為了應對一些未知調試器和一些不在程序入口點下斷的調試器。

函數將首先檢查user32.dll導出的BlockInput()函數,如果函數代碼是被調試器修改過的,那麽將直接退出,代碼如下[4]

BYTE *address=(BYTE *)GetProcAddress(LoadLibrary("user32.dll"),"BlockInput");;
bool modify=true;
for(int x=0;x<20;x++){
if(address[x]==0xff&&address[x+1]!=0xff){
              modify=false;
              break;
}
if(modify) ExitProcess(0);

檢查過BlockInput()函數正確性之後,函數將調用BlockInput(TRUE),阻塞用戶的鼠標和鍵盤輸入。但函數接下來並不立即取消阻塞,而在main()函數中發起一個異常後再取消阻塞。

這麽處理的理由很充分,在main()函數中發起異常將導致調試器捕獲異常,並暫停等待用戶輸入。而此時用戶輸入是被鎖定的,那麽程序就相當於被變相鎖死了,沒有辦法繼續調試。而當調試器不存在時,代碼中的__except()部分將直接獲得執行權,並取消阻塞,程序正常運行。main函數中的具體代碼如下:

__try{
       __asm{
              xor eax,eax
             div eax,eax
            xor eax,eax
       }
      ExitProcess(0);
}
__except(1,1){
       BlockInput(FALSE);
}    

值得說明的是,此次在main()函數中調用BlockInput()而不檢查也是有原因的。如果此時的BlockInput()函數被調試器修改過,那麽取消輸入鎖定將完全不能工作,那麽之後的整個過程也是無法調試的。

如果進一步深入,還可以測試異常返回所用的時間,不論過長或者過短,都能夠說明調試器的存在。此處不繼續展開了。

2.2.5 創建監視線程

此步目的是為了防止OllyDbg等調試器以進程附加的形式對程序進行調試。程序此步將創建一個子線程,監視調試器窗口的出現,如果發現調試器窗口,將其超類化為不可用。子線程的代碼如下:

DWORD WINAPI Monitor(LPVOID s){

while(sign==TRUE){

HWND hd=FindWindow("ollydbg",NULL);

SetWindowLong(hd,GWL_STYLE,WS_DISABLED);

.......處理其它調試器窗口

Sleep(50);

}

return 0;

}

3 實際測試

3.1 測試直接執行

測試方法為直接在資源管理器中雙擊執行。

技術分享圖片

3.2 測試用調試器加載

測試方法為分別使用VC自帶的調試器加載調試和使用OllyDebug加載生成的程序進行調試。其中OllyDbg使用從看雪論壇下載的OllyDebug 1.10 CHS,並打開了所有的隱藏插件。

測試結果:兩者調試的進程都直接退出,且沒有輸出任何信息。

技術分享圖片

圖2 使用VC自帶調試器調試,程序直接退出

技術分享圖片

圖3 使用OllyDebug 1.10,打開全部反反調試插件調試,程序直接退出

3.3 測試用調試器附加進程

測試方法為先在資源管理器中雙擊程序運行,然後打開OllyDebug試圖附加進程。

測試結果:OllyDebug窗口直接失效。

技術分享圖片

4 總 結

通過使用TLS技術作為反調試技術的載體,可以大大增強程序軟件的反盜版能力。如果能結合傳統技術,將使反調試技術發展至一新高度。

一種基於TLS的高級反調試技術