线程是比较重要的内核结构,思考一下其实可以想到线程结构体在64位下的变化应该不会很大最多只是扩充了一些内容,因为从我们之前分析段页时候会发现cpu更新的这些内容大部分不影响xp时候的线程切换机制,下面我们来验证一下
ETHREAD和KPCR都有点大就不全贴出来了只说一些常用的字段,一般熟悉了内核机制的话看名字很多都能猜出来
KPCR是cpu控制区,一个核心一个KPCR对象,里面存放的大多是cpu相关的一些数据以及进程线程相关的一些常用数据
因为0环时候gs:0指向它所以无论在那个内核函数里都能很快的访问到这个结构体
KPCR和NTTIB比较小直接全贴出来了
根据白皮书里描述30号中断为时钟中断,线程切换一定跟时钟中断相关,那么我们就先找一下时钟中断的函数叫什么
通过在windbg里查看可以知道时钟中断函数是KiHvInterrupt,我们到ida里搜一下可以搜到下面几个
因为我的虚拟机环境默认是没开kpti的所以中断函数直接指向了KiHvInterrupt如果开了的话则是指向KiHvInterruptShadow,不过不要紧我们之前分析过int 3的那个Shadow函数这个KiHvInterruptShadow跟那个基本一样,我这里只贴个图上来就不详细说这个跳板函数了
下面我们就看一下这个KiHvInterrupt函数
一开始就还是熟悉的保存trapframe流程
中间是硬件相关的一堆调用不管
然后就是存浮点相关,之后是一些检测然后增加中断次数跳到KiHvInterruptDispatch
我们再看一下KiHvInterruptDispatch
我们再看一下KiDpcInterruptBypass
又调用了KiDispatchInterrupt
在跟进去会发现我们要找的函数,swapContext
swapContext就是我们要找的线程切换函数
现在我们记录一下win10系统下时钟中断进入线程切换的函数调用流程吧
这个流程里有大量的代码有兴趣深入研究的可以按照这个流程看一下,线程切换涉及到了很多系统内核的其他内容我们这里下面直接分析SwapContext
先看一下进入SwapContext之前都传了那些参数进来,可以看到先是调用KiQueueReadyThread找到要切换的线程
大家可以自己到这个函数里分析一下
从KiDpcInterruptBypass这里开始看
现在的寄存器值是
rsp = trapframe
rbp =TRAP_FRAME + 80
rcx = CurrentThread
然后走到KiDispatchInterrupt
看图中圈出来的位置,rsp在调用KxDispatchInterrupt之前又恢复成了trapframe,所以现在的寄存器状态还是
rsp = trapframe
rbp = TRAP_FRAME + 80
rcx = CurrentThread
rbx = kpcr + 20
再看一下SwapContext都干了什么,我这里不一行一行去说了这个函数超级长,我把主要流程截图出来大家最好自己去逆一下会有自己的理解
先是判断要切换的线程是不是就是当前线程,是的话就不处理了,不是的话走下面更改线程状态
这里是切换线程的栈
判断俩线程是不是同一个进程不是的话要切换cr3
走到这里再往下就是收尾的动作了,就是复制进程和线程内容到kpcr里的过程
我们最后总结一下
线程切换的流程(这个总体流程跟xp时候差不多只不过调用的函数链路变了而且多了不少的检测和动作):
触发线程切换的条件(这里只带大家看了时钟中断其他的几个场景大家可以自己去验证一下):
KTHREAD
+
0x000
struct _DISPATCHER_HEADER Header; 跟之前的进程结构体一样是可等待对象都有的头部结构体
KTHREAD
+
0x018
VOID
*
SListFaultAddress 上一次用户模式互锁单链表POP操作发生页面错误的地址。
KTHREAD
+
0x028
VOID
*
InitialStack; 内核栈的原始栈位置(高地址)
KTHREAD
+
0x030
VOID
*
StackLimit; 内核栈低地址
KTHREAD
+
0x038
VOID
*
StackBase; 内核栈的栈基址
KTHREAD
+
0x058
VOID
*
KernelStack; 内核调用栈开始位置
KTHREAD
+
0x0C8
INT64 WaitStatus 等待的结果状态
KTHREAD
+
0x0F0
VOID
*
Teb 三环使用的线程环境块
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Apc相关的后面说Apc时候会讲
KTHREAD
+
0x098
ApcState _KAPC_STATE ApcState结构体
KTHREAD
+
0x258
SavedApcState KAPC_STATE 备份ApcState结构体
KTHREAD
+
0x24a
ApcStateIndex UChar 索引ApcState时候用的
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Apc相关的后面说Apc时候会讲
KTHREAD
+
0x184
State UChar 线程当前状态
KTHREAD
+
0x090
TrapFrame _KTRAP_FRAME 指向Trap_Frame结构体
KTHREAD
+
0x232
PreviousMode Char 存储了当前线程之前的模式是内核模式还是用户模式
KTHREAD
+
0x2f8
ThreadListEntry _LIST_ENTRY KTHREAD里的双向链表串起当前进程的所有线程
ETHREAD
+
0x4e8
ThreadListEntry _LIST_ENTRY 在Ethread里的这个链表也是圈起来了当前进程所有的线程
ETHREAD
+
0x478
Cid _CLIENT_ID 线程的Cid
ETHREAD
+
0x220
Process Ptr64 _KPROCESS 指向当前进程结构体
KTHREAD
+
0x000
struct _DISPATCHER_HEADER Header; 跟之前的进程结构体一样是可等待对象都有的头部结构体
KTHREAD
+
0x018
VOID
*
SListFaultAddress 上一次用户模式互锁单链表POP操作发生页面错误的地址。
KTHREAD
+
0x028
VOID
*
InitialStack; 内核栈的原始栈位置(高地址)
KTHREAD
+
0x030
VOID
*
StackLimit; 内核栈低地址
KTHREAD
+
0x038
VOID
*
StackBase; 内核栈的栈基址
KTHREAD
+
0x058
VOID
*
KernelStack; 内核调用栈开始位置
KTHREAD
+
0x0C8
INT64 WaitStatus 等待的结果状态
KTHREAD
+
0x0F0
VOID
*
Teb 三环使用的线程环境块
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Apc相关的后面说Apc时候会讲
KTHREAD
+
0x098
ApcState _KAPC_STATE ApcState结构体
KTHREAD
+
0x258
SavedApcState KAPC_STATE 备份ApcState结构体
KTHREAD
+
0x24a
ApcStateIndex UChar 索引ApcState时候用的
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Apc相关的后面说Apc时候会讲
KTHREAD
+
0x184
State UChar 线程当前状态
KTHREAD
+
0x090
TrapFrame _KTRAP_FRAME 指向Trap_Frame结构体
KTHREAD
+
0x232
PreviousMode Char 存储了当前线程之前的模式是内核模式还是用户模式
KTHREAD
+
0x2f8
ThreadListEntry _LIST_ENTRY KTHREAD里的双向链表串起当前进程的所有线程
ETHREAD
+
0x4e8
ThreadListEntry _LIST_ENTRY 在Ethread里的这个链表也是圈起来了当前进程所有的线程
ETHREAD
+
0x478
Cid _CLIENT_ID 线程的Cid
ETHREAD
+
0x220
Process Ptr64 _KPROCESS 指向当前进程结构体
0
: kd> dt _NT_TIB
ntdll!_NT_TIB
+
0x000
ExceptionList : Ptr64 _EXCEPTION_REGISTRATION_RECORD 当前的
0
环异常链表
+
0x008
StackBase : Ptr64 Void 从线程里复制出来的栈位置
+
0x010
StackLimit : Ptr64 Void 从线程里复制出来的栈低地址
+
0x018
SubSystemTib : Ptr64 Void
+
0x020
FiberData : Ptr64 Void
+
0x020
Version : Uint4B
+
0x028
ArbitraryUserPointer : Ptr64 Void
+
0x030
Self : Ptr64 _NT_TIB 指向自己
0
: kd> dt _KPCR
ntdll!_KPCR
+
0x000
NtTib : _NT_TIB
+
0x000
GdtBase : Ptr64 _KGDTENTRY64
+
0x008
TssBase : Ptr64 _KTSS64 指向Tss
+
0x010
UserRsp : Uint8B 指向用户层的栈
+
0x018
Self : Ptr64 _KPCR 指向自己
+
0x020
CurrentPrcb : Ptr64 _KPRCB 指向自己的KPRCB的位置
+
0x028
LockArray : Ptr64 _KSPIN_LOCK_QUEUE
+
0x030
Used_Self : Ptr64 Void
+
0x038
IdtBase : Ptr64 _KIDTENTRY64 指向IDT表基址
+
0x040
Unused : [
2
] Uint8B
+
0x050
Irql : UChar 存储了当前的irql
+
0x051
SecondLevelCacheAssociativity : UChar
+
0x052
ObsoleteNumber : UChar
+
0x053
Fill0 : UChar
+
0x054
Unused0 : [
3
] Uint4B
+
0x060
MajorVersion : Uint2B
+
0x062
MinorVersion : Uint2B
+
0x064
StallScaleFactor : Uint4B
+
0x068
Unused1 : [
3
] Ptr64 Void
+
0x080
KernelReserved : [
15
] Uint4B
+
0x0bc
SecondLevelCacheSize : Uint4B
+
0x0c0
HalReserved : [
16
] Uint4B
+
0x100
Unused2 : Uint4B
+
0x108
KdVersionBlock : Ptr64 Void
+
0x110
Unused3 : Ptr64 Void
+
0x118
PcrAlign1 : [
24
] Uint4B
+
0x180
Prcb : _KPRCB 下面是KPRCB一个很大的结构体
KPRCB有点大只介绍常用的字段了
KPRCB
+
0x008
CurrentThread : Ptr64 _KTHREAD 当前线程
KPRCB
+
0x004
LegacyNumber : UChar 是否是兼容模式,兼容模式时候启动是是
32
位内核了
KPRCB
+
0x010
NextThread : Ptr64 _KTHREAD 下一个线程
KPRCB
+
0x018
IdleThread : Ptr64 _KTHREAD 空闲线程,一般cpu空闲时候就会执行这个线程
KPRCB
+
0x028
RspBase : Uint8B 内核栈
KPRCB
+
0x8e88
RspBaseShadow : Uint8B kpti开启时候使用的跳板
0
环栈
KPRCB
+
0x8e90
UserRspShadow : Uint8B
3
环栈
KPRCB
+
0x7e9a
DeepSleep : UChar 深睡眠模式,在线程切换时候会查询不过不用太多关注跟硬件也有关系
KPRCB
+
0x7e80
InterruptCount : Uint4B 中断次数,在下面的逆向代码里能看到增加这个中断次数的代码
KPRCB
+
0
: kd> dt _NT_TIB
ntdll!_NT_TIB
+
0x000
ExceptionList : Ptr64 _EXCEPTION_REGISTRATION_RECORD 当前的
0
环异常链表
+
0x008
StackBase : Ptr64 Void 从线程里复制出来的栈位置
+
0x010
StackLimit : Ptr64 Void 从线程里复制出来的栈低地址
+
0x018
SubSystemTib : Ptr64 Void
+
0x020
FiberData : Ptr64 Void
+
0x020
Version : Uint4B
+
0x028
ArbitraryUserPointer : Ptr64 Void
+
0x030
Self : Ptr64 _NT_TIB 指向自己
0
: kd> dt _KPCR
ntdll!_KPCR
+
0x000
NtTib : _NT_TIB
+
0x000
GdtBase : Ptr64 _KGDTENTRY64
+
0x008
TssBase : Ptr64 _KTSS64 指向Tss
+
0x010
UserRsp : Uint8B 指向用户层的栈
+
0x018
Self : Ptr64 _KPCR 指向自己
+
0x020
CurrentPrcb : Ptr64 _KPRCB 指向自己的KPRCB的位置
+
0x028
LockArray : Ptr64 _KSPIN_LOCK_QUEUE
+
0x030
Used_Self : Ptr64 Void
+
0x038
IdtBase : Ptr64 _KIDTENTRY64 指向IDT表基址
+
0x040
Unused : [
2
] Uint8B
+
0x050
Irql : UChar 存储了当前的irql
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2023-10-7 20:51
被幺幺满地乱爬编辑
,原因: