吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4577|回复: 25
收起左侧

[调试逆向] 调试器原理分析及实现

  [复制链接]
Nattevak 发表于 2021-12-10 10:52
调试处理流程
        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执行到软件断点后,就会产生陷阱类异常,调试器就能够接收到异事件。

1

1

2

2


        在下软件断点之前,应先读取一个字节将其保存起来,再写入软件断点(即写入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;
}
// 处理用户输入的函数,完成目的3
void 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; // 跳出循环,返回到框架的第三层中
        }
    }
}

免费评分

参与人数 13威望 +1 吾爱币 +37 热心值 +12 收起 理由
xiany + 1 谢谢@Thanks!
junjia215 + 1 + 1 谢谢@Thanks!
菜鸟也想飞 + 1 + 1 谢谢@Thanks!
ZY1ZY + 1 + 1 我很赞同!
fenghl + 1 + 1 用心讨论,共获提升!
LegendSaber + 1 + 1 我很赞同!
jaffa + 1 + 1 谢谢@Thanks!
影风 + 1 + 1 热心回复!
杨辣子 + 1 + 1 谢谢@Thanks!
XhyEax + 3 + 1 我很赞同!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
福仔 + 2 + 1 用心讨论,共获提升!
lyl610abc + 3 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

jifL88 发表于 2021-12-10 11:28
谢谢分享~
szzzyan 发表于 2021-12-10 12:00
wlbxx 发表于 2021-12-10 12:16
_默默_ 发表于 2021-12-10 13:00
tf不写代码?
wangyuyan 发表于 2021-12-10 14:52
学习了,6666666666666
张海洋 发表于 2021-12-10 14:52
看到在说调试器,我“啪”的一下就点进来了啊,很快啊
new天方夜谭 发表于 2021-12-10 14:57
🐮啊,学习了。
wuaikirin 发表于 2021-12-10 19:01
牛逼呀,学废了学费了
头像被屏蔽
wxf2589 发表于 2021-12-11 07:06
提示: 作者被禁止或删除 内容自动屏蔽
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 00:45

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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