软件调试--调试器程序框架

c++

Posted by YiMiTuMi on November 17, 2021

调试器程序框架

这个调试器主要是测试支线软件调试的内容,只是一个具体框架,没有实现很复杂的细节。

软件调试原理

实现了:

1)软件断点

在进程开始的位置加的断点用于测试。

2)硬件断点

在软件断点中断后,在其后面的地址又加了硬件断点,因为硬件断点需要线程启动后才能添加,测试时只有在软件断点后确保线程启动了后才加的断点。换成内存断点后面也可以。

3)内存断点

在进程开始的位置加的断点用于测试。

4)单步异常(单步步入)

这个是在用户操作时加的事件。

5)单步步过 – 未实现

DebugTest.h

定义:

#pragma once
#include <iostream>
#include <windows.h>
#include <winbase.h.>
#include <string>

BOOL _bIsSystemInt3 = TRUE;
char _newModCode;
HANDLE _hOpenProcess = NULL;
DWORD _flOldProtect = 0;           //保存先前的页属性
DWORD _AddBreakpointAddrecss = 0;   //添加内存断点的地址

typedef HANDLE(WINAPI* FnOpenThread) (_In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwThreadId);

BOOL ExceptionManipulationFunction(LPDEBUG_EVENT pdebugEvent);

BOOL ExceptionBreakpoint(LPEXCEPTION_DEBUG_INFO pExceptionInfo, HANDLE hThread);   //处理软件断点

BOOL VisitAnomalyBreakpoint(LPEXCEPTION_DEBUG_INFO pExceptionInfo, HANDLE hThread); //处理内存断点

BOOL SingleStepExceptionProc(LPEXCEPTION_DEBUG_INFO pExceptionInfo, HANDLE hThread); //处理单步异常

BOOL WaitForUSerCommand(HANDLE hThread);  //用户输入事件

BOOL SetProcessOEPINT3(LPDEBUG_EVENT pdebugEvent); //设置软件断点

BOOL SetBreakPoint(LPDEBUG_EVENT pdebugEvent); //设置断点

BOOL SetInternalStorageBreakPointOEP(LPDEBUG_EVENT pdebugEvent); //设置内存断点

BOOL SetHardwareBreakpoint(HANDLE hThread, PVOID pAddress);  //添加硬件断点

DebugTest.cpp

实现:

#include <iostream>
#include <windows.h>
#include <winbase.h.>
#include <string>
#include "DebugTest.h"

using namespace std;

int main()
{
    BOOL bRet = FALSE;
    BOOL bIsContinue = TRUE;
    wstring wstrFilePath = L"C:\\Users\\WGH\\Desktop\\Dbgview.exe";    //设置路径
    DWORD dwContinueStatus = DBG_CONTINUE;

    DEBUG_EVENT debugEvent;
    memset(&debugEvent, 0, sizeof(debugEvent));

    //创建调试进程
    
    STARTUPINFO StartUpInfo;
    memset(&StartUpInfo, 0, sizeof(StartUpInfo));

    PROCESS_INFORMATION pInfo;
    memset(&pInfo, 0, sizeof(PROCESS_INFORMATION));

    GetStartupInfo(&StartUpInfo);  //取得进程在启动时被指定的 STARTUPINFO 结构。

    bRet = CreateProcessW(wstrFilePath.c_str(), NULL, NULL, NULL, TRUE, DEBUG_PROCESS || DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &StartUpInfo, &pInfo);
    if (bRet == FALSE)
    {
        printf("CreateProcess Error: %d \n", GetLastError());
    }
    
    //附加形式
    /*
    if (!DebugActiveProcess(21872))   //接受一个进程ID
    {
        return 0;
    }
    */

    //保存进程句柄
    _hOpenProcess = pInfo.hProcess;


    //调试循环
    while (bIsContinue)
    {
        bRet = WaitForDebugEvent(&debugEvent, INFINITE); //等待异常消息
        if (bRet == FALSE)
        {
            printf("WaitForDebugEvent Error: %d \n", GetLastError());
            return 0;
        }

        dwContinueStatus = DBG_CONTINUE;

        switch (debugEvent.dwDebugEventCode)
        {

        case EXCEPTION_DEBUG_EVENT :     //异常
            if (!ExceptionManipulationFunction(&debugEvent))  
            {
                //处理失败
                dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
            }
            break;

	case CREATE_THREAD_DEBUG_EVENT:     //创建线程
	    break;

	case CREATE_PROCESS_DEBUG_EVENT:     //创建进程

            SetBreakPoint(&debugEvent);

		break;

	case EXIT_THREAD_DEBUG_EVENT:        //退出线程
		break;

	case EXIT_PROCESS_DEBUG_EVENT:       //退出进程
		break;

	case LOAD_DLL_DEBUG_EVENT:           //加载DLL
		break;

	case UNLOAD_DLL_DEBUG_EVENT:           //卸载DLL
		break;
        }
        
        //DBG_CONTINUE, 表示编译器已经处理了改异常
        //DBG_EXCEPTION_NOT_HANDLED, 即表示调试器没有处理该异常,转回到用户态中执行,寻找可以处理该异常的异常处理器
        bRet = ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, dwContinueStatus);  //调试事件处理完毕
        if (bRet == FALSE)
        {
            printf("ContinueDebugEvent Error: %d \n", GetLastError());
        }
    }

	CloseHandle(pInfo.hThread);
	CloseHandle(pInfo.hProcess);

    return 0;
}

BOOL ExceptionManipulationFunction(LPDEBUG_EVENT pdebugEvent)
{
    BOOL bRetu = TRUE;
    HANDLE hThread = NULL;

    LPEXCEPTION_DEBUG_INFO pExceptionInfo = NULL;
    pExceptionInfo = &pdebugEvent->u.Exception;

   //获线程句柄
   FnOpenThread MyOpenThread = (FnOpenThread)GetProcAddress(LoadLibraryA("Kernel32.dll"), "OpenThread");
   if (MyOpenThread != NULL)
   {
      hThread = MyOpenThread(THREAD_ALL_ACCESS, FALSE, pdebugEvent->dwThreadId);
   }

    switch (pExceptionInfo->ExceptionRecord.ExceptionCode)
    {
         //INT 3 断点
    case EXCEPTION_BREAKPOINT:
        bRetu = ExceptionBreakpoint(pExceptionInfo, hThread);
	break;

        //内存不可访问断点
    case EXCEPTION_ACCESS_VIOLATION:
	bRetu = VisitAnomalyBreakpoint(pExceptionInfo, hThread);
	break;

        //单步异常
    case EXCEPTION_SINGLE_STEP:
        bRetu = SingleStepExceptionProc(pExceptionInfo, hThread);
        break;
    }

    CloseHandle(hThread);

    return bRetu;
}


BOOL ExceptionBreakpoint(LPEXCEPTION_DEBUG_INFO pExceptionInfo, HANDLE hThread)
{
    BOOL bRet = FALSE;
    DWORD dwSize = 0;
    DWORD dwOldProt = 0;
    DWORD dwNewProt = 0;


    CONTEXT context;
    memset(&context, 0, sizeof(CONTEXT));

    //判断是否为我们需要的 INT 3指令
    if (_bIsSystemInt3)  //第一次一般为系统的INT 3 即系统断点
    {
        _bIsSystemInt3 = FALSE;
        return TRUE;
    }

    //将INT 3修复为原来的数据
	if (VirtualProtectEx(_hOpenProcess, pExceptionInfo->ExceptionRecord.ExceptionAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProt))
	{
                WriteProcessMemory(_hOpenProcess, pExceptionInfo->ExceptionRecord.ExceptionAddress, &_newModCode, 1, &dwSize);

		VirtualProtectEx(_hOpenProcess, pExceptionInfo->ExceptionRecord.ExceptionAddress, 1, dwOldProt, &dwNewProt);
	}

    //显示断点位置
    printf("Int 3 Address: 0x%p \r\n", pExceptionInfo->ExceptionRecord.ExceptionAddress);

    //获取线程上下文,显示寄存器信息
    context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(hThread, &context);

    printf("EIP: 0x%p \r\n", context.Eip);

    //修复EIP
    context.Eip--;   //0xcc只占了1个字节
    SetThreadContext(hThread, &context);

    //添加一个硬件断点
    SetHardwareBreakpoint(hThread, (PVOID)((DWORD)(pExceptionInfo->ExceptionRecord.ExceptionAddress) + 5)); //在当前位置的后一个地址下断点

    //显示反汇编


    //等待用户命令
    bRet = WaitForUSerCommand(hThread);


    return bRet;
}

BOOL WaitForUSerCommand(HANDLE hThread)
{
    BOOL bRetu = FALSE;

    string strText;
    cin >> strText;

    if (strText == "g")   //继续执行
    {
        bRetu = TRUE;
    }
    else if (strText == "t")            //单步执行
    {
        bRetu = TRUE;

	CONTEXT context;
	memset(&context, 0, sizeof(CONTEXT));

	//获取线程上下文,显示寄存器信息
	context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
	GetThreadContext(hThread, &context);

        context.EFlags |= 0x100;   //设置 TF = 1

	//设置线程上下文
	SetThreadContext(hThread, &context);
    }

    return bRetu;
}

BOOL SetBreakPoint(LPDEBUG_EVENT pdebugEvent)
{
    BOOL bRetu = FALSE;

    //添加软件断点,将程序断在进程入口点,即在入口位置添加0xcc软件断点
    bRetu = SetProcessOEPINT3(pdebugEvent);

   //添加内存断点,将内存断点加载程序入口处
   //bRetu = SetInternalStorageBreakPointOEP(pdebugEvent);

    return TRUE;
}

BOOL SetProcessOEPINT3(LPDEBUG_EVENT pdebugEvent)
{
    DWORD dwSize = 0;
    HANDLE hThread = NULL;
    DWORD dwOldProt = 0;
    DWORD dwNewProt = 0;
    BOOL bRet = FALSE;
    
    LPCREATE_PROCESS_DEBUG_INFO pProcessInfo = &pdebugEvent->u.CreateProcessInfo;

    //写入 int 3
    char ModCodeInt3;
    ModCodeInt3 = 0xcc;

	if (VirtualProtectEx(_hOpenProcess, pProcessInfo->lpStartAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProt))
	{
		if (ReadProcessMemory(_hOpenProcess, pProcessInfo->lpStartAddress, &_newModCode, 1, &dwSize))
		{
		    if (WriteProcessMemory(_hOpenProcess, pProcessInfo->lpStartAddress, &ModCodeInt3, 1, &dwSize))
		    {
			bRet = TRUE;
		    }
		}

		VirtualProtectEx(_hOpenProcess, pProcessInfo->lpStartAddress, 1, dwOldProt, &dwNewProt);
	}

    return bRet;
}

BOOL SetInternalStorageBreakPointOEP(LPDEBUG_EVENT pdebugEvent)
{

    LPCREATE_PROCESS_DEBUG_INFO pProcessInfo = &pdebugEvent->u.CreateProcessInfo;

    //设置不可访问内存断点
    VirtualProtectEx(_hOpenProcess, pProcessInfo->lpStartAddress, 1, PAGE_NOACCESS, &_flOldProtect);

    //保存加断点的地址,若真正的编译器用链表
    _AddBreakpointAddrecss = (DWORD)pProcessInfo->lpStartAddress;
    
    return TRUE;
}

BOOL VisitAnomalyBreakpoint(LPEXCEPTION_DEBUG_INFO pExceptionInfo, HANDLE hThread)
{
    BOOL bRet = FALSE;
    DWORD dwAddressFlag = 0;
    DWORD dwAddressAdd = 0;
    DWORD newflOldProtect = 0;

    CONTEXT contex;
    memset(&contex, 0, sizeof(CONTEXT));

    //1)获取异常信息,异常原因,异常地址
    dwAddressFlag = pExceptionInfo->ExceptionRecord.ExceptionInformation[0]; //获取标志位
    dwAddressAdd = pExceptionInfo->ExceptionRecord.ExceptionInformation[1];  //异常地址

    printf("VisitAnomalyBreakpoint: Flag = %x, Address = %x \n", dwAddressFlag, dwAddressAdd);  //打印地址

    //判断断下的是否是我们下断点的地址,不是则放过,是则处理
    if (dwAddressAdd != _AddBreakpointAddrecss)
    {
        return TRUE;
    }

    //修改会原来的页属性
    VirtualProtectEx(_hOpenProcess, (VOID *)dwAddressAdd, 1, _flOldProtect, &newflOldProtect);

    //获取线程上线文:显示寄存器的信息
    contex.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(hThread, &contex);

    printf("EIP: 0x%p \r\n", contex.Eip);

    //显示反汇编

     //等待用户命令
     bRet = WaitForUSerCommand(hThread);


    return bRet;
}

BOOL SetHardwareBreakpoint(HANDLE hThread, PVOID pAddress)
{
   CONTEXT context;
   memset(&context, 0, sizeof(CONTEXT));

   //获取线程上下文,显示寄存器信息
   context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
   GetThreadContext(hThread, &context);

    //设置断点位置
    context.Dr0 = (DWORD)pAddress;
    context.Dr7 |= 1;   //设置为局部断点

    //清零
    context.Dr7 &= 0xFFF0FFFF;  //清零 16 - 19位  LEN0 和 RWE0

    //设置断点长度
    context.Dr7 |= 0x00000000; //18 - 19 置0 -- 1字节
    //contex.Dr7 |= 0x0000C0000; //18 - 19 置0011 -- 4字节

    //设置断点类型
    context.Dr7 |= 0x00000000;  //执行断点(17、18位置零)

    //设置线程上下文
    SetThreadContext(hThread, &context);

    return TRUE;
}

BOOL SingleStepExceptionProc(LPEXCEPTION_DEBUG_INFO pExceptionInfo, HANDLE hThread)
{
    BOOL bRet = FALSE;

    CONTEXT context;
    memset(&context, 0, sizeof(CONTEXT));

    //获取线程上下文,显示寄存器信息
    context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(hThread, &context);

    //判断是否是硬件断点导致的异常
    if (context.Dr6 & 0xF)
    {
        //显示异常信息,因为只设了 DR0, 不然要判断哪个硬件断点引起的异常
        printf("Hardware Breakpoint: %x, 0x%p", context.Dr7 & 0x0003000, context.Dr0);

        //显示寄存器信息

        //显示反汇编


        //去掉断点
        context.Dr0 = 0;
        context.Dr7 &= 0xfffffffc;
    }
    else
    {
        //单步断点
        printf("SingleStep: Eip = 0x%p \n", context.Eip);

        //还原
        context.EFlags |= 0xfffffeff;   //还原TF位
    }

    SetThreadContext(hThread, &context);

   //等待用户命令
    bRet = WaitForUSerCommand(hThread);

    return bRet;
}

蓝色妖姬 – 相守