查找 Windows 在上下文切换中保存其寄存器的位置
Finding where Windows saves its registers in Context-Switching
我正在搜索,Windows 在上下文切换过程中保存其寄存器的位置(内核模式下的寄存器和用户模式下的寄存器。)
然后我发现 问题描述了 Windows 在以下字段中将当前上下文保存在 nt!_KTHREAD
中:
+0x1b8 WaitPrcb : Ptr32 _KPRCB
我可以在 nt!_KPRCB
中找到以下字段:
+0x3658 Context : Ptr32 _CONTEXT
如您所知,nt!_CONTEXT
是包含上下文切换所需的几乎所有寄存器的结构。
为了找到这个位置,我使用 windbg 配置了 VMWare 内核调试,然后在来宾计算机中打开 xdbg64 并附加到 x64 进程以查看当前寄存器状态并使用主机的 windbg 和在 Windbg 中暂停来宾计算机首先我找到目标进程:
kd> !process 0 0
...
PROCESS ffff9387f70d05c0
SessionId: 1 Cid: 15e4 Peb: 35cf6bd000 ParentCid: 10b4
DirBase: 48b46000 ObjectTable: ffffba87f0b628c0 HandleCount: <Data Not Accessible>
Image: example.exe
...
然后找到这个进程的线程:
kd> !process ffff9387f70d05c0
PROCESS ffff9387f70d05c0
SessionId: 1 Cid: 15e4 Peb: 35cf6bd000 ParentCid: 10b4
DirBase: 48b46000 ObjectTable: ffffba87f0b628c0 HandleCount: <Data Not Accessible>
Image: example.exe
VadRoot ffff9387f6238750 Vads 1 Clone 0 Private 168. Modified 0. Locked 0.
DeviceMap ffffba87ef63f230
Token ffffba87e97ec060
ElapsedTime 00:16:35.173
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 0
Working Set Sizes (now,min,max) (0, 0, 0) (0KB, 0KB, 0KB)
PeakWorkingSetSize 0
VirtualSize 79 Mb
PeakVirtualSize 79 Mb
PageFaultCount 0
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 204
DebugPort ffff9387f6952400
Job ffff9387f82b4830
THREAD **ffff9387f62f1700** Cid 15e4.08c4 Teb: 00000035cf6be000 Win32Thread: ffff9387f7b64e50 WAIT: (Executive) KernelMode Non-Alertable
FreezeCount 1
fffffd8ec29cad80 SynchronizationEvent
Cannot read nt!_KWAIT_BLOCK at 0000000000000000 - error 1
Not impersonating
DeviceMap ffffba87ef63f230
Owning Process ffff9387f70d05c0 Image: example.exe
Attached Process N/A Image: N/A
Wait Start TickCount 4600127 Ticks: 3 (0:00:00:00.046)
Context Switch Count 1215
UserTime 00:00:00.000
KernelTime 00:00:00.015
Win32 Start Address 0x00007ff7e7a22440
Stack Init fffffd8ec29cbc90 Current fffffd8ec29ca970
Base fffffd8ec29cc000 Limit fffffd8ec29c6000 Call 0
Priority 10 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
Child-SP RetAddr Call Site
fffffd8e`c29ca9b0 00000000`00000000 nt!KiSwapContext+0x76
在最后一步中,我将上述(线程)地址映射到 nt!_kthread
:
kd> dt nt!_kthread ffff9387f62f1700
+0x000 Header : _DISPATCHER_HEADER
+0x018 SListFaultAddress : (null)
+0x020 QuantumTarget : 0x878eb54
+0x028 InitialStack : 0xfffffd8e`c29cbc90 Void
+0x030 StackLimit : 0xfffffd8e`c29c6000 Void
+0x038 StackBase : 0xfffffd8e`c29cc000 Void
...
+0x2c8 WaitPrcb : (null)
...
但是如您所见,WaitPrcb
为空!
所以我的问题是:
- 我的线程上下文指向
null
位置有什么问题? (还是我走错地方了?)
- 据我所知,每个线程应该有两个上下文,一个在用户模式下度过,另一个在内核模式下度过,所以Windows应该有两个
nt!_CONTEXT
结构!他们在哪里?
不确定你想做什么,你的问题有点难以回答
为什么将 waitprcb 与 _context
互连
windbgs kb (stack backtrace) 通常会显示 trapframe 的地址
!process 0 2 { process name } 将产生特定进程中的所有线程
这将是一个 Dml 输出,只需单击输出中的线程地址,就会显示每个线程的调用堆栈(执行的命令!线程地址)
或者你可以用dt nt!_ethread Tcb->TrapFrame->*
查看trapframe
这是你想要做的吗?
kd> !process 0 2 calc.exe
PROCESS 841fa930 SessionId: 1 Cid: 0228 Peb: 7ffdf000 ParentCid: 022c
DirBase: 06812000 ObjectTable: 9512f448 HandleCount: 88.
Image: calc.exe
THREAD 841fa648 Cid 0228.061c Teb: 7ffde000 Win32Thread: fe7196c0 WAIT:
THREAD 841f6a48 Cid 0228.067c Teb: 7ffdd000 Win32Thread: ff8cc918 WAIT:
THREAD 84975b48 Cid 0228.05b8 Teb: 7ffdc000 Win32Thread: fe246298 WAIT:
**THREAD 841f7850** Cid 0228.073c Teb: 7ffdb000 Win32Thread: 00000000 WAIT:
kd> .shell -ci "!thread 841fa648" grep TrapFrame
8e2e3d1c 772370b4 001eef48 00000000 00000000 nt!KiFastCallEntry+0x12aTrapFrame @ 8e2e3d34)
kd> .shell -ci "!thread 841f6a48" grep TrapFrame
8e21fd18 772370b4 00000002 0166f810 00000001 nt!KiFastCallEntry+0x12aTrapFrame @ 8e21fd34)
kd> .shell -ci "!thread 84975b48" grep TrapFrame
8c65fd18 772370b4 00000001 026cfde0 00000001 nt!KiFastCallEntry+0x12aTrapFrame @ 8c65fd34)
kd> .shell -ci **"!thread 841f7850"** grep TrapFrame
8e2d3d18 772370b4 00000003 00376558 00000001 nt!KiFastCallEntry+0x12aTrapFrame @ 8e2d3d34)
kd> **!thread 841f7850**
ChildEBP RetAddr Args to Child
8e2d3760 8286dd75 841f7850 82937f08 82934d20 nt!KiSwapContext+0x26 (FPO: [0,0,4])
8e2d3798 8286cbd3 841de4a0 841f7850 841f7964 nt!KiSwapThread+0x266
8e2d37c0 82868c59 841f7850 841f7910 00000000 nt!KiCommitThreadWait+0x1df
8e2d393c 82a11a89 00000003 8e2d3a74 00000001 nt!KeWaitForMultipleObjects+0x535
8e2d3bc8 82a117f6 00000003 8e2d3c00 00000001 nt!ObpWaitForMultipleObjects+0x262
8e2d3d18 8284787a 00000003 00376558 00000001 nt!NtWaitForMultipleObjects+0xcd
8e2d3d18 772370b4 00000003 00376558 01 nt!KiFastCallEntry+0x12a **TrapFrame @ 8e2d3d34**)
WARNING: Frame IP not in any known module. Following frames may be wrong.
0258fd48 00000000 00000000 00000000 00000000 0x772370b4
kd> dt nt!_ETHREAD Tcb->TrapFrame 841f7850
+0x000 Tcb :
+0x128 TrapFrame : **0x8e2d3d34 _KTRAP_FRAME**
我正在添加另一个答案,因为用新的细节编辑较早的答案会使它变得混乱
由于多种原因,可以切换上下文
1) 线程已经放弃并被阻塞等待一些输入(比方说 scanf())
2) 发生中断,运行 线程被中断(异常,高优先级线程变为可运行等)
3) 用户模式到内核模式的转换
lets suppose nt!KiSwapContext 是负责切换上下文的函数。
为了验证我们的假设或假设,我们可以在该函数上设置一个进程特定的条件断点并记录
debugger win 7 sp1 32 bit physical machine
debuggee win 7 sp1 32 bit vm
transport serial pipe
breakpoint list bl output we have one processes specific conditional bp
condition print the backward disassembly at the return address on stack
print callstack and continue execution
0 e Disable Clear 8288bf00 0001 (0001) nt!KiSwapContext "ub @$ra;kb;gc"
Match process data 842fe7d0
kd> g
短期内输出数千行我们将使用 wc.exe , sed , grep , awk , sort , uniq , gnuwin32 工具来分析文本输出
:\>wc -l swappy.txt
2109 swappy.txt
:\>grep debuggee swappy.txt
Debugger (not debuggee) time: Thu Mar 15 13:03:14.047 2018
Debugger (not debuggee) time: Thu Mar 15 13:06:20.077 2018
:\>grep call.*nt!KiSwapContext swappy.txt | wc -l
153
the output is 2109 lines in 3 minutes of trial time and nt!KiSwapContext has
been called 153 times during this time period for this specific process
这些调用的每次中断都会输出类似这样的内容
:\>head -n 23 swappy.txt | tail -n 16
kd> g
nt!KiQuantumEnd+0x2ca:
828b976a 8bd6 mov edx,esi
828b976c 8bcb mov ecx,ebx
828b976e c683870100001e mov byte ptr [ebx+187h],1Eh
828b9775 e87ed3faff call nt!KiQueueReadyThread (82866af8)
828b977a 8b542414 mov edx,dword ptr [esp+14h]
828b977e 8bcb mov ecx,ebx
828b9780 c6436a01 mov byte ptr [ebx+6Ah],1
828b9784 e87727fdff call nt!KiSwapContext (8288bf00)
# ChildEBP RetAddr Args to Child
00 80df94d0 828b9789 dcf83678 9601a27a 82959c00 nt!KiSwapContext
WARNING: Process directory table base 16DAC000 doesn't match CR3 00185000
WARNING: Process directory table base 16DAC000 doesn't match CR3 00185000
01 00000000 00000000 00000000 00000000 00000000 nt!KiQuantumEnd+0x2e9
nt!KiSwapThread+0x256:
我们可以像这样排序并获取每个调用的唯一出现次数
:\>grep -B10 call.*nt!KiSwapContext swappy.txt | grep +.*: | sort | uniq
nt!KiExitDispatcher+0x123:
nt!KiQuantumEnd+0x2ca:
nt!KiSwapThread+0x256:
:\>grep -B10 call.*nt!KiSwapContext swappy.txt | grep +.*: | sort | grep Exit | wc -l
12
:\>grep -B10 call.*nt!KiSwapContext swappy.txt | grep +.*: | sort | grep Quant | wc -l
101
:\>grep -B10 call.*nt!KiSwapContext swappy.txt | grep +.*: | sort | grep SwapThread | wc -l
40
我们可以从这个样本数据中推断出上下文很可能在很大程度上是由于时间片完成而被交换的
随后是线程放弃,然后是中断
让我们先研究最大的一次,其调用序列如下所示
所以一个就绪的线程被排队并且上下文被交换
我们可以看到正在使用 ebx 并且 ebx 似乎是一个结构(我们可以看到成员 @offset 0x187 和 0x6a 在调用序列中被访问)
:\>grep -m 3 -B10 call.*nt!KiSwapContext swappy.txt | grep -m 1 -A 10 +.*:
nt!KiQuantumEnd+0x2ca:
828b976a 8bd6 mov edx,esi
828b976c 8bcb mov ecx,ebx
828b976e c683870100001e mov byte ptr [ebx+187h],1Eh
828b9775 e87ed3faff call nt!KiQueueReadyThread (82866af8)
828b977a 8b542414 mov edx,dword ptr [esp+14h]
828b977e 8bcb mov ecx,ebx
828b9780 c6436a01 mov byte ptr [ebx+6Ah],1
828b9784 e87727fdff call nt!KiSwapContext (8288bf00)
让我们修改我们的断点并停止并手动使用 f5 或 g 继续,直到我们到达 QuantumEnd 调用序列
kd> bp /p 842fe7d0 nt!KiSwapContext ".printf \"%y\n\" , @$ra"
breakpoint 0 redefined
kd> g
nt!KiExitDispatcher+0x140 (8288be87) nt!KiSwapContext:
8288bf00 83ec10 sub esp,10h
kd> g
nt!KiQuantumEnd+0x2e9 (828b9789) nt!KiSwapContext:
8288bf00 83ec10 sub esp,10h
kd> r
eax=00000000 ebx=84e50b40 ecx=84e50b40 edx=84304030 esi=82959d20 edi=84e50b40
eip=8288bf00 esp=8c691b4c ebp=8c691b88
从寄存器中我们可以看到调用序列匹配的反汇编
ecx、ebx 和 edi 相同(指向新线程和 Ready Thread 的指针)
edx 匹配推送调整(调用使用一个双字一个 return 地址,所以我们检查 [esp+18] 而不是 [esp+14] )指向当前线程的指针
esi = prcb
kd> ? dwo(@esp+18)
Evaluate expression: -2077212624 = 84304030
kd> ? @$thread
Evaluate expression: -2077212624 = 84304030
kd> ? edx
Evaluate expression: -2077212624 = 84304030
kd> ?? @$prcb == (int *)(@esi)
bool true
kd> ? @$prcb ; ? @esi
Evaluate expression: -2104124128 = 82959d20
Evaluate expression: -2104124128 = 82959d20
kd> ? @ecx;? @ebx;? @edi;!thread @ebx 0
Evaluate expression: -2065364160 = 84e50b40
Evaluate expression: -2065364160 = 84e50b40
Evaluate expression: -2065364160 = 84e50b40
THREAD 84e50b40 Cid 0174.01ec Teb: 7ffd9000 Win32Thread: ff9461a0 READY on processor 0
因为我们确认 ebx = 将成为新线程的线程
我们可以确认 187h 和 6ah 偏移量指向什么
kd> .enable_long_status 1
kd> ?? #FIELD_OFFSET(nt!_KTHREAD , WaitReason)
long 0x187
kd> ?? #FIELD_OFFSET(nt!_KTHREAD , WaitIrql)
long 0x6a
我们还可以从头文件中确认 Wait Reason 和 WaitIrql
:\>grep WaitReason "c:\Program Files\Windows Kits\Include.0.16299.0\km\wdm.h"
MaximumWaitReason
_In_ _Strict_type_match_ KWAIT_REASON WaitReason,
_In_ _Strict_type_match_ KWAIT_REASON WaitReason,
:\>grep -n KWAIT_REASON "c:\Program Files\Windows Kits\Include.0.16299.0\km\wdm.h"
20139:typedef enum _KWAIT_REASON {
20181:} KWAIT_REASON;
20925: _In_ _Strict_type_match_ KWAIT_REASON WaitReason,
20941: _In_ _Strict_type_match_ KWAIT_REASON WaitReason,
:\>awk "NR==20139+0x1f" "c:\Program Files\Windows Kits\Include.0.16299.0\km\wdm.h"
WrQuantumEnd,
:\>grep -n define.*APC_LEVEL "c:\Program Files\Windows Kits\Include.0.16299.0\km\wdm.h"
175:#define APC_LEVEL 1 // APC interrupt level
既然我们已经破译了几乎所有的东西,我们现在可以研究函数
kd> uf .
nt!KiSwapContext:
8288bf00 83ec10 sub esp,10h
8288bf03 895c240c mov dword ptr [esp+0Ch],ebx
8288bf07 89742408 mov dword ptr [esp+8],esi
8288bf0b 897c2404 mov dword ptr [esp+4],edi
8288bf0f 892c24 mov dword ptr [esp],ebp
8288bf12 648b1d1c000000 mov ebx,dword ptr fs:[1Ch]
8288bf19 8bf9 mov edi,ecx
8288bf1b 8bf2 mov esi,edx
8288bf1d 0fb64f6a movzx ecx,byte ptr [edi+6Ah]
8288bf21 e87a010000 call nt!SwapContext (8288c0a0)
8288bf26 8b2c24 mov ebp,dword ptr [esp]
8288bf29 8b7c2404 mov edi,dword ptr [esp+4]
8288bf2d 8b742408 mov esi,dword ptr [esp+8]
8288bf31 8b5c240c mov ebx,dword ptr [esp+0Ch]
8288bf35 83c410 add esp,10h
8288bf38 c3 ret
所以该函数接受 fs:[1c],它是 self.pcr 新线程的 WaitIrql,并进入 nt!SwapContext () 进行实际交换
步到 nt!SwapContext,您将看到
kd>
nt!KiSwapContext+0x21:
8288bf21 e87a010000 call nt!SwapContext (8288c0a0)
kd> r
eax=00000000 ebx=82959c00 ecx=00000001 edx=84304030 esi=84304030 edi=84e50b40
eip=8288bf21 esp=8c691b3c ebp=8c691b88 iopl=0 nv up ei ng nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000286
nt!KiSwapContext+0x21:
8288bf21 e87a010000 call nt!SwapContext (8288c0a0)
这是一个开始
kd> r
eax=00000000 ebx=82959c00 ecx=00000001 edx=84304030 esi=84304030 edi=84e50b40
eip=8288c0a0 esp=8c691b38 ebp=8c691b88 iopl=0 nv up ei ng nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000286
nt!SwapContext:
8288c0a0 807e3900 cmp byte ptr [esi+39h],0 ds:0023:84304069=00
kd> ?? #FIELD_OFFSET( nt!_KTHREAD , Running)
long 0x39
kd> $$ checks if the current thread is running if it is running it stops it
with a pause if it is not running it sets the running member clears
interrupts updates the counters
kd> it is a big function check it out to see what registers are pushed , copied , moved to where
nt!SwapContext 调用这些函数 begin accumalation 调用保存浮点寄存器的条件
其他寄存器根据需要保存
nt!SwapContext (8288c0a0)
call to hal!HalRequestSoftwareInterrupt (82820258)
call to nt!KiBeginCounterAccumulation (8290d6a7)
call to nt!PsCheckThreadCpuQuota (829263f0)
call to nt!EtwTraceContextSwap (82847de8)
call to nt!KeBugCheckEx (8290940a)
提出或开始一个新话题,并链接该话题
我正在搜索,Windows 在上下文切换过程中保存其寄存器的位置(内核模式下的寄存器和用户模式下的寄存器。)
然后我发现 nt!_KTHREAD
中:
+0x1b8 WaitPrcb : Ptr32 _KPRCB
我可以在 nt!_KPRCB
中找到以下字段:
+0x3658 Context : Ptr32 _CONTEXT
如您所知,nt!_CONTEXT
是包含上下文切换所需的几乎所有寄存器的结构。
为了找到这个位置,我使用 windbg 配置了 VMWare 内核调试,然后在来宾计算机中打开 xdbg64 并附加到 x64 进程以查看当前寄存器状态并使用主机的 windbg 和在 Windbg 中暂停来宾计算机首先我找到目标进程:
kd> !process 0 0
...
PROCESS ffff9387f70d05c0
SessionId: 1 Cid: 15e4 Peb: 35cf6bd000 ParentCid: 10b4
DirBase: 48b46000 ObjectTable: ffffba87f0b628c0 HandleCount: <Data Not Accessible>
Image: example.exe
...
然后找到这个进程的线程:
kd> !process ffff9387f70d05c0
PROCESS ffff9387f70d05c0
SessionId: 1 Cid: 15e4 Peb: 35cf6bd000 ParentCid: 10b4
DirBase: 48b46000 ObjectTable: ffffba87f0b628c0 HandleCount: <Data Not Accessible>
Image: example.exe
VadRoot ffff9387f6238750 Vads 1 Clone 0 Private 168. Modified 0. Locked 0.
DeviceMap ffffba87ef63f230
Token ffffba87e97ec060
ElapsedTime 00:16:35.173
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 0
QuotaPoolUsage[NonPagedPool] 0
Working Set Sizes (now,min,max) (0, 0, 0) (0KB, 0KB, 0KB)
PeakWorkingSetSize 0
VirtualSize 79 Mb
PeakVirtualSize 79 Mb
PageFaultCount 0
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 204
DebugPort ffff9387f6952400
Job ffff9387f82b4830
THREAD **ffff9387f62f1700** Cid 15e4.08c4 Teb: 00000035cf6be000 Win32Thread: ffff9387f7b64e50 WAIT: (Executive) KernelMode Non-Alertable
FreezeCount 1
fffffd8ec29cad80 SynchronizationEvent
Cannot read nt!_KWAIT_BLOCK at 0000000000000000 - error 1
Not impersonating
DeviceMap ffffba87ef63f230
Owning Process ffff9387f70d05c0 Image: example.exe
Attached Process N/A Image: N/A
Wait Start TickCount 4600127 Ticks: 3 (0:00:00:00.046)
Context Switch Count 1215
UserTime 00:00:00.000
KernelTime 00:00:00.015
Win32 Start Address 0x00007ff7e7a22440
Stack Init fffffd8ec29cbc90 Current fffffd8ec29ca970
Base fffffd8ec29cc000 Limit fffffd8ec29c6000 Call 0
Priority 10 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
Child-SP RetAddr Call Site
fffffd8e`c29ca9b0 00000000`00000000 nt!KiSwapContext+0x76
在最后一步中,我将上述(线程)地址映射到 nt!_kthread
:
kd> dt nt!_kthread ffff9387f62f1700
+0x000 Header : _DISPATCHER_HEADER
+0x018 SListFaultAddress : (null)
+0x020 QuantumTarget : 0x878eb54
+0x028 InitialStack : 0xfffffd8e`c29cbc90 Void
+0x030 StackLimit : 0xfffffd8e`c29c6000 Void
+0x038 StackBase : 0xfffffd8e`c29cc000 Void
...
+0x2c8 WaitPrcb : (null)
...
但是如您所见,WaitPrcb
为空!
所以我的问题是:
- 我的线程上下文指向
null
位置有什么问题? (还是我走错地方了?) - 据我所知,每个线程应该有两个上下文,一个在用户模式下度过,另一个在内核模式下度过,所以Windows应该有两个
nt!_CONTEXT
结构!他们在哪里?
不确定你想做什么,你的问题有点难以回答 为什么将 waitprcb 与 _context
互连windbgs kb (stack backtrace) 通常会显示 trapframe 的地址
!process 0 2 { process name } 将产生特定进程中的所有线程
这将是一个 Dml 输出,只需单击输出中的线程地址,就会显示每个线程的调用堆栈(执行的命令!线程地址)
或者你可以用dt nt!_ethread Tcb->TrapFrame->*
这是你想要做的吗?
kd> !process 0 2 calc.exe
PROCESS 841fa930 SessionId: 1 Cid: 0228 Peb: 7ffdf000 ParentCid: 022c
DirBase: 06812000 ObjectTable: 9512f448 HandleCount: 88.
Image: calc.exe
THREAD 841fa648 Cid 0228.061c Teb: 7ffde000 Win32Thread: fe7196c0 WAIT:
THREAD 841f6a48 Cid 0228.067c Teb: 7ffdd000 Win32Thread: ff8cc918 WAIT:
THREAD 84975b48 Cid 0228.05b8 Teb: 7ffdc000 Win32Thread: fe246298 WAIT:
**THREAD 841f7850** Cid 0228.073c Teb: 7ffdb000 Win32Thread: 00000000 WAIT:
kd> .shell -ci "!thread 841fa648" grep TrapFrame
8e2e3d1c 772370b4 001eef48 00000000 00000000 nt!KiFastCallEntry+0x12aTrapFrame @ 8e2e3d34)
kd> .shell -ci "!thread 841f6a48" grep TrapFrame
8e21fd18 772370b4 00000002 0166f810 00000001 nt!KiFastCallEntry+0x12aTrapFrame @ 8e21fd34)
kd> .shell -ci "!thread 84975b48" grep TrapFrame
8c65fd18 772370b4 00000001 026cfde0 00000001 nt!KiFastCallEntry+0x12aTrapFrame @ 8c65fd34)
kd> .shell -ci **"!thread 841f7850"** grep TrapFrame
8e2d3d18 772370b4 00000003 00376558 00000001 nt!KiFastCallEntry+0x12aTrapFrame @ 8e2d3d34)
kd> **!thread 841f7850**
ChildEBP RetAddr Args to Child
8e2d3760 8286dd75 841f7850 82937f08 82934d20 nt!KiSwapContext+0x26 (FPO: [0,0,4])
8e2d3798 8286cbd3 841de4a0 841f7850 841f7964 nt!KiSwapThread+0x266
8e2d37c0 82868c59 841f7850 841f7910 00000000 nt!KiCommitThreadWait+0x1df
8e2d393c 82a11a89 00000003 8e2d3a74 00000001 nt!KeWaitForMultipleObjects+0x535
8e2d3bc8 82a117f6 00000003 8e2d3c00 00000001 nt!ObpWaitForMultipleObjects+0x262
8e2d3d18 8284787a 00000003 00376558 00000001 nt!NtWaitForMultipleObjects+0xcd
8e2d3d18 772370b4 00000003 00376558 01 nt!KiFastCallEntry+0x12a **TrapFrame @ 8e2d3d34**)
WARNING: Frame IP not in any known module. Following frames may be wrong.
0258fd48 00000000 00000000 00000000 00000000 0x772370b4
kd> dt nt!_ETHREAD Tcb->TrapFrame 841f7850
+0x000 Tcb :
+0x128 TrapFrame : **0x8e2d3d34 _KTRAP_FRAME**
我正在添加另一个答案,因为用新的细节编辑较早的答案会使它变得混乱
由于多种原因,可以切换上下文
1) 线程已经放弃并被阻塞等待一些输入(比方说 scanf())
2) 发生中断,运行 线程被中断(异常,高优先级线程变为可运行等)
3) 用户模式到内核模式的转换
lets suppose nt!KiSwapContext 是负责切换上下文的函数。
为了验证我们的假设或假设,我们可以在该函数上设置一个进程特定的条件断点并记录
debugger win 7 sp1 32 bit physical machine
debuggee win 7 sp1 32 bit vm
transport serial pipe
breakpoint list bl output we have one processes specific conditional bp
condition print the backward disassembly at the return address on stack
print callstack and continue execution
0 e Disable Clear 8288bf00 0001 (0001) nt!KiSwapContext "ub @$ra;kb;gc"
Match process data 842fe7d0
kd> g
短期内输出数千行我们将使用 wc.exe , sed , grep , awk , sort , uniq , gnuwin32 工具来分析文本输出
:\>wc -l swappy.txt
2109 swappy.txt
:\>grep debuggee swappy.txt
Debugger (not debuggee) time: Thu Mar 15 13:03:14.047 2018
Debugger (not debuggee) time: Thu Mar 15 13:06:20.077 2018
:\>grep call.*nt!KiSwapContext swappy.txt | wc -l
153
the output is 2109 lines in 3 minutes of trial time and nt!KiSwapContext has
been called 153 times during this time period for this specific process
这些调用的每次中断都会输出类似这样的内容
:\>head -n 23 swappy.txt | tail -n 16
kd> g
nt!KiQuantumEnd+0x2ca:
828b976a 8bd6 mov edx,esi
828b976c 8bcb mov ecx,ebx
828b976e c683870100001e mov byte ptr [ebx+187h],1Eh
828b9775 e87ed3faff call nt!KiQueueReadyThread (82866af8)
828b977a 8b542414 mov edx,dword ptr [esp+14h]
828b977e 8bcb mov ecx,ebx
828b9780 c6436a01 mov byte ptr [ebx+6Ah],1
828b9784 e87727fdff call nt!KiSwapContext (8288bf00)
# ChildEBP RetAddr Args to Child
00 80df94d0 828b9789 dcf83678 9601a27a 82959c00 nt!KiSwapContext
WARNING: Process directory table base 16DAC000 doesn't match CR3 00185000
WARNING: Process directory table base 16DAC000 doesn't match CR3 00185000
01 00000000 00000000 00000000 00000000 00000000 nt!KiQuantumEnd+0x2e9
nt!KiSwapThread+0x256:
我们可以像这样排序并获取每个调用的唯一出现次数
:\>grep -B10 call.*nt!KiSwapContext swappy.txt | grep +.*: | sort | uniq
nt!KiExitDispatcher+0x123:
nt!KiQuantumEnd+0x2ca:
nt!KiSwapThread+0x256:
:\>grep -B10 call.*nt!KiSwapContext swappy.txt | grep +.*: | sort | grep Exit | wc -l
12
:\>grep -B10 call.*nt!KiSwapContext swappy.txt | grep +.*: | sort | grep Quant | wc -l
101
:\>grep -B10 call.*nt!KiSwapContext swappy.txt | grep +.*: | sort | grep SwapThread | wc -l
40
我们可以从这个样本数据中推断出上下文很可能在很大程度上是由于时间片完成而被交换的 随后是线程放弃,然后是中断
让我们先研究最大的一次,其调用序列如下所示 所以一个就绪的线程被排队并且上下文被交换 我们可以看到正在使用 ebx 并且 ebx 似乎是一个结构(我们可以看到成员 @offset 0x187 和 0x6a 在调用序列中被访问)
:\>grep -m 3 -B10 call.*nt!KiSwapContext swappy.txt | grep -m 1 -A 10 +.*:
nt!KiQuantumEnd+0x2ca:
828b976a 8bd6 mov edx,esi
828b976c 8bcb mov ecx,ebx
828b976e c683870100001e mov byte ptr [ebx+187h],1Eh
828b9775 e87ed3faff call nt!KiQueueReadyThread (82866af8)
828b977a 8b542414 mov edx,dword ptr [esp+14h]
828b977e 8bcb mov ecx,ebx
828b9780 c6436a01 mov byte ptr [ebx+6Ah],1
828b9784 e87727fdff call nt!KiSwapContext (8288bf00)
让我们修改我们的断点并停止并手动使用 f5 或 g 继续,直到我们到达 QuantumEnd 调用序列
kd> bp /p 842fe7d0 nt!KiSwapContext ".printf \"%y\n\" , @$ra"
breakpoint 0 redefined
kd> g
nt!KiExitDispatcher+0x140 (8288be87) nt!KiSwapContext:
8288bf00 83ec10 sub esp,10h
kd> g
nt!KiQuantumEnd+0x2e9 (828b9789) nt!KiSwapContext:
8288bf00 83ec10 sub esp,10h
kd> r
eax=00000000 ebx=84e50b40 ecx=84e50b40 edx=84304030 esi=82959d20 edi=84e50b40
eip=8288bf00 esp=8c691b4c ebp=8c691b88
从寄存器中我们可以看到调用序列匹配的反汇编
ecx、ebx 和 edi 相同(指向新线程和 Ready Thread 的指针)
edx 匹配推送调整(调用使用一个双字一个 return 地址,所以我们检查 [esp+18] 而不是 [esp+14] )指向当前线程的指针
esi = prcb
kd> ? dwo(@esp+18)
Evaluate expression: -2077212624 = 84304030
kd> ? @$thread
Evaluate expression: -2077212624 = 84304030
kd> ? edx
Evaluate expression: -2077212624 = 84304030
kd> ?? @$prcb == (int *)(@esi)
bool true
kd> ? @$prcb ; ? @esi
Evaluate expression: -2104124128 = 82959d20
Evaluate expression: -2104124128 = 82959d20
kd> ? @ecx;? @ebx;? @edi;!thread @ebx 0
Evaluate expression: -2065364160 = 84e50b40
Evaluate expression: -2065364160 = 84e50b40
Evaluate expression: -2065364160 = 84e50b40
THREAD 84e50b40 Cid 0174.01ec Teb: 7ffd9000 Win32Thread: ff9461a0 READY on processor 0
因为我们确认 ebx = 将成为新线程的线程 我们可以确认 187h 和 6ah 偏移量指向什么
kd> .enable_long_status 1
kd> ?? #FIELD_OFFSET(nt!_KTHREAD , WaitReason)
long 0x187
kd> ?? #FIELD_OFFSET(nt!_KTHREAD , WaitIrql)
long 0x6a
我们还可以从头文件中确认 Wait Reason 和 WaitIrql
:\>grep WaitReason "c:\Program Files\Windows Kits\Include.0.16299.0\km\wdm.h"
MaximumWaitReason
_In_ _Strict_type_match_ KWAIT_REASON WaitReason,
_In_ _Strict_type_match_ KWAIT_REASON WaitReason,
:\>grep -n KWAIT_REASON "c:\Program Files\Windows Kits\Include.0.16299.0\km\wdm.h"
20139:typedef enum _KWAIT_REASON {
20181:} KWAIT_REASON;
20925: _In_ _Strict_type_match_ KWAIT_REASON WaitReason,
20941: _In_ _Strict_type_match_ KWAIT_REASON WaitReason,
:\>awk "NR==20139+0x1f" "c:\Program Files\Windows Kits\Include.0.16299.0\km\wdm.h"
WrQuantumEnd,
:\>grep -n define.*APC_LEVEL "c:\Program Files\Windows Kits\Include.0.16299.0\km\wdm.h"
175:#define APC_LEVEL 1 // APC interrupt level
既然我们已经破译了几乎所有的东西,我们现在可以研究函数
kd> uf .
nt!KiSwapContext:
8288bf00 83ec10 sub esp,10h
8288bf03 895c240c mov dword ptr [esp+0Ch],ebx
8288bf07 89742408 mov dword ptr [esp+8],esi
8288bf0b 897c2404 mov dword ptr [esp+4],edi
8288bf0f 892c24 mov dword ptr [esp],ebp
8288bf12 648b1d1c000000 mov ebx,dword ptr fs:[1Ch]
8288bf19 8bf9 mov edi,ecx
8288bf1b 8bf2 mov esi,edx
8288bf1d 0fb64f6a movzx ecx,byte ptr [edi+6Ah]
8288bf21 e87a010000 call nt!SwapContext (8288c0a0)
8288bf26 8b2c24 mov ebp,dword ptr [esp]
8288bf29 8b7c2404 mov edi,dword ptr [esp+4]
8288bf2d 8b742408 mov esi,dword ptr [esp+8]
8288bf31 8b5c240c mov ebx,dword ptr [esp+0Ch]
8288bf35 83c410 add esp,10h
8288bf38 c3 ret
所以该函数接受 fs:[1c],它是 self.pcr 新线程的 WaitIrql,并进入 nt!SwapContext () 进行实际交换
步到 nt!SwapContext,您将看到
kd>
nt!KiSwapContext+0x21:
8288bf21 e87a010000 call nt!SwapContext (8288c0a0)
kd> r
eax=00000000 ebx=82959c00 ecx=00000001 edx=84304030 esi=84304030 edi=84e50b40
eip=8288bf21 esp=8c691b3c ebp=8c691b88 iopl=0 nv up ei ng nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000286
nt!KiSwapContext+0x21:
8288bf21 e87a010000 call nt!SwapContext (8288c0a0)
这是一个开始
kd> r
eax=00000000 ebx=82959c00 ecx=00000001 edx=84304030 esi=84304030 edi=84e50b40
eip=8288c0a0 esp=8c691b38 ebp=8c691b88 iopl=0 nv up ei ng nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000286
nt!SwapContext:
8288c0a0 807e3900 cmp byte ptr [esi+39h],0 ds:0023:84304069=00
kd> ?? #FIELD_OFFSET( nt!_KTHREAD , Running)
long 0x39
kd> $$ checks if the current thread is running if it is running it stops it
with a pause if it is not running it sets the running member clears
interrupts updates the counters
kd> it is a big function check it out to see what registers are pushed , copied , moved to where
nt!SwapContext 调用这些函数 begin accumalation 调用保存浮点寄存器的条件 其他寄存器根据需要保存
nt!SwapContext (8288c0a0)
call to hal!HalRequestSoftwareInterrupt (82820258)
call to nt!KiBeginCounterAccumulation (8290d6a7)
call to nt!PsCheckThreadCpuQuota (829263f0)
call to nt!EtwTraceContextSwap (82847de8)
call to nt!KeBugCheckEx (8290940a)
提出或开始一个新话题,并链接该话题