1.PEB结构
+0x002 BeingDebugged : UChar
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
+0x018 ProcessHeap : Ptr32 Void
+0x068 NtGlobalFlag : Uint4B
...
1)BeingDebugged(+0x002)
当进程处于调试状态时,该标识为1,反之为0。
对应的API是IsDebuggerPresent(),用于获取当前进程的调试状态。
2)Ldr(+0x00c)
当进程处于调试状态时,堆内存区域会出现一些特殊标识,如0xEEFEEEFE等。而PEB.Ldr则是在堆内存中创建的,所以扫描该区域查看是否存在特殊标识。Ldr的结构类型是_PEB_LDR_DATA。
3)Process Heap(+0x18)
该成员的结构类型为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
对应获取该结构的API是GetProcessHeap();
其中Flags(+0xC)和Force Flags(+0x10)这两个变量常用来识别调试状态。当处于正常状态下,Flags的值为0x2,ForceFlags的值为0x0。当处于调试状态时,这两个值会发生变化。
4)NtGlobalFlag(+0x68)
当处于调试状态时,该变量的值为0x70。
2.NtQueryInformationProcess()
该函数可以获取到与进程调试相关的信息。定义如下:
NTSTATUS WINAPI NtQueryInformationProcess(
__in HANDLE ProcessHandle,
__in PROCESSINFOCLASS ProcessInformationClass,
__out PVOID ProcessInformation,
__in ULONG ProcessInformationLength,
__out_opt PULONG ReturnLength
);
其中第二个参数是用来获取指定信息的枚举类型,定义如下:
enum PROCESSINFOCLASS
{
ProcessBasicInformation = 0,
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort = 7,
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers,
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup,
ProcessPriorityClass,
ProcessWx86Information,
ProcessHandleCount,
ProcessAffinityMask,
ProcessPriorityBoost,
MaxProcessInfoClass,
ProcessWow64Information = 26,
ProcessImageFileName = 27,
ProcessDebugObjectHandle = 30,
ProcessDebugFlags = 31,
};
1)ProcessDebugPort(0x7)
当进程处于调试状态时,获取的值为0xFFFFFFFF,如果处于非调试状态,则获取的值为0.
对应的API为CheckRemoteDebuggerPresent(),该函数还可以获取其他进程的调试状态。
2)ProcessDebugObjectHandle(0x1E)
当进程处于调试状态时,会生成调试对象(Debug Object)。使用ProcessDebugObjectHandle则可以获取调试对象的句柄,如果处于非调试状态,返回结果为NULL。
3)ProcessDebugFlags(0x1F)
当进程处于调试状态时,获取的值为0。反之为1。
3.NtQuerySystemInformation()
该函数可以用来获取系统环境是否处于调试状态。类似的winDbg调试虚拟机,则虚拟机系统就处于一个被调试状态。因为想要调试虚拟机需要配置系统为调试状态。xp修改C:\boot.ini,win7使用bcdedit.exe来修改调试状态。
因此利用这种方式,可以知道当前进程是否在一个可调试系统中运行,因为常规电脑是不会被作为调试机的。
函数定义如下:
NTSTATUS WINAPI NtQuerySystemInformation(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__inout PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
);
其中第一个参数是一个枚举类型,如下:
typedef enum _SYSTEM_INFORMATION_CLASS{
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemKernelDebuggerInformation = 35,
...
};
其中的SystemKernelDebuggerInformation可以获取系统的调试状态信息。返回结构如下:
typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION
{
BOOLEAN DebuggerEnabled;
BOOLEAN DebuggerNotPresent;
}SYSTEM_KERNEL_DEBUGGER_INFORMATION;
当系统处于调试状态时,DebuggerEnabled的值为1,DebuggerNotPresent的值恒为1。
4.NtQueryObject()
系统中的某个调试器调试进程时,会创建一个调试对象类型的内核对象。检测该对象是否存在即可判断是否有进程正在被调试。
而NtQueryObject()用来获取系统中各类型内核对象信息。定义如下:
NTSTATUS NtQueryObject(
__in_opt HANDLE Handle,
__in OBJECT_INFORMATION_CLASS ObjectInformationClass,
__out_opt PVOID ObjectInformation,
__in ULONG ObjectInformationLength,
__out_opt PULONG ReturnLength
);
其中第二个参数是一个枚举类型,定义如下:
typedef enum _OBJECT_INFORMATION_CLASS{
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectAllTypesInformation,
ObjectHandleInformation
}OBJECT_INFORMATION_CLASS;
使用ObjectAllTypesInformation可以获取到系统中所有类型的对象信息。信息结构定义如下:
typedef struct _LSA_Unicode_STRING{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
}LSA_Unicode_STRING, *PLSA_Unicode_STRING, Unicode_STRING, *PUnicode_STRING;
typedef struct _OBJECT_TYPE_INFORMATION{
Unicode_STRING TypeName;
ULONG TotalNumberOfHandles;
ULONG TotalNumberOfObjects;
}OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_ALL_INFORMATION{
ULONG NumberOfObjectsTypes;
OBJECT_TYPE_INFORMATION ObjectTypeInformation[1];
}OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;
关键判断代码:
for(UINT i = 0; i<pObjectAllInfo->NumberOfObjectsTypes; i++)
{
pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)pObjectInfoLocation;
if(wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer) == 0)
{
bDebugging = (pObjectTypeInfo->TotalNumberOfObjects > 0) ? TRUE : FALSE;
break;
}
//计算下一个结构体
pObjectInfoLocation = (UCHAR*)pObjectTypeInfo->TypeName.Buffer;
pObjectInfoLocation += pObjectTypeInfo->TypeName.Length;
pObjectInfoLocation = (UCHAR*)(((ULONG)pObjInfoLocation & 0xFFFFFFFC)+sizeof(ULONG));
}
5.ZwSetInformationThread()
用来强制分离(Detach)被调试者和调试器。可以将自身从调试器中分离出来。函数定义如下:
typedef enum _THREAD_INFORMATION_CLASS{
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress,
ThreadIsIoPending,
ThreadHideFromDebugger = 17
}
NTSTATUS ZwSetInformationThread(
__in HANDLE ThreadHandle,
__in THREADINFOCLASS ThreadInformationClass,
__in PVOID ThreadInformation,
__in ULONG ThreadInformationLength
);
其中ThreadHandle用来接收当前线程句柄,ThreadInformationClass表示线程信息类型,如果将其设置为ThreadHideFromDebugger(0x11),则调用函数后,调试进程会被分离出来。如果是正常的进程则不会受到影响。如果是调试中的进程,调试器和被调试进程都会终止。
6.TLS回调函数
由于TLS回调函数可以先于EP代码运行,所以常用来做反调试使用。可以在回调函数中使用类似IsDebuggerPresent()来检测调试状态。
7.ETC
通过检测自身所处环境来达到反调试的目的,不需要刻意检测是否处于调试状态。检测周边环境的方法有如下几种:
1)检测OllyDbg窗口->FindWindow()
2)检测OllyDbg进程->CreateToolhelp32Snapshot()
3)检查计算机名称是否为“TEST”、“ANALYSIS”等->GetComputerName()
4)检查程序运行路径中是否存在“TEST”、“SAMPLE”等名称->GetCommandLine()
5)检测虚拟机是否处于运行状态(查看虚拟机特有的进程名称->VMWareService.exe、WMWareTray.exe、WMWareUser.exe等)。