Software debugging learning notes - hardware breakpoints

Posted by nova on Mon, 03 Jan 2022 21:00:41 +0100

hardware breakpoint

Description:

  1. Unlike software breakpoints and memory breakpoints, hardware breakpoints do not depend on the debugged program, but on the debug register in the CPU.
  2. There are 7 debug registers, Dr0~Dr7 respectively.
  3. The user can set up to 4 hardware breakpoints because only Dr0~Dr3 are used to store linear addresses.
  4. Among them, Dr4 and Dr5 are reserved.


Think: if you write a linear address in the Dr0 register, will all threads be affected?
Answer: No, each thread has an independent register. When switching threads, the value of the register will also be switched.

Set hardware breakpoints

1) Dr0~Dr3 are used to set hardware breakpoints. Since there are only 4 breakpoint registers, only 4 hardware debugging breakpoints can be set at most.
2) Dr7 is the most important register:

  1. L0/G0 ~ L3/G3: controls whether Dr0~Dr3 is effective, local or global; After each exception, Lx is cleared, but Gx is not cleared.
  2. Breakpoint length (LENx): 00 (1 byte), 01 (2 bytes), 11 (4 bytes)
  3. Breakpoint type (R/Wx): 00 (execution breakpoint), 01 (write breakpoint), 11 (access breakpoint)

Trigger hardware breakpoint

Debugged process:
1) When the CPU executes, it detects that the current linear address is equal to the linear address in the debug register (Dr0~Dr3).
2) Check the IDT table to find the corresponding interrupt processing function (nt!_KiTrap01)
3)CommonDispatchException
4)KiDispatchException
5) DbgkForwardException collects and sends debugging events

Finally call DbgkpSendApiMessage(x, x)
First parameter: message type
Second parameter: whether to suspend other threads

Debugger process:
1) Circular judgment
2) Remove debug events
3) List information: register, memory
4) User processing

Handling hardware breakpoints

1) The exception generated by the hardware debugging breakpoint is STATUS_SINGLE_STEP (single step exception)
2) Detect B0~B3 of Dr6 register: which register triggers the exception

Experiment: setting and processing of hardware breakpoints

Experimental accessories: https://pan.baidu.com/s/16p7PDlSsBJeTnIttBGaXlQ Password: 9lro
1) Compile and run the following code:

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>

#define DEBUGGEE "C:\\helloworld.exe"

//Debugged process ID, process handle, OEP
DWORD dwDebuggeePID = 0;

//Debugged thread handle
HANDLE hDebuggeeThread = NULL;
HANDLE hDebuggeeProcess = NULL;

//System breakpoint
BOOL bIsSystemInt3 = TRUE;

//Data overwritten by INT 3
CHAR OriginalCode = 0;

//Thread context
CONTEXT Context;

typedef HANDLE (__stdcall *FnOpenThread) (DWORD, BOOL, DWORD);

VOID InitDebuggeeInfo(DWORD dwPID, HANDLE hProcess)
{
	dwDebuggeePID = dwPID;
	hDebuggeeProcess = hProcess;
}

DWORD GetProcessId(LPTSTR lpProcessName)
{
	HANDLE hProcessSnap = NULL;
	PROCESSENTRY32 pe32 = {0};
	
	hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if(hProcessSnap == (HANDLE)-1)
	{
		return 0;
	}
	
	pe32.dwSize = sizeof(PROCESSENTRY32);
	
	if(Process32First(hProcessSnap, &pe32))
	{
		do 
		{
			if(!strcmp(lpProcessName, pe32.szExeFile))
				return (int)pe32.th32ProcessID;
		} while (Process32Next(hProcessSnap, &pe32));
	}
	else
	{
		CloseHandle(hProcessSnap);
	}
	
	return 0;
}

BOOL WaitForUserCommand()
{
	BOOL bRet = FALSE;
	CHAR command;

	printf("COMMAND>");

	command = getchar();

	switch(command)
	{
	case 't':
		bRet = TRUE;
		break;
	case 'p':
		bRet = TRUE;
		break;
	case 'g':
		bRet = TRUE;
		break;
	}

	getchar();
	return bRet;
}

VOID SetHardBreakPoint(PVOID pAddress)
{
	//1. Get thread context
	Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
	GetThreadContext(hDebuggeeThread, &Context);
	//2. Set breakpoint position
	Context.Dr0 = (DWORD)pAddress;
	Context.Dr7 |= 1;
	//3. Set breakpoint length and type
	Context.Dr7 &= 0xfffcffff;	//Execution breakpoint (16, 17 position 0) 1 byte (18, 19 position 0)
	//5. Set thread context
	SetThreadContext(hDebuggeeThread, &Context);
}

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

	//1. Repair INT 3 to the original data (if it is a system breakpoint, it does not need to be repaired)
	if(bIsSystemInt3)
	{
		bIsSystemInt3 = FALSE;
		return TRUE;
	}
	else
	{
		WriteProcessMemory(hDebuggeeProcess, pExceptionInfo->ExceptionRecord.ExceptionAddress, &OriginalCode, 1, NULL);
	}

	//2. Display breakpoint location
	printf("Int 3 Breakpoint: 0 x%p \r\n", pExceptionInfo->ExceptionRecord.ExceptionAddress);

	//3. Get thread context
	Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
	GetThreadContext(hDebuggeeThread, &Context);
	
	//4. Revise EIP
	Context.Eip--;
	SetThreadContext(hDebuggeeThread, &Context);

	//5. Display disassembly code, register, etc

	//Set hardware breakpoints
	SetHardBreakPoint((PVOID)((DWORD)pExceptionInfo->ExceptionRecord.ExceptionAddress+1));
	
	//6. Wait for user command
	while(bRet == FALSE)
	{
		bRet = WaitForUserCommand();
	}
	
	return bRet;
}

BOOL AccessExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo)
{
	BOOL bRet = TRUE;

	return bRet;
}

BOOL SingleStepExceptionProc(EXCEPTION_DEBUG_INFO *pExceptionInfo)
{
	BOOL bRet = FALSE;

	//1. Get thread context
	Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
	GetThreadContext(hDebuggeeThread, &Context);
	//2. Judge whether the abnormality is caused by hardware breakpoint
	if(Context.Dr6 & 0xF)	//B0~B3 are not empty hardware breakpoints
	{
		//2.1 display breakpoint information
		printf("Hardware breakpoints:%x 0x%p \n", Context.Dr7&0x00030000, Context.Dr0);
		//2.2 removing breakpoints
		Context.Dr0 = 0;
		Context.Dr7 &= 0xfffffffe;
	}
	else	//Single step exception
	{
		//2.1 display breakpoint information
		printf("Single step: 0 x%p \n", Context.Eip);
		//2.2 removing breakpoints
		Context.Dr7 &= 0xfffffeff;
	}

	SetThreadContext(hDebuggeeThread, &Context);

	//6. Wait for user command
	while(bRet == FALSE)
	{
		bRet = WaitForUserCommand();
	}
	
	return bRet;
}

BOOL ExceptionHandler(DEBUG_EVENT *pDebugEvent)
{ 
	BOOL bRet = TRUE;
	EXCEPTION_DEBUG_INFO *pExceptionInfo = NULL;
	pExceptionInfo = &pDebugEvent->u.Exception;
	//Get the thread handle, which will be used later
	FnOpenThread MyOpenThread = (FnOpenThread)GetProcAddress(LoadLibrary("kernel32.dll"), "OpenThread");
	hDebuggeeThread = MyOpenThread(THREAD_ALL_ACCESS, FALSE, pDebugEvent->dwThreadId);

	switch(pExceptionInfo->ExceptionRecord.ExceptionCode)
	{
	//INT 3 exception
	case EXCEPTION_BREAKPOINT:
		bRet = Int3ExceptionProc(pExceptionInfo);
		break;
	//Access exception
	case EXCEPTION_ACCESS_VIOLATION:
		bRet = AccessExceptionProc(pExceptionInfo);
		break;
	//Single step execution
	case EXCEPTION_SINGLE_STEP:
		bRet = SingleStepExceptionProc(pExceptionInfo);
		break;
	}

	return bRet;
}

VOID SetInt3BreakPoint(LPVOID addr)
{
	CHAR int3 = 0xCC;
	
	//1. Backup
	ReadProcessMemory(hDebuggeeProcess, addr, &OriginalCode, 1, NULL);
	//2. Modification
	WriteProcessMemory(hDebuggeeProcess, addr, &int3, 1, NULL);
}

int main(int argc, char* argv[])
{
	BOOL nIsContinue = TRUE;
	DEBUG_EVENT debugEvent = {0};
	BOOL bRet = TRUE;
	DWORD dwContinue = DBG_CONTINUE;

	//1. Create debugging process
	STARTUPINFO startupInfo = {0};
	PROCESS_INFORMATION pInfo = {0};
	GetStartupInfo(&startupInfo);

	bRet = CreateProcess(DEBUGGEE, NULL, NULL, NULL, TRUE, DEBUG_PROCESS || DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &startupInfo, &pInfo);
	if(!bRet)
	{
		printf("CreateProcess error: %d \n", GetLastError());
		return 0;
	}

	hDebuggeeProcess = pInfo.hProcess;

	//2. Commissioning cycle
	while(nIsContinue)
	{
		bRet = WaitForDebugEvent(&debugEvent, INFINITE);
		if(!bRet)
		{
			printf("WaitForDebugEvent error: %d \n", GetLastError());
			return 0;
		}

		switch(debugEvent.dwDebugEventCode)
		{
		//1. Abnormal
		case EXCEPTION_DEBUG_EVENT:
			bRet = ExceptionHandler(&debugEvent);
			if(!bRet)
				dwContinue = DBG_EXCEPTION_NOT_HANDLED;
			break;
		//2.
		case CREATE_THREAD_DEBUG_EVENT:
			break;
		//3. Create process
		case CREATE_PROCESS_DEBUG_EVENT:
			//Set INT 3 breakpoint
			SetInt3BreakPoint((PCHAR)debugEvent.u.CreateProcessInfo.lpStartAddress);
			break;
		//4.
		case EXIT_THREAD_DEBUG_EVENT:
			break;
		//5.
		case EXIT_PROCESS_DEBUG_EVENT:
			break;
		//6.
		case LOAD_DLL_DEBUG_EVENT:
			break;
		//7.
		case UNLOAD_DLL_DEBUG_EVENT:
			break;
		//8.
		case OUTPUT_DEBUG_STRING_EVENT:
			break;
		}
		
		bRet = ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
	}

	return 0;
}

Operation results:

2) Enter g and enter to trigger the hardware breakpoint

3) Enter g again and enter to continue the program