12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
游戏盒子— 各种稀有手游私服,可以下载 各种变态版 手游交流群:117375085 进入游戏
刀剑演武 现金BOSS,不氪金也能毕业! 创角就赠送至尊8 绝版GM装备送! 进入游戏
《绝世秘籍》 每日精彩活动嗨不停 充值赠送会员 积分换极品装备 进入游戏
《百战沙城》 不肝不氪也可群殴boss 装备满屏爆 超值首充 进入游戏
全民仙战 任意首充送纯白歌宴绝版绚丽时装! 元宝福利免费领 今日新开 进入游戏
查看: 362|回复: 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, 2022-10-5 12:52 , Processed in 0.109375 second(s), 22 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

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