使用特有的反调试技术调试程序

逆向工程 视窗 调试 反调试
2021-07-06 17:48:24

在我的 BA 论文中,我想展示一个适用于 Windows 的程序,使用各种反调试技术。

其中一个是我自己提出的(可能是众所周知的,但我没有在任何地方找到它)涉及通过使用信号处理程序来改变控制流。据我所知,当程序接收到某个信号并注册了一个函数来处理它时,主线程暂停并将执行传递给处理程序。例如,从函数返回后,主线程可以继续执行,或者甚至在传递SIGFPE或 的情况下终止SIGSEGV(请参阅此问题)。

但是,在这种情况下,可能会导致线程执行处理程序离开处理程序范围后没有停止。我之前提到的反调试技术利用了这一事实,并在下面介绍。

首先,我知道我将要展示的代码使用的概念不应该出现在实际程序中,应该在几乎所有情况下都应该避免并且永远不要在家里尝试:)。建立这个之后,考虑下面的程序:(我使用 GCC 目标 minGW 来编译它)

#include <csignal>
#include <windows.h>
#include <cstdio>

void handler(int)
{
    asm("jmp continue"); // do not return from this function; just jump to another place in the code
}

int main()
{
    signal(SIGFPE, handler); // I know that sigaction should be used instead, but it's simpler and, as far as I know it wouldn't work for Windows
    // and I know, that after receiving second signal, there will be undefined behaviour, but let's assume it won't happen
    int a = 1 / 0; // cause SIGFPE to happen
    asm("continue:");
    printf("continue execution\n");
    Sleep(5000); // to show a printed message for a while, so that the console is not being closed immediately
    ExitProcess(0);
}

所以我的问题来了:如何调试这样的程序,以便可以看到 printf("continue execution\n"); 正在执行的行?

当我尝试使用 Code::Blocks 调试器 (GDB) 执行此操作时,仅SIGFPE出现程序接收到的信息并且程序在不打印"continue execution"字符串的情况下被粉碎

另一方面,当我尝试在OllyDbg.

在“正常”执行时,它会按预期打印字符串结束。

编辑: 我也尝试将异常传递给应用程序(使用SHIFT+ F7),但我只收到消息说“调试的程序无法处理异常”并且我无法继续执行。

Edit2:我不会上传二进制文件,因为当我尝试下载它时,我的防病毒软件开始抱怨它,所以如果其他人下载它,它可能会被标记为可疑。

3个回答

此技术等效于 Windows 的结构化异常处理程序。应用程序注册一个异常处理程序,然后强制异常发生,从而将控制权转移给异常处理程序。但是,当存在调试器时,它通常会拦截异常以允许用户交互。如果您的调试器允许您将异常传递回程序,那么您将能够继续跟踪。

您可以在此处阅读有关该技术的更多信息:“终极”反调试参考 (pdf)

不确定您正在寻找的确切答案是否在那里,但此演示文稿是我在此主题上看到的最好的演示文稿之一:https : //www.blackhat.com/presentations/bh-usa-07/Yason/Presentation /bh-usa-07-yason.pdf

我想我终于找到了这个问题的答案。由于我在 Internet 上的任何地方都没有找到答案(包括 MSDN 文档signal、涵盖 Windows 上的反调试技术和异常处理的文章)并且问题存在于所有测试的调试器中(OllyDbg1/2、x64dbg、IDA 调试器、radare2、gdb ),我分析了Windows Internals 6th edition 中描述的异常处理机制以下图片和引用来自本书,第 126 页。

异常调度过程可以这样说明(动作会在图像上从上到下执行): 异常调度

执行的操作:

  1. 异常调度器采取的第一个动作是查看引发异常的进程是否有关联的调试器进程。如果是,异常分派器会向与进程关联的调试对象发送一条调试器对象消息(系统内部将其称为“端口”,以便与可能依赖于 Windows 2000 中的行为的程序兼容,后者使用 LPC 端口而不是调试对象)。

  2. 如果进程没有附加调试器进程或者如果调试器不处理异常,异常调度器切换到用户模式,将陷阱帧复制到格式化为 CONTEXT 数据结构 [...] 的用户堆栈,并调用一个查找结构化或向量化异常处理程序的例程

  3. 如果没有找到或没有处理异常,异常调度器切换回内核模式并再次调用调试器以允许用户进行更多调试。(这称为第二次机会通知。)

  4. 如果调试器没有运行并且没有找到用户模式异常处理程序,内核就会向与线程进程关联的异常端口发送一条消息。此异常端口(如果存在)已由控制此线程的环境子系统注册。异常端口为环境子系统提供了将异常转换为特定于环境的信号或异常的机会,该子系统可能正在端口上侦听。例如,当 UNIX 应用程序子系统从内核收到一条消息,表明它的一个线程生成了异常时,UNIX 应用程序子系统会向导致异常的线程发送 UNIX 样式的信号

  5. 但是,如果内核在处理异常时进展到此为止并且子系统不处理异常,则内核会向 Csrss(客户端/服务器运行时子系统)用于 Windows 错误报告 (WER) 的系统范围错误端口发送一条消息)

  6. 执行一个默认的异常处理程序,它只是终止其线程导致异常的进程

第 1.、2.、3.、5. 和 6. 点并不奇怪。但是,请注意第 4 点。它说,如果前三个点没有处理异常并且调试器没有运行,环境子系统可以将异常转换为特定于环境的信号,然后发送给应用程序。

但这仅在调试器未附加到进程时发生,这解释了为什么仅在程序在调试器之外运行时才调用异常处理程序。否则,上面列表中的第 4. 点根本没有执行。

因此,它不仅仅是 SEH 反调试技术(signal函数不会向 SEH 链添加处理程序)——事实上,它使用了不同的概念并以不同的方式检测调试器。

由于我还没有在任何地方找到这种反调试技术,我希望这些信息可以帮助某人,当他在分析过程中遇到这种技术时。

并且,由于书中没有说明操作系统如何在第 4 点检查应用程序是否正在调试,因此可能需要在处理异常时手动更改程序的控制流以强制它执行处理程序.