Windows 应用层反调试详述

admin 2024年10月8日14:39:31评论77 views字数 8680阅读28分56秒阅读模式

摘 要:从 Computer Usage Corporation 公司(世界上第一个专门从事软件开发和服务的公司)诞生至今,软件产业经历了几十年的快速发展,越来越多的人依赖各式各样的软件工作和生活。商业软件本身所蕴含的巨大价值以及人们对知识产权保护观念的淡薄,导致了许多破解商业软件的行为发生,比如盗版、数据篡改等,而大多数不法行为基本都会依赖各种调试工具来完成。以防止软件被破解,检测逆向分析调试工具为研究目的,分析了目前在 Windows 系统中调试工具广泛存在的固有特征和其所用到的技术,并对现有的反调试方法进行总结,对 Windows 系统应用层中调试器的检测,提升软件自身反调试、防破解能力具有较为重要的意义。

内容目录:

1 软件调试发展

1.1 大型机调试

1.2 小型机调试

2 Windows 反调试技术

2.1 检测特征

2.2 检测调试器行为

2.3 阻断调试器

3 结 语

从第一台计算机的诞生到互联网时代的崛起,人类进入了真正意义上的第三次工业革命。计算机软件产业以互联网为依托,得到了迅猛发展,在世界的方方面面发挥着不可替代的重要作用。正是由于软件产业给人们所带来的巨大利益,才导致了一些用户对软件进行非法的逆向分析和破解,其中以收费昂贵的工具软件和能带来巨大收益的游戏软件为甚。为此,各个国家在针对软件的知识产权保护的问题上都制定了明确的法律法规,以此来保护相关软件开发人员的权益 。但仅仅依靠相关法律,尤其是在一些法律法规和知识产权保护体系相对来说不太健全的国家,这是远远不够的,因此,通过相应的检测保护手段,在一定程度上保障计算机软件的安全性是非常必要的。

目前,应用于逆向工程的一些软件保护技术主要有代码混淆 、虚拟化、防篡改技术以及反调试 4 种,前两种是被动保护,后两种是 主 动 保 护。同 时, 由 于 Windows 系 统 对 软件支持比较全面,软件种类繁多,所以本文将结合国内外反调试技术的发展情况,对现有的Windows 系统下应用层反调试方法进行详细的阐述和总结,并对未来的发展趋势进行分析,让其能得到更清楚的认识和更广泛的应用。

1

软件调试发展

与其他传统行业相比,软件行业具有一定的特殊性。软件产品是虚拟交付物,在生产的过程中具有很大的不确定性,在软件(尤其是大型软件系统)真正交付之前,需要经历分析、需求、设计、开发、测试等多个阶段,等待其正式发布之后还需要支持维护和更新 。在软件设计、开发和测试的过程中往往会遇到很多问题(Bug),这时就需要通过软件调试来排查问题,从而保证产品的顺利交付,而软件调试的关键工具就是各种调试器。计算机系统在最初设计时,就考虑到了这个问题,设计中涵盖了软件和硬件的调试方法,调试器的演进也随之经历了大型机、小型机和微型机 3 个发展阶段,其在提高软件产品质量方面起到了不可或缺的作用。

1.1 大型机调试

1951 年由雷明顿兰德公司发售的通用自动计算机(Universal Automatic Computer I,UNIVAC I)是最早量产的商用电子计算机,与早前研发的 电 子 数 字 积 分 计 算 机(Electronic Numerical Integrator And Computer,ENIAC)一样同属于大型机。UNIVACI的系统主要由主机、磁带驱动器、控制台、打印设备和示波器组成 。

如图 1 所示,在 UNIVACI 的控制台面板上有很多指示灯和开关,其中与软件调试相关的是一个名为“中断操作开关(Interrupted Operation Switch,IOS)”的装置。开关设有上下左右和中间5 种运行模式,分别为单次操作(One Operation)、单指令(One Instruction)、单步(One Step)、单次加法(One Addition)和连续模式(Continuous)。当IOS 开关处于非连续模式时,CPU 在执行完一条指令后便会停下来,让用户检查相关寄存器和内存的状态。UNIVACI 计算机就是通过 IOS 开关的控制模式来完成软硬件的调试工作。

Windows 应用层反调试详述

图 1 UNIVACI 的控制台

1.2 小型机调试

美 国 迪 吉 多 电 脑 公 司 从 1960 年 开 始 发售 了 小 型 程 序 数 据 处 理 机(Programmed Data Processor,PDP), 它 的 体 积 相 当 于 现 在 的 服务器机柜。早期的 PDP-1 控制台上也可以发现类 似 UNIVACI 的 IOS 调 试 开 关, 随 着 PDP 系列计算机的更新换代,越来越多的研发人员开始为 PDP 编程,其中动态调试工具(Dynamic Debugging Tool,DDT)应运而生。

DDT-1 实现了断点(Breakpoints)、模式控制(Mode Control)、程序分析(Program Examination)、存储(Storage)、加载磁带(Loading Tapes)、打孔(Punching)和字搜索(Word Searches)等重要功能。早期的 DDT 功能比较简单,随着 DDT的更新迭代,功能逐渐强大,到 DDT-11 的时候已经具备命令实现单步跟踪、支持条件断点和远程调试等功能,为小型机的调试提供了重要的技术支撑。

1.3 微型机调试

Intel 公司于 1978 年推出了 8086 处理器,在其标志寄存器中正式加入了一个名为跟踪标志(Trace Flag,TF)的调试标志位,如图 2 所示。

Windows 应用层反调试详述

图 2 标志寄存器(FLAG)

TF 标志位的主要作用是为单步调试跟踪提供支持。处理器每执行完一条指令便会检测这个标志位的值,由此判断是否产生调试异常,并将该异常中断到调试器进行处理。

在微型机中调试的基本控制功能已经从硬件开关转变到寄存器实现。随着软件调试需求的不断深入,相关研发人员陆续发布了如 8086Monitor、CodeView、Turbo Debugger、SoftICE、交 互 式 反 汇 编 程 序(Interactive DisAssembler,IDA) 和 Windbg 等 调 试 器, 其 中 以 SoftICE、Windbg 和 IDA 为代表的一些调试器提供了较为完善的交互界面来提升调试效率。此外,美国国 家 安 全 局(National Security Agency,NSA)还专门研发了一款开源调试框架 Ghidra,该框架基于 Java 编写,包括一套功能齐全的软件分析工具,能跨平台支持 Windows、Linux 以及MacOS 系统,使微型机调试工具得到了比较全面的发展。

2

Windows 反调试技术

反调试技术的核心是检测系统上存在的调试器,相关技术类型可以分为 3 类:

(1)特征。通过调试器在计算机的安装或运行时对生成的信息进行检测的方式可以归纳为基于调试器特征的检测,比如查找调试器的安装文件,或查找调试器窗口的名字等。

(2)行为。通过调试器对被调试进程的操作行为的检测可以归纳为基于调试器行为的检测,比如打断点以及调试器对程序异常处理行为等。

(3)阻断。主动干扰调试器行为的保护技术,比如清空调试端口等技术手段。

2.1 检测特征

在 计 算 机 系 统 中, 以 获 取 调 试 器 相 关 特征来进行反调试的方法称为静态分析法。此方法是通过寻找进程环境块(Process Environment Block,PEB)、调试对象、注册表以及调试器名称和安装文件等特征信息,来达到反调试的目的。

2.1.1 检测进程环境块

Windows 系统为每个进程都分配了对应的进程空间 ,该空间是与物理地址对应的虚拟映射 ,保存与进程相关的信息,如 PEB 等。以x86 平台为例,在 Windbg 调试器中使用 dt 命令可查询其数据结构 ,如图 3 所示。

Windows 应用层反调试详述

图 3 PEB 数据结构

微软提供了IsDebuggerPresent()和CheckRemoteDebuggerPresent() 两个 API 来检测 PEB 结构中的BeingDebugged 字段,以达到反调试的目的,如图 4 所示。调用系统函数不需要关心底层 PEB结构问题,很好地兼容了 Windows 系统的各个版本。

Windows 应用层反调试详述

图 4 利用 BeingDebugged 反调试

另外,也可以通过段寄存器 FS:[x030] 来获取 PEB 地址,检测 ProcessHeap 和 NtGlobalFlags的内容  来实现反调试,如图 5 所示。

Windows 应用层反调试详述

图 5 利用 PEB.NtGlobalFlags 反调试

需要注意的是,使用 C++ 编写相关检测代码的时候,在不同的平台下 PEB 的结构偏移会有所变化。虽然可以通过微软提供的系统应用程序编程接口(Application Programming Interface,API)或内联汇编的方式来完成,但系统所提供的 API 具有更好的兼容性和稳定性。

2.1.2 获取调试器特征

用户在安装、配置以及运行调试器的过程中,都会有相应的信息被操作系统记录下来,Windows 通过一系列函数(包括导出和未导出)来检测系统中是否安装或者运行了调试器。

(1)进程名。通过进程名来检测调试器存在两种情况:一是检测父进程名。如果当前进程是用调试器加载启动的,那么调试器和被调试进程间就构成了父子关系,这时可以通过直接检测父进程的名字实现反调试。检测父进程可以先通过 系 统 函 数 ZwQueryInformationProcess() 来 获 取父 ID,然后用 GetProcessImageFileName() 来获取父进程名。二是遍历检测进程名。调试器如果在系统中运行,通过遍历进程的方式也可以达到反调试的效果。遍历进程在系统应用层中可以通过 系 统 函 数 Process32First()、Process32Next() 和CreateToolhelp32Snapshot() 配合完成,如图 6 所示。除此之外,函数 ZwQueryInformationProcess()也可以用来做进程遍历完成检测进程名的功能。

Windows 应用层反调试详述

图 6 遍历进程反调试

(2)窗口名。调试器通过操作界面控制相应调试功能,界面中包含很多控件,如图 7所示。通过查找调试器窗口判断系统中是否运行调试器。例如使用函数 FindWindow() 实现反调试。

(3)文件名。调试器如果在系统中没有运行,通常不需要进行检测,但如果用户想对相关数据进行收集,则可以通过安装文件、注册表以及运行的服务等信息来对系统环境进行分析,其原理类似于杀毒软件扫描 。通常遍历计算机磁盘上的文件夹,可以通过 FindFirstFile()和 FindNextFile() 函数来配合完成,将遍历出来的文件名进行对比,从而判断该用户系统是否存在调试软件。

Windows 应用层反调试详述

图 7 OllyDbg 调试器主界面

(4)注册表。程序在运行过程中会因为各种原因导致崩溃,当崩溃发生的时候系统会捕获异常。这时如果环境中设置有实时调试器,就可以读取注册表对实时调试器进行检测,检测位置如图 8 所示。

Windows 应用层反调试详述

图 8 实时调试器的注册表键值

(5)调试对象。调试器对进程进行调试通常有两种方式:一种是以 CreateProcess() 函数启动被调试进程,将 CreationFlags 参数设置为 DEBUG_PROCESS;另一种是使用 DebugActiveProcess() 函数直接附加进程进行调试。这两种方式内部实现都会先调用 DebugActiveProcess() 函数处理,要注意的是该函数内部的区别:调试器进程会通过NtCreateDebugObject() 在 Windows 系统内核创建一个调试对象(即 _DEBUG_OBJECT),而被调试进程则会通过 DbgkpSetProcessDebugObject 关联这个调试对象,二者通过 _DEBUG_OBJECT进行交互,如图 9 所示。正是利用了内核环境的特殊性 ,调试对象才能够充当不同进程之间交互的桥梁。

Windows 应用层反调试详述

图 9 调试器和被调试进程的交互

2.2 检测调试器行为

调试器在进行调试的时候会有一些必要的操作,比如对进程设置断点,捕获程序产生的异常,挂起调试线程以及单步调试代码等,基于这些行为进行检测就可以直接或间接达到反调试的目的。

2.2.1 时间推断

调试器在进行调试时基本上都会有一个单步调试的过程,而在进行单步调试的时候被调试的线程会处于挂起状态,这时可以通过获取系统时间或者外部可信时间,推断当前进程是否处于被调试的状态。微软提供了系统 API用来获取本地时间和系统当前运行时间,如GetSystemTime()、GetLocalTime()、GetTickCount()以及 QueryPerformanceCounter() 等。只需要在程序运行过程中记录时间并计算差值,就可以大致判断当前程序是否处于调试状态。

另外,根据文献 [18] 中提到的检测方式,在 CPU 中还有名为时间戳计数器(Time Stamp Counter,TSC) 的 64 位 寄 存 器。CPU 会 对 每个 时 钟 周 期 进 行 计 数 并 保 存 到 TSC 中, 通 过RDTSC 或 RDTSCP 汇编指令,就可以将 TSC 的值读入扩展数据寄存器(Extended Data Register,EDX)和扩展累加寄存器(Extended Accumulator Register,EAX)中,通过比对时间片差异就可以推断当前进程是否被调试器附加,代码如图10 所示。总的来说,与基于时间来检测调试器的原理基本相似,但这种推断方式需要注意的是,不同的 CPU 运行时间差值可能会有不一样的情况,有时候会有误判的情况发生。

Windows 应用层反调试详述

图 10 基于 TSC 检测调试器

2.2.2 异常处理机制

当正常运行的程序发生异常时,系统会接收异常并调用当前进程注册的结构化异常处理(Structured Exception Handling,SEH)。但如果这个进程正在被调试,那么调试器会优先于 SEH接收异常。利用 Windows 系统的异常处理机制 可以实现反调试的效果。调试器调试时会对进程设置软件断点(INT3),当进程运行到这个位 置 时, 就 会 触 发 EXCEPTION_BREAKPOINT异常。当该异常被触发时,若当前进程未被调试,系统会自动调用已经注册的 SEH 进行异常处理;若当前进程被调试,系统则会将进程控制权转给调试器。如果调试器没有对 SEH 代码做处理,被调试进程就会崩溃,从而实现反调试的目的,如图 11 所示。

Windows 应用层反调试详述

图 11 基于异常处理机制的反调试

基于异常处理机制的反调试,还可以在此基础上进行拓展。如果进程未安装 SEH 或无法处理内部异常,系统最终会调用 Unhandled ExceptionFilter() 函数来处理。此函数内部会通过调用 NtQueryInformationProcess() 函数来检查异常进程是否处于调试状态,从而决定最后是直接结束该进程,还是把控制权交给调试器来处理,这是 Windows 系统为保障进程正常、避免直接崩溃所做的最后一道保护屏障。通过这个处理逻辑,程序可以预先调用 SetUnhandledExceptionFilter()来安装一个处理函数,进而对调试器进行检测,其原理和 SEH 类似。

2.2.3 调试器断点

通常调试器所设置的断点按实现方式可分为 INT3 断点、硬件断点和内存断点。

(1)INT3 断点。它本质是通过改写被调试进程空间中执行代码的机器码为 0xCC,然后当进程运行到该位置时触发异常来实现。这种方式的反调试可以通过动态读取自身关键代码段进行哈希运算,再和预存结果进行比对,从而检测调试器,此类反调试技术在网络游戏保护软件中得到了广泛的应用,其中比较具有代表性的有 Tenprotect(TP)、nPeotectGameGuard 以及 AhnLabHackShield 等。

(2) 硬 件 断 点。该 断 点 是 依 赖 于 几 个调 试 寄 存 器(Dr0 ~ Dr7) 来 实 现 的。其 中Dr0 ~ Dr3 用于保存硬件断点的地址,所以硬件断点通常只能设置 4 个。Dr4 和 Dr5 作为保留寄存器,Dr6 和 Dr7 则用来标识硬件断点相关属性(读、写和执行等)。如果用户利用调试器在程序中设置了硬件断点,那么这时可以通过调用 GetThreadContext() 函数来获取 Dr0 ~ Dr3 的值来检测调试器,如图 12 所示。此外,还可以利用异常处理机制,在注册的 SEH 中直接通过线程 CONTEXT 读取 Dr 的值进行检测。

Windows 应用层反调试详述

图 12 通过 Dr 寄存器反调试

(3)内存断点。这种断点是通过修改进程内存的访问属性来实现的。利用内存属性检测调试器的方法在文献中有详细介绍,主要是利用 VirtualProtect() 函数来修改申请内存页 的 保 护 属 性, 当 陷 阱 EXCEPTION_GUARD_PAGE(0x80000001) 异常触发后,配合事先注册的异常处理程序达到检测的目的。

2.2.4 单步异常

调试器实现单步分析有两种方式:一种是设置 INT3 断点(非常规手段);另一种是修改EFLAGS 的 TF 标志位产生单步异常。当 CPU 检测到 TF 标志位为 1 时会进入单步执行模式,在该模式中,CPU 每执行一条指令后都会触发EXCEPTION_SINGLE_STEP 异 常(INT1), 并中断到调试器中,这样就实现了进程的单步运行,进程退出单步模式后 TF 标志位会被清零。

基于单步调试的实现原理,通过检测 TF 标志位可以获取当前进程是否处于调试模式。TF标志位状态的获取有两种方式:一是主动设置TF 的值,并结合 SEH 来检测调试器,和前面提到的利用异常处理机制检测调试器类似;二是利用关键指令 pop ss 来获取,如图 13 所示。实现过程需要注意的是,因为执行 pushfd 指令会让 TF 标志位自动清零,所以需要先执行 pop ss指令,让异常和中断挂起,直到它的下一条指令执行完毕,此时程序会暂停在 test 指令处,从而达到检测的目的。

Windows 应用层反调试详述

图 13 单步异常反调试

2.3 阻断调试器

干扰调试器的正常运行也是一种比较常见且重要的反调试技术手段,该方式与检测调试器特征和行为相比,其最大的特点是利用 Windows系统的一些特性,主动为调试器设置陷阱,导致调试器失效,或触发调试器检测行为,该方式具有较强的对抗性和隐蔽性。

2.3.1 自调试

文献 [21] 中提到了一种基于自调试(Self-Debug)的反调试技术,原理是调试器无法附加调试一个已经处于调试状态的进程。根据调试器的这个特性,用户可以在创建进程的时候,主动以调试方式创建需要保护的子进程,如图14 所示。另外,在创建子进程之后,父进程也可以通过调用 DebugActiveProcess() 对其进行附加,以达到相同的效果。这种反调试的技术在某些壳(Shell)保护软件里得到了应用,目前市面上比较具有代表性的就是穿山甲保护壳(Armadillo)。Armadillo 中的一个重要功能,就是利用自调试技术对需要保护的软件进行保护,并且通过父子进程之间的相互通信,来检测壳进程和保护进程的状态,从而达到防脱壳、防破解的效果。

Windows 应用层反调试详述

图 14 调试模式创建子进程

2.3.2 阻断调试事件

当 进 程 处 于 被 调 试 状 态 时, 文 献 [22] 对产生的相关调试事件做了详细说明,通过调用ZwSetInformationThread() 函数,可以使被调试线程(通常是主线程)脱离调试器。采用该函数阻断调试事件需要注意的是,函数第二个参数值必须传入 ThreadHideFromDebugger,这样调试器就无法接收该线程的调试事件,从而达到反调试的效果。

2.3.3 设置 TLS 回调

Windows 系统为多线程间资源的相互访问提供了一个访问机制,即线程本地存储(Thread Local Storage,TLS)。根 据 这 个 机 制, 进 程在创建主线程前会优先执行 TLS 函数,其执行的优先级顺序为 TLS> 入口点 >main() 函数。通常调试器启动调试一个进程的时候,会默认直接中断在进程的入口点(main() 函数),所以这种检测调试器是比较隐蔽的,调试器还没有运行到 main() 函数,就已经被检测,检测代码如图 15 所示。

Windows 应用层反调试详述

图 15 TLS 回调检测调试器

3

结 语

本文详细阐述了 Windows 应用层各种反调试技术的分类、实现方式及其实现原理。反调试技术在多年的发展和应用中,也引起了越来越多逆向工作人员的关注,大多数的反调试技术只要稍加注意都是可以发现与破解的。

反调试技术的攻击和改进是一个不断循环迭代的过程,本文没有特别针对反调试的攻击技术做相应的介绍,但想要在相应的对抗过程中取得优势,就必须对反调试技术做横向和纵向的拓展。就横向拓展来说,如果将反调试技术和软件加固技术综合利用,将反调试代码进行隐藏,就会大幅增加破解的难度。从目前情况来看,软件加固技术主要分为代码混淆、花指令、虚拟化等手段,这些技术是专门为了增加逆向分析人员的分析难度而存在的,其核心原理是通过改变原有代码的运行逻辑,使用无效代码干扰分析,大幅增加原有代码运行复杂度来实现,加固技术某种程度上会影响程序本身的稳定性和运行效率,所以在引入软件加固技术的时候需要事先对其造成的影响做一个综合评估。而纵向拓展更讲究反调试技术的深层次应用,例如,利用 Ring0 级权限禁止断点异常(如HOOK 系统中断表等),以及利用系统硬件层面提供的虚拟化技术(Virtualization Technology)监控并阻止进程内存的访问等,以上都是一些非常重要的手段。

在可以预见的未来,随着软件开发、调试不断引入新技术,反调试技术会面临更多的挑战,只有将多种软件加固技术结合起来,并且向硬件虚拟化  和内核反调试 不断深入,才能真正达到保护软件的效果,从而推动软件产业稳定、快速发展。

引用格式:李庆 .Windows 应用层反调试详述 [J]. 信息安全与通信保密 ,2023(4):73-82.

作者简介 >>>

李 庆,男,学士,中级工程师,主要研究方向为网络信息安全。

选自《信息安全与通信保密》2023年第4期(为便于排版,已省去原文参考文献)

Windows 应用层反调试详述

原文始发于微信公众号(信息安全与通信保密杂志社):Windows 应用层反调试详述

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月8日14:39:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows 应用层反调试详述http://cn-sec.com/archives/1930339.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息