6.硬件断点

调试寄存器

  • DR0~DR3:调试地址寄存器,用于设置硬件断点
  • DR4~DR5:保留,未公开具体作用
  • DR6:调试寄存器组状态寄存器
  • DR7:调试寄存器组控制寄存器

硬件断点的原理是使用DR0~DR3设置地址,使用DR7设置状态,因此最多设置4个。

6.硬件断点_第1张图片

<1> L0/G0 ~ L3/G3: L0/G0对应DR0,L1/G1对应DR1,以此类推…,用于控制Dr0~Dr3是否有效,Lx是局部的仅影响当前线程,Gx全局的影响当前进程中所有线程。
每次异常后,Lx都被清零,Gx不清零。

<2> 断点长度(LENx):x的下标为几就对应那个控制寄存器,00(1字节) 01(2字节) 11(4字节)

<3> 断点类型(R/Wx):x的下标为几就对应那个控制寄存器,00(执行断点) 01(写入断点) 11(访问断点)

被调试进程

  1. CPU执行时检测到调试寄存器(Dr0~Dr3)
  2. 查IDT表找到对应的断处理函数nt!KiTrap01(1号门)
  3. CommonDispatchException
  4. KiDispatchException।
  5. DbgkForwardException收集并发送调试事件
DbgkpSendApillessage (x, x)
1)第一个参数:消息结构每种消息都有自己的消息结构共有7种类型
2)第二个参数,要不要把本进程内除了自己之外的其他线程挂起.有些消息需要把其他线程挂起,比如异常

这个过程到第3步时,所有断点都一样执行流程都一样。

处理硬件断点:

  1. 硬件调试断点产生的异常是 STATUS_SINGLE_STEP(单步异常
  2. B0~B3:哪个调试地址寄存器触发的异常

设置硬件断点要设置到所属线程的上下文。

单步异常

不单只有硬件断点能触发单步异常,EFLAGS寄存器也可以
6.硬件断点_第2张图片

设置单步运行:

TF位置1

处理单步异常:

单步产生的异常是 STATUS_SINGLE_STEP(单步异常)

TF位置1的时候每走完一步就会触发单步异常。

单步步入:
遇到CALL指令会跟进去

单步步过:
遇到CALL不会跟进去,实现原理是计算当指令长度,当前指令长度+当前eip的地方下断(也就是call指令的下一行)。

如果写代码修改返回地址,单步步入没事,单步步过就会跑飞。
6.硬件断点_第3张图片

下面代码只有单步步入

//被调试进程句柄
HANDLE hDebuggeeProcess = NULL;
//被调试线程句柄
HANDLE hDebuggeeThread;
//系统断点
BOOL bIsSystemInt3 = TRUE;
//被INT 3覆盖的数据
char OriginalCode = 0;
//线程上下文
CONTEXT Context = { 0 };

BOOL WaitForUserCommand()
{
	BOOL bRet = FALSE;
	char cContext = 0;
	printf("COMMAND> ");
	scanf("%c",&cContext);

	switch (cContext)
	{

	case 't':
		bRet = TRUE;
		//1.获取线程上下文
		Context.ContextFlags = CONTEXT_FULL || CONTEXT_DEBUG_REGISTERS;
		GetThreadContext(hDebuggeeThread, &Context);
		//2.设置单步执行
		Context.EFlags |= 0x100;
		//3.设置线程上下文
		SetThreadContext(hDebuggeeThread, &Context);
		break;

	case 'p':
		bRet = TRUE;
		break;

	case  'g':
		bRet = TRUE;
		break;
	}

	return bRet;
}

VOID setHardBreakPoint(PVOID addr)
{
	//1.获取线程上下文
	Context.ContextFlags = CONTEXT_ALL;
	SuspendThread(hDebuggeeThread);//挂起线程
	GetThreadContext(hDebuggeeThread,&Context);
	

	//2.设置断点位置
	Context.Dr0 = (DWORD)addr;
	Context.Dr7 |= 1;

	//3.设置断点长度
	Context.Dr7 &= 0xfff0ffff;


	//4.设置线程上下文
	BOOL a =  SetThreadContext(hDebuggeeThread, &Context);
	ResumeThread(hDebuggeeThread);//恢复线程
}

BOOL Int3ExceptionProc(EXCEPTION_DEBUG_INFO* excp)
{
	BOOL bRet = FALSE;

	//1将INT 3修复为原来的数据(如果是系统断点不用修复)
	if (bIsSystemInt3)
	{
		bIsSystemInt3 = FALSE;
		return TRUE;
	}
	else
	{
		//还原
		WriteProcessMemory(hDebuggeeProcess, excp->ExceptionRecord.ExceptionAddress, &OriginalCode, 1, NULL);
	}


	//2.显示断点位置
	printf("int3 断点 0x%p\n", excp->ExceptionRecord.ExceptionAddress);

	//3.获取线程上下文
	Context.ContextFlags = CONTEXT_FULL || CONTEXT_DEBUG_REGISTERS;
	bool a = GetThreadContext(hDebuggeeThread, &Context);

	//4.修正EIP(0xcc断下已经走了一个字节,Eip要-1)
	Context.Eip --;
	SetThreadContext(hDebuggeeThread, &Context);

	//5.显示反汇编代码
	//省略..

	//硬件断点
	setHardBreakPoint((PVOID)((DWORD)excp->ExceptionRecord.ExceptionAddress+1));


	//6.等待调试者命令
	while (bRet == FALSE)
	{
		bRet = WaitForUserCommand();
	}

	return bRet;
}

BOOL SingleStepExceptionProc(EXCEPTION_DEBUG_INFO* excp)
{
	BOOL bRet = FALSE;
	//1.取线程上下文
	Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
	GetThreadContext(hDebuggeeThread, &Context);

	//2.判断是否是硬件断点导致的异常
	if (Context.Dr6 & 0xf)//B0-B3不为NULL
	{
		//2.1显示断点信息
		printf("硬件断点:%X %X\n", Context.Dr7 & 0x0003000, Context.Dr0);
		//2.2将断点去除
		Context.Dr0 = 0;
		Context.Dr7 = 0xfffffffe;
	}
	else
	{
		//2.1显示断点信息
		printf("单步->0x%p\n", Context.Eip);

		//2.2将单步去除
		Context.EFlags &= 0xfffffeff;

	}
	//3.设置线程上下文
	SetThreadContext(hDebuggeeThread, &Context);

	//等待用户命令
	while (bRet == FALSE)
	{
		bRet = WaitForUserCommand();
	}


	return bRet;
}

BOOL ExceptionHandler(DEBUG_EVENT* pDebugEvent)
{
	BOOL bRet = TRUE;
	EXCEPTION_DEBUG_INFO* pExceptionInfo = NULL;

	pExceptionInfo = &pDebugEvent->u.Exception;

	//得到线程句柄 后面要用
	pMyOpenThread MyOpenThread = (pMyOpenThread)GetProcAddress(LoadLibrary(L"Kernel32.dll"),"OpenThread");
	hDebuggeeThread = MyOpenThread(THREAD_ALL_ACCESS,FALSE,pDebugEvent->dwThreadId);


	if (pExceptionInfo->ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT)
	{
		bRet = Int3ExceptionProc(pExceptionInfo);
		return bRet;
	}


	if (pExceptionInfo->ExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP)
	{
		bRet = SingleStepExceptionProc(pExceptionInfo);
		return bRet;
	}

	return bRet;
}

void SetInt3BreakPoint(LPVOID addr)
{
	ReadProcessMemory(hDebuggeeProcess, addr, &OriginalCode, 1, NULL);

	BYTE int3[1] = { 0xcc };
	WriteProcessMemory(hDebuggeeProcess, addr, int3, 1, NULL);
}

void CALL()
{
	BOOL nIsConinue = TRUE;
	DEBUG_EVENT debugEvent = { 0 };
	DWORD dwContext = DBG_CONTINUE;

	//1.创建调试进程
	STARTUPINFO startupInfo = { 0 };
	PROCESS_INFORMATION pInfo = { 0 };
	GetStartupInfo(&startupInfo);

	WCHAR* _ProcessName = { L"C:\\Users\\Administrator\\Desktop\\控制台测试\\Debug\\0000.exe" };
	BOOL bRet = CreateProcess(_ProcessName, NULL, NULL, NULL, TRUE, DEBUG_PROCESS || DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &startupInfo, &pInfo);
	if (bRet == FALSE)
	{
		printf("CreateProcess error:%d\n", GetLastError());
		return;
	}
	hDebuggeeProcess = pInfo.hProcess;

	//调试循环
	while (nIsConinue)
	{
		BOOL bRet = WaitForDebugEvent(&debugEvent, INFINITE);//取DEBUG_EVENT
		if (!bRet)
		{
			printf("WaitForDebugEvent error:%d\n", GetLastError());
			return;
		}


		switch (debugEvent.dwDebugEventCode)
		{

		case EXCEPTION_DEBUG_EVENT:
			bRet = ExceptionHandler(&debugEvent);
			if (!bRet)
				dwContext = DBG_EXCEPTION_NOT_HANDLED;
			break;

		case CREATE_THREAD_DEBUG_EVENT:
			break;

		case  CREATE_PROCESS_DEBUG_EVENT:
			//进程创建的时候 在DEP处设置断点就是将OEP处的一个字节改为0xCC(INT 3)
			SetInt3BreakPoint((PCHAR)debugEvent.u.CreateProcessInfo.lpStartAddress);
			break;

		case EXIT_THREAD_DEBUG_EVENT:
			break;

		case EXIT_PROCESS_DEBUG_EVENT:
			break;

		case LOAD_DLL_DEBUG_EVENT:
			break;

		case UNLOAD_DLL_DEBUG_EVENT:
			break;

		}

		bRet = ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
	}

}

你可能感兴趣的:(软件调试)