调试器检测技术 请选择一个调试器

为了防止逆向工程,在很多的“壳”中,会检测进程运行的环境是否为调试器。如果检测到运行环境为调试器,进程将运行特殊的代码以达到反调试的效果。以下的内容归纳自《脱壳的艺术》一书,本人添加了一些关于fs寄存器,TEB结构,PEB结构的相关内容,以便更好地理解这些知识。

一、检测进程环境块(PEB)1中的BeingDebugged标志

最基本的调试器检测技术就是检测进程环境块(PEB)1中的BeingDebugged标志。kernel32!IsDebuggerPresent()API检查这个标志以确定进程是否正在被用户模式的调试器调试。

下面显示了IsDebuggerPresent()API的实现代码。首先访问线程环境块(TEB)2得到PEB的地址,然后检查PEB偏移0x02位置的BeingDebugged标志。

mov eax,large fs: 18h

mov eax,[eax+30h]

movzx eax,byte ptr [eax+2]

retn

除了直接调用IsDebuggerPresent(),有些壳会手工检查PEB中的BeingDebugged标志以防逆向分析人员在这个API上设置断点或打补丁。

注:

fs:18h 地址指向线程环境块_TEB
TEB
结构

TEBat7FFDE000
ExceptionList:12f830x0
StackBase:130000|0x4
StackLimit:126000|0x8
SubSystemTib:0|0xC
FiberData:1e00|0x10
ArbitraryUser:0|0x14
Self:7ffde000/0x18
EnvironmentPtr:00x1C
ClientId:4cc.4c80x20
RealClientId:4cc.4c80x24
RpcHandle:00x28
TlsStorage:00x2C
PEBAddress:7ffdf0000x30这里指向PEB表,即进程环境块
LastErrorValue:0
LastStatusValue:8000001a
CountOwnedLocks:0
HardErrorsMode:0

PEB (ProcessEnviroment Block)

struct_PEB(sizeof=488)
+000byteInheritedAddressSpace
+001byteReadImageFileExecOptions
+002byteBeingDebugged//Debug运行标志
+003byteSpareBool
+004void*Mutant
+008void*ImageBaseAddress//这里就是程序加载的基地址了
调试器检测技术 请选择一个调试器
+00cstruct_PEB_LDR_DATA*Ldr
+010struct_RTL_USER_PROCESS_PARAMETERS*ProcessParameters
+014void*SubSystemData
+018void*ProcessHeap堆入口地址

+01cvoid*FastPebLock
+020void*FastPebLockRoutine
+024void*FastPebUnlockRoutine
+028uint32EnvironmentUpdateCount
+02cvoid*KernelCallbackTable
+030uint32SystemReserved[2]
+038struct_PEB_FREE_BLOCK*FreeList
+03cuint32TlsExpansionCounter
+040void*TlsBitmap
+044uint32TlsBitmapBits[2]
+04cvoid*ReadOnlySharedMemoryBase
+050void*ReadOnlySharedMemoryHeap
+054void**ReadOnlyStaticServerData
+058void*AnsiCodePageData
+05cvoid*OemCodePageData
+060void*UnicodeCaseTableData
+064uint32NumberOfProcessors
+068uint32NtGlobalFlag
+070union_LARGE_INTEGERCriticalSectionTimeout
+070uint32LowPart
+074int32HighPart
+070struct__unnamed3u
+070uint32LowPart
+074int32HighPart
+070int64QuadPart
+078uint32HeapSegmentReserve
+07cuint32HeapSegmentCommit
+080uint32HeapDeCommitTotalFreeThreshold
+084uint32HeapDeCommitFreeBlockThreshold
+088uint32NumberOfHeaps
+08cuint32MaximumNumberOfHeaps
+090void**ProcessHeaps
+094void*GdiSharedHandleTable
+098void*ProcessStarterHelper
+09cuint32GdiDCAttributeList
+0a0void*LoaderLock
+0a4uint32OSMajorVersion
+0a8uint32OSMinorVersion
+0acuint16OSBuildNumber
+0aeuint16OSCSDVersion
+0b0uint32OSPlatformId
+0b4uint32ImageSubsystem
+0b8uint32ImageSubsystemMajorVersion
+0bcuint32ImageSubsystemMinorVersion
+0c0uint32ImageProcessAffinityMask
+0c4uint32GdiHandleBuffer[34]
+14cfunction*PostProcessInitRoutine
+150void*TlsExpansionBitmap
+154uint32TlsExpansionBitmapBits[32]
+1d4uint32SessionId
+1d8void*AppCompatInfo
+1dcstruct_UNICODE_STRINGCSDVersion
+1dcuint16Length
+1deuint16MaximumLength
+1e0uint16*Buffer

得到KERNEL32.DLL基址的方法
assume fs:nothing ;打开FS寄存器
mov eax,fs:[30h] ;得到PEB结构地址
mov eax,[eax + 0ch] ;得到PEB_LDR_DATA结构地址
mov esi,[eax + 1ch] ;InInitializationOrderModuleList
lodsd ;得到KERNEL32.DLL所在LDR_MODULE结构的InInitializationOrderModuleList地址
mov edx,[eax + 8h] ;得到BaseAddress,既Kernel32.dll基址

二、检测PEB.NtGlobalFlag , Heap.HeapFlags,Heap.ForceFlags

1.PEB.NtGlobalFlag:

PEB另一个成员被称作NtGlobalFlag(偏移0x68),壳也通过它来检测程序是否用调试器加载。通常程序没有被调试时,NtGlobalFlag成员值为0,如果进程被调试这个成员通常值为0x70(代表下述标志被设置)

FLG_HEAP_ENABLE_TAIL_CHECK(0X10)

FLG_HEAP_ENABLE_FREE_CHECK(0X20)

FLG_HEAP_VALIDATE_PARAMETERS(0X40)

这是我写的一段用于检测该标志位的代码:

intcheck_NtGlobalFlag()

{

int result;

_asm

{

xor esi,esi

mov eax,fs:[esi+18h]

mov eax,[eax+30h] //get PEB addr

mov eax,[eax+68h] //get NtGlobalFlag

mov dword ptr[result],eax

}

return result;

};

2.Heap flags ,HeapForceFlags

通常情况下为进程创建的第一个堆会将其Flags和ForceFlags4分别设为0x02(HEAP_GROWABLE)和0。因此,只要检查这两个标志就可以知道是否进程被调试

这是来自《脱壳的艺术》艺术的示例

Mov ebx,[fs:0x30] //ebx存放PEB入口

Cmp dword [ebx+0x68],0 //检测NtGlobalFlag

jne .debugger_found

Mov eax,[ebx+0x18]//eax指向PEB.ProcessHeap

Cmp dword [eax+0x0c],2 //检测flags 是否为2

jne .debugger_found

Cmp dword [eax+0x10],0 //检测ForceFlags 是否为0

jne .debugger_found

三、CheckRemoteDebuggerPresent()NtQueryInformationProcess()

CheckRemoteDebuggerPresent()是另一个可以用于确定是否有调试器被附加到进程的API。

Kernel32!CheckRemoteDebuggerPresent()接受2个参数,第1个参数是进程句柄,第2个参数是一个指向boolean变量的指针,如果进程被调试,该变量将包含TRUE返回值。

函数原型:

BOOL CheckRemoteDebuggerPresent(

HANDLEhProcess,

PBOOL pbDebuggerPresent

)

NtQueryInformationProcess()接收5个参数。

NTSTATUS NTAPI NtQueryInformationProcess

(

HANDLEProcessHandle,

PROCESSINFOCLASSProcessInformationClass,

PVOIDProcessInformation,

ULONGProcessInformationLength,

PULONGReturnLength

)

但进程被调试时,该函数返回一个非零值

四、Debugger Interrupts

在调试器中步过INT3和INT1指令的时候,由于调试器通常会处理这些调试中断,所以异常处理例程默认情况下将不会被调用,DebuggerInterrupts就利用了这个事实。这样壳可以在异常处理例程中设置标志,通过INT指令后如果这些标志没有被设置则意味着进程正在被调试。

注:

1.INT 3 是留给调试工具使用的中断,调试工具运行后会替换int3的向量,使得中断方式后执行自己的代码。在单步(例如Debug中的命令p)调试程序时,调试工具会将要执行代码的下一条指令改成int 3,这样执行完当前这行代码后就会执行调试工具的代码,而不会继续执行,从而实现单步调试。

2.INT1 是单步执行中断

基本上,CPU在执行完一条指令后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,则它引发的中断过程如下:
1) 取得中断类型码1;
2) 标志寄存器入栈,TF、IF设置为0;
3) CS、IP入栈;
4) (IP) =(1*4),(CS) =(1*4+2)。

五、Timing Checks

当进程被调试时,调试器事件处理代码、步过指令等将占用CPU循环。如果相邻指令之间所花费的时间如果大大超出常规,就意味着进程很可能是在被调试。

常用手段:

1.汇编指令 RDTSC

rdtsc指令得到CPU自启动以后的运行周期
在586以上的CPU上有
这个指令将放回的数值 高位返回到edx,低位放回到eax

可以在一段代码前后使用rdtsc,计算出相距的CPC周期。如果这个时间太长,这进程很可能被调试

2. 利用API:GetTickCount()

DWORDGetTickCount(void); 返回从操作系统启动到现在所经过的毫秒数

六、SeDebugPrivilege

默认情况下进程是没有SeDebugPrivilege权限的。然而进程通过OllyDbg和WinDbg之类的调试器载入的时候,SeDebugPrivilege权限被启用了。这种情况是由于调试器本身会调整并启用SeDebugPrivilege权限,当被调试进程加载时SeDebugPrivilege权限也被继承了。

一些壳通过打开CSRSS.EXE进程间接地使用SeDebugPrivilege确定进程是否被调试。如果能够打开CSRSS.EXE意味着进程启用了SeDebugPrivilege权限,由此可以推断进程正在被调试。

七、ParentProcess(检测父进程)

通常进程的父进程是explorer.exe(双击执行的情况下),父进程不是explorer.exe说明程序是由另一个不同的应用程序打开的,这很可能就是程序被调试了。

八、检查DebugObject类型内核对象的数量

这种方法之所以有效是因为每当一个应用程序被调试的时候,将会为调试对话在内核中创建一个DebugObject类型的对象。

DebugObject的数量可以通过ntdll!NtQueryObject()检索所有对象类型的信息而获得。

Debugger Window

调试器窗口的存在标志着有调试器正在系统内运行。由于调试器创建的窗口拥有特定类名(OllyDbg的是OLLYDBG,WinDbg的是WinDbgFrameClass),使用user32!FindWindow()或者user32!FindWindowEx()能很容易地识别这些调试器窗口。

十、DebuggerProcess

另外一种识别系统内是否有调试器正在运行的方法是列出所有的进程,检查进程名是否与调试器(如 OLLYDBG.EXE,windbg.exe等)的相符。实现很直接,利用Process32First/Next()然后检查映像名称是否与调试器相符就行了。

十一Device Drivers

调试器运行于内核模式。通过调用kernel32!CreateFile()检测内核模式调试器(如SoftICE)使用的那些众所周知的设备名称。

归纳自《脱壳的艺术》

个人体会

以上方法中,利用显示调用API函数达到检测调试器的方法很容易被攻破。可通过调试器在这些API 函数返回处设置断点,通过修改返回值和相关变量的方式很容易绕开这些反调试手段。

前两种检测TEB,PEB某些特定位置的方法也较容易采取相应对策。只要手工将这些位置置为特定的值即可。

比较好的方法如利用RDTSC 命令得到指令执行间隔的CPU周期。并通过花指令,垃圾指令等代码混淆方法对该方法进行隐藏应该能得到比较好的反调试效果。

  

爱华网本文地址 » http://www.413yy.cn/a/25101015/267371.html

更多阅读

转载 山东龙口:请放过一个失去母亲的女孩!

原文地址:山东龙口:请放过一个失去母亲的女孩!作者:李鸣生山东龙口,你们已经逼死了一位母亲,难道还要再逼死这位母亲的女儿吗?4年前,李宁的母亲在北京上访,被山东龙口公安强行抓回并非法关押致死;4年后,李宁为母亲申冤在北京上访,又被龙口公安

入侵检测技术综述 行人检测技术综述

1.什么是入侵检测,为什么需要入侵检测?1.1为什么需要入侵检测1.1.1黑客攻击日益猖獗,防范问题日趋严峻随着计算机技术的发展,在计算机上处理业务已由基于单机的数学运算、文件处理,基于简单连结的内部网络的内部业务处理、办公自动化等发

声明:《调试器检测技术 请选择一个调试器》为网友一纸深秋分享!如侵犯到您的合法权益请联系我们删除