反调试与破反调试笔记

获取中...

静态反调试

反调试技术知识点

TEB 线程环境块

TEB 是个结构体
TEB结构中的两个重要成员

+0x000         NtTib :_NT_TIB . . . 
+0x30         ProcessEnvironmentBlock :Ptr32_PEB

TEB 的Offet30移位处就是PEB的结构体指针

PEB 进程环境块,每个进程都对应一个PEB结构体

NtTib 线程信息块
结构体信息如下:

typedef struct    _NT_TIB{
     struct    _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
     PVOID StackBase;
     PVOID StackLimit; 
     PVOID SubSystemTib; 
     union {
        PVOID FiberData; 
        DWORD Version;
     }; 
     PVOID ArbitraryUserPointer;
      struct    _NT_TIB *Self;
} NT_TIB;
typedef NT_TIB *PNT_TIB;

ExceptionList成员指向_EXCEPTION_REGISTRATION_RECORD结构体组成的链表,它用于WindowsOS的SEH, SEH是Wiondows操作系统中的结构化异常处理机制,常用于反调试技术。
Self成员是_NT_TIB结构体的自引用指针,它指向_NT_TIB结构体,又因为_NT_TIB是TEB结构体的第一个成员,所以它也是指向TEB结构体的指针(它里面存着TEB结构体的地址)。

使用Ntdll.NtCurrentTeb()API访问TEB结构体,它返回当前线程的的TEB结构体的地址,通过OD看它的实现方法就是返回FS:[0x18]处的地址值(FS段寄存器用来指示当前线程的TEB结构体),而FS:0x18处正是Self指针,其含有的也正是TEB结构体的地址。
用一个公式总结:
FS:[0x18] = TEB.NtTib.Self = address of TIB = address of TEB = FS:0

所以:
FS:[0x18] = TEB起始地址
FS:[0x30] = PEB的起始地址
FS:[0] = TEB.NtTib.ExceptionList = address of SEH

就算不理解反正记住就行了。

PEB 进程环境块

PEB中重要的几个成员

+002    BeingDebugged         ;Uchar(可用于反调试技术)
...
+008    ImageBaseAddress      ;Ptr32 Void
...
+00c        Ldr              ;Ptr32 _PEB_LDR_DATA(可用于反调试技术)
...
+018    ProcessHeap      ;Ptr32 Void(可用于反调试技术)
...
+068    NtGlobalFlag       ;uint4B(可用于反调试技术)

PEB.BeingDebugged

BeingDebugged的作用就是标识当前进程是否处于调试状态,Kernel32.dll中的IsDebuggerPresent() API就是用来获取该处的值的(是,则返回1;否,则返回0)

IsDebuggerPresent() API的汇编代码形式

MOV EAX,DWORD PTR FS:[18] 
MOV EAX,DWORD PTR DS:[EAX+30] 
MOVZX EAX,BYTE PTR DS:[EAX+2] 
RETN

该值在代码逆向分析领域主要用于反调试技术。检测该值,若进程处于调试中,则终止进程。

**破解之法
只要借助OllyDbg调试器的编辑功能,将PEB.BeingDebugged的值修改为0(FALSE)即可。**

PEB.ImageBaseAddress成员用来表示进程的ImageBase
GetModuleHandle()API用来获取ImageBase。
将lpModuleName参数赋值为NULL,调用GetModuleHandle()函数将返回进程被加载的ImageBase

GetModuleHandle() API的部分代码

MOV    EAX,DWORD PTR FS:[18]         ;FS:[18] = TEB
MOV    EAX,DWORD PTR DS:[EAX+30]     ;DS:[EAX+30] = PEB
MOV    EAX,DWORD PTR DS:[EAX+8]      ;DS:[EAX+8] = PEB.ImageBaseAddress

PEB.Ldr

PEB.Ldr成员是指向_PEB_LDR_DATA结构体的指针,_PEB_LDR_DATA结构体如下。

+000 Length                         :Uint4B
+004 Initialized                        :UChar
+008 SsHandle                       :Ptr32 Void
+00c InLoadOrderModulelist          :_LIST_ENTRY
+014 InMemoryOrderModulelist        :_LIST_ENTRY
+01c InInitializationOrderModulelist:        _LIST_ENTRY
+024 EntryInProgress                :Ptr32 Void
+028 ShutdownInProgress             :UChar
+02c ShutdownThreadId               :Ptr32 Void

当模块(DLL)加载到进程后,通过PEB.Ldr成员可以直接获得该模块的加载基址,_PEB_LDR_DATA结构体中含有3个_LIST_ENTRY类型的成员,_LIST_ENTRY结构体如下。

typedef struct LIST_ENTRY{
     struct _LIST_ENTRY *Flink;
     struct _LIST_ENTRY *Bink; 
}LIST_ENTRY,*PLIST_ENTRY;

从上述结构体可以看出,_LIST_ENTRY结构体提供双向链表机制。链表中保存的是_LDR_DATA_TABLE_ENTRY结构体的信息,给结构体如下。

typedef struct _LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks; 
    PVOID Reserved2[2]; 
    PVOID D1lBase; 
    PVOID EntryPoint; 
    PVOID Reserved3;
    Unicode_STRING Ful1D1lName; 
    BYTE Reserved4[8]; 
    PVOID Reserved5[3]; 
    union{
        ULONG CheckSum; 
        PVOID Reserved6;
    };
    ULONG TimeDateStamp;
}LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;

每个加载到进程中的DLL模块都对应一个_LDR_DATA_TABLE_ENTRY结构体,这些结构体相互链接,最终形成了_LIST_ENTRY双向链表。_PEB_LDR_DATA结构体中存在3种_LIST_ENTRY双向链表,也就是说,存在多个_LDR_DATA_TABLE_ENTRY结构体,并且有三种链接方法可以将它们链接起来。

PEB.ProcessHeap & PEB.NtGolbalFlag

PEB.ProcessHeap与PEB.NtGlobalFlag成员(像PEB.BeingDebugged成员一样)应用于反调试技术。若进程处于调试状态,则ProcessHeap与NtGlobalFlag成员就持有特定值。由于它们具有这一个特征,所以常常应用于反调试技术。

PEB.ProcessHeap成员是指向HEAP结构体的指针,HEAP结构体如下。

+0x000 Entry    :_HEAP_ENTRY
+0x008 Signature    :Uint4B
+0x00c Flags    :Uint4B
+0x010 ForceFlags    :Uint4B
+0x014 VirtualMemoryThreshold     :Uint4B
+0x018 SegmentReserve    :Uint4B
+0x01c SegmentCommit    :Uint4B
+0x020 DeCommitFreeBlockThreshold     :Uint4B

进程处于被调试状态时,Flags(+0xC)与Force Flags(+ox10)成员被设置成特定的值。
PEB.ProcessHeap(PEB结构体中偏移0x18的位置)成员既可以从PEB结构体中直接获得,也可以通过GetProcessHeap() API获得。
GetProcessHeap() API代码的汇编形式基本类似于IsDebuggerPresent(),按照TEB→PEB→PEB.ProcessHeap顺序访问的ProcessHeap

**破解之法
只要将HEAP.Flags与HEAP.ForceFlags的值重新设置为2与0即可(HEAP.Flags=2,HEAP.ForceFlags=0)。
注意:该方法仅在WindowsXP系统中有效,Windows7系统不存在以上特征。此外,将运行中的进程附加到调试器时,也不会出现上述特征。**

静态反调试技术总结:

1.通过PEB.BeingDebugged 标识当前进程是否处于调试状态
**破解之法
只要借助OllyDbg调试器的编辑功能,将PEB.BeingDebugged的值修改为0(FALSE)即可。**

  1. Ldr(+0xC)
    当调试时,在堆内存区会出现一些特殊的标识,标识他正在被调试,比如未使用 的内存全部填充为0XEEFEEEFE
    PEB.Ldr 是指向的_PEB_LDR_DATA结构的指针,而_PEB_LDR_DATA 正好是在堆内存区域创建的,所以扫描该内存区可找到是否存在0XEEFEEEFE

    **破解之法
    把填充的0XEEFEEEFE改成NULL即可**

  2. PEB.ProcessHeap

    +0x000 Entry    :_HEAP_ENTRY
    +0x008 Signature    :Uint4B
    +0x00c Flags    :Uint4B
    +0x010 ForceFlags    :Uint4B
    +0x014 VirtualMemoryThreshold     :Uint4B
    +0x018 SegmentReserve    :Uint4B
    +0x01c SegmentCommit    :Uint4B
    +0x020 DeCommitFreeBlockThreshold     :Uint4B
    

调试时,Flags(+0xC) 与+0x010 ForceFlags 被设定成特定值

可以通过GetProcessHeap() API获得ProcessHeap
进程正常运行,Flags=0x2,ForceFlags =0x0,被调试的时候就会改变

**破解之法
把通过OD把他们的值改回正常的值即可**

4.PEB.NtGolbalFlag

被调试时,NtGolbalFlag的成员会被设置成0x70,检测该值就可以
**破解之法
把他设置成0即可**

5.NtQueryInformationProcess

// NtQueryInformationProcess 函数原型
__kernel_entry NTSTATUS NtQueryInformationProcess(
  IN HANDLE           ProcessHandle,                // 进程句柄
  IN PROCESSINFOCLASS ProcessInformationClass,        // 检索的进程信息类型
  OUT PVOID           ProcessInformation,            // 接收进程信息的缓冲区指针
  IN ULONG            ProcessInformationLength,        // 缓冲区指针大小
  OUT PULONG          ReturnLength                    // 实际接收的进程信息大小
);

用法:为NtQueryInformationProcess 函数的第二个参数ProcessInformationClass 指定特定值并调用,相关信息就会设置到第三个参数ProcessInformation内

// PROCESSINFOCLASS 结构体原型

typedef enum _PROCESSINFOCLASS
{
    ProcessBasicInformation,                     // 0x0
    ProcessQuotaLimits, 
    ProcessIoCounters, 
    ProcessVmCounters, 
    ProcessTimes, 
    ProcessBasePriority, 
    ProcessRaisePriority,
    ProcessDebugPort,                            // 0x7
    ProcessExceptionPort, 
    ProcessAccessToken, 
    ProcessLdtInformation, 
    ProcessLdtSize, 
    ProcessDefaultHardErrorMode, 
    ProcessIoPortHandlers, 
    ProcessPooledUsageAndLimits, 
    ProcessWorkingSetWatch,
    ProcessUserModeIOPL,
    ProcessEnableAlignmentFaultFixup, 
    ProcessPriorityClass, 
    ProcessWx86Information,
    ProcessHandleCount, 
    ProcessAffinityMask, 
    ProcessPriorityBoost, 
    ProcessDeviceMap, 
    ProcessSessionInformation, 
    ProcessForegroundInformation,
    ProcessWow64Information,                     // 0x1A
    ProcessImageFileName,                         // 0x1B
    ProcessLUIDDeviceMapsEnabled, 
    ProcessBreakOnTermination, 
    ProcessDebugObjectHandle,                    // 0x1E
    ProcessDebugFlags,                             // 0x1F
    ProcessHandleTracing, 
    ProcessIoPriority, 
    ProcessExecuteFlags, 
    ProcessResourceManagement, 
    ProcessCookie, 
    ProcessImageInformation, 
    ProcessCycleTime, 
    ProcessPagePriority, 
    ProcessInstrumentationCallback, 
    ProcessThreadStackAllocation, 
    ProcessWorkingSetWatchEx,
    ProcessImageFileNameWin32, 
    ProcessImageFileMapping, 
    ProcessAffinityUpdateMode, 
    ProcessMemoryAllocationMode, 
    ProcessGroupInformation,
    ProcessTokenVirtualizationEnabled, 
    ProcessConsoleHostProcess, 
    ProcessWindowInformation, 
    ProcessHandleInformation,
    ProcessMitigationPolicy,
    ProcessDynamicFunctionTableInformation,
    ProcessHandleCheckingMode,
    ProcessKeepAliveCount,
    ProcessRevokeFileHandles,
    ProcessWorkingSetControl,
    ProcessHandleTable, 
    ProcessCheckStackExtentsMode,
    ProcessCommandLineInformation,
    ProcessProtectionInformation,
    ProcessMemoryExhaustion,
    ProcessFaultInformation, 
    ProcessTelemetryIdInformation, 
    ProcessCommitReleaseInformation, 
    ProcessDefaultCpuSetsInformation,
    ProcessAllowedCpuSetsInformation,
    ProcessSubsystemProcess,
    ProcessJobMemoryInformation, 
    ProcessInPrivate, 
    ProcessRaiseUMExceptionOnInvalidHandleClose, 
    ProcessIumChallengeResponse,
    ProcessChildProcessInformation, 
    ProcessHighGraphicsPriorityInformation,
    ProcessSubsystemInformation, 
    ProcessEnergyValues, 
    ProcessActivityThrottleState, 
    ProcessActivityThrottlePolicy,
    ProcessWin32kSyscallFilterInformation,
    ProcessDisableSystemAllowedCpuSets, 
    ProcessWakeInformation,
    ProcessEnergyTrackingState,
    ProcessManageWritesToExecutableMemory,REDSTONE3
    ProcessCaptureTrustletLiveDump,
    ProcessTelemetryCoverage,
    ProcessEnclaveInformation,
    ProcessEnableReadWriteVmLogging, 
    ProcessUptimeInformation,
    ProcessImageSection,
    ProcessDebugAuthInformation, 
    ProcessSystemResourceManagement,
    ProcessSequenceNumber,
    ProcessLoaderDetour,
    ProcessSecurityDomainInformation, 
    ProcessCombineSecurityDomainsInformation, 
    ProcessEnableLogging, 
    ProcessLeapSecondInformation,
    ProcessFiberShadowStackAllocation,
    ProcessFreeFiberShadowStackAllocation,
    MaxProcessInfoClass
} PROCESSINFOCLASS;

跟调试探测有关的的为:

ProcessDebugPort,                            // 0x7
ProcessDebugObjectHandle,                    // 0x1E
ProcessDebugFlags,                             // 0x1F

ProcessDebugPort

进程在调试的时候的系统会为他分配调试端口,当PROCESSINFOCLASS的 ProcessDebugPort 的值为7,调用NtQueryInformationProcess 就可以获取调试端口,如果没有调试则返回为0

ProcessDebugObjectHandle

调试的时候会生成调试对象,当PROCESSINFOCLASS的ProcessDebugObjectHandle
为0x1E时,第三个参数就可以获取到调试对象句柄,否则该句柄为NULL

ProcessDebugFlags

当 ProcessDebugFlags为0x1F 时,第三个参数返回为0为被调试状态,正常运行为1

**破解之法
Hook NtQueryInformationProcess 函数修改返回值**

基于环境检测反调试

NtQuerySystemInformation

基于调试环境的反调试技术. NTQuerySystemInformation()API是一个系统函数, 可以获取OS信息.

lh0kq6sq.png

第一个参数SystemInformationClass传入SystemKernelDebuggerInformation, 参数中指定需要系统信息类型,返回的时候会把结构体地址放在第二个参数里

当第一个参数传入 0x23 (SystemInterruptInformation) 时,会返回一个 SYSTEM_KERNEL_DEBUGGER_INFORMATION 结构,里面的成员KdKdDebuggerEnable=1 和 KdDebuggerNotPresent 标志系统是否启用内核调试。

**破解之法
命令行执行 “bcdedit/debug off” 若重启系统要用正常模式启动
Xp下 系统中编辑boot.ini 文件。删除 /debugport=com1 /baudrate=115200 /Debug 的值**

NtQueryObject

除了识别进程是否被调试之外,其他的调试器检测技术牵涉到检查系统当中是否有调试器正在运行。逆向论坛中讨论的一个有趣的方法就是检查DebugObject类型内核对象的数量。这种方法之所以有效是因为每当一个应用程序被调试的时候,将会为调试对话在内核中创建一
个DebugObject类型的对象。

DebugObject的数量可以通过ntdll!NtQueryObject()检索所有对象类型的信息而获得。NtQueryObject接受5个参数,为了查询所有的对象类型,ObjectHandle参数被设为NULL,ObjectInformationClass参数设为ObjectAllTypeInformation(3):

NTSTATUS NTAPI NtQueryObject(                                
IN    HANDLE                                         ObjectHandle,                
IN    OBJECT_INFORMATION_CLASS   ObjectInformationClass,        
OUT   PVOID                                           ObjectInformation,            
IN    ULONG                                           Length                    ,
OUT   PULONG                                        ResultLength                
)                                                        

这个API返回一个OBJECT_ALL_INFORMATION结构,其中NumberOfObjectsTypes成员为所有的对象类型在ObjectTypeInformation数组中的计数:

typedef struct _OBJECT_ALL_INFORMATION{
ULONG                                          NumberOfObjectsTypes;
OBJECT_TYPE_INFORMATION         ObjectTypeInformation[1];
}
检测例程将遍历拥有如下结构的ObjectTypeInformation数组:
typedef struct _OBJECT_TYPE_INFORMATION{
[00] UNICODE_STRING        TypeName;
[08] ULONG                          TotalNumberofHandles;
[0C] ULONG                  TotalNumberofObjects;
...more fields...
}

TypeName成员与UNICODE字符串"DebugObject"比较,然后检查TotalNumberofObjects 或 TotalNumberofHandles 是否为非0值。

**破解之法
Hook NtQueryObject**

ZwSetInformationThread

可以用来将线程隐藏,从而使调试器接收不到信息
其原型如下:

NTSTATUS
NTAPI
ZwSetInformationThread (
    __in HANDLE ThreadHandle,
    __in THREADINFOCLASS ThreadInformationClass,
    __in_bcount(ThreadInformationLength) PVOID ThreadInformation,
    __in ULONG ThreadInformationLength
    );
//
// Thread Information Classes定义如下
//ntpsapi.h
typedef 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

如下:ZwSetInformationThread(GetCurrentThread( ), ThreadHideFromDebugger, NULL, 0); 这样就可以将当前线程对调试器隐藏了!

**破解之法
Hook ZwSetInformationThread**

利用TLS技术,因为TLS回调函数会先于EP代码执行,所以可以在该函数内部使用IsDebuggerPresent 等函数判断是否在调试

还可以通过检测OD的窗口、进程是否存在来判断是否调试,判断虚拟机进程等

打赏
评论区
头像