Windows反調試技術(上)


寫在前面

在逆向工程中為了防止破解者調試軟件,通常都會在軟件中采用一些反調試技術來防破解。下面就是一些在逆向工程中常見的反調試技巧與示例。

BeingDebuged

利用調試器加載程序時調試器會通過CreateProcess()創建調試進程,同時會創建調試內核對象並保存在當前線程環境塊的DbgSsReserved[1]字段中,此時此線程就不同於普通線程了(一般稱為調試器線程)。接着CreateProcess()會判斷是否有調試器,無則直接返回,有則調用PspCreateProcess(),此函數會根據DbgSsReserved[1]字段設置創建進程的EPROCESS的DebugPort字段,這樣此被創建進程就可以稱為被調試進程。接着PspCreateProcess()會調用MmCreatePeb(),此函數會根據EPROCESS的Debugport字段設置PEB的BeingDebuged字段。(無調試器時其值為0)
我們可以直接通過內聯匯編來訪問BeingDebuged字段,也可以通過IsDebuggerPresent()這個API來得到此字段的值。下面是示例程序。

int main(int argc, char *argv[])
{
	DWORD	dwBeingDebuged;
	TCHAR	szTest[]	= TEXT("已檢測到調試器!");
	TCHAR	szSuccess[]	= TEXT("運行正常!");

  	dwBeingDebuged		= IsDebuggerPresent();
	if(0 != dwBeingDebuged)
	{
		MessageBox(NULL, szTest, NULL, MB_OK);
		ExitProcess(NULL);
	}
	MessageBox(NULL, szSuccess, NULL, MB_OK);
	return 0;
}

NtGlobalFlag

當BeingDebuged被設為“TRUE”后程序會將PEB的NtGlobalFlag設置0x70h標志。我們通過內聯匯編檢測進程環境快的NtGlobalFlag字段。

int main(int argc, char *argv[])
{
	DWORD	dwNtGlobalFlag;
	TCHAR	szTest[]	= TEXT("已檢測到調試器!");
	TCHAR	szSuccess[]	= TEXT("運行正常!");
	
  	dwNtGlobalFlag		= _AntiDebug();
	if(0 != dwNtGlobalFlag)
	{
		MessageBox(NULL, szTest, NULL, MB_OK);
		ExitProcess(NULL);
	}
	MessageBox(NULL, szSuccess, NULL, MB_OK);
	return 0;
}

DWORD _AntiDebug()
{
	_asm{
	mov eax,fs:[0x30]
	mov eax,[eax + 0x68]
	and eax,0x70
	}
}

ProcessHeap的Flags和ForceFlags

當NtGlobalFlag字段被設置0x70標志后,其會改變PEB的字段ProcessHeap所指向的Flags和ForceFlags的值。
對於x86平台而言Flags和ForceFlags偏移地址分別是相對於ProcessHeap指向地址的0x40和0x44處。當程序未被調試器調試時Flags的值應包含0x00000002標志,ForceFlags的值應為0,下面是通過內聯匯編檢測二者的值。

int main(int argc, char *argv[])
{
	DWORD	dwReturn;
	TCHAR	szTest[]	= TEXT("已檢測到調試器!");
	TCHAR	szSuccess[]	= TEXT("運行正常!");
	
  dwReturn			= _AntiDebug();
	if(0 != dwReturn)
	{
		MessageBox(NULL, szTest, NULL, MB_OK);
		ExitProcess(NULL);
	}

	MessageBox(NULL, szSuccess, NULL, MB_OK);
	return 0;
}


DWORD _AntiDebug()
{
	_asm{
	push ebx
	mov eax,fs:[0x30]
	mov eax,[eax + 0x18]

	mov ebx,dword ptr [eax + 0x40]
	and ebx,0x00000002
	cmp ebx,0
	je	end0
	mov ebx,dword ptr [eax + 0x44]
	cmp ebx,0
	jne end0
	xor eax,eax
	jmp end
end0:
	mov eax,1
end:
	pop ebx
	}
}

Heap Magic

當進程被調試器調試時其進程堆會被一些特殊的標記填充,這些特殊標記分別是0xABABABAB , 0xBAADF00D , 0xFEEEFEEE。
通過從進程堆中創建內存塊來獲得進程堆的起始地址,然后通過對整個進程堆雙字遍歷來計算這些標志的數量。(如果大於10則證明進程被調試器調試了)

int main(int argc, char *argv[])
{
	DWORD	dwNum = 0;
	PDWORD	pHeap;
	TCHAR	szTest[]	= TEXT("已檢測到調試器!");
	TCHAR	szSuccess[]	= TEXT("運行正常!");

	pHeap = (DWORD*)HeapAlloc(GetProcessHeap(), NULL, 0x100);	//通過在自己的進程默認堆中申請內存得到堆中內存的地址,(還可以通過PEB的LDR_MODULE字段中得到這些標志)
	_try{
	for(;;){						        //檢測Heap Magic標志。
		switch(*pHeap++){
		case 0xABABABAB:
		case 0xBAADF00D:
		case 0xFEEEFEEE:
			dwNum++;
			break;
		}	
	      }
	}
	_except(EXCEPTION_EXECUTE_HANDLER){
		if(dwNum > 10)
		{
			MessageBox(NULL, szTest, NULL, MB_OK);
			ExitProcess(NULL);
		}
	}
	MessageBox(NULL, szSuccess, NULL, MB_OK);
	return 0;

}

ProcessDebugPort

如果進程被調試則進程會通過一個調試端口與調試子系統通信,進而與調試器通信。我們可以通過CheckRemoteDebuggerPresent()來檢測調試端口是否存在ProcessDebugPort實際就是EPROCESS的DebugPort字段。同時CheckRemoteDebuggerPresent()不僅可以檢測自身是否存在調試端口,還可以檢測其他進程是否存在調試端口。

int main(int argc, char *argv[])
{
	
	BOOL	bDebuggerPresent;
	TCHAR	szTest[]	= TEXT("已檢測到調試器!");
	TCHAR	szSuccess[]	= TEXT("運行正常!");
	
  	CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent);

	if(TRUE == bDebuggerPresent)
	{
		MessageBox(NULL, szTest, NULL, MB_OK);
		ExitProcess(NULL);
	}
	MessageBox(NULL, szSuccess, NULL, MB_OK);
	return 0;
}

實際CheckRemoteDebuggerPresent()函數內部是通過調用NtQueryInformationProcess()來檢測進程是否存在調試端口的,所以我們也可以直接通過動態調用NtQueryInformationProcess()來檢測是否存在調試端口。

typedef NTSTATUS(NTAPI  *pfnNtQueryInformationProcess)(						//typedef后可以掩飾復合類型
	_In_      HANDLE           ProcessHandle,
        _In_      UINT             ProcessInformationClass,
        _Out_     PVOID            ProcessInformation,
        _In_      ULONG            ProcessInformationLength,
        _Out_opt_ PULONG           ReturnLength
	);
//表示檢測進程的調試端口
UINT ProcessDebugPort = 7;	      												

int main(int argc, char *argv[])
{
	
	TCHAR		szTest[]	= TEXT("已檢測到調試器!");
	TCHAR		szSuccess[]	= TEXT("運行正常!");
	DWORD		dwDebuggerPresent = 0;
	NTSTATUS	stNtstatus;

	pfnNtQueryInformationProcess NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtQueryInformationProcess"));
  	stNtstatus = NtQueryInformationProcess(
		GetCurrentProcess(),
		ProcessDebugPort,
		&dwDebuggerPresent,
		sizeof(DWORD),
		NULL);

	if(0 != dwDebuggerPresent && stNtstatus == 0x00000000)
	{
		MessageBox(NULL, szTest, NULL, MB_OK);
		ExitProcess(NULL);
	}

	MessageBox(NULL, szSuccess, NULL, MB_OK);
	return 0;
}

ProcessDebugObjectHandle

如果進程被調試會創建一個調試內核對象,通過NtQueryInformationProcess()可以檢測是否存在調試內核對象句柄。

typedef NTSTATUS(NTAPI  *pfnNtQueryInformationProcess)(								
    _In_      HANDLE           ProcessHandle,
    _In_      UINT             ProcessInformationClass,
    _Out_     PVOID            ProcessInformation,
    _In_      ULONG            ProcessInformationLength,
    _Out_opt_ PULONG           ReturnLength
);

UINT ProcessDebugObjectHandle = 0x1E;

int main(int argc, char* argv[])
{
	HANDLE		hDebugHandle; 
	NTSTATUS	stNtstatus;
	pfnNtQueryInformationProcess NtQueryInformationProcess;
		
	NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtQueryInformationProcess"));

	stNtstatus = NtQueryInformationProcess(
		GetCurrentProcess(),
		ProcessDebugObjectHandle,
		&hDebugHandle,
		sizeof(HANDLE),
		NULL);
	if(0x00000000 == stNtstatus && NULL != isDebuggerPresent)
	{

		MessageBox(NULL, TEXT("已檢測到調試器!"),NULL, MB_OK);
		ExitProcess(NULL);
	}

	MessageBox(NULL, TEXT("程序正常運行!"), NULL, MB_OK);
	return 0;
}

ProcessDebugFlags

當進程被調試時其進程內核對象EPROCESS的NoDebugInherit字段會被置空,調用NtQueryInformationProcess可以檢測此字段的值。

typedef NTSTATUS(NTAPI  *pfnNtQueryInformationProcess)(								
	_In_      HANDLE           ProcessHandle,
    _In_      UINT             ProcessInformationClass,
    _Out_     PVOID            ProcessInformation,
    _In_      ULONG            ProcessInformationLength,
    _Out_opt_ PULONG           ReturnLength
);

UINT ProcessDebugFlags = 0x1F;

int main(int argc, char* argv[])
{
	ULONG		DebugFlags; 
	NTSTATUS	stNtstatus;
	pfnNtQueryInformationProcess NtQueryInformationProcess;
		
	NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtQueryInformationProcess"));

	stNtstatus = NtQueryInformationProcess(
		GetCurrentProcess(),
		ProcessDebugFlags,
		&DebugFlags,
		sizeof(ULONG),
		NULL);
	if(0x00000000 == stNtstatus && NULL == DebugFlags)
	{

		MessageBox(NULL, TEXT("已檢測到調試器!"),NULL, MB_OK);
		ExitProcess(NULL);
	}

	MessageBox(NULL, TEXT("程序正常運行!"), NULL, MB_OK);
	return 0;
}

ProcessBasicInformation

調用NtQuertyInformationProcess檢測ProcessBasicInformation得到一個數據結構。通過這個數據結構可以得到進程ID,進一步通過PID創建進程快照得到父進程的PID從而進一步得到父進程的用戶名。通過判斷父進程的用戶名是不是調試器而進行反調試。

#include <Windows.h>
#include <winternl.h>
#include <TlHelp32.h>
typedef NTSTATUS(NTAPI  *pfnNtQueryInformationProcess)(								
	_In_      HANDLE           ProcessHandle,
    _In_      UINT             ProcessInformationClass,
    _Out_     PVOID            ProcessInformation,
    _In_      ULONG            ProcessInformationLength,
    _Out_opt_ PULONG           ReturnLength
);

const UINT uProcessBasicInformation = 0;

DWORD GetParentPID(DWORD dwPid);
BOOL GetExeNameByPID(char * szExeName, DWORD dwPid);
int main(int argc, char* argv[])
{ 
	char 		szExeName[256] = {0};
	DWORD		dwParent;
	NTSTATUS	stNtstatus;
	pfnNtQueryInformationProcess NtQueryInformationProcess;
	PROCESS_BASIC_INFORMATION stProcessBasicInformation;							

	NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtQueryInformationProcess"));

	stNtstatus = NtQueryInformationProcess(
		GetCurrentProcess(),
		uProcessBasicInformation,
		&stProcessBasicInformation, 
		sizeof(PROCESS_BASIC_INFORMATION),
		NULL);
	dwParent = GetParentPID(stProcessBasicInformation.UniqueProcessId);
	GetExeNameByPID(szExeName, dwParent);
	
	if(0x00000000 == stNtstatus && (lstrcmp(szExeName, "explorer.exe") && lstrcmp(szExeName, "cmd.exe") && lstrcmp(szExeName, "services.exe")))
	{

		MessageBox(NULL, TEXT("已檢測到調試器!"),NULL, MB_OK);
		ExitProcess(NULL);
	}

	MessageBox(NULL, TEXT("程序正常運行!"), NULL, MB_OK);
	return 0;
}


//獲取父進程的PID
DWORD GetParentPID(DWORD dwPid)
{
	HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	PROCESSENTRY32 pe32;
	pe32.dwSize = sizeof(PROCESSENTRY32);
	
	if(hProcessSnap == INVALID_HANDLE_VALUE)
	{
		CloseHandle(hProcessSnap);
		return 0;
	}
	
	if(!Process32First(hProcessSnap, &pe32))
	{
		CloseHandle(hProcessSnap);
		return 0;

	}
	do{

		if(pe32.th32ProcessID == dwPid)
			break;
	}while(Process32Next(hProcessSnap, &pe32));
	return pe32.th32ParentProcessID;

}


//獲取對應PID進程的程序名稱
BOOL GetExeNameByPID(char szExeName[], DWORD dwPid)
{
	HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	PROCESSENTRY32 pe32;
	pe32.dwSize = sizeof(PROCESSENTRY32);
	
	if(hProcessSnap == INVALID_HANDLE_VALUE)
	{
		CloseHandle(hProcessSnap);
		return 0;
	}
	
	if(!Process32First(hProcessSnap, &pe32))
	{
		CloseHandle(hProcessSnap);
		return 0;

	}
	do{

		if(pe32.th32ProcessID == dwPid)
		{
			

			lstrcpy(szExeName, pe32.szExeFile);
			break;
		}

	}while(Process32Next(hProcessSnap, &pe32));
	return 1;
}

HideFromDebugger

被調試進程通過調試端口DebugPort與調試子系統通信進一步與調試器通信。如果我們能將DebugPort清除就可以阻止進程與調試器通信達到反調試的目的,但是DebugPort一旦被創建就無法被在設置,所以我們這種思路走不通。
我們發現進程如果產生異常,其會檢測ETHREAD的HideFromDebugger成員是否為“FALSE”,如果是就會向當前進程的調試端口發送調試事件消息(為"TRUE"表示該異常對用戶調試器不可見)。我們可以通過ZwSetInformationThread()設置HideFromDebugger字段的值為TRUE從而令用戶調試器接收不到調試事件,達到反調試的目的。

#include <winternl.h>
typedef NTSTATUS (NTAPI *pfnZwSetInformationThread)(
	_In_ HANDLE ThreadHandle,
	_In_ UINT	ThreadInformationClass,
	_In_ PVOID	ThreadInformation,
	_In_ ULONG	ThreadInformationLength
	);

UINT uThreadHideFromDebugger = 0x11;

int main(int argc, char * argv[])
{
	NTSTATUS stNtstatus;
	pfnZwSetInformationThread ZwSetInformationThread;
	
	ZwSetInformationThread = (pfnZwSetInformationThread)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("ZwSetInformationThread"));

	ZwSetInformationThread(
		GetCurrentThread(),
		uThreadHideFromDebugger,
		0,
		0);

	return 0;
}

NtCreateThreadEx

通過CreateThread( )的時候利用THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER標志,則在創建該線程時,它將對調試器隱藏。

//通過CreateThread( )的時候利用THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER標志,則在創建該線程時,它將對調試器隱藏。
//#include <Windows.h>
typedef NTSTATUS(NTAPI* pfnNtCreateThreadEx) (
    _Out_    PHANDLE              ThreadHandle,
    _In_     ACCESS_MASK          DesiredAccess,
    _In_opt_ PVOID		  ObjectAttributes,		//POBJECT_ATTRIBUTES
    _In_     HANDLE               ProcessHandle,
    _In_     PVOID                StartRoutine,
    _In_opt_ PVOID                Argument,
    _In_     ULONG                CreateFlags,
    _In_opt_ ULONG_PTR            ZeroBits,
    _In_opt_ SIZE_T               StackSize,
    _In_opt_ SIZE_T               MaximumStackSize,
    _In_opt_ PVOID                AttributeList
);
void ThreadProc();

int main(int argc, char* argv[])
{
	HANDLE hThread;
	pfnNtCreateThreadEx NtCreateThreadEx;
	NtCreateThreadEx = (pfnNtCreateThreadEx)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtCreateThreadEx"));
	NtCreateThreadEx(
		&hThread,
		0x1FFFFF,
		NULL,
		GetCurrentProcess(),
		(LPTHREAD_START_ROUTINE)ThreadProc,
		NULL,
		0x00000004,					//直接執行並且對調試器隱藏THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER
		NULL,
		NULL,
		NULL,
		NULL
		);

	WaitForSingleObject(hThread,INFINITE);
	int n = GetLastError();
	return 0;
}
//線程回調函數
void ThreadProc()
{
	MessageBox(NULL, TEXT("我是一個新線程!"), NULL, MB_OK);
}

參考資料: 看雪學院《加密解密》
張銀奎《軟件調試》
https://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM