首页
社区
课程
招聘
[原创]Rockwell反调试技术对抗以及一些常规反调试技术手段盘点
发表于: 2021-12-16 14:50 11157

[原创]Rockwell反调试技术对抗以及一些常规反调试技术手段盘点

2021-12-16 14:50
11157

本次基于协议分析的目的进行新版本的Rockwell上位机进行正常的逆向分析。在分析过程中发现使用OD加载主进程之后,在载入程序之后,并没有停在正常的调试选项中的Winmain,并且继续启动程序之后会立即将主程序关闭,很明显这版的Rockwell组态软件使用了相关的反调试技术。
本文章主要分为三个部分进行说明,主要包括常用反调试技术的介绍,Rockwell组态软件调试现象以及反调试技术分析,Rockwell组态软件反调试对抗。
1反调试技术介绍
1.1反调试技术简介
反调试技术,被调试代码用它识别是否被调试,或者让调试器失效。软件开发者意识到分析人员经常使用调试器来观察软件的操作,因此他们使用反调试技术尽可能地延长分析时间。为了阻止调试器的分析,当被调试的软件意识到自己被调试时,它们可能改变正常的执行路径或者修改自身程序让自己崩溃,从而增加调试时间和复杂度。很多种反调试技术可以达到反调试效果。
对于安全研究人员来说,调试过程中经常会碰到反调试技术,原因很简单:调试可以窥视程序的运行“秘密”,而程序作者想要通过反调试手段隐藏他们的“秘密”,普通程序需要防止核心代码被调试逆向,被调试程序需要隐藏自己的恶意行为防止被跟踪。就像病毒和杀软的关系一样,为了顺利的逆向分析,有反调试手段就有对应的破解方法-反反调试。
1.2常见的反调试技术
1.2.1探测Windows调试器
被调试程序可能会使用多种技术探测调试器调试它的痕迹,其中包括使用Windows API、手动检测调试器人工痕迹的内存结构和查询调试器遗留在系统中的痕迹等。调试器探测是最常用的反调试技术。
1.2.1.1使用Windows API
使用Windows API函数检测调试器是否存在是最简单的反调试技术。Windows操作系统中提供了这样一些API,应用程序可以通过调用这些API,来检测自己是否正在被调试。这些API中有些是专门用来检测调试器的存在的,而另外一些API是出于其他目的而设计的,但也可以被改造用来探测调试器的存在。其中很小部分API函数没有在微软官方文档显示。通常,防止被调试程序使用API进行反调试的最简单的办法是在被调试程序运行期间修改被调试程序,使其不能调用探测调试器的API函数,或者修改这些API函数的返回值,确保被调试程序执行合适的路径。与这些方法相比,较复杂的做法是挂钩这些函数,如使用rootkit技术。
⑴ IsDebuggerPresent
IsDebuggerPresent查询进程环境块(PEB)中的IsDebugged标志。如果进程没有运行在调试器环境中,函数返回0;如果调试附加了进程,函数返回一个非零值。

⑵ CheckRemoteDebuggerPresent
CheckRemoteDebuggerPresent同IsDebuggerPresent几乎一致。它不仅可以探测系统其他进程是否被调试,通过传递自身进程句柄还可以探测自身是否被调试。
图片描述
⑶ NtQueryInformationProcess
这个函数是Ntdll.dll中一个API,它用来提取一个给定进程的信息。它的第一个参数是进程句柄,第二个参数告诉我们它需要提取进程信息的类型。为第二个参数指定特定值并调用该函数,相关信息就会设置到第三个参数。第二个参数是一个枚举类型,其中与反调试有关的成员有ProcessDebugPort(0x7)、ProcessDebugObjectHandle(0x1E)和ProcessDebugFlags(0x1F)。例如将该参数置为ProcessDebugPort,如果进程正在被调试,则返回调试端口,否则返回0。

⑷ GetLastError
编写应用程序时,经常需要涉及到错误处理问题。许多函数调用只用TRUE和FALSE来表明函数的运行结果。一旦出现错误,MSDN中往往会指出请用GetLastError()函数来获得错误原因。被调试程序可以使用异常来破坏或者探测调试器。调试器捕获异常后,并不会立即将处理权返回被调试进程处理,大多数利用异常的反调试技术往往据此来检测调试器。多数调试器默认的设置是捕获异常后不将异常传递给应用程序。如果调试器不能将异常结果正确返回到被调试进程,那么这种异常失效可以被进程内部的异常处理机制探测。
对于OutputDebugString函数,它的作用是在调试器中显示一个字符串,同时它也可以用来探测调试器的存在。使用SetLastError函数,将当前的错误码设置为一个任意值。如果进程没有被调试器附加,调用OutputDebugString函数会失败,错误码会重新设置,因此GetLastError获取的错误码应该不是我们设置的任意值。但如果进程被调试器附加,调用OutputDebugString函数会成功,这时GetLastError获取的错误码应该没改变。
图片描述
对于DeleteFiber函数,如果给它传递一个无效的参数的话会抛出ERROR_INVALID_PARAMETER异常。如果进程正在被调试的话,异常会被调试器捕获。所以,同样可以通过验证LastError值来检测调试器的存在。如代码所示,0x57就是指ERROR_INVALID_PARAMETER。
图片描述
同样还可以使用CloseHandle、CloseWindow产生异常,使得错误码改变。
图片描述
⑸ ZwSetInformationThread
ZwSetInformationThread拥有两个参数,第一个参数用来接收当前线程的句柄,第二个参数表示线程信息类型,若其值设置为ThreadHideFromDebugger(0x11),使用语句ZwSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0);调用该函数后,调试进程就会被分离出来。该函数不会对正常运行的程序产生任何影响,但若运行的是调试器程序,因为该函数隐藏了当前线程,调试器无法再收到该线程的调试事件,最终停止调试。还有一个函数DebugActiveProcessStop用来分离调试器和被调试进程,从而停止调试。两个API容易混淆,需要牢记它们的区别。
1.2.1.2手动检测数据结构
虽然使用Windows API是探测调试器存在的最简单办法,但手动检查数据结构是被调试程序编写者最常使用的办法。这是因为很多时候通过Windows API实现的反调试技术无效,例如这些API函数被rootkit挂钩,并返回错误信息。因此,被调试程序编写者经常手动执行与这些API功能相同的操作。在手动检测中,PEB结构中的一些标志暴露了调试器存在的信息。这里,我们关注检测调试器存在常用的一些标志。
⑴ 检测BeingDebugged属性
Windows操作系统维护着每个正在运行的进程的PEB结构,它包含与这个进程相关的所有用户态参数。这些参数包括进程环境数据,环境数据包括环境变量、加载的模块列表、内存地址,以及调试器状态。

进程运行时,位置fs:[30h]指向PEB的基地址。为了实现反调试技术,被调试程序通过这个位置检查BeingDebugged标志,这个标志标识进程是否正在被调试。
图片描述
这种检查有多种形式,最终,条件跳转决定代码的路径。避免这种问题最简单的方法是在执行跳转指令前,手动修改零标志,强制执行跳转(或者不跳转)。
可以或者手动修改BeingDebugged属性值为0。在OllyDbg中安装命令行插件,为了启动该插件,用OllyDbg加载被调试程序,选择Plugins->Command Line->Command Line选项,在命令行窗口输入下面的命令。

如图所示,这条命令会将BeingDebugged属性转储到转储面板窗口。右键单击BeingDebugged属性,选择Binary->Fill With 00's,这时属性被设置为0。
OllyDbg的一些插件可以帮助我们修改BeingDebugged标志。其中最流行的有HideDebugger、Hidedebug和PhantOm。以PhantOm为例,同样将dll文件拷贝到OllyDbg的安装目录下就会自动安装。选择Plugins->PhantOm->Options选项,勾选hide from PEB即可。

⑵ 检测ProcessHeap属性
Reserved数组中一个未公开的位置叫作ProcessHeap,它被设置为加载器为进程分配的第一个堆的位置。ProcessHeap位于PEB结构的0x18处。第一个堆头部有一个属性字段,它告诉内核这个堆是否在调试器中创建。这些属性叫作ForceFlags和Flags。在Windows XP系统中,ForceFlags属性位于堆头部偏移量0x10处;在Windows 7系统中,对于32位的应用程序来说ForceFlags属性位于堆头部偏移量0x44处。
图片描述
同样,被调试程序也可以检查Windows XP系统中偏移量0x0C处,或者Windows 7系统中偏移量0x40处的Flags属性。这个属性总与ForceFlags属性大致相同,但通常情况下Flags与值2进行比较。
图片描述
避免这种问题方法和前面的差不多。如果用OllyDbg的命令行插件修改,输入的命令为dump ds:[fs:[30]+0x18]+0x10。如果用PhantOm插件,它会禁用调试堆创建功能而不需要手动设置。
⑶ 检测NTGlobalFlag
由于调试器中启动进程与正常模式下启动进程有些不同,所以它们创建内存堆的方式也不同。系统使用PEB结构偏移量0x68处的一个未公开位置,来决定如何创建堆结构。如果这个位置的值为0x70,我们就知道进程正运行在调试器中。
图片描述
操作系统创建堆时,值0x70是下列标志的一个组合。如果进程从调试器启动,那么进程的这些标志将被设置。
(FLG_HEAP_ENABLE_TAIL_CHECK|FLG_HEAP_ENABLE_FREE_CHECK|FLG_HEAP_VALIDATE_PARAMETERS)
避免这种问题方法和前面的差不多。如果用OllyDbg的命令行插件修改,输入的命令为dump fs:[30]+0x68。如果用PhantOm插件,它会逃避使用NTGlobalFlag的反调试技术而不需要手动设置。
1.2.1.3系统痕迹检测
通常,我们使用调试工具来分析被调试程序,但这些工具会在系统中驻留一些痕迹。被调试程序通过搜索这种系统痕迹,来确定你是否试图分析它。
⑴ 查找调试器引用的注册表项
下面是调试器在注册表中的一个常用位置。
SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug(32位系统)
SOFTWARE\Wow6432Node\Microsoft\WindowsNT\CurrentVersion\AeDebug(64位系统)
该注册表项指定当应用程序发生错误时,触发哪一个调试器。默认情况下,它被设置为Dr.Watson。如果该这册表的键值被修改为OllyDbg,则被调试程序就可能确定它正在被调试。

⑵ 查找窗体信息
FindWindow函数检索处理顶级窗口的类名和窗口名称匹配指定的字符串。

EnumWindows函数枚举所有屏幕上的顶层窗口,并将窗口句柄传送给应用程序定义的回调函数。

图片描述
GetForegroundWindow获取一个前台窗口的句柄。

为了防范这种技术,在OllyDbg的PhantOm插件中勾选hide OllyDbg windows。
⑶ 查找进程信息

1.2.2识别调试器行为
在逆向工程中,为了帮助被调试程序分析人员进行分析,可以使用调试器设置一个断点,或是单步执行一个进程。然而,在调试器中执行这些操作时,它们会修改进程中的代码。因此,被调试程序常使用几种反调试技术探测软件/硬件断点、完整性校验、时钟检测等几种类型的调试器行为。直接运行被调试程序与在调试器中运行被调试程序也会在一些细节上不同,如父进程信息、STARTUPINFO信息、SeDebugPrivilege权限等。
1.2.2.1软件断点检查
调试器设置断点的基本机制是用软件中断指令INT 3临时替换运行程序中的一条指令,然后当程序运行到这条指令时,调用调试异常处理例程。INT 3指令的机器码是0xCC,因此无论何时,使用调试器设置一个断点,它都会插入一个0xCC来修改代码。被调试程序常用的一种反调试技术是在它的代码中查找机器码0xCC,来扫描调试器对它代码的INT 3修改。repne scasb指令用于在一段数据缓冲区中搜索一个字节。EDI需指向缓冲区地址,AL则包含要找的字节,ECX设为缓冲区的长度。当ECX=0或找到该字节时,比较停止。

1.2.2.2硬件断点检查

在OllyDbg的寄存器窗口按下右键,点击View debug registers可以看到DR0、DR1、DR2、DR3、DR6和DR7这几个寄存器。DR0、Dr1、Dr2、Dr3用于设置硬件断点,由于只有4个硬件断点寄存器,所以同时最多只能设置4个硬件断点。DR4、DR5由系统保留。 DR6、DR7用于记录Dr0-Dr3中断点的相关属性。如果没有硬件断点,那么DR0、DR1、DR2、DR3这4个寄存器的值都为0。

1.2.2.3执行代码校验和检查
被调试程序可以计算代码段的校验并实现与扫描中断相同的目的。与扫描0xCC不同,这种检查仅执行被调试程序中机器码CRC或者MD5校验和检查。

1.2.2.4时钟检测
被调试时,进程的运行速度大大降低,例如,单步调试大幅降低被调试程序的运行速度,所以时钟检测是被调试程序探测调试器存在的最常用方式之一。有如下两种用时钟检测来探测调试器存在的方法。
记录一段操作前后的时间戳,然后比较这两个时间戳,如果存在滞后,则可以认为存在调试器。
记录触发一个异常前后的时间戳。如果不调试进程,可以很快处理完异常,因为调试器处理异常的速度非常慢。默认情况下,调试器处理异常时需要人为干预,这导致大量延迟。虽然很多调试器允许我们忽略异常,将异常直接返回程序,但这样操作仍然存在不小的延迟。
⑴ 使用rdtsc指令
较常用的时钟检测方法是利用rdtsc指令(操作码0x0F31),它返回至系统重新启动以来的时钟数,并且将其作为一个64位的值存入EDX:EAX中。被调试程序运行两次rdtsc指令,然后比较两次读取之间的差值。
图片描述
⑵ 使用QueryPerformanceCounter和GetTickCount
同rdtsc指令一样,这两个Windows API函数也被用来执行一个反调试的时钟检测。使用这种方法的前提是处理器有高分辨率能力的计数器-寄存器,它能存储处理器活跃的时钟数。为了获取比较的时间差,调用两次QueryPerformanceCounter函数查询这个计数器。若两次调用之间花费的时间过于长,则可以认为正在使用调试器。GetTickCount函数返回最近系统重启时间与当前时间的相差毫秒数(由于时钟计数器的大小原因,计数器每49.7天就被重置一次)。
图片描述
1.2.2.5判断父进程是否是explorer.exe
一般双击运行的进程的父进程都是explorer.exe,但是如果进程被调试父进程则是调试器进程。也就是说如果父进程不是explorer.exe则可以认为程序正在被调试。

1.2.2.6判断STARTUPINFO信息
explorer.exe创建进程的时候会把STARTUPINFO结构中的值设为0,而非explorer.exe创建进程的时候会忽略这个结构中的值,也就是结构中的值不为0。所以可以利用STARTUPINFO来判断程序是否在被调试。

1.2.2.7判断是否具有SeDebugPrivilege权限
默认情况下进程是没有SeDebugPrivilege权限的,但是当进程通过调试器启动时,由于调试器本身启动了SeDebugPrivilege权限,当调试进程被加载时SeDebugPrivilege也就被继承了。所以我们可以检测进程的SeDebugPrivilege权限来间接判断是否存在调试器,而对SeDebugPrivilege权限的判断可以用能否打开csrss.exe进程来判断。

1.2.3干扰调试器的功能
被调试程序可以用一些技术来干扰调试器的正常运行。例如线程本地存储(TLS)回调、插入中断、异常等。这些技术当且仅当程序处于调试器控制之下时才试图扰乱程序的运行。
1.2.3.1使用TLS回调
Thread Local Storage(TLS),即线程本地存储,是Windows为解决一个进程中多个线程同时访问全局变量而提供的机制。TLS可以简单地由操作系统代为完成整个互斥过程,也可以由用户自己编写控制信号量的函数。当进程中的线程访问预先制定的内存空间时,操作系统会调用系统默认的或用户自定义的信号量函数,保证数据的完整性与正确性。下面是一个简单的TLS回调的例子,TLS_CALLBACK1函数在main函数执行前调用IsDebuggerPresent函数检查它是否正在被调试。

要在程序中使用TLS,必须为TLS数据单独建一个数据段,用相关数据填充此段,并通知链接器为TLS数据在PE文件头中添加数据。_tls_callback[]数组中保存了所有的TLS回调函数指针。数组必须以NULL指针结束,且数组中的每一个回调函数在程序初始化时都会被调用,程序员可按需要添加。但程序员不应当假设操作系统已何种顺序调用回调函数。如此则要求在TLS回调函数中进行反调试操作需要一定的独立性。
正常运行这个程序会打印下面的内容。
TLS_CALLBACK: No Debugger Present!
233
如果把在OllyDbg中运行,在OllyDbg暂停之前会打印下面的内容。
TLS_CALLBACK: Debugger Detected!
使用PEview查看.tls段,可以发现TLS回调函数。通常情况下,正常程序不使用.tls段,如果在可执行程序中看到.tls段,应该立即怀疑它使用了反调试技术。

在OllyDbg中选择Options->Debugging Options->Events,然后设置System break-point作为第一个暂停的位置,这样就可以让OllyDbg在TLS回调执行前暂停。

在IDA Pro中按Ctrl+E快捷键看到二进制的入口点,该组合键的作用是显示应用程序所有的入口点,其中包括TLS回调。双击函数名可以浏览回调函数。

由于TLS回调已广为人知,因此同过去相比,被调试程序使用它的次数已经明显减少。为数不多的合法程序使用TLS回调,所以可执行程序中的.tls段特别突出。
1.2.3.2利用中断
因为调试器使用INT 3来设置软件断点,所以一种反调试技术就是在合法代码段中插入0xCC(INT 3)欺骗调试器,使其认为这些0xCC机器码是自己设置的断点。
图片描述
除了使用_try和_except以外还可以直接使用汇编代码安装SEH。在下面的代码中如果进程没有处于调试中,则正常终止;如果进程处于调试中,则跳转到非法地址0xFFFFFFFF处,无法继续调试。
图片描述
双字节操作码0xCD03也可以产生INT 3中断,这是被调试程序干扰WinDbg调试器的有效方法。在调试器外,0xCD03指令产生一个STATUS_BREAKPOINT异常。然而在WinDbg调试器内,由于断点通常是单字节机器码0xCC,因此WinDbg会捕获这个断点然后将EIP加1字节。这可能导致程序在被正常运行的WinDbg调试时,执行不同的指令集(OllyDbg可以避免双字节INT 3的攻击)。
图片描述
INT 2D原为内核模式中用来触发断点异常的指令,也可以在用户模式下触发异常。但程序调试运行时不会触发异常,只是忽略。INT 2D指令在ollydbg中有两个有趣的特性。在调试模式中执行INT 2D指令,下一条指令的第一个字节将被忽略。使用StepInto(F7)或者StepOver(F8)命令跟踪INT 2D指令,程序不会停在下一条指令开始的地方,而是一直运行,就像RUN(F9)一样。在下面的代码中,程序调试运行时,执行INT 2D之后不会运行SEH,而是跳过NOP,把bDebugging标志设置为1,跳转到normal_code;程序正常运行时,执行INT 2D之后触发SEH,在异常处理器中设置EIP并把bDebugging标志设置为0。
图片描述
片内仿真器(ICE)断点指令ICEBP(操作码0xF1)是Intel未公开的指令之一。由于使用ICE难以在任意位置设置断点,因此ICEBP指令被设计用来降低使用ICE设置断点的难度。运行ICEBP指令将会产生一个单步异常,如果通过单步调试跟踪程序,调试器会认为这是单步调试产生的异常,从而不执行先前设置的异常处理例程。利用这一点,被调试程序使用异常处理例程作为它的正常执行流程。为了防止这种反调试技术,执行ICEBP指令时不要使用单步。
图片描述
1.2.3.3设置陷阱标志位
EFLAGS寄存器的第八个比特位是陷阱标志位。如果设置了,就会产生一个单步异常。
图片描述
1.2.3.4使用异常
前面已经讨论了各种使用异常机制的反调试手段。
⑴ RaiseException
RaiseException函数产生的若干不同类型的异常可以被调试器捕获。
图片描述
⑵ SetUnhandledExceptionFilter
进程中发生异常时若SEH未处理或注册的SEH不存在,会调用UnhandledExceptionFilter,它会运行系统最后的异常处理器。UnhandledExceptionFilter内部调用了前面提到过的NtQueryInformationProcess以判断是否正在调试进程。若进程正常运行,则运行最后的异常处理器;若进程处于调试,则将异常派送给调试器。SetUnhandledExceptionFilter函数可以修改系统最后的异常处理器。下面的代码先触发异常,然后在新注册的最后的异常处理器内部判断进程正常运行还是调试运行。进程正常运行时pExcept->ContextRecord->Eip+=4;将发生异常的代码地址加4使得其能够继续运行;进程调试运行时产生无效的内存访问异常,从而无法继续调试。

在OllyDbg中,选择Options->Debugging Options->Exceptions来设置把异常传递给应用程序。

1.2.4调试器漏洞
与所有软件一样,调试器也存在漏洞,有时被调试程序编写者为了防止被调试,会攻击这些漏洞。这里我们展示几种OllyDbg调试器处理PE格式文件时的常见漏洞。
1.2.4.1 PE头漏洞
OllyDbg非常严格地遵循了微软对PE文件头部的规定。在PE文件的头部,通常存在一个叫作IMAGE_OPTIONAL_HEADER的结构。

需要特别注意这个结构中的最后几个元素。NumberOfRvaAndSizes属性标识后面DataDirectory数组中的元素个数。DataDirectory数组表示在这个可执行文件中的什么地方可找到其他导入可执行模块的位置,它位于可选头部结构的末尾,是一个比IMAGE_DATA_DIRECTORY略大一些的数组。数组中每个结构目录都指明了目录的相对虚拟地址和大小。DataDirectory数组的大小被设置为IMAGE_NUMBEROF_DIRECTORY_ENTRIES,它等于0x10。因为DataDirectory数组不足以容纳超过0x10个目录项,所以当NumberOfRvaAndSizes大于0x10时,Windows加载器将会忽略NumberOfRvaAndSizes。OllyDbg遵循了这个标准,并且无论NumberOfRvaAndSizes是什么值,OllyDbg都使用它。因此,设置NumberOfRvaAndSizes为一个超过0x10的值,会导致在程序退出前,OllyDbg对用户弹出一个窗口。如图所示,使用LordPE打开可执行文件,修改RVA数及大小并保存,再用OllyDbg打开,会提示错误Bad or unknown format of 32-bit executable file。


另一种PE头的欺骗与节头部有关。文件内容中包含的节包括代码节、数据节、资源节,以及一些其他信息节。每个节都拥有一个IMAGE_SECTION_HEADER结构的头部。

VirtualSize和SizeOfRawData是其中两个比较重要的属性。根据微软对PE的规定,VirtualSize应该包含载入到内存的节大小,SizeOfRawData应该包含节在硬盘中的大小。Windows加载器使用VirtualSize和SizeOfRawData中的最小值将节数据映射到内存。如果SizeOfRawData大于VirtualSize,则仅将VirtualSize大小的数据复制入内存,忽略其余数据。因为OllyDbg仅使用SizeOfRawData,所以设置SizeOfRawData为一个类似0x77777777的大数值时,会导致OllyDbg崩溃。如图所示,使用LordPE打开可执行文件,点击区段,在区段表上右击,点击编辑区段,修改物理大小并保存,再用OllyDbg打开,会提示同样的错误。

对抗这种反调试技术的最简单方法是用类似的编辑器手动修改PE头部。OllyDbg2.0和WinDbg不存在这种漏洞。
1.2.4.2 OutputDebugString漏洞
被调试程序常尝试利用OllyDbg1.1的格式化字符串漏洞,为OutputDebugString函数提供一个%s字符串的参数,让OllyDbg崩溃。因此,需要注意程序中可疑的OutputDebugString调用,例如OutputDebugString("%s%s%s%s%s%s%s%s%s")。如果执行了这个调用,OllyDbg将会崩溃。
2 Rockwell组态软件调试现象以及反调试技术分析
2.1 Rockwell组态软件调试现象
调试器加载Rockwell主程序之后,并没有在主函数入口断下,也就是WinMain()函数出停下,而我们的调试软件的调试选项已经将WinMain()函数设置为第一次暂停程序的位置。


点击F9也就是将主程序运行起来之后,没有出现正常的模块加载过程,而是直接跳到了Windows内核的异常处理模块中,将主进程关闭。

2.2 Rockwell组态软件反调试技术分析
以上是非常明显的反调试机制所带来的影响。而根据所展示的现象很明显基本排除了识别调试器行为,利用调试器漏洞的可能也比较小,此时做的仅仅是调试器加载了主程序,并未进行任何的操作。
根据可能性分析,最大的可能是使用探测Windows调试器的功能进行的一些内核的功能API函数来进行一个比较简单的反调试操作。
2.2.1 IsDebuggerPresent
进一步进行分析,首先想到的一定是IsDebuggerPresent()这个系统API函数,一般程序开发人员使用比较普遍的内核的功能API函数,先查看程序加载之后所有可引用的模块,在这之中搜索此API函数。

首先在程序段中使用的IsDebuggerPresent()都下程序断点,就行运行,观察程序运行结果,运行程序确实能够在该函数的入口地址断下,实现方式在汇编的角度看主要是:
76703D80 64:A1 30000000 mov eax,dword ptr fs:[0x30]
76703D86 0FB640 02 movzx eax,byte ptr ds:[eax+0x2]
首先将FS寄存器的fs:[0x30]处的四个字节的数据放到eax中,其中FS寄存器指向当前活动线程的TEB结构(线程结构)具体如下:


也就是将0x002ca000+0x2的地址中所存数据取四字节放入eax寄存器,作为最后的返回值进行判断,但此时返回值依然是0,继续运行也直接停止进程,也就是说,此API函数默认并没有调试器在运行,所以排除IsDebuggerPresent()的可能性。


2.2.2 GetLastError
这也是个常用的反调试手段,原理在前文已经提到。利用内核的异常处理机制,当程序运行在当前主程序领空时,调用OutputDebugString函数会失败,错误码会重新设置,因此GetLastError获取的错误码应该不是我们设置的任意值,但是当程序被调试时,该进程的抛出的异常信号将会被调试器捕获,此时运行在调试器的用户领空,调用OutputDebugString函数会成功,这时GetLastError获取的错误码应该没改变。
同样此时应该先查看程序加载之后所有可引用的模块,在这之中搜索此API函数。SetLastError(),OutputDebugString(),GetLastError()都已经加载到用户空间。



在GetLastError()函数设置断点,此函数的汇编主要也就是这两行,同样观察eax的值,是否会发生变化。
766E1A20 64:A1 18000000 mov eax,dword ptr fs:[0x18]
766E1A26 8B40 34 mov eax,dword ptr ds:[eax+0x34]
在多次的连续执行下,此函数的返回值也就是eax的值始终没有改变,猜测此值就是初始设定值0x0000007f。也侧面印证了,此程序调用的异常处理机制正是此次所要解决的反调试问题。



3 Rockwell组态软件反调试对抗
我们已经知道了此反调试机制的原理,也确定了此程序所反调试手段,对于其的破解自然是得心应手的,最简单的方式就是任意哪一次程序断在了GetLastError()处,我们修改此函数运行完之后eax寄存器的值为0x0即可,在这之后关闭断点继续运行程序,程序会自动跳过此异常机制,不会再次进行异常的捕获而直接按照正常的流程进行执行。




总结:当然,我们可以使用一些OD的插件来进行一些反调试的对抗类似于StrongOD、SharpOD x64。但是这些插件无法解决所有问题,甚至有些反调试机制能够利用这些插件的漏洞,所以我们应该将提高自身的专业技术素养作为第一要务。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 3
支持
分享
最新回复 (6)
雪    币: 199
活跃值: (161)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
怎么好像在哪见过
2021-12-16 15:24
0
雪    币: 10852
活跃值: (14639)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
3
这个我玩过,通信认证的算法很简单。
2021-12-16 16:35
0
雪    币: 218
活跃值: (497)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
wmsuper 这个我玩过,通信认证的算法很简单。
是的,主要这一版的Logix Designer上壳了
2021-12-16 16:44
0
雪    币: 471
活跃值: (4013)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
5
ICEBP当时在某个rk里面看到,牙膏厂未公开指令猫腻啊 
2021-12-18 10:31
0
雪    币: 26398
活跃值: (63257)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
6
这文章是转载的吗?和这文章一样的:https://blog.csdn.net/JiangBuLiu/article/details/104867710
2021-12-21 21:54
0
雪    币: 218
活跃值: (497)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
Editor 这文章是转载的吗?和这文章一样的:https://blog.csdn.net/JiangBuLiu/article/details/104867710
前面是一些盘点,后面关于Rockwell的是具体的原创实战
2021-12-24 10:44
0
游客
登录 | 注册 方可回帖
返回
//