异常和中断——调试与反调试

文章目录

  • 使用PEB进行反调试
    • 使用PEB中的BeginDebug字段来检测是否处于调试环境。
    • 使用 IsDebuggerPresent()来判断是否是处于调试环境。
    • 检测PEB的GLobal来判断是否处于调试环境
  • 使用 NtQueryInformationProcess 进行反调试
    • 使用ProcessDebugPort
    • 使用ProcessDebugObjectHandle
    • 使用ProcessDebugFlags
    • 通过查看父进程ID
    • 判断系统是否处于调试状态
  • 检测是否存在调试对象
  • 攻击调试器——ZwSetInformationThread
  • 指令混淆


使用PEB进行反调试

使用PEB中的BeginDebug字段来检测是否处于调试环境。

//若返回值为TRUE则表示处于调试状态
bool CheckDebug()
{
	bool bDebuged = false;
	_asm push eax;
	_asm mov eax, fs:[0x30];// eax保存PEB首地址
	_asm mov al,byte ptr ds:[eax + 2];//BeginDebug字段的值
	_asm mov bDebuged, al;
	_asm pop eax
	return bDebuged;
}

使用 IsDebuggerPresent()来判断是否是处于调试环境。

原理和上面一个一样;如:

// IsDebuggerPresent()返回值为True则表示处于调试状态
	bool bDebug = IsDebuggerPresent();//它使用的方式,和上一个是一样的
	if (bDebug)
	{
		MessageBox(NULL, L"正在被调试", L"注意", 0);
	}
	else
	{
		MessageBox(NULL, L"现在很安全", L"恭喜", 0);
	}

检测PEB的GLobal来判断是否处于调试环境

在PEB+0x68处为peb的global字段,当其值=0x70时表示处于调试环境。

bool CheckDebug()
{
	DWORD dwSign = 0;
	_asm push eax;
	_asm mov eax, fs:[0x30];
	_asm mov eax, [eax + 0x68];
	_asm mov dwSign, eax;
	_asm pop eax
	return dwSign==0x70;
}

使用 NtQueryInformationProcess 进行反调试

API在ntdll.lib中,要使用的话必须导入库文件。
#pragma comment(lib,"ntdll.lib")
NtQueryInformationProcess
源码实现:

//没注释,要我自己跟,我还没来得及跟。。。先就这样吧。
NTSTATUS NTAPI NtMyQueryInformationProcess(
	IN HANDLE ProcessHandle,
	IN PROCESSINFOCLASS ProcessInformationClass,
	OUT PVOID ProcessInformation,
	IN ULONG ProcessInformationLength,
	OUT PULONG ReturnLength OPTIONAL
)
{
	_asm {


		mov         eax, 16h
		xor         ecx, ecx
		lea         edx, [esp + 4]
		call        dword ptr fs : [0C0h]
		add         esp, 4
		ret         14h
		mov         eax, 17h
		mov         ecx, 1Eh
		lea         edx, [esp + 4]
		call        dword ptr fs : [0C0h]
		add         esp, 4
		ret         14h
	}
}

原型:

__kernel_entry NTSTATUS NtQueryInformationProcess(
  IN HANDLE           ProcessHandle,
  IN PROCESSINFOCLASS ProcessInformationClass,
  OUT PVOID           ProcessInformation,
  IN ULONG            ProcessInformationLength,
  OUT PULONG          ReturnLength
);

第2个参数是个枚举类型. 主要有3个值与调试有关:

ProcessDebugPort = 7
ProcessDebugObjectHandle = 30
ProcessDebugFlags  = 31

当ProcessInformationClass 参数为7时,ProcessInformation返回调试状态.
即当处于非调试状态将传入变量置0,否则置0xffffffff

同理,当ProcessInformationClass 参数为30时,ProcessInformation返回调试对象句柄
即当调试状态时将传入变量置句柄值,否则置0
此外,API CheckRemoteDebuggerPresent就是通过这个方式来检测是否存在调试器

当ProcessInformationClass 参数为31时,ProcessInformation返回调试标记
即当调试状态时将传入变量置0,否则置1

使用ProcessDebugPort

bool CheckDebug()
{
	int nDebugPort = 0;
	NtQueryInformationProcess(
		GetCurrentProcess(),
		ProcessDebugPort,
		&nDebugPort,
		sizeof(nDebugPort),
		NULL);
	//当nDebugPort的值为-1时表示处于调试状态
	return nDebugPort==-1;
}

使用ProcessDebugObjectHandle

bool CheckDebug()
{
	HANDLE nDebugPort = 0;
	NtQueryInformationProcess(
		GetCurrentProcess(),
		(PROCESSINFOCLASS)0x1E,
		&nDebugPort,
		sizeof(nDebugPort),
		NULL);
	//当nDebugPort返回值不为空时表示进程处于调试状态
	return nDebugPort == NULL?false:true;
}

使用ProcessDebugFlags

bool CheckDebug()
{
	DWORD nDebugFlag = 0;
	NtQueryInformationProcess(
		GetCurrentProcess(),
		(PROCESSINFOCLASS)0x1F,//DebugFlag
		&nDebugFlag,
		sizeof(nDebugFlag),
		NULL);
	///当nDebugFlag返回值为0时表示进程处于调试状态
	return nDebugFlag == 0 ? true : false;
}

通过查看父进程ID

因为双击打开程序的话,父进程都是资源管理器,窗口名为Progman.
只要比对本进程的父进程ID和窗口名为Progman的进程ID是否相同即可。

bool NQIP_CheckParentProcess()
{
	struct PROCESS_BASIC_INFORMATION {
		ULONG ExitStatus; 		 // 进程返回码
		PPEB  PebBaseAddress; 		 // PEB地址
		ULONG AffinityMask; 		 // CPU亲和性掩码
		LONG  BasePriority; 		 // 基本优先级
		ULONG UniqueProcessId; 		 // 本进程PID
		ULONG InheritedFromUniqueProcessId; // 父进程PID
	}stcProcInfo;
	NtQueryInformationProcess(
		GetCurrentProcess(), 
		ProcessBasicInformation, //查看进程的基本信息,其中能够查看到父进程的PID
		&stcProcInfo,
		sizeof(stcProcInfo), NULL
		);
	DWORD ExplorerPID = 0;
	//获取本进程中的父进程ID
	DWORD CurrentPID = stcProcInfo.InheritedFromUniqueProcessId;
	//获取窗口名为"Progman"的进程ID
	GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerPID);
	printf("%d", ExplorerPID);
	return ExplorerPID == CurrentPID ? false : true;
}

判断系统是否处于调试状态

SystemKernelDebuggerInformation = 35 既(0x23);
使用这个字段(SYSTEM_INFORMATION_CLASS)SystemKernelDebuggerInformation ;
当操作系统开启内核调试模式时,会有一定特征,可以通过NtQuerySystemInformation API来获取

NTSTATUS NtQuerySystemInformation(
  SYSTEM_INFORMATION_CLASS SystemInformationClass,
  PVOID SystemInformation,
  ULONG SystemInformationLength,
  PULONG ReturnLength);

SYSTEM_INFORMATION_CLASS是个枚举类型:
当为0x23时为获取系统是否在被调试信息存放到第2个参数指向的结构中,该结构是2个1字节的结构,当处于调试时这2字节都会被写入1.

bool CheckDebug()
{
	struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION  {
		BOOLEAN DebuggerEanbled;
		BOOLEAN DebuggerNotPresent;
	}DebuggerInfo = { 0 };

	NtQuerySystemInformation(
		(SYSTEM_INFORMATION_CLASS)0x23,
		&DebuggerInfo,
		sizeof(DebuggerInfo),
		NULL);
	//能够检测当前操作系统是否处于调试模式,
	//处于调试模式,可能当前正在进行内核调试(Windbg);
	return DebuggerInfo.DebuggerEanbled;
}

检测是否存在调试对象

遍历系统内是否存在调试对象,若存在调试对象,则说明很可能被调试。

bool NQO__NtQueryObject()
{
	typedef struct _OBJECT_TYPE_INFORMATION {
		UNICODE_STRING TypeName;
		ULONG TotalNumberOfHanders;
		ULONG TotalNumberOfObjects;
	}OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
	
	typedef struct _OBJECT_ALL_INFORMATION {
		ULONG NumberOfObjectsTypes;
		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 + 200];
	NtQueryObject(NULL, 
		(OBJECT_INFORMATION_CLASS)0x03, 
		pObjectAllInfo, 
		uSize, 
		&uSize);
	
	// 3. 循环遍历并处理对象信息
	POBJECT_TYPE_INFORMATION pObjTypeInfo = pObjectAllInfo->ObjectTypeInfo;
	for (int i = 0; 
		i < pObjectAllInfo->NumberOfObjectsTypes;
		i++)
	{
		// 3.1 查看此对象的类型是否为DebugObject,还需要判断对象的数量,大于0则说明有调试对象
		if (!wcscmp(L"DebugObject", pObjTypeInfo->TypeName.Buffer))
			return true;
		
		// 3.2 获取对象名占用空间的大小(考虑到了结构体对齐问题)
		ULONG uNameLength = pObjTypeInfo->TypeName.Length;
		ULONG uDataLength = uNameLength - uNameLength%sizeof(ULONG) + sizeof(ULONG);
		// 3.3 指向下一个对象信息
		pObjTypeInfo = (POBJECT_TYPE_INFORMATION)pObjTypeInfo->TypeName.Buffer;
		pObjTypeInfo = (POBJECT_TYPE_INFORMATION)((PBYTE)pObjTypeInfo + uDataLength);
	}
	delete[] pObjectAllInfo;
	return false;
}

攻击调试器——ZwSetInformationThread

通过调用ntdll.dll中的ZwSetInformationThread(),使其第二个参数为ThreadHideFromDebugger,则不会将异常分发给调试器,做到对调试器的隐藏。

typedef enum THREAD_INFO_CLASS{
	ThreadHideFromDebugger = 17
};
typedef NTSTATUS(NTAPI *ZW_SET_INFORMATION_THREAD)(
	IN  HANDLE 			ThreadHandle,
	IN  THREAD_INFO_CLASS	ThreadInformaitonClass,
	IN  PVOID 			ThreadInformation,
	IN  ULONG 			ThreadInformationLength);

void ZSIT_DetachDebug()
{
	ZW_SET_INFORMATION_THREAD Func;
	Func = (ZW_SET_INFORMATION_THREAD)GetProcAddress(
		LoadLibrary(L"ntdll.dll"), "ZwSetInformationThread");
	//攻击调试器,将本进程和调试器分离。
	Func(GetCurrentThread(), ThreadHideFromDebugger, NULL, NULL);
}

指令混淆

通过加入一大堆无用代码来拉伸、混淆代码,例如:

void _declspec(naked) fun()
{
	
	_asm push 0;	//  6A 00 
	_asm jmp  hehe; //  EB 02
	_asm __emit 0xE8;// E8
	_asm __emit 0x12;// 12
hehe:
	_asm push 0;	 // 6A 00
	_asm jmp  haha;//   EB 02
	_asm __emit 0x0f;// 0F
	_asm __emit 0x15;// 15
haha:
	_asm push 0;
	_asm jmp  heihei;
	_asm __emit 0x56;
	_asm __emit 0x78;
heihei:
	_asm push 0;
	_asm call MessageBoxA;
	_asm ret;
}

你可能感兴趣的:(调试与反调试)