12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
查看: 796|回复: 0

调试器原理分析及实现

[复制链接]
发表于 2022-1-28 22:17:39 | 显示全部楼层 |阅读模式
调试处理流程
        TF置1 -> 执行代码 -> CPU产生中断 -> IDT函数被调用 -> 操纵系统进行异常分发 ->
调试器子系统发送调试事件 -> 调试器得到EXCEPTION_DEBUG_EVENT异常事件 -> 调试器显示反汇编信息
一、实现单步断点
        单步断点是靠CPU中的标志寄存器的TF标志位实现的,将线程环境中的TF标志位设置为1即可。(TF:调试标志位。当TF=1时,处理器每次只执行一条指令,即单步执行)
        若被调试历程有多个线程,仅设置此中一个,极大概率会出现TF断点无效的情况
        解决方法:设置所有线程的环境块,或获取当前产生异常的线程的环境块。
[C] 纯文本查看 复制代码    //获取线程环境块    CONTEXT ct = { 0 };    ct.ContextFlags = CONTEXT_CONTROL;    GetThreadContext(hThread, &ct);    //将TF标志位置置1    PREG_EFLAGS pEflags = (PREG_EFLAGS)&ct.EFlags;    pEflags->TF = 1;    //设置线程环境块    SetThreadContext(hThread, &ct);
二、实现软件断点
        软件断点实质上就是利用int3指令实现的,当CPU执行int3指令时,就会产生一个陷阱类异常,int3指令对应的机器码为0xCC,设置软件断点就是将0xCC写入到必要设置断点的位置,当CPU执行到软件断点后,就会产生陷阱类异常,调试器就可以或许接收到异事件。



        在下软件断点之前,应先读取一个字节将其保存起来,再写入软件断点(即写入0xCC),中断之后,将原来的一个字节数据写回内存中去,再EIP减1,得到异常真正产生的地点。
[C] 纯文本查看 复制代码    //读取历程内存,保存一个字节的数据    DWORD dwSize = 0;    if (!ReadProcessMemory(hProcess, pAddress, oldByte, 1, &dwSize))    {        return FALSE;    }    //写入一个字节,\xcc就是int3指令的机器码    BYTE cc = '\xcc';    if (!WriteProcessMemory(hProcess, pAddress, &cc, 1, &dwSize))    {        return FALSE;    }    return TRUE;
        移除软件断点
[C] 纯文本查看 复制代码    DWORD dwSize = 0;    return WriteProcessMemory(hProcess, pAddress, &oldByte, 1, &dwSize);
三、实现硬件断点
        实现硬件断点,必要设置调试寄存器,将断点的地点设置到DR0~DR3中,将断点长度设置到DR7的LEN0~LEN3中,将断点类型设置到DR7的RW0~RW3中,将是否启用断点设置到DR7的L0~L3中。
[C] 纯文本查看 复制代码typedef struct _DBG_REG7    //调试寄存器DR7的位段信息结构体{    // 局部断点(L0~3)与全局断点(G0~3)的标记位    unsigned L0 : 1;        // 对Dr0保存的地点启用 局部断点    unsigned G0 : 1;        // 对Dr0保存的地点启用 全局断点    unsigned L1 : 1;        // 对Dr1保存的地点启用 局部断点    unsigned G1 : 1;        // 对Dr1保存的地点启用 全局断点    unsigned L2 : 1;        // 对Dr2保存的地点启用 局部断点    unsigned G2 : 1;        // 对Dr2保存的地点启用 全局断点    unsigned L3 : 1;        // 对Dr3保存的地点启用 局部断点    unsigned G3 : 1;        // 对Dr3保存的地点启用 全局断点    // LE,GE【已经弃用】用于低落CPU频率,以方便准确检测断点异常    unsigned LE : 1;        // 保留字段    unsigned GE : 1;        // 保留字段    unsigned Reserve1 : 3;    // 保护调试寄存器标志位,如果此位为1,则有指令修改条是寄存器时会触发异常    unsigned GD : 1;        // 保留字段    unsigned Reserve2 : 2;    // 保存Dr0~Dr3地点所指向位置的断点类型(RW0~3)与断点长度(LEN0~3),状态形貌如下:    unsigned RW0 : 2;       // 设定Dr0指向地点的断点类型    unsigned LEN0 : 2;      // 设定Dr0指向地点的断点长度    unsigned RW1 : 2;       // 设定Dr1指向地点的断点类型    unsigned LEN1 : 2;      // 设定Dr1指向地点的断点长度    unsigned RW2 : 2;       // 设定Dr2指向地点的断点类型    unsigned LEN2 : 2;      // 设定Dr2指向地点的断点长度    unsigned RW3 : 2;       // 设定Dr3指向地点的断点类型    unsigned LEN3 : 2;      // 设定Dr3指向地点的断点长度}DBG_REG7, * PDBG_REG7;
1.设置硬件执行断点
[C] 纯文本查看 复制代码    CONTEXT ct = { CONTEXT_DEBUG_REGISTERS };    //获取线程环境块    GetThreadContext(hThread, &ct);    DBG_REG7* pDr7 = (DBG_REG7*)&ct.Dr7;        if (pDr7->L0 == 0)  //DR0没有被利用    {        ct.Dr0 = uAddress;        pDr7->RW0 = 0;        pDr7->LEN0 = 0; //长度域设置为0        pDr7->L0 = 0;   //启用第0个断点    }    else if (pDr7->L1 == 0)  //DR1没有被利用    {        ct.Dr1 = uAddress;        pDr7->RW1 = 0;        pDr7->LEN1 = 0; //长度域设置为0        pDr7->L1 = 0;   //开启第1个断点    }    else if (pDr7->L2 == 0)  //DR2没有被利用    {        ct.Dr2 = uAddress;        pDr7->RW2 = 0;        pDr7->LEN2 = 0; //长度域设置为0        pDr7->L2 = 0;   //开启第2个断点    }    else if (pDr7->L3 == 0)  //DR3没有被利用    {        ct.Dr3 = uAddress;        pDr7->RW3 = 0;        pDr7->LEN3 = 0; //长度域设置为0        pDr7->L3 = 0;   //开启第3个断点    }    else    {        return FALSE;    }    SetThreadContext(hThread, &ct);    return TRUE;
2.设置硬件读写断点
[C] 纯文本查看 复制代码    //获取线程环境块    CONTEXT ct = { 0 };    ct.ContextFlags = CONTEXT_DEBUG_REGISTERS;    GetThreadContext(hThread, &ct);    //对地点和长度进行对齐处理(向上取整)    if (dwLen == 1)         //2字节的对齐粒度    {        uAddress = uAddress - uAddress % 2;    }    else if (dwLen == 3)    //4字节的对齐粒度    {        uAddress = uAddress - uAddress % 4;    }    else if (dwLen > 3)    {        return FALSE;    }    //判定哪些寄存器没有被利用    DBG_REG7* pDr7 = (DBG_REG7*)&ct.Dr7;    if (pDr7->L0 == 0)  //DR0没有被利用    {        ct.Dr0 = uAddress;        pDr7->RW0 = type;        pDr7->LEN0 = dwLen;    }    else if (pDr7->L1 == 0)  //DR1没有被利用    {        ct.Dr1 = uAddress;        pDr7->RW1 = type;        pDr7->LEN1 = dwLen;    }    else if (pDr7->L2 == 0)  //DR2没有被利用    {        ct.Dr2 = uAddress;        pDr7->RW2 = type;        pDr7->LEN2 = dwLen;    }    else if (pDr7->L3 == 0)  //DR3没有被利用    {        ct.Dr3 = uAddress;        pDr7->RW3 = type;        pDr7->LEN3 = dwLen;    }    else    {        return FALSE;    }    SetThreadContext(hThread, &ct);    return TRUE;
四、实现内存访问断点
        内存访问断点就是利用内存访问异常,当程序访问一个没有任何访问权限的内存分页时,就是产生内存访问异常,如果想要下执行断点,就可以将这个地点的所在的内存分页设置为没有任何访问权限,读写断点同理。
[C] 纯文本查看 复制代码    VirtualProtectEx(process, LPVOID((DWORD)address & 0xfffff000), 0x1000, dwNewProtect, &dwOldProtect);
        产生内存访问异常时,表示异常信息的EXCEPTION_RECORD结构体将内存访问异常的详细信息保存再ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]数组中,当产生的是内存访问异常时,数组的第0个元素保存的是内存访问异常的详细异常方式,保存0时表示读取时异常,保存1时表示写入时异常,保存8时表示执行时异常,第二个元素保存的是发生异常的线性虚拟地点。

五、组合断点
1.API断点
        API断点即得到函数名对应的地点,然后对该地点下一个软件断点
        获取函数名对应地点的方法:
                ①遍历所有模块导出表,匹配函数名
                ②利用调试符处理器,通过符号名得到地点
        这里利用通过符号名,获取对应地点的方法。
[C] 纯文本查看 复制代码    char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];    PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;    pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);    pSymbol->MaxNameLen = MAX_SYM_NAME;    //根据名字查询符号信息,输出到pSymbol中    if (!SymFormName(hProcess,pszName,pSymbol)    {        return 0;    }    //返回函数地点    return (SIZE_T)pSymbol->Address;
2.单步步过断点
        单步步过断点 = TF断点 + 软件断点
                ①如果当前要执行的指令不是CALL或者REP,则利用TF断点
                ②如果是CALL或者REP,则在当前指令的下一条指令下一个软件断点,运行调试历程后就能使其在当前指令的下一条指令处断下,以达到单步步过的目的。

附:调试器三层架构
1.1 建立调试循环的目的有以下3点
        1.1.1 为了可以或许持续的接收到目标历程的调试事件.
        1.1.2 为了可以或许在恰当的时间输出反汇编信息,线程环境块等信息
        1.1.3 为了可以或许接受用户的控制
1.2 搭建调试循环的框架
        1.2.1 框架第一层(完成目的1)
                接收调试事件,并将调试事件交给一个函数处理,用这个函数的返回值来作为ContinueDebugEvent的第三个参数
        1.2.2 框架的第二层
                框架的第二层将调试事件分为两部分,历程创建和退出,线程创建和退出,DLL加载和卸载,调试字符串输出,内部错误作为一部分, 异常事件独立作为一部分
        1.2.3 框架的第三层(完成目的2和3)
                框架的第三次处理的是异常事件,由于异常可以细分为多种类型的,不同类型的异常的规复手段不一样,因此必要进行分类处理
                别的,将信息输出给用户,接收用户的输入也是在第三层中
[C] 纯文本查看 复制代码// 框架的第一层void StartDebug(const char* pszFile /*目标历程的路径*/){    if (pszFile == nullptr)        return;    STARTUPINFOA stcStartupInfo = { sizeof(STARTUPINFOA) };    PROCESS_INFORMATION stcProcInfo = { 0 }; // 历程信息    /* 创建调试历程程 */    BOOL bRet = FALSE;    bRet = CreateProcessA(        pszFile,                                        // 可执行模块路径        NULL,                                           // 下令行        NULL,                                           // 安全形貌符        NULL,                                           // 线程属性是否可继续        FALSE,                                          // 否从调用历程处继续了句柄        DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE,   // 以调试的方式启动        NULL,                                           // 新历程的环境块        NULL,                                           // 新历程的当前工作路径(当前目录)        &stcStartupInfo,                                // 指定历程的主窗口特性        &stcProcInfo                                    // 接收新历程的辨认信息    );    /*建立调试循环*/    DEBUG_EVENT dbgEvent = { 0 };    DWORD dwRet = DBG_CONTINUE;    while (1)    {        /*框架的第一层*/        WaitForDebugEvent(&dbgEvent, -1);// 等待调试事件        dwRet = DispatchEvent(&dbgEvent); // 分发调试事件,进入框架的第二层        ContinueDebugEvent(            dbgEvent.dwProcessId,            dbgEvent.dwThreadId,            dwRet);// 复兴调试事件的处理效果,如果不复兴,目标历程将会一直处于暂停状态.    }}// 框架的第二层DWORD DispatchEvent(DEBUG_EVENT* pDbgEvent){    // 框架的第二层    // 第二层框架将调试事件分为两部分来处理    DWORD dwRet = 0;    switch (pDbgEvent->dwDebugEventCode)    {        // 第一部分是异常调试事件    case EXCEPTION_DEBUG_EVENT:        dwRet = DispatchException(&pDbgEvent->u.Exception); //进入到第三层分发异常        return dwRet; // 返回到框架的第一层    // 第二部分是其他调试事件    default:        return DBG_CONTINUE;    }}// 框架的第三层DWORD DispatchException(EXCEPTION_DEBUG_INFO* pExcDbgInfo){    // 框架的第三层    // 第三层是专门负责修复异常的.    // 如果是调试器自身设置的异常,那么可以修复,返回DBG_CONTINUE    // 如果不是调试器自身设置的异常,那么不能修复,返回DBG_EXCEPTION_NOT_HANDLED    switch (pExcDbgInfo->ExceptionRecord.ExceptionCode)    {    case EXCEPTION_BREAKPOINT: // 软件断点    {        // 修复断点    }    break;    case EXCEPTION_SINGLE_STEP: // 硬件断点和TF断点    {        // 修复断点    }    break;    case EXCEPTION_ACCESS_VIOLATION:// 内存访问断点    {        // 修复断点    }    break;    default:        return DBG_EXCEPTION_NOT_HANDLED;    }    UserInput();    //和用户进行交互    // 返回到框架的第二层中    return DBG_CONTINUE;}// 处理用户输入的函数,完成目的3void UserInput(){    // 输出信息,完成目的2    printf("断点在地点 % 08X上触发\n", pDbgEvent->u.Exception.ExceptionRecord.ExceptionAddress);    // 输出反汇编代码    // 输出寄存器信息    // 接收用户输入,完成目的3    char buff[100];    while (1)    {        printf("请输入下令: ");        gets_s(buff, 100);        if (buff[0] == 't') // 单步步入        {        }        else if (strcmp(buff, "bp") == 0)// 设置断点        {        }        else if (buff[0] == 'g')        {            break; // 跳出循环,返回到框架的第三层中        }    }}
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-4-24 13:52 , Processed in 0.140625 second(s), 31 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表