Ant1-D3bug

反調試


幾乎任何技術在經過構造後都能變成反調試技術。

注意,測試時不要用vs,因爲vs調試並不能算調試器;也不要用帶插件的調試器(OD)。推薦windbg。

1. 反調試分類

1.1 靜態反調試

特點:調試時開始阻攔。一般找到原因就能突破。

PEB

  • BeingDebugged
  • Ldr
  • Heap
  • NtGlobalFlag

TEB

  • StaticUnicodeString:靜態緩衝區

原始API

  • NtQueryInformationProcess():後面整理的4種類型
  • NtQuerySystemInfo():
  • NtQueryObject():遍歷系統內核對象

其它

分離調試ZwSetInformatioThread()

利用TLS回調函數。

打開進程檢查SeDubugPrivilege()

還有普通API:

  • 檢查父進程
  • 檢查窗口名
  • 遍歷進程名
  • 檢查文件名/路徑
  • 檢查註冊表

1.2 動態反調試

特點:一般在調試過程中阻攔。

使用SEH:

  • 異常
  • 斷點
  • SetUnhandledExceptionFilter()

時間檢查:RDTSC,彙編指令,讀取時間戳計數器。

補丁檢查(常用CRC):

  • 掃描0xcc
  • 掃描關鍵代碼段hash
  • 掃描API斷點,第一個字節是否爲0xcc

反反彙編(阻止opcode->指令):

  • 指令截斷:截斷第一條指令,進而導致後續指令解析錯誤;
  • 指令混淆
  • 指令膨脹
  • 代碼亂序:用jmp連接。

偷取代碼(類似殼):

  • 移動OEP加密執行
  • 移動API

分頁保護:

  • 運行時保護分頁:修改代碼及數據段保護屬性干擾分析。

殼:

  • 壓縮殼:配合OEP加密
  • 加密殼:再添加各種反調試手段

虛擬機(類似解釋型語言):

  • API虛擬機:針對常用API
  • 指令級虛擬機:囊括任意指令

還有單步檢查、封鎖鍵盤鼠標、禁用窗口。


2. 部分方法的示例代碼

2.1 PEB

PEB地址存於fs:[0x30]

BeingDebugged

PEB偏移爲2的字段。

可直接用IsDebuggerPresent()API,它的反彙編代碼如下。

IsDebbugerPresent()

BOOL PEB_BeingDubugged()
{
	__asm {
		mov eax, dword ptr fs : [0x30]
		movzx eax, dword ptr[eax + 0x02];
	}
}

ProcessHeap

PEB偏移0x18的字段,如果是64位,則位於

heap結構體的Flags(0x40)和ForceFlags(0x44)在正常運行時分別爲2和0。

bool CheckProcessHeap()
{
	DWORD Flags = 0, ForceFlags = 0;

	__asm
	{
		; 找到 PEB->FS:[0x30]
		mov eax, dword ptr fs : [0x30]

		; 找到 ProcessHeap->Peb + 0x18
		mov eax, dword ptr[eax + 0x18]

		; 找到 Flags->HEAP + 0x40
		mov edx, dword ptr[eax + 0x40]
		mov Flags, edx

		; 找到 ForceFlags->HEAP + 0x44
		mov ecx, dword ptr[eax + 0x44]
		mov ForceFlags, ecx
	}
	printf("%08x %08x\n", Flags, ForceFlags);
	return !(Flags == 2 && ForceFlags == 0);
}

NtGlobalFlag;

位於PEB偏移0x68處,正常值爲0x70。

bool CheckNtGlobalFlag()
{
	__asm {
		mov eax, dword ptr fs:[0x30];
		mov eax, dword ptr [eax + 0x68]
	}
}

2.2 NtQueryInformationProcess

NtQueryInformationProcess()是一個可以在R0和R3運行的函數,用來查看進程信息。這個函數沒有官方文檔,也就是說未公開。

第二個參數是信息類型,我們有4種選擇:

  • 0x00,ProcessBasicInformation
  • 0x07,ProcessDebugPort
  • 0x1e,ProcessDebugObjectHandle
  • 0x1f,ProcessDebugFlag

注意要包含頭文件和導入庫。

ProcessDebugPort(0x07)

ProcessDebugPort可以獲取調試端口,如果正常運行,則端口爲0,否則爲-1。

#include <winternl.h>
#pragma comment(lib, "ntdll.lib")

BOOL NQIP_ProcessDebugPort()
{
	int nDebugPort = 0;
	NtQueryInformationProcess(
		GetCurrentProcess(),    //獲取僞句柄
		ProcessDebugPort,
		&nDebugPort,
		sizeof(nDebugPort),
		NULL);
	return nDebugPort == -1 ? TRUE : FALSE;
}

ProcessDebugObjectHandle(0x1e)

也可以查詢目標進程的調試對象句柄,如果正常運行應該得到NULL。

BOOL NQIP_ProcessDebugPort()
{
	HANDLE hProcessDebugObjectHandle = 0;
	
	NtQueryInformationProcess(
		GetCurrentProcess(),
		(PROCESSINFOCLASS)0x1e,
		&hProcessDebugObjectHandle,
		sizeof(hProcessDebugObjectHandle),
		NULL);
	return hProcessDebugObjectHandle ? TRUE : FALSE;
}

ProcessDebugFlag(0x1f)

獲取目標調試標記,正常爲0,調試爲1。

BOOL NQIP_ProcessDebugFlag()
{
	BOOL bProcessDebugFlag = 0;
	
	NtQueryInformationProcess(
		GetCurrentProcess(),
		(PROCESSINFOCLASS)0x1f,
		&bProcessDebugFlag,
		sizeof(bProcessDebugFlag),
		NULL);
	return bProcessDebugFlag;
}

ProcessBasicInformation(0x00)

最後就是查詢父進程PID,與explorer.exe的PID對比,不匹配則證明本進程不是雙擊運行的。

bool NQIP_PPID()
{
	struct PROCESS_BASIC_INFORMATION {
		ULONG ExitStatus;			// 進程返回碼
		PPEB  PebBaseAddress;		// PEB地址
		ULONG AffinityMask;			// CPU親和性掩碼
		LONG  BasePriority;			// 基本優先級
		ULONG UniqueProcessId;		// 本進程PID
		ULONG InheritedFromUniqueProcessId; // 父進程PID
	} stcProcInfo;

	NtQueryInformationProcess(
		GetCurrentProcess(),
		ProcessBasicInformation,
		&stcProcInfo,
		sizeof(stcProcInfo),
		NULL);

	DWORD ExplorerPID = 0;
	DWORD CurrentPID = stcProcInfo.InheritedFromUniqueProcessId;
	GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerPID);
	return ExplorerPID == CurrentPID ? false : true;
}

當然,在vs裏ctrl+f5也顯示是調試狀態。

2.2 檢查系統是否處於調試狀態

通過一個api獲取系統是否開啓調試模式。

/*
typedef enum _SYSTEM_INFORMATION_CLASS {
    SystemBasicInformation = 0,
    SystemPerformanceInformation = 2,
    SystemTimeOfDayInformation = 3,
    SystemProcessInformation = 5,
    SystemProcessorPerformanceInformation = 8,
    SystemInterruptInformation = 23,
    SystemExceptionInformation = 33,
    SystemRegistryQuotaInformation = 37,
    SystemLookasideInformation = 45,
    SystemPolicyInformation = 134,
} SYSTEM_INFORMATION_CLASS;
*/

BOOL CheckSystemKernelDebuggerInfomation()
{
	struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION {
		BOOLEAN KernelDebuggerEnabled;
		BOOLEAN KernelDebuggerNotPresent;
	} DebuggerInfo = { 0 };

	NtQuerySystemInformation(
		SystemInterruptInformation,
		&DebuggerInfo,
		sizeof(DebuggerInfo),
		NULL);
	return DebuggerInfo.KernelDebuggerEnabled;
}

2.3 NtQueryObject

BOOL NQO_NtQueryObject()
{
	typedef struct _OBJECT_TYPE_INFORMATION{
		UNICODE_STRING TypeName;
		ULONG TotalNumberOfHandlers;
		ULONG TotalNumberOfObjects;
	}OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;

	typedef struct _OBJECT_ALL_INFORMATION {
		ULONG NumberOfObjectTypes;
		OBJECT_TYPE_INFORMATION	ObjectTypeInfo[1];
	}OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;


	//	1. 查詢信息大小
	ULONG uSize = 0;
	NtQueryObject(NULL, (OBJECT_INFORMATION_CLASS)0X03, &uSize, sizeof(uSize), &uSize);
	
	//	2. 獲取信息
	POBJECT_ALL_INFORMATION pObjectAllInfo = (POBJECT_ALL_INFORMATION)new BYTE[uSize]();
	NtQueryObject(NULL, (OBJECT_INFORMATION_CLASS)0X03, pObjectAllInfo, uSize, &uSize);

	//	3. 遍歷並處理
	POBJECT_TYPE_INFORMATION pObjectTypeInfo = pObjectAllInfo->ObjectTypeInfo;
	for (int i = 0; i < pObjectAllInfo->NumberOfObjectTypes; ++i)
	{
		//	3.1 查看類型是否爲DebugObject;
		if (!wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer))
		{
			return TRUE;
		}
		
		//	3.2 獲取對象名空間大小(考慮對齊)
		ULONG uNameLength = pObjectTypeInfo->TypeName.Length;
		ULONG uDataLength = uNameLength - uNameLength % sizeof(ULONG) + sizeof(ULONG);

		//	3.3 指向下一個對象
		pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)pObjectTypeInfo->TypeName.Buffer;
		pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)((PBYTE)pObjectTypeInfo + uDataLength);
	}

	delete[] pObjectAllInfo;  
	return FALSE;
}

2.4 脫離調試器

API名叫ZwSetInformationThread()

原理,當 DbgkForwardException 檢查到線程存在ThreadHideFromDebugger 的時候,就不會向調試器發送調試信息了。

#include <iostream>
#include <windows.h>

typedef enum THREAD_INFO_CLASS {
	ThreadHideFromDebugger = 17
};

typedef NTSTATUS (NTAPI *ZW_SET_INFORMATION_THREAD)(
	_In_ HANDLE ThreadHandle,
	_In_ THREAD_INFO_CLASS ThreadInformationClass,
	_In_reads_bytes_(ThreadInformationLength) PVOID ThreadInformation,
	_In_ ULONG ThreadInformationLength
);

void ZSIT_DetachDebug()
{
	ZW_SET_INFORMATION_THREAD Func;

	Func = (ZW_SET_INFORMATION_THREAD)GetProcAddress(
		LoadLibraryW(L"ntdll.dll"),
		"ZwSetInformationThread");
	Func(
		GetCurrentThread(),
		ThreadHideFromDebugger,
		NULL, NULL);
}

int main()
{
	ZSIT_DetachDebug();
	printf("running...\n");
	
	system("pause");
	return 0;
}

複習

反調試的目的:

  • 防止程序被調試
  • 防止程序被暴力破解

如何破解常見反調試手段:

  • 對於PEB手段,將值重寫即可;
  • 對於無文檔的API,HOOK後根據參數判斷是否獲取調試信息,是的話替換稱錯誤參數即可。
NTSTATUS WINAPI MyNtQueryInformationProcess(
    HANDLE  ProcessHandle,
   PROCESSINFOCLASS            ProcessInformationClass,
   PVOID            ProcessInformation,
   ULONG            ProcessInformationLength,
   PULONG           ReturnLength){
    
    // 查詢調試端口
   if(ProcessInformationClass== ProcessDebugPort)
   {
        *(PDWORD*) ProcessInformation=0xFFFFFFFF;
        if(ReturnLength) *ReturnLength = 4;
        return 0;
   }
   else{
        // 調用原版函數:
        return pSrcNtQueryInformationProcess(ProcessHandle,
            ProcessInformationClass,
            ProcessInformation,
            ProcessInformationLength,
            ReturnLength);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章