静态反调试
反调试技术知识点
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)即可。**
Ldr(+0xC)
当调试时,在堆内存区会出现一些特殊的标识,标识他正在被调试,比如未使用 的内存全部填充为0XEEFEEEFE
PEB.Ldr 是指向的_PEB_LDR_DATA结构的指针,而_PEB_LDR_DATA 正好是在堆内存区域创建的,所以扫描该内存区可找到是否存在0XEEFEEEFE**破解之法
把填充的0XEEFEEEFE改成NULL即可**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信息.
第一个参数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的窗口、进程是否存在来判断是否调试,判断虚拟机进程等