反硬件断点

本文转自梦织未来(www.mengwuji.net)
地址:http://www.mengwuji.net/forum.php?mod=viewthread&tid=1404
作者:mengwuji

检测硬件断点,是一件很有趣的事儿,至少这玩意儿可以让xx工具威力丧失一半儿。不过对于这方面的探讨是比较少的,主要在于一般检测硬件断点的方法,不外乎用NtGetContextThread函数进行获取。
NtGetContextThread内部是向目标线程插入一个apc,然后在目标线程环境中进行获取工作。这个apc呀,实现是比较底层的。一般我们可以通过系统提供的api进行相关插入apc的相关操作。
但是鲜有某工具字节写apc插入来检测硬件断点,原因我想可能考虑到兼容性是个很大的问题......实际apc相关的底层实现,也不是很麻烦,主要是对数据结构的各种操作,自己实现一个apc也不费事儿,但还是兼容性的问题导致了一些商业工具没这样做。

那么,既然是通过NtGetContextThread获取线程上下文来检测硬件断点的话,我们在内核层拦截到这样的操作,然后处理之~~,这样就可对基本市面上大部分的商业保护进行反检测了。所以对硬件断点的处理在反反调试上就不是那么被津津乐道的话题。

有没有不用什么硬编码或者用很少硬编码就能检测硬件断点的呢?答案是存在的。

我以windows7 32位为例,进行探讨一下。

首先看看我们内核线程对象:

kd> dt nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 CycleTime        : Uint8B
   +0x018 HighCycleTime    : Uint4B
   +0x020 QuantumTarget    : Uint8B
   +0x028 InitialStack     : Ptr32 Void
   +0x02c StackLimit       : Ptr32 Void
   +0x030 KernelStack      : Ptr32 Void
   +0x034 ThreadLock       : Uint4B
   +0x038 WaitRegister     : _KWAIT_STATUS_REGISTER
   +0x039 Running          : UChar
   +0x03a Alerted          : [2] UChar
   +0x03c KernelStackResident : Pos 0, 1 Bit
   +0x03c ReadyTransition  : Pos 1, 1 Bit
   +0x03c ProcessReadyQueue : Pos 2, 1 Bit
   +0x03c WaitNext         : Pos 3, 1 Bit
   +0x03c SystemAffinityActive : Pos 4, 1 Bit
   +0x03c Alertable        : Pos 5, 1 Bit
   +0x03c GdiFlushActive   : Pos 6, 1 Bit
   +0x03c UserStackWalkActive : Pos 7, 1 Bit
   +0x03c ApcInterruptRequest : Pos 8, 1 Bit
   +0x03c ForceDeferSchedule : Pos 9, 1 Bit
   +0x03c QuantumEndMigrate : Pos 10, 1 Bit
   +0x03c UmsDirectedSwitchEnable : Pos 11, 1 Bit
   +0x03c TimerActive      : Pos 12, 1 Bit
   +0x03c SystemThread     : Pos 13, 1 Bit
   +0x03c Reserved         : Pos 14, 18 Bits
   +0x03c MiscFlags        : Int4B
   +0x040 ApcState         : _KAPC_STATE
   +0x040 ApcStateFill     : [23] UChar
   +0x057 Priority         : Char
   +0x058 NextProcessor    : Uint4B
   +0x05c DeferredProcessor : Uint4B
   +0x060 ApcQueueLock     : Uint4B
   +0x064 ContextSwitches  : Uint4B
   +0x068 State            : UChar
   +0x069 NpxState         : Char
   +0x06a WaitIrql         : UChar
   +0x06b WaitMode         : Char
   +0x06c WaitStatus       : Int4B
   +0x070 WaitBlockList    : Ptr32 _KWAIT_BLOCK
   +0x074 WaitListEntry    : _LIST_ENTRY
   +0x074 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x07c Queue            : Ptr32 _KQUEUE
   +0x080 WaitTime         : Uint4B
   +0x084 KernelApcDisable : Int2B
   +0x086 SpecialApcDisable : Int2B
   +0x084 CombinedApcDisable : Uint4B
   +0x088 Teb              : Ptr32 Void
   +0x090 Timer            : _KTIMER
   +0x0b8 AutoAlignment    : Pos 0, 1 Bit
   +0x0b8 DisableBoost     : Pos 1, 1 Bit
   +0x0b8 EtwStackTraceApc1Inserted : Pos 2, 1 Bit
   +0x0b8 EtwStackTraceApc2Inserted : Pos 3, 1 Bit
   +0x0b8 CalloutActive    : Pos 4, 1 Bit
   +0x0b8 ApcQueueable     : Pos 5, 1 Bit
   +0x0b8 EnableStackSwap  : Pos 6, 1 Bit
   +0x0b8 GuiThread        : Pos 7, 1 Bit
   +0x0b8 UmsPerformingSyscall : Pos 8, 1 Bit
   +0x0b8 VdmSafe          : Pos 9, 1 Bit
   +0x0b8 UmsDispatched    : Pos 10, 1 Bit
   +0x0b8 ReservedFlags    : Pos 11, 21 Bits
   +0x0b8 ThreadFlags      : Int4B
   +0x0bc ServiceTable     : Ptr32 Void
   +0x0c0 WaitBlock        : [4] _KWAIT_BLOCK
   +0x120 QueueListEntry   : _LIST_ENTRY
   +0x128 TrapFrame        : Ptr32 _KTRAP_FRAME
   +0x12c FirstArgument    : Ptr32 Void
   +0x130 CallbackStack    : Ptr32 Void
   +0x130 CallbackDepth    : Uint4B
   +0x134 ApcStateIndex    : UChar
   +0x135 BasePriority     : Char
   +0x136 PriorityDecrement : Char
   +0x136 ForegroundBoost  : Pos 0, 4 Bits
   +0x136 UnusualBoost     : Pos 4, 4 Bits
   +0x137 Preempted        : UChar
   +0x138 AdjustReason     : UChar
   +0x139 AdjustIncrement  : Char
   +0x13a PreviousMode     : Char
   +0x13b Saturation       : Char
   +0x13c SystemCallNumber : Uint4B
   +0x140 FreezeCount      : Uint4B
   +0x144 UserAffinity     : _GROUP_AFFINITY
   +0x150 Process          : Ptr32 _KPROCESS
   +0x154 Affinity         : _GROUP_AFFINITY
   +0x160 IdealProcessor   : Uint4B
   +0x164 UserIdealProcessor : Uint4B
   +0x168 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
   +0x170 SavedApcState    : _KAPC_STATE
   +0x170 SavedApcStateFill : [23] UChar
   +0x187 WaitReason       : UChar
   +0x188 SuspendCount     : Char
   +0x189 Spare1           : Char
   +0x18a OtherPlatformFill : UChar
   +0x18c Win32Thread      : Ptr32 Void
   +0x190 StackBase        : Ptr32 Void
   +0x194 SuspendApc       : _KAPC
   +0x194 SuspendApcFill0  : [1] UChar
   +0x195 ResourceIndex    : UChar
   +0x194 SuspendApcFill1  : [3] UChar
   +0x197 QuantumReset     : UChar
   +0x194 SuspendApcFill2  : [4] UChar
   +0x198 KernelTime       : Uint4B
   +0x194 SuspendApcFill3  : [36] UChar
   +0x1b8 WaitPrcb         : Ptr32 _KPRCB
   +0x194 SuspendApcFill4  : [40] UChar
   +0x1bc LegoData         : Ptr32 Void
   +0x194 SuspendApcFill5  : [47] UChar
   +0x1c3 LargeStack       : UChar
   +0x1c4 UserTime         : Uint4B
   +0x1c8 SuspendSemaphore : _KSEMAPHORE
   +0x1c8 SuspendSemaphorefill : [20] UChar
   +0x1dc SListFaultCount  : Uint4B
   +0x1e0 ThreadListEntry  : _LIST_ENTRY
   +0x1e8 MutantListHead   : _LIST_ENTRY
   +0x1f0 SListFaultAddress : Ptr32 Void
   +0x1f4 ThreadCounters   : Ptr32 _KTHREAD_COUNTERS
   +0x1f8 XStateSave       : Ptr32 _XSTATE_SAVE
成员很多,但是与我们本主题相关的只有一个:
+0x000 Header           : _DISPATCHER_HEADER

这个是内核线程的开头成员,是个分发器对象,这个分发器呢,是实现线程同步的基础,当然与我们本题目也没什么关系,我们看看这个_DISPATCHER_HEADER数据结构。

kd> dt _DISPATCHER_HEADER
hal!_DISPATCHER_HEADER
   +0x000 Type             : UChar
   +0x001 TimerControlFlags : UChar
   +0x001 Absolute         : Pos 0, 1 Bit
   +0x001 Coalescable      : Pos 1, 1 Bit
   +0x001 KeepShifting     : Pos 2, 1 Bit
   +0x001 EncodedTolerableDelay : Pos 3, 5 Bits
   +0x001 Abandoned        : UChar
   +0x001 Signalling       : UChar
   +0x002 ThreadControlFlags : UChar
   +0x002 CpuThrottled     : Pos 0, 1 Bit
   +0x002 CycleProfiling   : Pos 1, 1 Bit
   +0x002 CounterProfiling : Pos 2, 1 Bit
   +0x002 Reserved         : Pos 3, 5 Bits
   +0x002 Hand             : UChar
   +0x002 Size             : UChar
   +0x003 TimerMiscFlags   : UChar
   +0x003 Index            : Pos 0, 1 Bit
   +0x003 Processor        : Pos 1, 5 Bits
   +0x003 Inserted         : Pos 6, 1 Bit
   +0x003 Expired          : Pos 7, 1 Bit
   +0x003 DebugActive      : UChar
   +0x003 ActiveDR7        : Pos 0, 1 Bit
   +0x003 Instrumented     : Pos 1, 1 Bit
   +0x003 Reserved2        : Pos 2, 4 Bits
   +0x003 UmsScheduled     : Pos 6, 1 Bit
   +0x003 UmsPrimary       : Pos 7, 1 Bit
   +0x003 DpcActive        : UChar
   +0x000 Lock             : Int4B
   +0x004 SignalState      : Int4B
   +0x008 WaitListHead     : _LIST_ENTRY
结构也挺多,我们主要看看:
+0x003 DebugActive      : UChar
这个成员名字是不是很敏感?
好,我们现在知道了这个成员,那么我们转回NtSetContextThread函数看看。这个函数是设置硬件断点的,我们的调试器就是通过这个函数设置硬件断点,它的执行流程如下:
NtSetContextThread-->PsSetContextThread-->PspSetContextThreadInternal-->PspGetSetContextSpecialApc;
最终是执行了PspGetSetContextSpecialApc函数后,设置了线程的Context。PspGetSetContextSpecialApc函数不仅可以设置Context,也可以获取Context。
用户层设置Context的时候,它内部实际通过一个KeContextToKframes实现的,KeContextToKframes通过给定的Context设置线程的对象的内核栈的一个陷阱帧达到目的。
KeContextToKframes函数,内部会设置线程的内核栈的陷阱帧后,这个函数会判断当前设置的各个调试寄存器,从DR0~DR7,其中dr4和dr5忽略。这8个调试寄存器刚好对应一个1字节变量的8位。通过判断是dr0~dr7来设置一个BYTE类型变量。此变量设置好后,就会给赋值给我们的线程Header的DebugActive。所以我们当前线程分发器的DebugActive就是代表了哪些调试寄存器是有值的。

现在就知道了,实际我们可以通过Thread->Header.DebugActive成员判断当前是否存在硬件断点。
那么,既然有这种方式进行判断,我们就可以用一个定时器去不断的改写目标进程所有线程的Thread->Header.DebugActive为零就行了。


这貌似看起来是一种解决手段,但是如果写程序实现可能会有问题,那么我们更加深入一点儿的研究下这个Thread->Header.DebugActive;


我们拿之前的int 3断点的陷阱处理器来说吧。
KiTrap03开头会做一点儿事情:

nt!KiTrap03+0x40:
8407dc60 7596            jne     nt!V86_kit3_a (8407dbf8)
8407dc62 648b0d24010000  mov     ecx,dword ptr fs:[124h]
8407dc69 fc              cld
8407dc6a 83652c00        and     dword ptr [ebp+2Ch],0
8407dc6e f64103df        test    byte ptr [ecx+3],0DFh                   //这里ecx就是当前线程,+0x3的值实际就是Thread->Header.DebugActive
8407dc72 0f8500ffffff    jne     nt!Dr_kit3_a (8407db78)
上面代码实际就是Thread->Header.DebugActive & DFh,这里的df转换成2进制就是11011111,这里进行的操作就是只要DR0、DR1、DR2、DR3、DR6、DR7中有超过1位存在,那就跳转。其中第五位不为零,这里不是代表DR4是否存在,而是代表DR4和DR5是否被遗弃.....
那么这个跳转处我们看看:

nt!Dr_kit3_a:
8407db78 f7457000000200  test    dword ptr [ebp+70h],20000h
8407db7f 750a            jne     nt!Dr_kit3_a+0x13 (8407db8b)
8407db81 f6456c01        test    byte ptr [ebp+6Ch],1
8407db85 0f84ed000000    je      nt!KiTrap03+0x58 (8407dc78)
8407db8b 0f21c3          mov     ebx,dr0
8407db8e 0f21c9          mov     ecx,dr1
8407db91 0f21d7          mov     edi,dr2
8407db94 895d18          mov     dword ptr [ebp+18h],ebx
8407db97 894d1c          mov     dword ptr [ebp+1Ch],ecx
8407db9a 897d20          mov     dword ptr [ebp+20h],edi
8407db9d 0f21db          mov     ebx,dr3
8407dba0 0f21f1          mov     ecx,dr6
8407dba3 0f21ff          mov     edi,dr7
8407dba6 895d24          mov     dword ptr [ebp+24h],ebx
8407dba9 894d28          mov     dword ptr [ebp+28h],ecx
8407dbac 33db            xor     ebx,ebx
8407dbae 897d2c          mov     dword ptr [ebp+2Ch],edi
8407dbb1 0f23fb          mov     dr7,ebx
8407dbb4 648b3d20000000  mov     edi,dword ptr fs:[20h]
8407dbbb 8b9ff4020000    mov     ebx,dword ptr [edi+2F4h]
8407dbc1 8b8ff8020000    mov     ecx,dword ptr [edi+2F8h]
8407dbc7 0f23c3          mov     dr0,ebx
8407dbca 0f23c9          mov     dr1,ecx
8407dbcd 8b9ffc020000    mov     ebx,dword ptr [edi+2FCh]
8407dbd3 8b8f00030000    mov     ecx,dword ptr [edi+300h]
8407dbd9 0f23d3          mov     dr2,ebx
8407dbdc 0f23d9          mov     dr3,ecx
8407dbdf 8b9f04030000    mov     ebx,dword ptr [edi+304h]
8407dbe5 8b8f08030000    mov     ecx,dword ptr [edi+308h]
8407dbeb 0f23f3          mov     dr6,ebx
8407dbee 0f23f9          mov     dr7,ecx
8407dbf1 e982000000      jmp     nt!KiTrap03+0x58 (8407dc78)
这里所做的操作主要是把各个调试寄存器保存到KiTrap03所构造的陷阱帧中。注意,这里很重要,基本KiTrap00---KiTrap0F这几个异常大致都会如此做。


我们在看看KiTrap03就去分发异常了,分发异常我们可以忽略,虽然内部也也操作了Thread->Header.DebugActive......
那么KiTrap03的最后清理工作呢,是执行了KiExceptionExit,我们看看重要的一点儿代码:

nt!KiExceptionExit+0x4a:
8407d2a2 fa              cli
8407d2a3 ebc0            jmp     nt!KiExceptionExit+0xd (8407d265)
8407d2a5 8d4900          lea     ecx,[ecx]
8407d2a8 8b54244c        mov     edx,dword ptr [esp+4Ch]
8407d2ac 64891500000000  mov     dword ptr fs:[0],edx
8407d2b3 f744242cff23ffff test    dword ptr [esp+2Ch],0FFFF23FFh
8407d2bb 754f            jne     nt!KiExceptionExit+0xb4 (8407d30c)
8407d2bd f744247000000200 test    dword ptr [esp+70h],20000h
上面的test    dword ptr [esp+2Ch],0FFFF23FFh就是测试我们的调试寄存器DR7其中某些值是否存在,存在的话,我们进行跳转。跳过去执行的工作就是刚好和上面保存的工作相反,把保存在陷阱帧中的各个调试寄存器恢复给我们实际的调试寄存器。


上面大致介绍了下流程,我们回过头来看看如果对Thread->Header.DebugActive清零后的影响。
如果Thread->Header.DebugActive执行了清零操作,那么进入陷阱处理器KiTrap03后,线程的各个调试寄存器不会被保存到陷阱帧中。而在退出的恢复工作中,由于陷阱帧中的DR7没有值,所以也就不会恢复各种调试寄存器。这里这个恢复工作是很重要的,调试器设置硬件断点可以用此种方式进行设置,如果是通过这种方式设置硬件断点的调试器,你所下的硬件断点会让你淹没掉之前的硬件断点...

Thread->Header.DebugActive这玩意儿也不是只会被设置一次,而是在会在你每次设置硬件断点的时候都会更新,更新的函数主要是KeContextFromKframes。

于是,我们得到了一种比较郁闷的检测硬件断点的方法。对于反检测,直接清零会不会带来什么不可预知的问题,我没试验。我想会带来问题,由于处理器控制块的调试寄存器我没找到是哪儿给赋予的,所以暂时不清楚此清零带来的问题,更多细节在以后的试验中再说吧。

更多内容请关注梦织未来(www.mengwuji.net)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值