软件中对抗逆向工程的8种方法

admin 2024年7月3日09:07:18评论3 views字数 4426阅读14分45秒阅读模式

摘要

尽管没有一种完美的方法来保护自己的软件不被逆向分析, 但可以通过设置各种技术障碍来增加软件被逆向或破解的难度, 相反恶意软件也通过增加各种反逆向技术来增加分析人员的技术研究时间成本。在这篇文章中主要研究Windows x86的二进制文件保护方法。

方法一:IsDebugerPresent()函数

逆向工程师和恶意软件分析人员使用的主要工具之一就是调试器。调试器是一种用于测试和控制其他软件的软件,换句话说,它可以看到软件后台发生的事情,例如在某个流程中停止软件的执行、查看堆栈和寄存器等。最直接简单的反调试技术之一是使用IsDebugerPresent()函数来检测调试器。

软件中对抗逆向工程的8种方法

从函数名称来看,它显然是检查调用进程是否正在被调试,使用此函数不需要任何参数:

软件中对抗逆向工程的8种方法

如果在没有调试器的情况下运行可执行文件,它将继续显示消息“未检测到调试器”:

软件中对抗逆向工程的8种方法

但在调试器下,它会检测到自己正在被调试,然后继续执行 ExitProcess() 函数并终止。另外,任何调试检测都不会导致软件终止,恶意软件作者可以改变工作流程来欺骗分析师:

软件中对抗逆向工程的8种方法

那么检测的内部实现原理是什么呢? 要回答这个问题,首先需要了解什么是 (TEB) 和 (PEB):

  • 线程环境块(TEB):是一种数据结构,用于存储有关进程中当前正在运行的线程的信息,每个线程都有自己的 TEB 结构,在 Win32 环境中,FS寄存器始终指向 TEB,而在 Win64 环境中,它是GS寄存器。

  • 进程环境块(PEB): PEB 是一个包含有关进程的数据的结构,当查看 TEB 时,会发现指向PEB结构的指针。

TEB结构:

软件中对抗逆向工程的8种方法

PEB结构:

软件中对抗逆向工程的8种方法

从PEB结构中可以看到,第二个字节是BeingDebugged成员,这是IsDebuggerPresent函数用来检查以确定进程是否正在调试的内容。如果是,则值为 1,否则为 0。如果跟踪对IsDebuggerPresent函数的调用,会发现以下内容:

软件中对抗逆向工程的8种方法

  • 第 1 行:将TEB结构的地址移动到eax寄存器

  • 第2行:将PEB结构的地址移动到eax寄存器,即TEB地址+30= PEB地址。

  • 第3行:将BeingDebugged成员值移动到eax寄存器。

  • line4:返回要测试的eax值。

软件中对抗逆向工程的8种方法

在测试了寄存器eax之后,根据结果,将进入ExitProcess或跳转并继续执行软件。现在要绕过这种反调试技术,可以在返回后将eax寄存器的值从0x1更改为0x0 ,或者可以更改PEB结构中BeingDebugged成员的值,这是最好的方法,可以转到Memory Map并查找PEB的地址,即7EFDE000,然后右键单击,然后在 Dump 中跟踪,或者在软件的入口点,会发现ebx寄存器指向PEB结构,同样在Dump 中跟踪,之后右键单击值 > Binary > Edit 或使用快捷键ctrl+e并更改值,下图是在x64dbg中按照PED进行dump:

软件中对抗逆向工程的8种方法

入口点处的ebx寄存器如下图:

软件中对抗逆向工程的8种方法

编辑BeingDebugged成员的值如下图:

软件中对抗逆向工程的8种方法

但是调用IsDebuggerPresent API 对分析师来说非常明显,这就是为什么有时恶意软件作者会手动进行此检查,例如:

软件中对抗逆向工程的8种方法

在调试器下的代码将如下所示:

软件中对抗逆向工程的8种方法

这里也使用了相同的绕过技术。

方法二:NtGlobalFlag(全局标志)

在 win32 环境中,如果有调试器附加在进程中,NtGlobalFlag的值通常包含一些特定的标志位, 用于指示调试状态和启用各种调试功能, 以下是一些常见的调试标志位:

  • FLG_HEAP_ENABLE_TAIL_CHECK (0x10):启用堆尾部检查。

  • FLG_HEAP_ENABLE_FREE_CHECK (0x20):启用堆释放检查。

  • FLG_HEAP_VALIDATE_PARAMETERS (0x40):启用堆参数验证。

这些标志位的组合(0x10、0x20、0x40)相加正好为 0x70。因此,在被调试的情况下,NtGlobalFlag 的值可能为 0x70

为了绕过这种反调试技术,必须将值更改为0x0, 如下图:

软件中对抗逆向工程的8种方法

检查NtGlobalFlag的实现代码如下:

#include <windows.h>
#include <stdio.h>

typedef struct _PEB {
    BYTE Reserved1[2];
    BYTE BeingDebugged;
    BYTE Reserved2[1];
    PVOID Reserved3[2];
    PVOID Ldr;
    PVOID ProcessParameters;
    BYTE Reserved4[104];
    PVOID Reserved5[52];
    PVOID PostProcessInitRoutine;
    BYTE Reserved6[128];
    PVOID Reserved7[1];
    ULONG SessionId;
    ULONG NtGlobalFlag;
} PEB, *PPEB;

#ifdef _WIN64
#define PEB_OFFSET 0x60
#else
#define PEB_OFFSET 0x30
#endif

DWORD GetNtGlobalFlag() {
    PPEB pPeb = NULL;

#ifdef _WIN64
    pPeb = (PPEB)__readgsqword(PEB_OFFSET);
#else
    pPeb = (PPEB)__readfsdword(PEB_OFFSET);
#endif

    return pPeb->NtGlobalFlag;
}

int main() {
    DWORD NtGlobalFlag = GetNtGlobalFlag();

    printf("NtGlobalFlag: 0x%xn", NtGlobalFlag);

    if (NtGlobalFlag & 0x70) { // 0x70 = 0x10 | 0x20 | 0x40
        printf("Debugger detected!n");
    } else {
        printf("No debugger detected.n");
    }

    return 0;
}

在上面的代码中, 主要完成以下功能:

PEB 结构

  • 定义 PEB 结构,包含 NtGlobalFlag 字段。

  • 结构偏移地址定义:在 32 位系统中,偏移为 0x30;在 64 位系统中,偏移为 0x60

获取 PEB 地址

  • 在 32 位系统中,使用 __readfsdword(0x30) 获取 PEB 地址。

  • 在 64 位系统中,使用 __readgsqword(0x60) 获取 PEB 地址。

读取 NtGlobalFlag

  • 从 PEB 结构中读取 NtGlobalFlag 字段的值。

检查标志位

  • 检查 NtGlobalFlag 是否包含 0x100x200x40 这些标志位的组合。

方法三:CheckRemoteDebuggerPresent()函数

CheckRemoteDebuggerPresent 的原理是通过调用底层的操作系统 API 来检查目标进程的调试标志位。具体步骤如下:

  1. 获取目标进程的 PEB(Process Environment Block):PEB 结构中包含一个标志位(BeingDebugged),用于指示进程是否处于被调试状态。

  2. 检查 BeingDebugged 标志位:如果该标志位为真(即非零),则表示进程正在被调试。

该函数定义如下:

软件中对抗逆向工程的8种方法

此函数检查该进程是否正在被另一个进程调试,并且它接受两个参数,如上所示,重要的是它是ZwQueryInformationProcess的包装器:

软件中对抗逆向工程的8种方法

通过指定ProcessDebugPort的第二个参数,此函数将检索有关进程的信息(无论它是否在调试器下):

软件中对抗逆向工程的8种方法

以下是在x64dbg中CheckRemoteDebuggerPresent函数内部情况:

软件中对抗逆向工程的8种方法

如上图所示,传递给ZwQueryInformationProcess的第二个参数是 7,它将检索有关进程是否处于调试器下的信息。要绕过这种技术,需要将上图中突出显示的两条指令都置为NOP ,原因是使用指令SETNE时, eax寄存器将设置为 1 - 如果零标志为零,则将操作数中的字节设置为 1,否则将操作数设置为 0 , 然后将eax中的值移动到ESI寄存器指向的位置,以便在从此例程返回后进行比较:

软件中对抗逆向工程的8种方法

从例程返回后,可以看到值 0 与 ESP指向的位置之间的比较,并且在右下角显示NOP指令后的值为0,在正常情况下该值为1 ,因此继续执行后将收到消息“未检测到调试器”:

软件中对抗逆向工程的8种方法

软件中对抗逆向工程的8种方法

实现检测关键代码如下:

软件中对抗逆向工程的8种方法

方法四:代码执行计时技术

当使用调试器分析可执行文件时,有时会使用单步执行来遍历一些汇编指令,执行时间会比正常执行长很多,例如下面的代码:

软件中对抗逆向工程的8种方法

在代码的开头,调用了GetTickCount函数,该函数返回自系统启动以来经过的毫秒数。根据在测试机器上测量的结果,这段代码到执行ShellExecuteW 函数消耗了 30 到 47 毫秒。之后,再次调用GetTickCount函数来获取时间,然后获取两次调用值之间的差值,如果时间超过 70 毫秒,则退出进程,否则打印消息。

当然, 还有其他 Windows API 可用于获取时间,例如:

  • GetLocalTime().

  • GetSystemTime().

  • QueryPerformanceCounter()

方法五:软件断点

软件断点是恶意攻击者常用的一种反调试技术,旨在让恶意软件更难被逆向。该技术涉及在程序中的特定位置插入特定代码指令(称为断点)。执行程序时,断点将导致程序停止运行,从而使调试器难以分析程序的行为。

使用汇编语言实现断点的一种方法是使用指令int 3。该指令是一个软件中断,可导致程序停止运行并将控制权转移到调试器。以下是如何int 3在 C++ 中使用该指令的示例:

软件中对抗逆向工程的8种方法

方法六:硬件断点

硬件断点是一种反调试技术,它使用 CPU 的专用硬件功能来检测和阻止程序调试。该技术涉及在特定内存地址或寄存器上设置断点,然后指示 CPU 在程序访问或修改指定地址或寄存器时触发中断。

下面是如何检查是否设置了硬件断点的示例:

软件中对抗逆向工程的8种方法

任何调试寄存器中的非零值都可能表明该进程正在设置硬件断点的调试器下运行。

方法七:Self-Debugging

恶意软件作者使用的一种技术,用于阻止调试器附加到进程或检查进程是否在调试器下。它涉及使用调试器来调试程序本身,以检测并阻止其他调试器附加到程序。

下面是一个实例示例,该实例创建了进程的第二个实例并创建了名为“debugger_present”的命名事件。进程的第二个实例将尝试将调试器附加到父进程,如果失败,它将设置事件,该事件将向父进程指示另一个调试器已附加到它:

父进程:

软件中对抗逆向工程的8种方法

子进程:

软件中对抗逆向工程的8种方法

方法八:UnhandledExceptionFilter()函数

如果程序遇到任何已注册的异常处理程序都无法处理的异常,则会调用 kernel32!UnhandledExceptionFilter() 函数。可以使用 kernel32!SetUnhandledExceptionFilter() 注册自定义未处理异常过滤器。但是,如果程序正在调试,则不会调用自定义过滤器,而是将异常传递给调试器。因此,如果将控制权传递给未处理的异常过滤器,则可以推断该程序未在调试器下运行:

软件中对抗逆向工程的8种方法

原文始发于微信公众号(二进制空间安全):软件中对抗逆向工程的8种方法

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月3日09:07:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   软件中对抗逆向工程的8种方法http://cn-sec.com/archives/2840874.html

发表评论

匿名网友 填写信息