richard_ljd 发表于 2024-5-6 12:03

关于设置硬件断点的一些问题

本帖最后由 richard_ljd 于 2024-5-6 22:04 编辑

这个问题已经被解决了!下面附这个问题的程序和dll项目代码,如果有人需要可以看看
链接:https://pan.baidu.com/s/1OLJnzHMTqU0YO3kg-5Yylg?pwd=tqn4
提取码:tqn4
程序在Debug文件下,如果用vs2022打开可能生成不了(我用vs2022就无法用C++的_asm内联,换的vs2015才可以)
在跟着教程学习,讲到了如何设置硬件断点的方法。基本思想是通过对DR寄存器的操作来做到硬件断点,DR0保存断点地址,DR7是调试控制寄存器,其中的第0位代表DR0是否中断


于是我跟着写代码,下面是wintools.drv的代码

//设置硬件断点
void setBreakPoint()
{
      CONTEXT ctx;
      ctx.ContextFlags = CONTEXT_ALL;
      GetThreadContext(GetCurrentThread(), &ctx);
      ctx.Dr7 = (DWORD)0x55;                                        //因为用0x1设置了也没法断下来,就把L0~L3全部设置了,结果还是没断下来
      ctx.Dr0 = (DWORD)0x00401053;                            //断点位置
      SetThreadContext(GetCurrentThread(), &ctx);
      GetThreadContext(GetCurrentThread(), &ctx);
}


//异常处理
DWORD NTAPI ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{

      //0x00401053
      if ((DWORD)pExceptionInfo->ExceptionRecord->ExceptionAddress == 0x00401053)      
      {
                MessageBox(0, "发现硬件断点,尝试将eip向后移动", "⑨", 0);
                pExceptionInfo->ContextRecord->Eip += 6;   //指令向后移动一位

                return EXCEPTION_CONTINUE_EXECUTION;         //回到产生异常的位置
      }
      return EXCEPTION_CONTINUE_SEARCH; //不是需要的报错位置,交给下一个异常处理器
}


//dll的入口函数
BOOL APIENTRY DllMain( HMODULE hModule,
                      DWORDul_reason_for_call,
                      LPVOID lpReserved
                      )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
      {      
                        AddVectoredContinueHandler(NULL,(PVECTORED_EXCEPTION_HANDLER)ExceptionHandler);
                        setBreakPoint();

                        MessageBox(0, "设置硬件断点了,应该是", "⑨", 0);
            g_hCurrentModule = hModule;
            DisableThreadLibraryCalls(hModule);
            if ( !NsLoad() )
                return FALSE;
            break;
      }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
      break;
    }

    return TRUE;
}



编译好后运行程序,也加载了winpools,执行了我设置的messagebox设置硬件断点,但是程序没有断下来,也没有修改。我使用dbg跟进程序,
程序运行起来后DR0~7全部清空,不清楚为什么。想要问一下(运行系统是windows10,程序是32位程序,dbg是4月11日更新的版本,劫持代码生成器使用的是Baymax)

richard_ljd 发表于 2024-5-6 21:48

董督秀 发表于 2024-5-6 16:36
遍历当前进程(即,待破解程序)的所有线程,获取到线程句柄,然后根据每个线程句柄,在每一个线程设置硬 ...

(悲),我刚刚了解了一些线程和进程的相关内容,然后修改了一下代码,让程序在这个进程下的每个子线程下硬件断点。
我的思路是:
程序加载winpools---->进入dllmain----->注册异常----->分出监视线程,线程用于监视程序是否解压完毕,dll加载完毕

监视线程--发现程序已经解压--->遍历获取进程下所有的线程id---->全部写入

然后按照这个思路进行编写:

//用于储存主线程的句柄储存
HANDLE main_tread;

//线程遍历函数
void PrintThreadsOfProcess(DWORD processID) {
        THREADENTRY32 te32;
        HANDLE hThreadSnap = INVALID_HANDLE_VALUE;

        // 创建线程快照
        hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
        if (hThreadSnap == INVALID_HANDLE_VALUE) {
                MessageBox(0, "CreateToolhelp32Snapshot error", "⑨", 0);
                return;
        }


        te32.dwSize = sizeof(THREADENTRY32);

        // 遍历快照中的第一个线程,开始遍历
        if (!Thread32First(hThreadSnap, &te32)) {
                MessageBox(0, "Thread32First error", "⑨", 0);
                CloseHandle(hThreadSnap);
                return;
        }

        CONTEXT ctx;
        ctx.ContextFlags = CONTEXT_ALL;

        char buffer;
        do {
                if (te32.th32OwnerProcessID == processID) {

//strcpy(buffer, "");
//sprintf(buffer, "ThreadID:%d", te32.th32ThreadID);
//MessageBox(0, buffer, "⑨", 0);
//这三行是因为当时一直没有成功,程序直接退出,我不知道自己代码是否正确,用来验证的,成功就注销了

//获取线程的寄存器状态,其中ys主要是在调试时我来判断获取是否出错的,没有其他用处
                        BOOL ys = GetThreadContext(OpenThread(PROCESS_ALL_ACCESS, true, te32.th32ThreadID), &ctx);
                        ctx.Dr7 = (DWORD)0x1;
                        ctx.Dr0 = (DWORD)0x00401053;                            //断点位置
                        SetThreadContext(OpenThread(PROCESS_ALL_ACCESS, true, te32.th32ThreadID), &ctx);


//当时程序直接运行报错我以为是因为我全部线程下硬断哪里出问题了,根据隔壁的测试大概总结:一般获取的第一个线程id就是主线程,所以这里执行一次直接退出,相当于只修改主线程
                        break;      
                }

        } while (Thread32Next(hThreadSnap, &te32));

        CloseHandle(hThreadSnap);
}


//线程函数,用来检测程序是否解压完毕
DWORDWINAPI ThreadProc(_In_ LPVOID lpParameter)
{
        byte byteRead = 0;
        while (true)   //循环读取目标位置,查看目标位置是否已经解压完毕
        {
                ReadProcessMemory(main_tread, (LPCVOID)0x00401053, &byteRead, 1, NULL);

                if (byteRead == 0x0F)//代表程序已经解压完毕了,退出检测循环
                        break;

                byteRead = 0;
                Sleep(100);
        }


        PrintThreadsOfProcess(GetCurrentProcessId());    //调用函数下硬件断点

        MessageBox(0, "线程感觉到程序已解压,设置硬件断点完毕", "⑨", 0);
        ExitThread(NULL);
}



//异常处理
DWORD NTAPI ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{

        //0x00401053
        if ((DWORD)pExceptionInfo->ExceptionRecord->ExceptionAddress == 0x00401053)
        {
                MessageBox(0, "发现硬件断点,尝试将eip向后移动", "⑨", 0);
                pExceptionInfo->ContextRecord->Eip += 6;   //指令向后移动一位

                return EXCEPTION_CONTINUE_EXECUTION;       //回到产生异常的位置
        }
        return EXCEPTION_CONTINUE_SEARCH; //不是需要的报错位置,交给下一个异常处理器
}


//dll的入口函数
BOOL APIENTRY DllMain(HMODULE hModule,
        DWORDul_reason_for_call,
        LPVOID lpReserved
)
{
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
        {

                main_tread = OpenProcess(PROCESS_ALL_ACCESS, true, GetCurrentProcessId());

//注册异常函数
                AddVectoredContinueHandler(NULL, (PVECTORED_EXCEPTION_HANDLER)ExceptionHandler);

//创造监视线程
                CreateThread(NULL,NULL,ThreadProc,NULL,NULL,NULL);

                g_hCurrentModule = hModule;
                DisableThreadLibraryCalls(hModule);
                if (!NsLoad())
                        return FALSE;
                break;
        }
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
                break;
        }

        return TRUE;
}


我生成以后当时直接运行被系统接管异常,当时直接退出了,我以为是自己代码又有问题,于是在遍历线程里面加了一句break,后来用dbg直接运行就好了,断下来然后修改eip成功。感动人心(喜)
感谢帮助

richard_ljd 发表于 2024-5-6 15:24

本帖最后由 richard_ljd 于 2024-5-6 15:37 编辑

董督秀 发表于 2024-5-6 13:19
1.一般需要遍历线程设置硬件断点;除非特殊情况,仅在当前线程设置硬件断点。
2.部分带壳程序,需要目标地 ...
我阅读了你的回答才突然想起来这个程序是已经加过壳的程序,说不定在程序解压时也会把DR寄存器给覆盖

我根据这个思路写了一个线程用来检查程序是否被解压成功,解压完毕后再调用函数设置硬件断点。

更改后的代码如下:



HANDLE main_tread;   //用于储存主线程的句柄储存

//设置硬件断点
void setBreakPoint()
{
      CONTEXT ctx;

      ctx.ContextFlags = CONTEXT_ALL;

      BOOL by = GetThreadContext(main_tread, &ctx);
      ctx.Dr7 = (DWORD)0x1;
      ctx.Dr0 = (DWORD)0x00401053;
      SetThreadContext(main_tread, &ctx);
      GetThreadContext(main_tread, &ctx);
}



//异常处理
DWORD NTAPI ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{

      //0x00401053
      if ((DWORD)pExceptionInfo->ExceptionRecord->ExceptionAddress == 0x00401053)
      {
                MessageBox(0, "发现硬件断点,尝试将eip向后移动", "⑨", 0);
                pExceptionInfo->ContextRecord->Eip += 6;   //指令向后移动一位

                return EXCEPTION_CONTINUE_EXECUTION;         //回到产生异常的位置

      }

      return EXCEPTION_CONTINUE_SEARCH; //不是需要的报错位置,交给下一个异常处理器
}

//新增的线程函数,用来检测程序是否解压完毕
DWORDWINAPI ThreadProc(_In_ LPVOID lpParameter)
{
      byte byteRead = 0;
      while (true)   //循环读取目标位置,查看目标位置是否已经解压完毕
      {
                ReadProcessMemory(main_tread, (LPCVOID)0x00401053, &byteRead, 1, NULL);
               
                if (byteRead == 0x0F)//代表程序已经解压完毕了,退出检测循环
                        break;

                byteRead = 0;
                Sleep(100);
      }
               

      setBreakPoint();    //调用函数下硬件断点
      MessageBox(0,"线程感觉到程序已解压,设置硬件断点完毕","⑨",0);
      ExitThread(NULL);
}



//入口函数
BOOL APIENTRY DllMain( HMODULE hModule,
                      DWORDul_reason_for_call,
                      LPVOID lpReserved
                      )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
      {      
                        main_tread = OpenProcess(PROCESS_ALL_ACCESS, true, GetCurrentProcessId());   //获取当前线程的主线程句柄,因为这是一个dll程序,程序运行时主线程加载,这边直接获取自身GetCurrentProcessId

                        AddVectoredContinueHandler(NULL,(PVECTORED_EXCEPTION_HANDLER)ExceptionHandler);   //添加报错处理

CreateThread(NULL, NULL, ThreadProc, NULL, NULL, NULL);    //创造监视线程

            g_hCurrentModule = hModule;
            DisableThreadLibraryCalls(hModule);
            if ( !NsLoad() )
                return FALSE;
            break;
      }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
      break;
    }



    return TRUE;
}



我通过vs调试可以确认线程确实是检测到了程序解压(目标位置变为0xFF),但是运行还是没有断下..我刚刚查了一下设置断点的几个函数调用没有问题,vs调试也没有返回空值,然后我用dbg运行程序,依旧没有修改DR0~7。现在不清楚是哪里的问题了()

kanlai 发表于 2024-5-6 12:52

本帖最后由 kanlai 于 2024-5-6 13:01 编辑

ctx.Dr7 |= 1ULL << (id * 2ULL);
ctx.Dr7 |= type << ((id * 4ULL) + 16ULL);
ctx.Dr7 |= length << ((id * 4ULL) + 18ULL);

你按这个要求试试id 是 0-3,就也就是对应Dr0-3. type是断点类型0,1,2,3 分别是执行,写,IO读写,读写 ,基本就是0 ,1, 3 。 lenght是断点长度0 1 2 3 对应 字节,双字节 4字节 8字节。
记得暂停线程,然后写进入,再恢复线程
32位 记得换成U64是ULL

董督秀 发表于 2024-5-6 13:19

1.一般需要遍历线程设置硬件断点;除非特殊情况,仅在当前线程设置硬件断点。
2.部分带壳程序,需要目标地址解码后,才能设置硬断。

richard_ljd 发表于 2024-5-6 15:33

kanlai 发表于 2024-5-6 12:52
ctx.Dr7 |= 1ULL

刚刚根据你的说法改了一下代码,运行以后也断不下来,我进dbg运行看DR0~7一个都没修改,脑壳疼。

程序为32位程序,所以我全部换用U

        ctx.Dr7 |= 1U << (0 * 2U);            //因为我断点就设置在DR0,所以我填了0,不用移动直接放在第一位
        ctx.Dr7 |= 0 << ((0 * 4U) + 16U);    //这是一个执行断点,当程序执行到这个地方时让其断下
        ctx.Dr7 |= 0 << ((0 * 4U) + 18U);    //设置了单字节的断点

        ctx.Dr0 = (DWORD)0x00401053;                            //断点位置

董督秀 发表于 2024-5-6 15:45

richard_ljd 发表于 2024-5-6 15:24
我阅读了你的回答才突然想起来这个程序是已经加过壳的程序,说不定在程序解压时也会把DR寄存器给覆盖

...

需要遍历线程,在每一个线程下硬件断点。另外,即使是dll下硬断生效,也可能出现当dbg运行后dll硬断失效的情况,这是因为dbg中的某些反调试插件将dr清空了。

richard_ljd 发表于 2024-5-6 16:06

董督秀 发表于 2024-5-6 15:45
需要遍历线程,在每一个线程下硬件断点。另外,即使是dll下硬断生效,也可能出现当dbg运行后dll硬断失效 ...

我这个dbg没什么反调(也没找到什么插件来着,当时挂到一个safe壳的直接调试器炸了.)

遍历线程是指遍历当前进程的所有线程么?还是说我去把整个系统的线程都打印一遍?确实不了解这个,如果理解错了到时候白忙活了,想问一下具体是这个意思么。。

董督秀 发表于 2024-5-6 16:36

richard_ljd 发表于 2024-5-6 16:06
我这个dbg没什么反调(也没找到什么插件来着,当时挂到一个safe壳的直接调试器炸了.)

遍历线程是指遍 ...

遍历当前进程(即,待破解程序)的所有线程,获取到线程句柄,然后根据每个线程句柄,在每一个线程设置硬件断点。

666888tzq 发表于 2024-5-6 19:26

没见到暂停线程、恢复线程的代码。

lies2014 发表于 2024-5-6 20:30

我想2、9楼讲的才是你断点不成功的主要原因吧,你不可能对一个活动的线程下断点的,你得另外启动一个线程把目标进程挂起才能设置成功,然后再恢复目标线程
对于硬件执行断点来说程序是否被解压成功并不重要,你可以用调试器在程序入口的时候对未解压的地址设置硬件执行断点就知道了,解压后执行到这里一样可以断下来
如果你确定需要修改的东西在主线程的话,也可以不遍历线程
我这贴有个例子就是用硬件断点实现的,虽然是 Free Pascal 写的,但原理是一样的,代码也不复杂:
https://www.52pojie.cn/thread-1826880-1-1.html
页: [1] 2
查看完整版本: 关于设置硬件断点的一些问题