10.1 调试事件读取寄存器

调试,事件,读取,寄存器 · 浏览次数 : 6

小编点评

**程序完善后的代码:** ```c++ #include #include #include #define CREATE_PROCESS_DEBUG_EVENT 1 // 获取进程入口地址 DWORD dwAddr = 0x0 + (DWORD)de.u.CreateProcessInfo.lpStartAddress; // 暂停线程 SuspendThread(de.u.CreateProcessInfo.hThread); // 读取入口地址处的字节码 ReadProcessMemory(de.u.CreateProcessInfo.hProcess, (const void*)dwAddr, &bCode, sizeof(BYTE), &dwNum); // 在入口地址处写入 0xCC 即写入 INT 3 暂停进程执行 WriteProcessMemory(de.u.CreateProcessInfo.hProcess, (void*)dwAddr, &bCC, sizeof(BYTE), &dwNum); // 恢复线程 ResumeThread(de.u.CreateProcessInfo.hThread); // 处理异常断点 switch (dwCC_Count) { case 0: // 第一次系统断点,跳过 break; case 1: // 第一次断点,设置异常处理函数 OnException(&de, &bCode); dwCC_Count++; break; } // 异常处理函数 void OnException(DEBUG_EVENT* pDebug, BYTE* bCode) { // 获取进程句柄信息 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pDebug->dwProcessId); HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pDebug->dwThreadId); // 获取异常首地址 ReadProcessMemory(hProcess, pDebug->u.Exception.ExceptionRecord.ExceptionAddress, &bTmp, sizeof(BYTE), &dwNum); // 获取线程上下文 GetThreadContext(hThread, &context); // 打印寄存器状态 printf("EAX = 0x%08X | EBX = 0x%08X | ECX = 0x%08X | EDX = 0x%08X \\", context.Eax, context.Ebx, context.Ecx, context.Edx); printf("EBP = 0x%08X | ESP = 0x%08X | ESI = 0x%08X | EDI = 0x%08X \\", context.Ebp, context.Esp, context.Esi, context.Edi); printf("EIP = 0x%08X | EFLAGS = 0x%08X\\\", context.Eip, context.EFlags); // 取消断点 WriteProcessMemory(hProcess, pDebug->u.Exception.ExceptionRecord.ExceptionAddress, bCode, sizeof(BYTE), &dwNum); // 设置线程上下文 SetThreadContext(hThread, &context); // 恢复线程执行 ResumeThread(hThread); // 关闭线程和进程句柄 CloseHandle(hThread); CloseHandle(hProcess); } ``` **输出信息:** ``` EAX = 0x00000000 | EBX = 0x00000000 | ECX = 0x00000000 | EDX = 0x00000000 EBP = 0x00000000 | ESP = 0x00000000 | ESI = 0x00000000 | EDI = 0x00000000 EIP = 0x74c10000 | EFLAGS = 0x00000000 ```

正文

当读者需要获取到特定进程内的寄存器信息时,则需要在上述代码中进行完善,首先需要编写CREATE_PROCESS_DEBUG_EVENT事件,程序被首次加载进入内存时会被触发此事件,在该事件内首先我们通过lpStartAddress属性获取到当前程序的入口地址,并通过SuspendThread暂停程序的运行,当被暂停后则我没就可以通过ReadProcessMemory读取当前位置的一个字节机器码,目的是保存以便于后期的恢复,接着通过WriteProcessMemory向对端(void*)dwAddr地址写出一个0xCC断点,该断点则是int3停机指令,最后ResumeThread恢复这个线程的运行,此时程序中因存在断点,则会触发一个EXCEPTION_DEBUG_EVENT异常事件。

case CREATE_PROCESS_DEBUG_EVENT:
{
    // 获取入口地址 0x0 可以增加偏移到入口后任意位置
    DWORD dwAddr = 0x0 + (DWORD)de.u.CreateProcessInfo.lpStartAddress;

    // 暂停线程
    SuspendThread(de.u.CreateProcessInfo.hThread);

    // 读取入口地址处的字节码
    ReadProcessMemory(de.u.CreateProcessInfo.hProcess, (const void*)dwAddr, &bCode, sizeof(BYTE), &dwNum);

    // 在入口地址处写入 0xCC 即写入 INT 3 暂停进程执行
    WriteProcessMemory(de.u.CreateProcessInfo.hProcess, (void*)dwAddr, &bCC, sizeof(BYTE), &dwNum);

    // 恢复线程
    ResumeThread(de.u.CreateProcessInfo.hThread);
    break;
}

当异常断点被触发后,则下一步就会触发两次异常,第一次异常我们可以使用break直接跳过,因为此断点通常为系统断点,而第二次断点则是我们自己设置的int3断点,此时需要将该请求发送至OnException异常处理函数对其进行处理,在传递时需要给与&de调试事件,以及&bCode原始的机器码;

case EXCEPTION_DEBUG_EVENT:
{
    switch (dwCC_Count)
    {
        // 第0次是系统断点,这里我们直接跳过
    case 0:
        dwCC_Count++; break;

        // 第1次断点,我们让他执行下面的函数
    case 1:
        OnException(&de, &bCode); dwCC_Count++; break;
    }
}

异常事件会被流转到OnException(DEBUG_EVENT* pDebug, BYTE* bCode)函数内,在本函数内我们首先通过使用OpenProcess/OpenThread两个函数得到当前进程的句柄信息,接着使用SuspendThread(hThread)暂时暂停进程内线程的执行,通过调用ReadProcessMemory得到线程上下文异常产生的首地址,当得到首地址后,则可以调用GetThreadContext(hThread, &context)得到当前线程的上下文,一旦上下文被获取到则读者即可通过context.的方式得到当前程序的所有寄存器信息,为了让程序正常执行当读取结束后,通过WriteProcessMemory我们将原始机器码写回到内存中,并SetThreadContext设置当前上下文,最后使用ResumeThread运行该线程;

void OnException(DEBUG_EVENT* pDebug, BYTE* bCode)
{
    CONTEXT context;
    DWORD dwNum;
    BYTE bTmp;

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pDebug->dwProcessId);
    HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pDebug->dwThreadId);

    // 暂停指定的线程
    SuspendThread(hThread);

    // 读取出异常首地址
    ReadProcessMemory(hProcess, pDebug->u.Exception.ExceptionRecord.ExceptionAddress, &bTmp, sizeof(BYTE), &dwNum);

    context.ContextFlags = CONTEXT_FULL;
    GetThreadContext(hThread, &context);

    printf("\n");

    printf("EAX = 0x%08X  |  EBX = 0x%08X  |  ECX = 0x%08X  |  EDX = 0x%08X \n",
        context.Eax, context.Ebx, context.Ecx, context.Edx);
    printf("EBP = 0x%08X  |  ESP = 0x%08X  |  ESI = 0x%08X  |  EDI = 0x%08X \n\n",
        context.Ebp, context.Esp, context.Esi, context.Edi);

    printf("EIP = 0x%08X  |  EFLAGS = 0x%08X\n\n", context.Eip, context.EFlags);

    // 将刚才的CC断点取消,也就是回写原始指令集
    WriteProcessMemory(hProcess, pDebug->u.Exception.ExceptionRecord.ExceptionAddress, bCode, sizeof(BYTE), &dwNum);
    context.Eip--;

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

    // printf("进程句柄:   0x%08X \n", pDebug->u.CreateProcessInfo.hProcess);
    // printf("主线程句柄: 0x%08X \n", pDebug->u.CreateProcessInfo.hThread);
    printf("虚拟入口点: 0x%08X \n", pDebug->u.CreateProcessInfo.lpBaseOfImage);

    // 恢复线程执行
    ResumeThread(hThread);
    CloseHandle(hThread);
    CloseHandle(hProcess);
}

当这段程序被运行后,读者可看到如下图所示的输出信息,该进程中当前寄存器的状态基本上都可以被获取到;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/94ad4ba.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

与10.1 调试事件读取寄存器相似的内容:

10.1 调试事件读取寄存器

当读者需要获取到特定进程内的寄存器信息时,则需要在上述代码中进行完善,首先需要编写`CREATE_PROCESS_DEBUG_EVENT`事件,程序被首次加载进入内存时会被触发此事件,在该事件内首先我们通过`lpStartAddress`属性获取到当前程序的入口地址,并通过`SuspendThrea...

10.2 调试事件获取DLL装载

理解了如何通过调试事件输出当前进程中寄存器信息,那么实现加载DLL模块也会变得很容易实现,加载DLL模块主要使用`LOAD_DLL_DEBUG_EVENT`这个通知事件,该事件可检测进程加载的模块信息,一旦有新模块被加载或装入那么则会触发一个通知事件,利用该方法并配合磁盘路径获取函数则可很容易的实现进程模块加载的监控。

10.3 调试事件转存进程内存

我们继续延申调试事件的话题,实现进程转存功能,进程转储功能是指通过调试API使获得了目标进程控制权的进程,将目标进程的内存中的数据完整地转存到本地磁盘上,对于加壳软件,通常会通过加密、压缩等手段来保护其代码和数据,使其不易被分析。在这种情况下,通过进程转储功能,可以将加壳程序的内存镜像完整地保存到本...

10.0 探索API调试事件原理

本章笔者将通过`Windows`平台下自带的调试API接口实现对特定进程的动态转存功能,首先简单介绍一下关于调试事件的相关信息,调试事件的建立需要依赖于`DEBUG_EVENT`这个特有的数据结构,该结构用于向调试器报告调试事件。当一个程序发生异常事件或者被调试器附加时,就会产生对应的`DEBUG_...

Advanced .Net Debugging 10:事后调试

一、介绍 这是我的《Advanced .Net Debugging》这个系列的第十篇文章。这篇文章的内容是原书的第三部分的【高级主题】的第八章【事后调试】。前面几篇文章,我们介绍了很多工具,可以帮助大家找出问题的所在。但是,有一类问题我们是没办法使用这些工具来解决的,那就是已经发布的程序。在程序发布

【转帖】10个Linux 系统性能监控命令行工具

引言: 系统一旦跑起来,我们就希望它能够稳定运行,不要宕机,不出现速度变慢。因此,对于Linux 系统管理员来说每天监控和调试 Linux 系统的性能问题是一项繁重却又重要的工作。监控和保持系统启动并运行是很不容易的一件事。 下面是小编总结的十个实用的 Linux 系统监控命令,让你轻松保持系统的实

[apue] 进程环境那些事儿

atexit 注册的处理器中可以再调 atexit 或 exit 吗?putenv 或 setenv 增加一个环境变量后 environ 指针地址为什么变了?setjmp & longjmp 跨函数跳转后自动变量为什么回退了?设置 RLIMIT_NPROC 为 10 为何连一个子进程也 fork 不了?设置 RLIMIT_NOFILE 后为何 sysconf 的返回值也受到了影响?本文为你一一解答

[转帖]基于 Nginx 实现 10万+ 并发,Linux 内核优化

来源:http://t.cn/EyQTMwG 由于默认的Linux内核参数考虑的是最通用场景,这明显不符合用于支持高并发访问的Web服务器的定义,所以需要修改Linux内核参数,是的Nginx可以拥有更高的性能; 在优化内核时,可以做的事情很多,不过,我们通常会根据业务特点来进行调整,当Nginx作

8.10 TLS线程局部存储反调试

TLS(Thread Local Storage)用来在进程内部每个线程中存储私有的数据。每个线程都会拥有独立的`TLS`存储空间,可以在`TLS`存储空间中保存线程的上下文信息、变量、函数指针等。TLS其目的是为了解决多线程变量同步问题,声明为TLS变量后,当线程去访问全局变量时,会将这个变量拷贝到自己线程中的TLS空间中,以防止同一时刻内多次修改全局变量导致变量不稳定的情况,先来看一段简单的案

【译】VisualStudio.Extensibility 17.10:用 Diagnostics Explorer 调试您的扩展

VisualStudio. Extensibility 帮助您构建在主 IDE 进程之外运行的扩展,以提高性能和可靠性。它还提供了一个时尚而直观的基于 .NET 8 的 API 和全面且维护良好的文档,可以帮助您开发出色的扩展。