Mov ecx,100
Call 0x1000
Mov eax,ecx
0x1000--->改变了ecx的值
0x1000: mov eax,0
0x1006: Int 3 ------->进入内核,找到陷阱处理器KiTrap03
0x1007: Mov eax,1
一,中断和异常
概述:
中断和异常是导致处理器转向正常控制流之外代码的两种操作系统条件。
中断是一个异步事件(可以在任何时候发生),并且与处理器当前正在执行的任务毫无关系。中断主要由I/O设备、处理器时钟,或者定时器产生的。
异常是一个同步事件,它是一个特殊指令执行的结果,与当前处理器正在执行的任务有关。
二,IDT(中断描述符表)
系统为每一种中断或者异常提供一个陷阱处理器,当中断或者异常发生时,当前指令的执行就会转到特定的陷阱处理器中,此陷阱处理器完成中断或异常的处理工作。由于中断或异常的类型很多,系统为了方便管理,把各种陷阱处理器放入到一张表中(数组),然后中断和异常发生时,cpu就根据中断或者异常的类型,转入到表中某项去执行中断或异常的处理工作。此表,我们称之为IDT(中断描述符表)。由于中断或异常发生时,转到IDT表项的工作是CPU负责完成的,所以系统中每个CPU都对应一张IDT表,而且每个CPU的IDT表内容都相同。
IDT的定义如下:
typedef struct _IDTR
{
USHORT limit; //范围
ULONG base; //IDT表项起始地址
}IDTR,*PIDTR;
typedef struct _IDTENTRY
{
unsignedshort LowOffset;
unsignedshort selector;
unsignedchar retention:5;
unsignedchar zero1:3;
unsignedchar gate_type:1;
unsignedchar zero2:1;
unsignedchar interrupt_gate_size:1;
unsignedchar zero3:1;
unsignedchar zero4:1;
unsignedchar DPL:2;
unsignedchar P:1;
unsignedshort HiOffset;
} IDTENTRY,*PIDTENTRY;
执行陷阱处理器之前,要把该异常发生时的现场保存下来,方便我们后续指令继续执行。
陷阱帧定义:
typedef struct_X86_KTRAP_FRAME {
ULONG DbgEbp;
ULONG DbgEip;
ULONG DbgArgMark;
ULONG DbgArgPointer;
ULONG TempSegCs;
ULONG TempEsp;
ULONG Dr0;
ULONG Dr1;
ULONG Dr2;
ULONG Dr3;
ULONG Dr6;
ULONG Dr7;
ULONG SegGs;
ULONG SegEs;
ULONG SegDs;
ULONG Edx;
ULONG Ecx;
ULONG Eax;
ULONG PreviousPreviousMode;
ULONG ExceptionList;
ULONG SegFs;
ULONG Edi;
ULONG Esi;
ULONG Ebx;
ULONG Ebp;
ULONG ErrCode;
ULONG Eip;
ULONG SegCs;
ULONG EFlags;
ULONG HardwareEsp; // WARNING - segSS:espare only here for stacks
ULONG HardwareSegSs; // that involve aring transition.
ULONG V86Es; // these will bepresent for all transitions from
ULONG V86Ds; // V86 mode
ULONG V86Fs;
ULONG V86Gs;
} X86_KTRAP_FRAME,*PX86_KTRAP_FRAME;
获取所有kpcr代码:
typedef KAFFINITY
(*KESETAFFINITYTHREAD)(
__inout PKTHREAD Thread,
__in KAFFINITY Affinity
);
#define MAX_PROCESSORCOUNT 0x40
ULONG gkv_KiProcessorBlock[MAX_PROCESSORCOUNT];
BOOLEAN PsGetKiProcessorBlock()
{
ULONG Index,Affinity,CurrentAffinity;
ULONG fnpKeSetAffinityThread;
ULONG kPrcb;
UNICODE_STRING usFuncName;
RtlInitUnicodeString(&usFuncName,L"KeSetAffinityThread");
fnpKeSetAffinityThread =(ULONG)MmGetSystemRoutineAddress(&usFuncNam);
if (fnpKeSetAffinityThread==0)
{
return FALSE;
}
Affinity = KeQueryActiveProcessors(); //KeQueryActiveProcessors获取处理器相关的位图(这里的位图可以理解为个数,比如返回1代表一个处理器,返回3表示两个处理器,返回7表示三个处理器,依此类推。也就是说从有多少个处理器,那么Affinity的值就会从低位到高位依此填充多少位)
CurrentAffinity = 1;
Index = 0;
while(Affinity)
{
//下面只是个简单的算法,使当前线程运行到不同的处理器上
Affinity &= ~CurrentAffinity;
((KESETAFFINITYTHREAD)fnpKeSetAffinityThread)(PsGetCurrentThread(),(KAFFINITY)CurrentAffinity);
CurrentAffinity <<= 1;
__asm{
push eax
mov eax,fs:[0x20]
mov kPrcb,eax
pop eax
}
//得到我们要的东西
gkv_KiProcessorBlock[Index] =kPrcb;
Index++;
}
return TRUE;
}
硬件断点是cpu直接支持的,通过对DR0~DR7寄存器的相关设置,就可以达到监控某地址的执行或者读写情况。
内核层中的硬件断点,是可以直接设置DR0~DR7寄存器。
用户层中的硬件断点,是不可以直接设置DR0~DR7寄存器。
Windows为我们提供了两个API,可以去帮我我们设置硬件断点。
BOOL GetThreadContext(
HANDLE hThread,
LPCONTEXT lpContext
);
BOOL SetThreadContext(
HANDLE hThread,
CONST CONTEXT* lpContext
);
以上两个api可以获取和设置一个线程的上下文。
以前说过,设置硬件断点是通过改写DR0---DR7寄存器进行的。
设置和获取线程上下文最终是通过KeContextToKframes和KeContextFromKframes实现的。
KeContextFromKframes内部是通过内核栈上面保存的一个_KTRAP_FRAME结构来获取相关的线程上下文内容。
其中获取调试寄存器的内容后,通过KiUpdateDr7此函数更新一些东西。
线程对象的0x3偏移位置是DebugActive成员,这个成员,是记录哪些调试寄存器是被设置了。
KeContextToKframes内部是通过设置内核栈上面保存的一个_KTRAP_FRAME结构来更新线程上下文的。
其中并没有对我们的调试寄存器进行直接的设置工作。这里虽然没有直接的相关设置工作,那么它肯定是在某个地方进行了设置,不然的话,用户层是没办法实现硬件断点的。
首先用户层调用api的原理是:
xxx----进入内核----KiFastCallEntry-----xxx对应的内核函数-----KiFastCallEntry-----退出内核(KiServiceExit)-----返回xxx
设置硬件断点的工作如下:
在NtSetContextThread内部更新了目标线程内核栈上的陷阱帧。然后当目标线程切换进内核并退出时,目标线程会把它内核栈上的陷阱帧更新到对应的DR0---DR7调试寄存器中,这样就达到了设置硬件断点的目的。
Windows为了区分用户模式硬件断点和内核模式硬件的,它的设计思路如下:
当是内核模式硬件断点的时候,可以直接设置调试寄存器。
当是用户模式硬件断点的时候,因为具体的设置工作是要在内核中进行的,而如果直接在KeContextToKframes中进行设置调试寄存器的工作的话,那是就等于是设置内核模式的调试寄存器了。所以windows选择在目标线程退出内核的那一刻时,进行设置,那么退出内核后,硬件断点就生效了。
总结:
1,反硬件断点,我们可以通过对某线程的DebugActive成员进行判断,如果存在值的话,那么就是存在硬件断点。
2,可以通过对目标线程内核栈上的陷阱帧进行获取和设置工作,也可以达到反硬件断点与检测的目的。
NtGetContextThread(HANDLEThreadHandle, PCONTEXT Context)
NtSetContextThread(HANDLEThreadHandle, PCONTEXT Context)
PsGetContextThread(PETHREADThread, PCONTEXT Context,KPROCESSORMODE AccessMode)
PsSetContextThread(PETHREADThread, PCONTEXT Context,KPROCESSORMODE AccessMode)