复习一下调试器,简单写点代码,调试器调试程序有两种方式,一种是创建进程,另一种便是附加:
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处理器都忽略 这两位的设置。此前这两位是用 来启用或禁用数据断点匹配的 |
GD | General Detect | 13 | 启用或禁用对调试寄存器的保护 当设为1时,如果CPU检测到将 修改调试寄存器(DR0-DR7) 的指令时,CPU在执行这条指 令前产生一个调试异常 |
调试状态寄存器DR6
简称 | 全称 | 比特位 | 描述 |
---|---|---|---|
B0 | BreakPoint 0 | 0 | 如果处理器检测到满足条件0的情况, 那么处理器在调用异常处理程序前将此位置为1 |
B1 | BreakPoint 1 | 1 | 如果满足条件1,同上 |
B2 | BreakPoint 2 | 2 | 如果满足条件2,同上 |
B3 | BreakPoint 3 | 3 | 如果满足条件3,同上 |
BD | Debug Register Access Detected | 13 | 这一位与DR7的GD位相联系,当GD位被置为1 当CPU发现DR0~DR7被修改时,将此位置1 |
BS | Single Step | 14 | 这一位与EFLAGS的TF标志位相联系,如果该位为1 表示异常是由单步执行(Single Step)触发的 |
BT | Task 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;
}
}
主要还是学习一些思路。。。