编写一个简易调试器

复习一下调试器,简单写点代码,调试器调试程序有两种方式,一种是创建进程,另一种便是附加:

while (TRUE)
	{
		printf("1.创建新的调试进程\n");
		printf("2.附加待调试的进程\n");
		int input = 0;
		scanf_s("%d", &input);
		if (input == 1)
		{
			printf("请输入调试进程的路径:\n");
			char Path[MAX_PATH] = { 0 };
			scanf_s("%s", Path, MAX_PATH);
			CreateDebugProcess(Path);//创建
			break;
		}
		else if (input == 2)
		{

			printf("请输入附加进程的PID\n");
			DWORD dwPid = 0;
			scanf_s("%d", &dwPid);
			if (!DebugActiveProcess(dwPid))//windows API附加进程
			{
				DisplayError("AttachProcess Error");
			}
			break;
		}
		else
		{
			printf("请重新输入\n");
		}
	}

实现CreateDebugProcess(),创建一个调试进程

VOID CreateDebugProcess(char* ProcessPath)
{
	PROCESS_INFORMATION ProcessInfo = { 0 };
	STARTUPINFOA StartupInfo = { sizeof(STARTUPINFO) };
	// 以调试方式创建进程
	BOOL result = CreateProcessA(ProcessPath, NULL, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE, NULL, NULL, &StartupInfo, &ProcessInfo);
	if (!result)
	{
		DisplayError("CraeteProcessA Error");
	}
	CloseHandle(ProcessInfo.hProcess);//泄露句柄
	CloseHandle(ProcessInfo.hThread);//泄露句柄
	system("cls");
}

然后进入调试主循环,附加调试那块有点问题,还没修复。

VOID DebugRun()
{
	/*参考微软的调试器主循环 https://docs.microsoft.com/en-us/windows/win32/debug/writing-the-debugger-s-main-loop */
	
	DEBUG_EVENT DebugEvent = { 0 };
	csh caphandle = NULL;//接收反汇编引擎capstone句柄
	caphandle = InitCapstoneEngine();//初始化capstone引擎
	while (WaitForDebugEvent(&DebugEvent, INFINITE))//等待事件通知
	{
		switch (DebugEvent.dwDebugEventCode)
		{
			case EXCEPTION_DEBUG_EVENT://异常调试事件
			{
				BOOL ret = ExceptionEvent(DebugEvent, caphandle);
				break;
			}
			case CREATE_PROCESS_DEBUG_EVENT://进程创建事件
			{
				LPVOID ptrEntryPoint = DebugEvent.u.CreateProcessInfo.lpStartAddress;//指向线程起始地址的指针
				SetSoftBreakPoint(DebugEvent.dwProcessId, ptrEntryPoint, TRUE);//一次性CC断点
				//ExceptionEvent(DebugEvent, caphandle);
				break;
			}
			case EXIT_PROCESS_DEBUG_EVENT://进程退出事件
			{
				DebugActiveProcessStop(DebugEvent.dwProcessId);
				printf_s("[*]  进程已退出......");
				break;     
			}
			/*更多功能待续...*/
		}
		ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_CONTINUE);
	}
}

设置软件断点,将对应地址处的一字节指令改为CC

BOOL SetSoftBreakPoint(DWORD PID, LPVOID Address, BOOL BpAttribute)
{
	HANDLE ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
	/*定义一个结构体来存放cc断点信息*/
	CCBREAKPOINTINFO ccInfo = { Address };
	ccInfo.BpAttribute = BpAttribute;//是否为永久CC断点
	ReadProcessMemory(ProcessHandle, Address, &ccInfo.code, 1, NULL);
	WriteProcessMemory(ProcessHandle, Address, "\xCC", 1, NULL);
	BreakPointList.push_back(ccInfo);//将设置的断点保存到链表中
	CloseHandle(ProcessHandle);

	return TRUE;
}

编写调试的异常处理函数ExceptionEvent();具体异常未编写,待与用户交互完回来写比较方便。

BOOL ExceptionEvent(DEBUG_EVENT DebugEvent, csh caphandle)
{
	PROCESSINFO ProcessInfo = { caphandle };
	ProcessInfo.ExceptionCode = DebugEvent.u.Exception.ExceptionRecord.ExceptionCode;
	ProcessInfo.ExceptionAddr = DebugEvent.u.Exception.ExceptionRecord.ExceptionAddress;
	ProcessInfo.hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DebugEvent.dwProcessId);
	ProcessInfo.hThread = OpenThread(PROCESS_ALL_ACCESS, FALSE, DebugEvent.dwThreadId);
	ProcessInfo.PID = DebugEvent.dwProcessId;
	ProcessInfo.TID = DebugEvent.dwThreadId;

	//当进程被创建的时候,操作系统会检测当前的进程是否处于被调试状态,如果被调试了,就会通过 int 3 设置一个软件断点,这个断点通常不需要处理
	if (first && ProcessInfo.ExceptionCode == EXCEPTION_BREAKPOINT)
	{
		ProcessInfo.ExceptionAddr = (char*)ProcessInfo.ExceptionAddr + 1;
		first = FALSE;
		return TRUE;
	}
	switch (ProcessInfo.ExceptionCode)
	{
		case EXCEPTION_ACCESS_VIOLATION://线程试图读取或写入对其没有适当访问权限的虚拟地址。
		
			break;

		case EXCEPTION_BREAKPOINT://遇到断点。
		
			break;
			
		case EXCEPTION_SINGLE_STEP://跟踪陷阱或其他单指令机制表明已执行了一条指令。
            
			break;
		
		default:
		// Handle other exceptions.
		{
			printf_s("未处理异常类型(%08X): %p\n", ProcessInfo.ExceptionCode, ProcessInfo.ExceptionAddr);
			break;
		}
	}
	//反汇编
	Disassembler(ProcessInfo, ProcessInfo.ExceptionAddr, 10);
	//命令交互
	CmdCommend(ProcessInfo);
	CloseHandle(ProcessInfo.hProcess);
	CloseHandle(ProcessInfo.hThread);
	return TRUE;
}

首先忽略系统设置的哪个异常

//当进程被创建的时候,操作系统会检测当前的进程是否处于被调试状态,如果被调试了,就会通过 int 3 设置一个软件断点,这个断点通常不需要处理
	if (first && ProcessInfo.ExceptionCode == EXCEPTION_BREAKPOINT)
	{
		ProcessInfo.ExceptionAddr = (char*)ProcessInfo.ExceptionAddr + 1;
		first = FALSE;
		return TRUE;
	}

反汇编函数Disassembler(),展示之前把CC改回来,展示完了把CC改回去。

VOID  Disassembler(PROCESSINFO ProcessInfo, LPVOID addr, DWORD num)
{
	DWORD dwWrite = 0;
	cs_insn* ins = nullptr;//反汇编引擎的指令位置指针
	PCHAR buff = new CHAR[num * 16]();
	RemoveSoftBreakPoint(ProcessInfo, FALSE, NULL);//清除CC
	// 读取指定长度的内存空间
	ReadProcessMemory(ProcessInfo.hProcess, (LPVOID)addr, buff, num * 16, &dwWrite);
	size_t nCount = cs_disasm(ProcessInfo.capHandle, (uint8_t*)buff, num * 16, (uint64_t)addr, 0, &ins);//接收反汇编指令
	for (DWORD i = 0; i < nCount && i < num; ++i)
	{
		printf("%08X\t", (UINT)ins[i].address);

		int tmp = 0;
		while (ins[i].size)
		{
			printf_s("%02X", ins[i].bytes[tmp]);//循环打印机器码
			tmp++;
			ins[i].size -= 1;
		}
		// 输出对应的反汇编
		printf("\t\t%s  %s\t", ins[i].mnemonic, ins[i].op_str);
		// 如果截取到的是call指令
		printf("\n");
	}
	printf("\n");
	// 释放动态分配的空间
	delete[] buff;
	cs_free(ins, nCount);
	//恢复CC
	RecoverSoftBreakPoint(ProcessInfo, FALSE, NULL);
}

移除CC断点,得判断是单个断点,还是所有断点,以此判断是要反汇编,还是CC断点命中了。

VOID RemoveSoftBreakPoint(PROCESSINFO ProcessInfo, BOOL boole, LPVOID addr) 
{
	if (boole == TRUE)//修复单个断点
	{
		for (int i = 0; i < BreakPointList.size(); i++)
		{
			if (BreakPointList[i].addr == addr)
			{
				CONTEXT ct = { CONTEXT_CONTROL };
				GetThreadContext(ProcessInfo.hThread, &ct);
				ct.Eip -= 1;
				SetThreadContext(ProcessInfo.hThread, &ct);
				WriteProcessMemory(ProcessInfo.hProcess, BreakPointList[i].addr, &(BreakPointList[i].code), 1, NULL);

			}
		}
	}
	else//修复所有断点
	{
		for (int i = 0; i < BreakPointList.size(); i++)
		{
			WriteProcessMemory(ProcessInfo.hProcess, BreakPointList[i].addr, &(BreakPointList[i].code), 1, NULL);
		}
	}
}

恢复CC断点

VOID RecoverSoftBreakPoint(PROCESSINFO ProcessInfo, BOOL boole, LPVOID addr)
{
	if (boole == TRUE)
	{
		for (int i = 0; i < BreakPointList.size(); i++)
		{
			if (BreakPointList[i].addr == addr)
			{
				CONTEXT ct = { CONTEXT_CONTROL };
				GetThreadContext(ProcessInfo.hThread, &ct);
				ct.Eip -= 1;
				SetThreadContext(ProcessInfo.hThread, &ct);
				WriteProcessMemory(ProcessInfo.hProcess, BreakPointList[i].addr, "\xCC", 1, NULL);
			}
		}
	}
	else
	{
		for (int i = 0; i < BreakPointList.size(); i++)
		{
			CONTEXT cont = { 0 };
			cont.ContextFlags = CONTEXT_CONTROL;
			GetThreadContext(ProcessInfo.hThread, &cont);
			if (cont.Eip == (DWORD)(BreakPointList[i].addr))
			{
				break;//当断点是EIP时不需要恢复
			}
			WriteProcessMemory(ProcessInfo.hProcess, BreakPointList[i].addr, "\xCC", 1, NULL);
		}
	}
}

然后和用户进行交互,获取线程上下文

VOID CmdCommend(PROCESSINFO ProcessInfo)
{
	char input[MAX_PATH] = { 0 };
	CONTEXT ct = { 0 };
	ct.ContextFlags = CONTEXT_ALL;
	GetThreadContext(ProcessInfo.hThread, &ct);
	while (true)
	{
		scanf_s("%s", &input, MAX_PATH);
		fflush(stdin);
		if (!strcmp(input,"g"))
		{
			break;
		}
		else if (!strcmp(input, "u")|| !strcmp(input, "U"))
		{
			//反汇编
			DWORD Address = ct.Eip, lines = 5;
			if (!strcmp(input, "U"))
			{
				scanf_s("%x %d", &Address, &lines);
			}
			Disassembler(ProcessInfo, (LPVOID)Address, lines);
		}
		else if (!strcmp(input, "r") || !strcmp(input, "reg"))
		{
			//查看寄存器
			GetRegister(ProcessInfo.hThread);
		}
		else if (!strcmp(input, "k"))
		{
			//栈信息
			GetStack(ProcessInfo.hProcess, ProcessInfo.hThread);
		}
		else if (!strcmp(input, "d") || !strcmp(input, "dd"))
		{
			DWORD Address = 0;
			scanf_s("%x", &Address);
			//查看内存
			GetMemory(ProcessInfo.hProcess, Address);
		}
		else if (!strcmp(input, "lm"))
		{
			//查看模块
			GetModules(ProcessInfo.PID);
		}
		else if (!strcmp(input, "bl"))
		{
			//查看断点
			ViewBreakPoint(ProcessInfo);
		}
		else if (!strcmp(input, "bp"))
		{
			//下CC断点
			DWORD Address = 0;
			scanf_s("%x", &Address, sizeof(DWORD));
			SetSoftBreakPoint(ProcessInfo.PID, (LPVOID)Address, TRUE);
		}
		else if (!strcmp(input, "ba"))
		{
			//下硬件断点
			DWORD Address = 0, len = 0;
			CHAR flag[MAX_PATH] = { 0 };
			scanf_s("%s", flag, MAX_PATH);
			scanf_s("%d %x", &len, &Address);
			SetHBreakPoint(ProcessInfo.hThread, flag, len, Address);
		}
		else if (!strcmp(input, "bm"))
		{
			//下内存断点
			DWORD Address = 0, len = 0;
			CHAR flag[MAX_PATH] = { 0 };
			scanf_s("%s", flag, MAX_PATH);
			scanf_s("%x", &Address);
			SetMemBreakPoint(ProcessInfo.hProcess, flag, Address);
		}
		else if (!strcmp(input, "bc"))
		{
			//清除断点
			DWORD Address = 0;
			scanf_s("%x", &Address);
			ClearBreakPoint(ProcessInfo, (PVOID)Address);
		}
		else if (!strcmp(input, "t"))
		{
			//单步步入
			SetTFFlag(ProcessInfo.hThread);
			break;
		}
		else if (!strcmp(input, "p"))
		{
			//单步步过
			SetStepFlag(ProcessInfo);
			break;
		}
		else if (!strcmp(input, ".r"))
		{
			//修改内存值.寄存器值
			DWORD value = 0;
			CHAR  Register[MAX_PATH] = { 0 };
			scanf_s("%s", Register, MAX_PATH);
			scanf_s("%x", &value);
			ChangeRegValue(ProcessInfo, Register, value);
		}
		else if (!strcmp(input, "em"))
		{
			//修改内存值.寄存器值
			DWORD address = 0, value = 0;
			scanf_s("%x %x", &address, &value);
			ChangeMemValue(ProcessInfo, address, value);
		}
		else if (!strcmp(input, "asm"))
		{
			//修改汇编,暂未实现
			break;
		}
		else if (!strcmp(input, "dump"))
		{
			//dump内存暂未实现,windbg格式 .writemem c:\\1.bin Addr  L1000
			break;
		}
		else if (!strcmp(input, "h") || !strcmp(input, "help"))
		{
			//帮助
			GetHelp();
		}
		else
		{
			printf_s("!!!指令错误,重新输入\n");
		}
	}
}

查看寄存器,通过线程上下文***CONTEXT***结构体来实现。

/*获取寄存器*/
VOID GetRegister(HANDLE hThread)
{
	CONTEXT context = { 0 };
	context.ContextFlags = CONTEXT_ALL;
	GetThreadContext(hThread, &context);
	printf("EAX = %08X\t", context.Eax);
	printf("EBX = %08X\t", context.Ebx);
	printf("ECX = %08X\t", context.Ecx);
	printf("EDX = %08X\n", context.Edx);
	printf("ESI = %08X\t", context.Esi);
	printf("EDI = %08X\t", context.Edi);
	printf("ESP = %08X\t", context.Esp);
	printf("EBP = %08X\n", context.Ebp);
	printf("\tEIP = %08X\t\t\t", context.Eip);
	printf("EFLAGS = %08X\n", context.EFlags);
	printf("CS = %04X  ", context.SegCs);
	printf("SS = %04X  ", context.SegSs);
	printf("DS = %04X  ", context.SegDs);
	printf("ES = %04X  ", context.SegEs);
	printf("FS = %04X  ", context.SegFs);
	printf("GS = %04X \n", context.SegGs);
}

获取栈信息,取ESP的值,然后获取栈

VOID GetStack(HANDLE hProcess, HANDLE hThread)
{
	PWORD buf[512] = { 0 };
	DWORD dwRead = 0;
	int tmp = 0;
	CONTEXT context = { 0 };
	context.ContextFlags = CONTEXT_ALL;
	GetThreadContext(hThread, &context);
	
	ReadProcessMemory(hProcess, (LPVOID)context.Esp, buf, 4 * 20, &dwRead);
	while (tmp < 20)
	{
		printf_s("[%08X]\t%08X\n", context.Esp + tmp * 4, buf[tmp]);
		tmp++;
	}
}

获取内存数据,直接ReadProcessMemory()就行

/*获取内存数据*/
VOID GetMemory(HANDLE hProcess, DWORD Address)
{
	PDWORD buf[512] = { 0 };
	DWORD dwWrite = 0;
	ReadProcessMemory(hProcess, (LPVOID)Address, buf, 0x200, &dwWrite);
	for (int tmp = 0; (tmp * 4) < dwWrite; tmp++)
	{
		if ((tmp * 4) % 0x10 == 0)
		{
			printf("\n[%08X]\t", Address + tmp * 4);//0x10为首地址
		}
		printf_s("%08X ", buf[tmp]);//单字节打印
	}
	printf("\n");
}

查看模块,通过遍历快照实现

VOID GetModules(DWORD PID)
{
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, PID);
	if (hSnap == INVALID_HANDLE_VALUE)
		return;
	MODULEENTRY32 me = { sizeof(MODULEENTRY32) };
	if (!Module32First(hSnap, &me))
	{
		CloseHandle(hSnap);
		return;
	}
	BOOL ret = TRUE;
	while (ret)
	{
		printf_s("[%08X]\t", me.modBaseAddr);
		printf_s("[%s]\n", (PWCHAR)me.szExePath);
		ret = Module32Next(hSnap, &me);
	}
}

查看断点,通过线程上下文***CONTEXT***结构体来拿到DRn寄存器,首先DR7是调试控制寄存器,通过其对应位数进行判别。
在这里插入图片描述

简称全称比特位描述
R/W0 ~ R/W3读写域R/W0:16、17
R/W1:20、21
R/W2:24、25
R/W3:28、29
•00:执行对应地址的指令时中断
•01:向对应地址写数据时中断
•10:向对应地址读写时中断
•11:向对应地址读数据时中断
LEN0 ~ LEN3长度域LEN0:18,19
LEN1:22,23
LEN2:26,27
LEN3:30,31
•00:1字节长
•01:2字节长
•10:8字节长
•11:4字节长
L0 ~ L3局部断点启用L0:0
L1:2
L2:4
L3:6
•0:禁止对应硬件断点
•1:启用对应硬件断点(一次性中断)
G0 ~ G3
全部断点启用G0:1
G1:3
G2:5
G3:7
•0:禁用对应硬件断点
•1:启用对应硬件断点
LE和GE本地和全局断点启用LE:8
GE:9
从486开始的IA-32处理器都忽略
这两位的设置。此前这两位是用
来启用或禁用数据断点匹配的
GDGeneral Detect13启用或禁用对调试寄存器的保护
当设为1时,如果CPU检测到将
修改调试寄存器(DR0-DR7)
的指令时,CPU在执行这条指
令前产生一个调试异常

调试状态寄存器DR6
在这里插入图片描述

简称全称比特位描述
B0BreakPoint 00如果处理器检测到满足条件0的情况,
那么处理器在调用异常处理程序前将此位置为1
B1BreakPoint 11如果满足条件1,同上
B2BreakPoint 22如果满足条件2,同上
B3BreakPoint 33如果满足条件3,同上
BDDebug Register
Access Detected
13这一位与DR7的GD位相联系,当GD位被置为1
当CPU发现DR0~DR7被修改时,将此位置1
BSSingle Step14这一位与EFLAGS的TF标志位相联系,如果该位为1
表示异常是由单步执行(Single Step)触发的
BTTask Switch
15
15这一位与任务状态段TSS的T标志位相联系,
CPU在切换任务时发现下一个任务TSS的T标志为1时
会将BT位置为1

函数实现

/*查看断点*/
VOID  ViewBreakPoint(PROCESSINFO ProcessInfo)
{
	printf_s("CC断点列表:\n");
	for (int i = 0; i < BreakPointList.size(); i++)
	{
		printf_s("[%d]\t%08X\n", i + 1, BreakPointList[i].addr);
	}
	CONTEXT context = { 0 };
	context.ContextFlags = CONTEXT_ALL;
	GetThreadContext(ProcessInfo.hThread, &context);
	printf_s("DR断点列表:\n");
	printf_s("[*]\tDR0 = %08X\t", context.Dr0);//16-17
	//详情参看《软件调试》
	DWORD flag = context.Dr7 & 0x30000;
	if (context.Dr0)
	{
		if (flag == 0x30000)
		{
			printf_s("W");
		}
		else if (flag == 0)
		{
			printf_s("E");
		}
		else
		{
			printf_s("R");
		}
	}
	printf_s("\n[*]\tDR1 = %08X\t", context.Dr1);//20-21
	flag = context.Dr7 & 0x300000;
	if (context.Dr1)
	{
		if (flag == 0x300000)
		{
			printf_s("W");
		}
		else if (flag == 0)
		{
			printf_s("E");
		}
		else
		{
			printf_s("R");
		}
	}
	printf_s("\n[*]\tDR2 = %08X\t", context.Dr2);//24-25
	flag = context.Dr7 & 0x3000000;
	if (context.Dr2)
	{
		if (flag == 0x3000000)
		{
			printf_s("W");
		}
		else if (flag == 0)
		{
			printf_s("E");
		}
		else
		{
			printf_s("R");
		}
	}
	printf_s("\n[*]\tDR3 = %08X\t", context.Dr3);//28-29
	flag = context.Dr7 & 0x30000000;
	if (context.Dr3)
	{
		if (flag == 0x30000000)
		{
			printf_s("W");
		}
		else if (flag == 0)
		{
			printf_s("E");
		}
		else
		{
			printf_s("R");
		}
	}
	printf_s("\n内存断点列表:\n");
	for (int y = 0; y < MemoryBreakList.size(); y++)
	{
		printf_s("[%d]\t%08X\t", y + 1, MemoryBreakList[y].addr);
		if (MemoryBreakList[y].dwNewProtect == PAGE_EXECUTE_WRITECOPY)
		{
			printf_s("R\n");
		}
		else if (MemoryBreakList[y].dwNewProtect == PAGE_EXECUTE_READ)
		{
			printf_s("W\n");
		}
		else
		{
			printf_s("E\n");
		}
	}
}

设置硬件断点,先判断下的是什么类型的断点,然后判断长度,然后修改DRn寄存器,进行设置

/*设置硬件断点*/
VOID SetHBreakPoint(HANDLE hThread, char* flag, DWORD len, DWORD Address)
{
	DWORD type = 0, les = 0;
	CONTEXT ct = { CONTEXT_DEBUG_REGISTERS };
	GetThreadContext(hThread, &ct);
	if (flag)//类型
	{
		if (!strcmp(flag, "r"))
		{
			type = 1;//01
		}
		else if (!strcmp(flag, "w"))
		{
			type = 3;//11
		}
		else if (!strcmp(flag, "e"))
		{
			type = 0;//00
		}
	}
	if (len)//长度
	{
		if (len == 1)
		{
			les = 0;//01
		}
		else if (len == 2)
		{
			les = 1;//11
		}
		else if (len == 4)
		{
			les = 3;//00
		}
	}
	if (type == 1 || type == 3)//读写断点需要内存对齐
	{
		if (les == 1)//对齐粒度
		{
			Address = (Address % 2) ? (Address - (Address % 2)) : Address;
		}
		else if (les == 3)
		{
			Address = (Address % 4) ? (Address - (Address % 4)) : Address;
		}
	}
	if (Address)
	{
		//00:执行         01:写入        11:读写
		//00:1字节       01:2字节      11:4字节
		if ((ct.Dr7 & 0x1) == 0)//0、2、4、6
		{
			//DR0空闲
			ct.Dr0 = Address;
			ct.Dr7 |= 0x1;
			ct.Dr7 |= (les * 0x40000);//18-19
			ct.Dr7 |= (type * 0x10000);//16-17

		}
		else if ((ct.Dr7 & 0x4) == 0)
		{
			//DR1空闲
			ct.Dr1 = Address;
			ct.Dr7 |= 0x4;
			ct.Dr7 |= (les * 0x400000);
			ct.Dr7 |= (type * 0x100000);
		}
		else if ((ct.Dr7 & 0x10) == 0)
		{
			//DR2空闲
			ct.Dr2 = Address;
			ct.Dr7 |= 0x10;
			ct.Dr7 |= (les * 0x4000000);
			ct.Dr7 |= (type * 0x1000000);
		}
		else if ((ct.Dr7 & 0x40) == 0)
		{
			//DR3空闲
			ct.Dr3 = Address;
			ct.Dr7 |= 0x40;
			ct.Dr7 |= (les * 0x4000000);
			ct.Dr7 |= (type * 0x10000000);
		}
		else
		{
			printf_s("硬件断点已用完");
		}
	}
	SetThreadContext(hThread, &ct);
}

设置内存断点,先判断是否在MemoryBreakList里面,然后通过VirtualProtectEx修改内存保护属性进行设置

/*设置内存断点*/
VOID SetMemBreakPoint(HANDLE hProcess, char* flag, DWORD Address)
{
	MEMORYPOINTINFO mbp = { 0 };
	mbp.addr = Address & 0xFFFFF000;
	for (int i = 0; i < MemoryBreakList.size(); i++)
	{
		if (mbp.addr == MemoryBreakList[i].addr)
		{
			printf_s("目标内存页已存在内存断点\n");
			return;//防止一页内存多个内存断点
		}

	}
	if (!strcmp(flag, "r"))
	{
		mbp.dwNewProtect = PAGE_NOACCESS;
		//暂时用内存访问断点代替
	}
	else if (!strcmp(flag, "w"))
	{
		mbp.dwNewProtect = PAGE_EXECUTE_READ;
	}
	else if (!strcmp(flag, "e"))
	{
		mbp.dwNewProtect = PAGE_READWRITE;
	}
	else
	{
		printf("不存在页面属性");
		return;
	}
	if (!VirtualProtectEx(hProcess, (LPVOID)mbp.addr, 0x1000, mbp.dwNewProtect, &mbp.dwOldProtect))
	{
		printf_s("内存断点下达失败\n");
		return;
	}
	MemoryBreakList.push_back(mbp);
}

清除断点,遍历断点列表进行清除

/*清除断点*/
VOID ClearBreakPoint(PROCESSINFO ProcessInfo, PVOID address)
{
	for (int i = 0; i < BreakPointList.size(); i++)
	{
		if (BreakPointList[i].addr == address)
		{
			WriteProcessMemory(ProcessInfo.hProcess, BreakPointList[i].addr, &(BreakPointList[i].code), 1, NULL);//修复后删除
			BreakPointList.erase(BreakPointList.begin() + i);
		}
	}
}

单步步入模式,通过线程上下文***CONTEXT***结构体来拿到EFlags寄存器,设置EFlags寄存器第8位的TF标志位来实现,CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程,单步中断的中断类型码为1。

在这里插入图片描述

代码实现

/*修改TF标志位为单步模式*/
VOID SetTFFlag(HANDLE hThread)
{
	CONTEXT ct = { 0 };
	ct.ContextFlags = CONTEXT_CONTROL;
	GetThreadContext(hThread, &ct);
	// 将TF标志位设置为1,单步执行,TF位是EFLAGS寄存器中的第8位(从0开始)
	ct.EFlags |= 0x100;
	SetThreadContext(hThread, &ct);
}

单步步过模式,遇到call、rep等指令时,在其下一条指令设置CC断点

/*单步步过*/
VOID SetStepFlag(PROCESSINFO ProcessInfo)
{
	CONTEXT ct = { 0 };
	ct.ContextFlags = CONTEXT_CONTROL;
	GetThreadContext(ProcessInfo.hThread, &ct);
	DWORD Address = ct.Eip;
	cs_insn* ins = nullptr;
	PCHAR buf[16] = { 0 };
	ReadProcessMemory(ProcessInfo.hProcess, (LPVOID)Address, buf, 16, NULL);//必须先ReadProcessMemory到调试器中
	cs_disasm(ProcessInfo.capHandle, (uint8_t*)buf, (size_t)16, (uint64_t)Address, 0, &ins);
	if (!memcmp(ins->mnemonic, "call", 4) || !memcmp(ins->mnemonic, "rep", 3))
	{
		SetSoftBreakPoint(ProcessInfo.PID, (LPVOID)(Address + ins->size), TRUE);
	}
	else
	{
		SetTFFlag(ProcessInfo.hThread);
	}
}

通过线程上下文***CONTEXT***结构体修改寄存器的值

/*修改寄存器值*/
VOID ChangeRegValue(PROCESSINFO ProcessInfo, char* flag, DWORD Value)
{
	CONTEXT context = { CONTEXT_INTEGER };
	GetThreadContext(ProcessInfo.hThread, &context);
	if (!strcmp(flag, "eax")) //eax
	{
		context.Eax = Value;
	}
	else if (!strcmp(flag, "ebx"))//ebx
	{
		context.Ebx = Value;
	}
	else if (!strcmp(flag, "ecx"))//ecx
	{
		context.Ecx = Value;
	}
	else if (!strcmp(flag, "edx"))//edx
	{
		context.Edx = Value;
	}
	else if (!strcmp(flag, "edi"))//edi
	{
		context.Edi = Value;
	}
	else if (!strcmp(flag, "esi"))//esi
	{
		context.Esi = Value;
	}
	else
	{
		printf("[*]\t %s 寄存器暂不能更改值\n", flag);
	}
	SetThreadContext(ProcessInfo.hThread, &context);
}

通过WriteProcessMemory修改内存值

/*修改内存值*/
VOID ChangeMemValue(PROCESSINFO ProcessInfo, DWORD Address, DWORD Value)
{
	SIZE_T writen = 0;
	WriteProcessMemory(ProcessInfo.hProcess, (LPVOID)Address, &Value, sizeof(DWORD), &writen);
}

修改DR7、DR6标志位清除硬件断点

/*命中后清除硬件断点*/
BOOL ClearHBreakPoint(PROCESSINFO ProcessInfo)
{
	CONTEXT ct = { CONTEXT_DEBUG_REGISTERS };
	GetThreadContext(ProcessInfo.hThread, &ct);

	if ((DWORD)ProcessInfo.ExceptionAddr == ct.Dr0)
	{
		ct.Dr7 = ct.Dr7 & 0xFFFFFFFE;
		ct.Dr0 = 0;
		ct.Dr6 = ct.Dr6 & 0xFFFFFFFE;
	}
	if ((DWORD)ProcessInfo.ExceptionAddr == ct.Dr1)
	{
		ct.Dr7 = ct.Dr7 & 0xFFFFFFFD;
		ct.Dr1 = 0;
		ct.Dr6 = ct.Dr6 & 0xFFFFFFFD;
	}
	if ((DWORD)ProcessInfo.ExceptionAddr == ct.Dr2)
	{
		ct.Dr7 = ct.Dr7 & 0xFFFFFFEF;
		ct.Dr2 = 0;
		ct.Dr6 = ct.Dr6 & 0xFFFFFFEF;
	}
	if ((DWORD)ProcessInfo.ExceptionAddr == ct.Dr3)
	{
		ct.Dr7 = ct.Dr7 & 0xFFFFFFDF;
		ct.Dr3 = 0;
		ct.Dr6 = ct.Dr6 & 0xFFFFFFDF;
	}
	SetThreadContext(ProcessInfo.hThread, &ct);
	return TRUE;
}

遍历断点列表删除断点

/*删除断点*/
BOOL DeleteSoftBreakPoint(PROCESSINFO ProcessInfo)
{
	for (int i = 0; i < BreakPointList.size(); i++)
	{
		if ((ProcessInfo.ExceptionAddr == BreakPointList[i].addr) && (BreakPointList[i].BpAttribute == TRUE))
		{
			RemoveSoftBreakPoint(ProcessInfo, TRUE, ProcessInfo.ExceptionAddr);//恢复OPCODE
			BreakPointList.erase(BreakPointList.begin() + i);
		}
	}
	return TRUE;
}

最后再回过来去看异常事件处理哪里,对应的异常事件怎么处理的,访问异常就将属性改回去;断点异常则删除断点

switch (ProcessInfo.ExceptionCode)
	{
		case EXCEPTION_ACCESS_VIOLATION://线程试图读取或写入对其没有适当访问权限的虚拟地址。
		// First chance: Pass this on to the system.  Last chance: Display an appropriate error. 
		{
			DWORD64 MemAddress = (DWORD)(ProcessInfo.ExceptionAddr) & 0xFFFFF000;//内存页
			DWORD64 MemAddress2 = DebugEvent.u.Exception.ExceptionRecord.ExceptionInformation[1] & 0xFFFFF000;//第二个数组元素指定不可访问数据的虚拟地址
			for (int i = 0; i < MemoryBreakList.size(); i++)
			{
				if (MemAddress == MemoryBreakList[i].addr)
				{
					printf_s("内存断点--> %p\n", MemAddress);
					VirtualProtectEx(ProcessInfo.hProcess, (LPVOID)MemAddress, 0x1000, MemoryBreakList[i].dwOldProtect, &MemoryBreakList[i].dwNewProtect);//恢复访问
					//暂时将内存断点和硬件断点均写为一次性断点
					MemoryBreakList.erase(MemoryBreakList.begin() + i);
				}
				else if (MemAddress2 == MemoryBreakList[i].addr)
				{
					printf_s("内存断点--> %p\n", MemAddress2);
					VirtualProtectEx(ProcessInfo.hProcess, (LPVOID)MemAddress2, 0x1000, MemoryBreakList[i].dwOldProtect, &MemoryBreakList[i].dwNewProtect);//恢复访问
					//暂时将内存断点和硬件断点均写为一次性断点
					MemoryBreakList.erase(MemoryBreakList.begin() + i);
				}
			}
			break;
		}

		case EXCEPTION_BREAKPOINT://遇到断点。
		// First chance: Display the current instruction and register values. 
		{
			if (DeleteSoftBreakPoint(ProcessInfo))
			{
				break;
			}
			else
			{
				printf_s("命中硬件中断断点: %p\n", ProcessInfo.ExceptionAddr);
				RemoveSoftBreakPoint(ProcessInfo, TRUE, ProcessInfo.ExceptionAddr);//在反汇编函数里会统一进行修复
				break;
			}
		}
			
		case EXCEPTION_SINGLE_STEP://跟踪陷阱或其他单指令机制表明已执行了一条指令。
		// First chance: Update the display of the current instruction and register values.
		{
			if (ClearHBreakPoint(ProcessInfo))
			{
				printf_s("命中断点: %p\n", ProcessInfo.ExceptionAddr);
			}
			break;
		}

		default:
		// Handle other exceptions.
		{
			printf_s("未处理异常类型(%08X): %p\n", ProcessInfo.ExceptionCode, ProcessInfo.ExceptionAddr);
			break;
		}
	}

主要还是学习一些思路。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值