C/C++ 要實現程式反除錯有多種方法,BeingDebugged,NtGlobalFlag,ProcessHeap,CheckRemoteDebuggerPresent,STARTUPINFO,IsDebuggerPresent,父程式檢測,TLS 執行緒區域性儲存,RDTSC時鐘檢測反除錯,MapFileAndCheckSum,等都可實現反除錯,這裡我分別編寫了一些案例,基本上一鍋端了。
使用WinDBG隨便載入一個二進位制檔案,並載入除錯符號連結檔案.
0:000> .sympath srv*c:\symbols*http://msdl.microsoft.com/download/symbols
0:000> .reload
0:000> srv*c:\\symbols\*http://www.blib.cn/symbols
0:000> .reload
在建立程式時,作業系統會為每個執行緒分配TEB(執行緒環境塊)
,而且環境塊FS段暫存器總是被設定為fs:[0]
的位置上,也就是預設指向當前執行緒的TEB資料,首先我們可以通過萬用字元找到TEB結構的具體名稱.
0:000> dt ntdll!*teb*
ntdll!_TEB (執行緒環境塊)
ntdll!_TEB32
ntdll!_TEB64
ntdll!_TEB_ACTIVE_FRAME_CONTEXT
ntdll!_TEB_ACTIVE_FRAME
ntdll!_GDI_TEB_BATCH64
ntdll!_GDI_TEB_BATCH32
ntdll!_GDI_TEB_BATCH
ntdll!_TEB_ACTIVE_FRAME_CONTEXT
接著可通過dt命令,查詢下ntdll!_TEB
結構,如下我們可以看到偏移為+0x018
的位置就是TEB結構頭指標,在該地址基礎上向下偏移0x30
就可以得到PEB(程式環境塊)
的基地址.
0:000> dt -rv ntdll!_TEB
struct _TEB, 66 elements, 0xfb8 bytes
+0x000 NtTib : struct _NT_TIB, 8 elements, 0x1c bytes # NT_TIB結構
+0x018 Self : Ptr32 to struct _NT_TIB, 8 elements, 0x1c bytes # NT_TIB結構(TEB自身)
+0x000 ExceptionList : Ptr32 to struct _EXCEPTION_REGISTRATION_RECORD, 2 elements, 0x8 bytes
+0x004 StackBase : Ptr32 to Void
+0x008 StackLimit : Ptr32 to Void
+0x00c SubSystemTib : Ptr32 to Void
+0x010 FiberData : Ptr32 to Void
+0x010 Version : Uint4B
+0x014 ArbitraryUserPointer : Ptr32 to Void
+0x018 Self : Ptr32 to struct _NT_TIB, 8 elements, 0x1c bytes
+0x020 ClientId : struct _CLIENT_ID, 2 elements, 0x8 bytes # 程式與執行緒ID
+0x000 UniqueProcess : Ptr32 to Void # 程式的PID
+0x004 UniqueThread : Ptr32 to Void # 程式的PPID
+0x02c ThreadLocalStoragePointer : Ptr32 to Void
+0x030 ProcessEnvironmentBlock : Ptr32 to struct _PEB, 111 elements, 0x480 bytes # 指向了PEB結構體
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
接著再來驗證一下,首先偏移地址0x18
是TEB結構基地址,也就是指向自身偏移fs:[0x18]
的位置,而!teb
地址加0x30
正是PEB
的位置,在teb的基礎上加上0x30
就可以得到PEB的基地址,拿到PEB基地址就可以幹很多事了.
0:000> r $teb # 使用系統符號解析
$teb=0081e000
0:000> dd $teb+0x18 # 手動驗證地址
0081e018 0081e000 00000000 0000139c 0000194c
0081e028 00000000 0081e02c 0081b000 00000000
0:000> dd $teb + 0x30 # 在teb基礎上+30 得到PEB基地址
0081e030 0081b000 00000000 00000000 00000000
0:000> !teb
TEB at 0081e000
ExceptionList: 00b3f8e0
StackBase: 00b40000
StackLimit: 00b3d000
ClientId: 0000139c . 0000194c
Tls Storage: 0081e02c
PEB Address: 0081b000 # 此處地址一致
獲取程式/執行緒PID: 首先我們需要fs:[0x18]
定位到TEB(執行緒環境塊)
然後在此基礎上加上0x20
得到ClientId
.
0:000> dd fs:[0x18] # 找到TEB基地址
0053:00000018 0081e000 00000000 0000139c 0000194c
0053:00000028 00000000 0081e02c 0081b000 00000000
0:000> dt _teb 0081e000
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID # 將TEB+0x20定位到這裡(程式與執行緒資訊)
+0x028 ActiveRpcHandle : (null)
0:004> dt _CLIENT_ID 0081e000 # 檢視程式詳細結構
ntdll!_CLIENT_ID
+0x000 UniqueProcess : 0x00b3f774 Void # 獲取程式PID
+0x004 UniqueThread : 0x00b40000 Void # 獲取執行緒PID
知道了流程,接著我們通過以下公式計算得出本程式的程式與執行緒ID.
#include <stdio.h>
#include <Windows.h>
DWORD GetSelfPid()
{
DWORD Pid = 0;
__asm
{
mov eax, fs:[0x18] // 獲取到PEB基地址
add eax,0x20 // 加上20得到 _CLIENT_ID
add eax,0x0 // 加上偏移0得到 UniqueProcess
mov eax, [eax] // 取出記憶體地址中的值
mov Pid,eax
}
return Pid;
}
DWORD GetSelfTid()
{
DWORD Pid = 0;
__asm
{
mov eax, fs:[0x18] // 獲取到PEB基地址
add eax, 0x20 // 加上20得到 _CLIENT_ID
add eax, 0x04 // 加上偏移04得到 UniqueThread
mov eax, [eax] // 取出記憶體地址中的值
mov Pid, eax
}
return Pid;
}
int main(int argc,char* argv[])
{
printf("程式 Pid = %d \n", GetSelfPid());
printf("執行緒 Tid = %d \n", GetSelfTid());
system("pause");
return 0;
}
BeingDebugged 反除錯: 程式執行時,位置FS:[30h]
指向PEB的基地址,為了實現反除錯,惡意程式碼通過這個位置來檢查BeingDebugged
標誌位是否為1,如果為1則說明程式被除錯,則刪除自身等.
首先我們可以使用dt _teb
命令解析一下TEB的結構,如下TEB結構的起始偏移為0x0,而0x30的位置指向的是ProcessEnvironmentBlock
也就是指向了程式環境塊PEB,
0:000> dt _teb
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB // PEB 程式環境塊
只需要在程式環境塊的基礎上+0x2
就能定位到執行緒環境塊TEB中BeingDebugged
的標誌,此處的標誌位如果為1則說明程式正在被除錯,為0則說明沒有被除錯.
0:000> dt _peb
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
我們手動來驗證一下,首先執行緒環境塊地址是007f1000
,在此基礎上加0x30
即可得到程式環境快的基地址,位置FS:[0x30]
指向PEB的基地址,007ee000
繼續加0x2即可得到BeingDebugged
的狀態ffff0401
此處我們只需要看byte位是否為1即可.
0:000> r $teb
$teb=007f1000
0:000> dd 007f1000 + 0x30
007f1030 007ee000 00000000 00000000 00000000
007f1040 00000000 00000000 00000000 00000000
0:000> r $peb
$peb=007ee000
0:000> dd 007ee000 + 0x2
007ee002 ffff0401 0000ffff 0c400112 19f0775f
007ee012 0000001b 00000000 09e0001b 0000775f
梳理一下知識點我們可以寫出一下反除錯程式碼,本程式碼單獨執行程式不會出問題,一旦被偵錯程式附加則會提示正在被除錯,當然除了自己使用匯編程式碼來實現反除錯以外,還可以使用IsDebuggerPresent()
這個API函式來完成,其兩者原理完全相同.
#include <stdio.h>
#include <Windows.h>
int IsDebugA()
{
BYTE Debug = 0;
__asm
{
mov eax, dword ptr fs:[0x30]
mov bl, byte ptr[eax + 0x2]
mov Debug,bl
}
return Debug;
}
int IsDebugB()
{
BYTE Debug = 0;
__asm
{
push dword ptr fs : [0x30]
pop edx
mov al, [edx + 2]
mov Debug,al
}
return Debug;
}
int IsDebugC()
{
DWORD Debug = 0;
__asm
{
mov eax, fs:[0x18] // TEB Self指標
mov eax, [eax+0x30] // PEB
movzx eax, [eax+2] // PEB->BeingDebugged
mov Debug,eax
}
return Debug;
}
int main(int argc,char* argv[])
{
if (IsDebugC())
printf("正在被除錯");
else
printf("沒有被除錯");
system("pause");
return 0;
}
如果惡意程式碼中使用該種技術阻礙我們正常除錯,我們只需要在X64DBG的命令列中執行dump fs:[30]+2
來定位到BeingDebugged()
的位置,並將其數值改為0然後執行程式,會發現反除錯已經被繞過了.
NtGlobalFlag 反除錯: 首先定位dt -rv ntdll!_TEB
找到TEB結構並通過TEB找到PEB結構,然後找到+0x068 NtGlobalFlag
,這個位置的NtGlobalFlag
類似於BeingDebugged
,如果是除錯狀態NtGlobalFlag
的值會是0x70
,所以我們可以判斷這個標誌是否為0x70
來判斷程式是否被除錯了,首先我們來使用匯編程式碼解決.
#include <stdio.h>
#include <windows.h>
DWORD IsDebug()
{
DWORD Debug = 0;
__asm
{
mov eax, fs:[0x18] // TEB基地址
mov eax, [eax + 0x30] // 找到PEB
mov eax, [eax + 0x68] // 找打 NtGlobalFlag
mov Debug,eax // 取出值
}
if (Debug == 112)
printf("程式正在被調戲 \n");
else
printf("程式正常 \n");
return Debug;
}
int main(int argc, char * argv[])
{
printf("返回狀態: %d \n", IsDebugA());
system("pause");
return 0;
}
除了使用匯編實現反除錯外,我們也可以使用Native API
中的ZwQueryInformationProcess()
這個函式來讀取到程式中的PET資料,然後判斷PebBase+0x68
是否等於70,由於這個函式並沒有公開,所以在使用時應該自行宣告一下結構型別.
#include <stdio.h>
#include <windows.h>
#include <winternl.h>
typedef NTSTATUS(NTAPI *typedef_ZwQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
DWORD IsDebug()
{
HANDLE hProcess = NULL;
DWORD ProcessId = 0;
PROCESS_BASIC_INFORMATION Pbi;
typedef_ZwQueryInformationProcess pZwQueryInformationProcess = NULL;
ProcessId = GetCurrentProcessId();
hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcessId);
if (hProcess != NULL)
{
HMODULE hModule = LoadLibrary(L"ntdll.dll");
pZwQueryInformationProcess = (typedef_ZwQueryInformationProcess)GetProcAddress(hModule, "ZwQueryInformationProcess");
NTSTATUS Status = pZwQueryInformationProcess(hProcess,ProcessBasicInformation,&Pbi,
sizeof(PROCESS_BASIC_INFORMATION),NULL);
if (NT_SUCCESS(Status))
{
DWORD ByteRead = 0;
WORD NtGlobalFlag = 0;
ULONG PebBase = (ULONG)Pbi.PebBaseAddress;
if (ReadProcessMemory(hProcess, (LPCVOID)(PebBase + 0x68), &NtGlobalFlag, 2, &ByteRead) && ByteRead == 2)
{
if (NtGlobalFlag == 0x70)
return 1;
}
}
CloseHandle(hProcess);
}
return 0;
}
int main(int argc, char * argv[])
{
if (IsDebug() == 1)
{
printf("正在被調戲. \n");
}
system("pause");
return 0;
}
ProcessHeap 反除錯: 該屬性是一個未公開的屬性,它被設定為載入器為程式分配的第一個堆的位置(程式堆標誌),ProcessHeap
標誌位於PEB結構中偏移為0x18
處,第一個堆頭部有一個屬性欄位,這個屬性叫做ForceFlags
屬性偏移為0x44
,該屬性為0說明程式沒有被除錯,非0說明被除錯,另外的Flags
屬性不為2說明被除錯,不為2則說明沒有被除錯.
0:000> dt !_peb
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x018 ProcessHeap : Ptr32 Void // 找到Process偏移地址
0:000> !heap // 找出堆區首地址
Heap Address NT/Segment Heap
1270000 NT Heap
0:000> !heap -a 1270000 // 查詢heep的記憶體
Index Address Name Debugging options enabled
1: 01270000
Segment at 01270000 to 0136f000 (00006000 bytes committed)
Flags: 40000062
ForceFlags: 40000060
Granularity: 8 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
0:000> dt _HEAP 1270000 // 找到ForceFlags標誌的偏移地址
ntdll!_HEAP
+0x000 Segment : _HEAP_SEGMENT
+0x000 Entry : _HEAP_ENTRY
+0x040 Flags : 0x40000062
+0x044 ForceFlags : 0x40000060
這裡需要注意的是堆區在不同系統中偏移值是不同的,在WindowsXP系統中ForceFlags
屬性位於堆頭部偏移量為0x10
處,對於Windows10系統來說這個偏移量為0x44
,而預設情況如果被除錯則ForceFlags
屬性為0x40000060
,而Flags
標誌為0x40000062
,下面通過彙編分別讀取出這兩個堆頭的引數.
#include <stdio.h>
#include <windows.h>
int IsDebugA()
{
DWORD Debug = 0;
__asm
{
mov eax, fs:[0x18] // TED基地址
mov eax, [eax + 0x30] // PEB基地址
mov eax, [eax + 0x18] // 定位 ProcessHeap
mov eax, [eax + 0x44] // 定位到 ForceFlags
mov Debug, eax
}
return Debug;
}
int IsDebugB()
{
DWORD Debug = 0;
__asm
{
mov eax, fs:[0x18] // TED基地址
mov eax, [eax + 0x30] // PEB基地址
mov eax, [eax + 0x18] // 定位 ProcessHeap
mov eax, [eax + 0x40] // 定位到 Flags
mov Debug, eax
}
return Debug;
}
int main(int argc, char * argv[])
{
int ret = IsDebugA();
if (ret != 0)
printf("程式正在被除錯: %x \n", ret);
int ret2 = IsDebugB();
if (ret2 != 2)
printf("程式正在被除錯: %x \n", ret2);
system("pause");
return 0;
}
另一種通過C語言實現的反除錯版本,其反除錯原理與上方相同,只不過此處我們使用了系統的API來完成檢測標誌位的.
#include <stdio.h>
#include <windows.h>
#include <winternl.h>
typedef NTSTATUS(NTAPI *typedef_ZwQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
DWORD IsDebug()
{
HANDLE hProcess = NULL;
DWORD ProcessId = 0;
PROCESS_BASIC_INFORMATION Pbi;
typedef_ZwQueryInformationProcess pZwQueryInformationProcess = NULL;
ProcessId = GetCurrentProcessId();
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
if (hProcess != NULL)
{
HMODULE hModule = LoadLibrary(L"ntdll.dll");
pZwQueryInformationProcess = (typedef_ZwQueryInformationProcess)GetProcAddress(hModule, "ZwQueryInformationProcess");
NTSTATUS Status = pZwQueryInformationProcess(hProcess, ProcessBasicInformation, &Pbi,
sizeof(PROCESS_BASIC_INFORMATION), NULL);
if (NT_SUCCESS(Status))
{
DWORD ByteRead = 0;
DWORD ProcessHeap = 0;
ULONG PebBase = (ULONG)Pbi.PebBaseAddress;
DWORD ForceFlagsValue = 1;
ReadProcessMemory(hProcess, (LPCVOID)(PebBase + 0x18), &ProcessHeap, 2, &ByteRead);
ReadProcessMemory(hProcess, (LPCVOID)(ProcessHeap + 0x40), &ForceFlagsValue, 4, &ByteRead);
if (ForceFlagsValue != 0)
{
printf("正在被調戲. \n");
}
}
CloseHandle(hProcess);
}
return 0;
}
int main(int argc, char * argv[])
{
IsDebug();
system("pause");
return 0;
}
CheckRemoteDebuggerPresent 反除錯:除了使用匯編實現反除錯以外,也可以使用以下方法實現反除錯,這個反除錯很強大,我還沒有發現能夠繞過的方法.
#include <stdio.h>
#include <windows.h>
typedef BOOL(WINAPI *CHECK_REMOTE_DEBUG_PROCESS)(HANDLE, PBOOL);
BOOL CheckDebugger()
{
BOOL bDebug = FALSE;
CHECK_REMOTE_DEBUG_PROCESS CheckRemoteDebuggerPresent;
HINSTANCE hModule = GetModuleHandle("kernel32");
CheckRemoteDebuggerPresent = (CHECK_REMOTE_DEBUG_PROCESS)GetProcAddress(hModule, "CheckRemoteDebuggerPresent");
HANDLE hProcess = GetCurrentProcess();
CheckRemoteDebuggerPresent(hProcess, &bDebug);
return bDebug;
}
int main(int argc,char *argv[])
{
if (CheckDebugger() == 1)
printf("正在被除錯 \n");
system("pause");
return 0;
}
STARTUPINFO 反除錯: 程式啟動時預設會通過explorer
資源管理器,呼叫CreateProcess()
函式建立的時候會把STARTUPINFO
結構體中的值設定為0,但如果通過偵錯程式啟動程式時該值並不會發生變化,我們可以通過判斷結構體中的dwFlags
引數來實現反除錯.
#include <Windows.h>
#include <stdio.h>
int IsDebug()
{
STARTUPINFO si = {0};
GetStartupInfo(&si);
if (si.dwFlags != 1)
return 1;
return 0;
}
int main(int argc, char * argv[])
{
int ret = IsDebug();
printf("%d \n", ret);
system("pause");
return 0;
}
IsDebuggerPresent 函式反除錯: 這個函式同樣可以實現判斷是否被除錯,不過由於這個函式的實現過於簡單,很容易就能夠被分析者突破,因此現在也沒有軟體再使用該函式來進行反除錯了.
#include <stdio.h>
#include <Windows.h>
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
while (TRUE)
{
//檢測用 ActiveDebugProcess()來建立除錯關係
if (IsDebuggerPresent() == TRUE)
{
printf("當前程式正在被除錯 \r\n");
DebugBreak(); // 產生int3異常
break;
}
Sleep(1000);
}
return 0;
}
int main(int argc, char * argv[])
{
HANDLE hThread = CreateThread(0, 0, ThreadProc, 0, 0, 0);
if (hThread == NULL)
return -1;
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
system("pause");
return 0;
}
父程式檢測實現反除錯: 該反除錯的原理非常簡單,我們的系統在執行程式的時候,都是由Explorer.exe
這個程式派生出來,也就是說如果沒有被除錯得到的父程式就是Explorer.exe
的程式ID,如果被除錯則該程式的父程式ID就會變成偵錯程式的PID,並直接直接使用TerminateProcess(hProcess, 0);
直接將偵錯程式的父程式幹掉.
#include <Windows.h>
#include <stdio.h>
#include <tlhelp32.h>
int IsDebug()
{
DWORD ExplorerId = 0;
PROCESSENTRY32 pe32 = { 0 };
DWORD ProcessId = GetCurrentProcessId();
GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerId);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hProcessSnap != INVALID_HANDLE_VALUE)
{
pe32.dwSize = sizeof(PROCESSENTRY32);
Process32First(hProcessSnap, &pe32);
do
{ // 先判斷是不是我們自己程式的PID
if (ProcessId == pe32.th32ProcessID)
{ // 判斷父程式是否是 Explorer.exe
if (pe32.th32ParentProcessID != ExplorerId)
{ // 如果被偵錯程式附加了,我們直接強制幹調偵錯程式
HANDLE h_process = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ParentProcessID);
TerminateProcess(h_process, 0);
return 1;
}
}
} while (Process32Next(hProcessSnap, &pe32));
}
return 0;
}
int main(int argc, char * argv[])
{
int ret = IsDebug();
if (ret == 1)
{
printf("程式正在被除錯 \n");
}
system("pause");
return 0;
}
異常處理實現反除錯: 通過安裝異常處理函式,然後手動觸發函式,如果被偵錯程式附加則會不走異常處理,此時IsDebug
將會返回預設的False
,並直接走_asm call pBuff;
在偵錯程式不忽略int3
中斷的情況下,除錯將會被終止.
#include <Windows.h>
#include <stdio.h>
BOOL Exceptioni = FALSE;
LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo)
{
Exceptioni = TRUE;
return EXCEPTION_CONTINUE_EXECUTION;
}
BOOL IsDebug()
{
ULONG OldProtect = 0;
LPTOP_LEVEL_EXCEPTION_FILTER lpsetun;
// 安裝自己實現的 ExceptionFilter 自定義異常處理函式
lpsetun = SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ExceptionFilter);
LPVOID pBuff = VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE);
*((PWORD)pBuff) = 0xc3;
VirtualProtect(pBuff, 0x1000, PAGE_EXECUTE_READ | PAGE_GUARD, &OldProtect);
_asm call pBuff; // 如果被除錯,則執行中斷,不會進行異常處理
SetUnhandledExceptionFilter(lpsetun); // 恢復異常處理
return Exceptioni;
}
int main(int argc, char * argv[])
{
if (!IsDebug())
printf("程式正在被除錯 \n");
system("pause");
return 0;
}
RDTSC時鐘檢測反除錯: 使用時鐘檢測方法是利用rdtsc
這個彙編指令,它返回至系統重新啟動以來的時鐘數,並且將其作為一個64位的值存入EDX:EAX
暫存器中,通過執行兩次rdstc
指令,然後計算出他們的差值,來判斷是否被除錯了.
#include <Windows.h>
#include <stdio.h>
int IsDebug()
{
int Debug = 0;
__asm
{
rdtsc // 呼叫時鐘
xor ecx,ecx
add ecx,eax // 將eax與ecx相加
rdtsc // 再次呼叫時鐘
sub eax,ecx // 兩次結果做差值
mov Debug,eax
}
//printf("列印差值: %d \n", Debug);
if (Debug >= 21)
return 1;
return 0;
}
int main(int argc, char * argv[])
{
int ret = IsDebug();
if (ret == 1)
printf("被除錯了 \n");
system("pause");
return 0;
}
TLS 執行緒區域性儲存反除錯: TLS是為了解決多執行緒變數同步問題,宣告為TLS變數後,當執行緒去訪問全域性變數時,會將這個變數拷貝到自己執行緒中的TLS空間中,以防止同一時刻內多次修改全域性變數導致變數不穩定的情況,先來看一段簡單的案例:
#include <Windows.h>
#include <stdio.h>
#pragma comment(linker, "/INCLUDE:__tls_used")
// TLS變數
__declspec (thread) int g_nNum = 0x11111111;
__declspec (thread) char g_szStr[] = "TLS g_nNum = 0x%p ...\r\n";
// 當有執行緒訪問tls變數時,該執行緒會複製一份tls變數到自己tls空間
// 執行緒只能修改自己的空間tls變數,不會修改到全域性變數
// TLS回撥函式A
void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Red)
{
if (DLL_THREAD_DETACH == Reason) // 如果執行緒退出則列印資訊
printf("t_TlsCallBack_A -> ThreadDetach!\r\n");
return;
}
// TLS回撥函式B
void NTAPI t_TlsCallBack_B(PVOID DllHandle, DWORD Reason, PVOID Red)
{
if (DLL_THREAD_DETACH == Reason) // 如果執行緒退出則列印資訊
printf("t_TlsCallBack_B -> ThreadDetach!\r\n");
/* Reason 什麼事件觸發的
DLL_PROCESS_ATTACH 1
DLL_THREAD_ATTACH 2
DLL_THREAD_DETACH 3
DLL_PROCESS_DETACH 0 */
return;
}
// 註冊TLS回撥函式,".CRT$XLB"
#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK p_thread_callback[] = { t_TlsCallBack_A, t_TlsCallBack_B, };
#pragma data_seg()
DWORD WINAPI t_ThreadFun(PVOID pParam)
{
printf(g_szStr, g_nNum);
g_nNum = 0x22222222;
printf(g_szStr, g_nNum);
return 0;
}
int main(int argc, char * argv[])
{
CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0);
Sleep(100);
CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0);
system("pause");
return 0;
}
前面的那幾種反除錯手段都是在程式執行後進行判斷的,這種判斷可以通過OD斷下後進行修改從而繞過反除錯,但TLS則是在程式執行前搶佔式執行TLS中斷,所以這種反除錯技術更加的安全,但也不絕對仍然能夠被繞過.
#include <Windows.h>
#include <stdio.h>
// linker spec 通知連結器PE檔案要建立TLS目錄
#ifdef _M_IX86
#pragma comment (linker, "/INCLUDE:__tls_used")
#pragma comment (linker, "/INCLUDE:__tls_callback")
#else
#pragma comment (linker, "/INCLUDE:_tls_used")
#pragma comment (linker, "/INCLUDE:_tls_callback")
#endif
void NTAPI __stdcall TLS_CALLBACK(PVOID DllHandle, DWORD dwReason, PVOID Reserved)
{
if (IsDebuggerPresent())
{
MessageBox(NULL, L" TLS_CALLBACK: 請勿除錯本程式 !", L"TLS Callback", MB_ICONSTOP);
ExitProcess(0);
}
}
// 建立TLS段
EXTERN_C
#ifdef _M_X64
#pragma const_seg (".CRT$XLB")
PIMAGE_TLS_CALLBACK _tls_callback = TLS_CALLBACK;
#else
#pragma data_seg (".CRT$XLB")
PIMAGE_TLS_CALLBACK _tls_callback = TLS_CALLBACK;
#endif
int main(int argc ,char * argv [])
{
return 0;
}
MapFileAndCheckSum反破解: 通過使用系統提供的API實現反破解,該函式主要通過檢測,PE可選頭IMAGE_OPTIONAL_HEADER
中的Checksum欄位來實現的,一般的EXE預設為0而DLL中才會啟用,當然你可以自己開啟,讓其支援這種檢測.
// C/C++ -> 常規 -> 除錯資訊格式 --> 程式資料庫
// 聯結器 -> 常規 -> 啟用增量連結 -> 否
// 聯結器 -> 高階 -> 設定校驗和 -> 是
#include <stdio.h>
#include <windows.h>
#include <Imagehlp.h>
#pragma comment(lib,"imagehlp.lib")
int main(int argc,char *argv[])
{
DWORD HeadChksum = 1, Chksum = 0;
char text[512];
GetModuleFileName(GetModuleHandle(NULL), text, 512);
if (MapFileAndCheckSum(text, &HeadChksum, &Chksum) != CHECKSUM_SUCCESS)
return 0;
if (HeadChksum != Chksum)
printf("檔案校驗和錯誤 \n");
else
printf("檔案正常 \n");
system("pause");
return 0;
}
利用In指令檢測虛擬機器: Vmware為真主機與虛擬機器之間提供了相互溝通的通訊機制,它使用IN指令來讀取特定埠的資料以進行兩機通訊,但由於IN指令屬於特權指令,在真機中執行將會觸發EXCEPTION_PRIV_INSTRUCTION
異常,而在虛擬機器中並不會發生異常,我們可以利用這個特性判斷程式碼是否在虛擬機器中.
#include <windows.h>
#include <stdio.h>
bool IsInsideVM()
{
bool VmWare = true;
__try
{
__asm
{
mov eax, 'VMXh'
mov ebx, 0
mov ecx, 10 // 指定功能號
mov edx, 'VX'
in eax, dx // 從埠dx讀取VMware版本到eax
cmp ebx, 'VMXh' // 判斷ebx中是否包含VMware版本VMXh
setz[VmWare] // 設定返回值 True/False
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
VmWare = false; // 如果未處於虛擬機器中,將會產生異常
}
return VmWare;
}
int main()
{
int ret = IsInsideVM();
if (ret == 1)
printf("當前程式碼在虛擬機器中 \n");
else
printf("宿主機 \n");
system("pause");
return 0;
}