一種基於TLS的高階反除錯技術
盜版行為日益猖獗,嚴重影響到軟體開發者和開發商的智慧財產權及利益,反盜版技術的重要性也越來越引起人們的重視。在反盜版技術中,起最大作用的當屬反除錯技術。然而傳統的反除錯技術都存在一個弱點:他們都在程式真正開始執行之後才採取反除錯手段。實際上在反除錯程式碼被執行前,偵錯程式有大量的時間來影響程式的執行,甚至可以在程式入口處插入斷點命令來除錯程式。對於使用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技術作為反除錯技術的載體,可以大大增強程式軟體的反盜版能力。如果能結合傳統技術,將使反除錯技術發展至一新高度。