1.反调试技术:
恶意代码编写者利用反调试技术来判断恶意代码是否被调试,以此来阻止调试器分析,或者使调试器失效。注意,也可以使用反调试来保护我们的加密代码,不一定是干扰或者破坏调试器,
参考资料:
1.逆向工程核心原理
2.恶意代码分析实战
2.检测调试器
2-1:利用windows数据结构
PEB结构包含关于进程的诸多信息,其中也包含调试信息。关于进程是否处于调试状态与以下几个成员有关。
如何过去PEB的结构呢?FS寄存器指向的是PEB结构,PEB.ProcessEvnivornmentBlock(+0x30)指向的是PEB的结构体。由此可以来访问PEB结构了。
关于BeingDebugged是利用这个成员是否为0来判断进程是否处于调试状态。Ldr成员指向的PEB_LDR_DATA数据区域,如果进程处于调试状态,这个区域填充着特殊字符(0xfeeeeeee)
ProcessHeap[4]是一个数组,关于反调试的是Flag(0xC或者0x40)和ForseFlag(0x10或者0x44)这两个成员。
调试中的进程和正常运行的进程时不同的,凭借NtGlobalFlag这个未公开的成员可以判断进程是否处于调试状态,如果该成员的值是0x70得话,那么这个进程处于调试状态。
2-2:windowsAPI
一下这两个函数是检测IsDebugged这个成员的。
利用NtQueryInformationProcess()来进行反调试。
- 如果第二个参数是ProcessDebugPort的话,通过检查该参数返回的值来判断进程是否被调试,如果返回0,说明没有被调试,否是说明进程正在被调试。
OutPutDebugString()函数用于返回调试信息,如果进程没有被调试,如果调用此函数,该函数会返回失败。利用如下代码
利用NtQuerySystemInformation()来判断系统是否处于调试状态。
- 如果进程处于调试状态,在第一个参数传入SystemKernelDebuggerInformation(0x23)后,返回第二个参数指向的system_kernel_Debug_Information的结构中的Debugedable设置为1.
利用NtQueryObject来判断,当某个调试器在调试进程的时候,系统会创建一个内核调试对象,通过检查内核信息链表来查找是否存在内核调试对象。
- 在第二个参数传递ObjectBasicInformation共枚举类型中的ObjectAllTypesInformation(3号),通过返回在第三个参数中的指针对应的ObjectInformation来查找是否存在调试内核对象。
破解:修改传入参数为ObjectBasicInformtion(0号)来避免反调试
利用ZwSetInformationThread()来隐藏被调试进程以达到反调试的效果。1234567891011121314151617181920212223242526272829303132NTSTATUS ZwSetInformationThread(_In_ HANDLE ThreadHandle,_In_ THREADINFOCLASS ThreadInformationClass,_In_ PVOID ThreadInformation,_In_ ULONG ThreadInformationLength);//ntpsapi.htypedef enum _THREADINFOCLASS {ThreadBasicInformation,ThreadTimes,ThreadPriority,ThreadBasePriority,ThreadAffinityMask,ThreadImpersonationToken,ThreadDescriptorTableEntry,ThreadEnableAlignmentFaultFixup,ThreadEventPair_Reusable,ThreadQuerySetWin32StartAddress,ThreadZeroTlsCell,ThreadPerformanceCount,ThreadAmILastThread,ThreadIdealProcessor,ThreadPriorityBoost,ThreadSetTlsArrayAddress,ThreadIsIoPending,ThreadHideFromDebugger,//这个就是用来将线程对调试器隐藏ThreadBreakOnTermination,ThreadSwitchLegacyState,ThreadIsTerminated,MaxThreadInfoClass} THREADINFOCLASS;// end_ntddk end_ntifs在第二个参数传入ThreadHideFromDebugger,如果进程处于调试状态,该API函数会使调试器和进程终止.
- 应对方法:修改传入参数,为0即可。
2-3:检测系统痕迹
通过扫描调试器在系统中残留的痕迹来判断进程是否处于调试状态。可以选择注册表HKLM\SOFTWARE\MICROSOTF\WINDOWS NT\CURRENTVERSION\AeDebug或者查看窗口是否存有调试器名称(FindWindow)或者使进程等方面来判断。
3.TIME CHECK(时钟检测)
逐行跟踪代码比正常运行代码所花费的时间多得多。通过比较计算运行代码之间的时间差来判断进程是否处于调试状态。一般的,利用windows提供的时间和CPU的计数器(TSC)来测量时间差。TSC基于cpu内部的计数器,所以计算精度会更高。利用以下伪代码实现
3-1:rdtsc指令
CPU中存在一个名为TSC(时间戳计数器)的64为寄存器。RDTSC是一个读取该寄存器的指令,得到的值高32位存储在EDX中,低32位保存在EAX中。
3-2:利用GetTickCount或者QueryPerformanceCounter来判断
利用这两个函数方法和伪代码一样。if(Time2-Time2>0xfffffffff)语句中0xfffffffff是一个处于0xffff到0xffffffff中的一个任意值,因为单步一个指令所用的时间必定大于0xffffffff
4.干扰调试器
4-1:陷阱标志SEH反调试
CPU第9个标志位TF为陷进标志位,当TF位1的时候,CPU进入单步模式,没执行一条指令,就会触发一个单步异常。如果进程处于调试状态,触发异常后,异常会交给调试器,此时不会执行SEH处理函数。如过程序处于非调试状态,则会触发SEH异常处理。如图,在401011处安装一个SEH处理函数,401024用于置陷进标志位,因为无法直接修改寄存器,先用栈保存寄存器数据,然后修改第8位比特数。利用nop触发单步异常,如果进程处于调试状态就会触发这个异常,进程转入40102F和401034,如果进程没有处于调试状态,就会转入异常处理函数,然后结束异常处理。【关于陷进标志,请看逆向工程核心原理第566页】
解决方法:先忽略异常,在SEH处理函数和处理完毕后下断点,运行即可。
4-2:Int 3结合SEH反调试
Int 3是软件中断,操作码是0xcc,一般反调试的手法都是结合SEH来进行的,如果进程处于调试状态,触发异常后,异常会交给调试器,此时不会执行SEH处理函数,一般这个地方都是设置一个趋于死亡的函数跳转如:mov eax,1;jmp eax。如果程序正常,则异常交给SEH处理函数,通过SEH处理函数,最后回归正常的代码。如图所示:
- 在调试器选项中勾选忽略所有异常(这里是断点异常)
- 在SEH处理函数处(40102A)和结束异常处理后(401044)设置断点。
- 运行程序即可。(部分环境下使用单步会使得调试奔溃,建议使用F9运行)
4-3:Int 2D
Int 2D是内核模式用于触发断点的指令,在用户模式也是可以执行的,但是调试器不会触发此异常,只是忽略,如果遇到Int 2D指令,调试器无法执行单步指令,知道遇到断点才能中断。如图遇到int 2d断点异常。
- 使用隐藏OD插件吧,我调了很久还是过不了.
4-4:TLS反调试
利用TLS(线程本地存储技术)可以让代码优先于程序制动的OEP入口点运行,根据恶意代码查杀实战的说法可以使得一些敏感代码处于TLS回调函数中优先执行,这样就不会被调试,我想采取更加积极主动的反调试措施,在TLS回调函数中直接插入反调试代码进行反调试(书上只是隐藏关键代码)。
解决方法:利用TLS技术,程序会产生TLS节区,如果存在这个节区就要怀疑程序使用了反调试,我们应该设置调试器,中断在system Break-Poit让od在tls回调函数之前暂停。关于TLS反调试技术请见浅谈TLS反调试技术。5.调试器特征检测
5-1:0xcc检测
0xCC是软件断点的机器码,od在下断点的时候是否0xCC来代替原指令,但是又偷梁换柱的显示的是原指令,这样起到了中断的效果。但是,单纯的检测0xCC指令是不对的,因为很多其他指令也是使用0xCC机器码(移位,立即数)。5-2:API检测
我们如果要调试一个程序的局部功能,最简单的方法是对API函数下个断点,然后执行到返回(或者根据堆栈查看返回地址)所以这种API下断也成为恶意代码编写者设置反调试的重点区域。在机器码层面上,调试器如果对某个API下断,其首个机器码变成0xcc(但是没显示出来)通过检测API的首个机器码来判断程序是否被调试。
解决方法:不对API函数的第一条指令下断点,或者使用硬件断点(0xcc属于软件断点)5-3:求校验和
微小的更改都可以使得代码缓冲区校验和发生改变,通过比较原始校验和和新校验和的值来判断是否进行调试。6.利用调试器的漏洞
od1.0版本存在两个PE结构处理的漏洞,所以可以凭此来反调试,但是在2.0已经修复了该漏洞。