逆向分析KiSwapContext和SwapContext函数并回答九个问题
一、
九个问题:
1、SwapContext 有几个参数,分别是什么?
2、SwapContext 在哪里实现了线程切换
3、线程切换的时候,会切换CR3吗?切换CR3的条件是什么?
4、中断门提权时,CPU会从TSS得到ESP0和SS0,TSS中存储的一定是当前线程的ESP0和SS0吗?如何做到的?
5、FS:[0]在3环指向TEB,但是线程有很多,FS:[0]指向的是哪个线程的TEB,如何做到的?
6、0环的 ExceptionList 在哪里备份的?
7、IdleThread是什么?什么时候执行?找到这个函数.
8、如何找到下一个就绪线程?
9、模拟线程切换与Windows线程切换有哪些区别?
二、
0x00、逆向KiSwapContext函数:
KiSwapContext: .text:00404828 ; __fastcall KiSwapContext(x) .text:00404828 @KiSwapContext@4 proc near .text:00404828 .text:00404828 var_10 = dword ptr -10h .text:00404828 var_C = dword ptr -0Ch .text:00404828 var_8 = dword ptr -8 .text:00404828 var_4 = dword ptr -4 .text:00404828 .text:00404828 sub esp, 10h .text:0040482B mov [esp+10h+var_4], ebx .text:0040482F mov [esp+10h+var_8], esi .text:00404833 mov [esp+10h+var_C], edi .text:00404837 mov [esp+10h+var_10], ebp //这四个参数相当于压栈,保存现场了// .text:0040483A mov ebx, ds:0FFDFF01Ch //0x1c指向KPCR地址 .text:00404840 mov esi, ecx //ecx存放SwapThread函数传递出去的eax,即下一个可用线程// .text:00404842 mov edi, [ebx+124h] //KPCRB的CurrentThread成员// .text:00404848 mov [ebx+124h], esi //一方面保存当前线程结构体地址,另一方面还要替换下一个线程结构体// .text:0040484E mov cl, [edi+58h] //CurrentThread.WaitIrql// .text:00404851 call SwapContext //进入SwapContext函数// .text:00404856 mov ebp, [esp+10h+var_10] .text:00404859 mov edi, [esp+10h+var_C] .text:0040485D mov esi, [esp+10h+var_8] .text:00404861 mov ebx, [esp+10h+var_4] .text:00404865 add esp, 10h .text:00404868 retn .text:00404868 @KiSwapContext@4 endp .text:00404868
0x01、逆向SwapContext:
SwapContext: .text:00404924 SwapContext proc near .text:00404924 //传了esi,ebx,edi三个参数 .text:00404924 or cl, cl //_usercall 是编译器开过完全优化以后,会以任意寄存器作为参数传 递。 .text:00404926 mov byte ptr es:[esi+2Dh], 2 //NextThread.state = 2//修改新线程状态为 2// .text:0040492B pushf //保存标志寄存器// .text:0040492C .text:0040492C loc_40492C: .text:0040492C mov ecx, [ebx] //mov ecx,KPCR.ExceptionList//// .text:0040492E cmp dword ptr [ebx+994h], 0 //cmp DpcRoutineActive// .text:00404935 push ecx //保存KPCR的异常链表//第六题的答案 .text:00404936 jnz loc_404A70 //跳转执行了 _KeBugCheck 函数// //百度百科:NTOSKNRL导出的函数,产生蓝屏。在内部,KebugCheck只是简单的调用KebugCheckEx。 .text:0040493C cmp ds:_PPerfGlobalGroupMask, 0 //.. .text:00404943 jnz loc_404A47 .text:00404949 .text:00404949 loc_404949: .text:00404949 .text:00404949 mov ebp, cr0 .text:0040494C mov edx, ebp .text:0040494E mov cl, [esi+2Ch] //NextThread.DebugActive// .text:00404951 mov [ebx+50h], cl //KPCR.DebugActive = NextThread.DebugActive///改变调试状态 .text:00404954 cli //置IF位为0,屏蔽中断// .text:00404955 mov [edi+28h], esp //CurrentThread.KernelStack = esp//线程切换1 .text:00404958 mov eax, [esi+18h] //mov eax,NextThread.InitialStack//栈底 .text:0040495B mov ecx, [esi+1Ch] //NextThread.StackLimit// .text:0040495E sub eax, 210h //海哥在某节课说过,0x210是一堆浮点寄存器的大小(忘了哪节课了)// .text:00404963 mov [ebx+8], ecx //mov KPCR.StackLimit,NextThread.StackLimit// .text:00404966 mov [ebx+4], eax //StackBase// .text:00404969 xor ecx, ecx .text:0040496B mov cl, [esi+31h] //mov cl,NextThread.NpxState//NpxState域反映了浮点处理器的状态 //参考链接https://www.cnblogs.com/LittleHann/p/3456697.html// .text:0040496E and edx, 0FFFFFFF1h .text:00404971 or ecx, edx .text:00404973 or ecx, [eax+20Ch] .text:00404979 cmp ebp, ecx //比较是否运行了浮点寄存器// .text:0040497B jnz loc_404A3F loc_404A3F: //这段是我额外抠出来的,如果没有运行浮点寄存器的话,跳到这里,改变CR0的值// mov cr0, ecx jmp loc_404983 .text:00404981 lea ecx, [ecx] .text:00404983 .text:00404983 loc_404983: .text:00404983 test dword ptr [eax-1Ch], 20000h //test _KTrap_Frame.SegCs,20000h//判断是否是虚拟8086模式 //浮点寄存器上面就是trap_frame结构体 .text:0040498A jnz short loc_40498F .text:0040498C sub eax, 10h //eax = Trap_Frame.HardwareSegSs// 这里参考//https://blog.csdn.net/qq_18059143/article/details/103396737 .text:0040498F .text:0040498F loc_40498F: .text:0040498F mov ecx, [ebx+40h] //mov ecx,KPCR.TSS//取TSS地址 .text:00404992 mov [ecx+4], eax //mov esp0,eax//给esp0赋值//更新TSS.esp0//
//第四题的答案.text:00404995 mov esp, [esi+28h] //mov esp,NextThread.KernelStack//线程切换2 .text:00404998 mov eax, [esi+20h] //NextThread.Teb// .text:0040499B mov [ebx+18h], eax //mov KPCR._NT_TIB.Self,NextThread.teb// .text:0040499E sti //恢复中断// .text:0040499F mov eax, [edi+44h] //CurrentThread.Process// .text:004049A2 cmp eax, [esi+44h] //NextThread.Process// .text:004049A5 mov byte ptr [edi+50h], 0 //CurrentThread.IdleSwapBlock = 0// .text:004049A9 jz short loc_4049D7 //如果是一个进程内的线程切换 跳转 .text:004049AB mov edi, [esi+44h] //EPROCESS(NextThread) .text:004049AE test word ptr [edi+20h], 0FFFFh //test _KPROCESS.LdtDescriptor.LimitLow , 0FFFFh//判断 LDT .text:004049B4 jnz short loc_404A11 // .text:004049B6 xor eax, eax .text:004049B8 .text:004049B8 loc_4049B8: .text:004049B8 lldt ax //加载局部描述符ldt// .text:004049BB xor eax, eax .text:004049BD mov gs, eax //GS段寄存器清0// .text:004049BF assume gs:GAP .text:004049BF mov eax, [edi+18h] //mov eax,EPROCESS(NextThread).Pcb.DirectoryTableBase//CR3 .text:004049C2 mov ebp, [ebx+40h] //mov ebp,KPCR.TSS// .text:004049C5 mov ecx, [edi+30h] //mov ecx,EPROCESS.Pcb.IopmOffset// .text:004049C8 mov [ebp+1Ch], eax //给Tss的Cr3赋值//mov TSS.cr3,EPROCESS(NextThread).Pcb.DirectoryTableBase// .text:004049CB mov cr3, eax //切换CR3 //mov cr3,EPROCESS(NextThread).Pcb.DirectoryTableBase//
第三个问题的答案就在这里.text:004049CE mov [ebp+66h], cx //mov TSS.I/O Map Base Address,EPROCESS.Pcb.IopmOffset// //存储IO权限位图到TSS 当前线程的IO权限位图 Windows2000以后不用了 .text:004049D2 jmp short loc_4049D7 .text:004049D2 ; --------------------------------------------------------------------------- .text:004049D4 db 8Dh, 49h, 0 .text:004049D7 ; --------------------------------------------------------------------------- .text:004049D7 .text:004049D7 loc_4049D7: .text:004049D7 .text:004049D7 mov eax, [ebx+18h] //mov eax,KPCR._NT_TIB.Self(里面存放的是teb)// .text:004049DA mov ecx, [ebx+3Ch] //mov ecx,KPCR.GDT// .text:004049DD mov [ecx+3Ah], ax // .text:004049E1 shr eax, 10h // .text:004049E4 mov [ecx+3Ch], al // .text:004049E7 mov [ecx+3Fh], ah //这是在用Teb的地址构造段描述符// //三环fs就是0x3B,gdt表的第七个成员,正好是刚修改的Teb段描述符
第5个问题的答案
.text:004049EA inc dword ptr [esi+4Ch] //NextThread.ContextSwitches += 1// .text:004049ED inc dword ptr [ebx+61Ch] //KPCR.KPRCB.KeContextSwitches +=1// .text:004049F3 pop ecx //取出新线程的异常链表地址// .text:004049F4 mov [ebx], ecx //传给了KPCR,恢复了过去// .text:004049F6 cmp byte ptr [esi+49h], 0 //NextThread._KAPC_STATE.KernelApcPending// .text:004049FA jnz short loc_404A00 .text:004049FC popf .text:004049FD xor eax, eax .text:004049FF retn .text:00404A00 ; --------------------------------------------------------------------------- .text:00404A00 .text:00404A00 loc_404A00: .text:00404A00 popf .text:00404A01 jnz short loc_404A06 .text:00404A03 mov al, 1 .text:00404A05 retn .text:00404A06 ; --------------------------------------------------------------------------- .text:00404A06 .text:00404A06 loc_404A06: .text:00404A06 mov cl, 1 .text:00404A08 call ds:__imp_@HalRequestSoftwareInterrupt@4 ; HalRequestSoftwareInterrupt(x) //软件中断.// .text:00404A0E xor eax, eax .text:00404A10 retn .text:00404A11 ; ---------------------------------------------------------------------------
这两个函数大体上做了一下的事情
1、修改新线程状态
2、保存KPCR的异常链表
3、KPCR的DebugActive被换成NextThread的DebugActive
屏蔽中断 cli
4、当前线程的栈顶改成esp
5、KPCR的StackLimit变为NextThread的StackLimit
6、KPCR的InitialStack栈底变成NextThread的InitialStack成员减去0x210后的结果
7、判断是否运行浮点寄存器
8、判断是否是虚拟8086模式
9、更新TSS的esp0
10、把下一个线程的KernelStack给esp
11、把下一个线程的Teb给KPCR的self
恢复时钟中断 sti
12、判断是否是一个进程的线程切换
如果不是的话:
13、判断LDT是否为-1 (下面我假设是)
14、加载ldt表,gs清0
15、把下一个线程的进程的CR3赋值给TSS的CR3,同时切换cr3
16、存储IO权限位图到TSS Windows2000以后不用了
如果不是结束
17、用KPCR的teb值 的地址构造段描述符在GDT表的0x38位置,正好是3环fs段描述符的位置
18、取出下一个线程的异常链表,传给了KPCR
三、后来发现逆向这两个函数是不够解答这九个问题的,于是又逆向了KiFindReadyThread函数与部分KiSwapThread函数:
KiSwapThread:(这个函数只逆向了一部分):
KiSwapThread: .text:004050BF .text:004050BF ; _DWORD __cdecl KiSwapThread() .text:004050BF @KiSwapThread@0 proc near ; CODE XREF: KeDelayExecutionThread(x,x,x):loc_405017↑p .text:004050BF ; KeWaitForSingleObject(x,x,x,x,x):loc_40513E↓p ... .text:004050BF .text:004050BF ; FUNCTION CHUNK AT .text:0040EA85 SIZE 00000015 BYTES .text:004050BF ; FUNCTION CHUNK AT .text:004109AF SIZE 00000009 BYTES .text:004050BF .text:004050BF mov edi, edi .text:004050C1 push esi .text:004050C2 push edi .text:004050C3 db 3Eh ; .text:004050C3 mov eax, ds:0FFDFF020h //eax = kprcb .text:004050C9 mov esi, eax //esi = kprcb .text:004050CB mov eax, [esi+8] //eax = _KPRCB.NextThread .text:004050CE test eax, eax // .text:004050D0 mov edi, [esi+4] //edi =_KPRCB.CurrentThread// .text:004050D3 jnz loc_4109AF //如果_KPRCB.NextThread中有值,把该值置0后直接去交换线程// //如果无值,则寻找下一个就绪的线程// .text:004050D9 push ebx // .text:004050DA movsx ebx, byte ptr [esi+10h] //KPRCB.Number// .text:004050DE xor edx, edx .text:004050E0 mov ecx, ebx //也不知道为啥给这ecx赋值,后面明明没用到...// .text:004050E2 call @KiFindReadyThread@8 ; KiFindReadyThread(x,x) .text:004050E7 test eax, eax ; 找到下一个就绪线程 .text:004050E9 jz loc_40EA85 .text:004050EF .text:004050EF loc_4050EF: .text:004050EF pop ebx .text:004050F0 .text:004050F0 loc_4050F0: .text:004050F0 mov ecx, eax .text:004050F2 call @KiSwapContext@4 ; KiSwapContext(x) .text:004050F7 test al, al .text:004050F9 mov cl, [edi+58h] ; NewIrql .text:004050FC mov edi, [edi+54h] .text:004050FF mov esi, ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x) .text:00405105 jnz loc_415ADB .text:0040510B .text:0040510B loc_40510B: .text:0040510B call esi ; KfLowerIrql(x) ; KfLowerIrql(x) .text:0040510D mov eax, edi .text:0040510F pop edi .text:00405110 pop esi .text:00405111 retn .text:00405111 @KiSwapThread@0 endp .text:00405111 .text:0040EA85 loc_40EA85: ; CODE XREF: KiSwapThread()+2A↑j //若无可调度的线程,使用空闲线程// .text:0040EA85 mov eax, [esi+0Ch] .text:0040EA88 xor edx, edx .text:0040EA8A inc edx .text:0040EA8B mov ecx, ebx .text:0040EA8D shl edx, cl .text:0040EA8F or ds:_KiIdleSummary, edx .text:0040EA95 jmp loc_4050EF .text:0040EA95 ; END OF FUNCTION CHUNK FOR @KiSwapThread@
KiFindReadyThread:
KiFindReadyThread: //寻找可调度的线程// .text:00405052 xor eax, eax .text:00405054 inc eax .text:00405055 mov ecx, edx .text:00405057 shl eax, cl .text:00405059 push 10h .text:0040505B pop ecx //ecx = 0x10 .text:0040505C dec eax //0x0 .text:0040505D not eax //0xffffffff// .text:0040505F and eax, ds:_KiReadySummary //_KiReadySummary全局变量,DWORD高效查找//32位对应32个链表圈 .text:00405065 mov edx, eax // .text:00405067 shr edx, 10h // .text:0040506A jnz short loc_405070 //右移16位后,cf存放的是最后一位的值//若此时edx中结果不为0则跳//研究左16位 .text:0040506C xor ecx, ecx //ecx = 0// .text:0040506E mov edx, eax //edx = 0xffffffff// .text:00405070 .text:00405070 loc_405070: .text:00405070 test edx, 0FFFFFF00h //判断edx的第8到15位//(edx之前已经右移过了) .text:00405076 jz short loc_40507B //如果8到15位无值//跳//注意test后结果如果是0,zf置一!!!跳转// .text:00405078 add ecx, 8 //反之,ecx = 8// .text:0040507B .text:0040507B loc_40507B: .text:0040507B mov edx, eax // .text:0040507D shr edx, cl //右移16位// .text:0040507F push esi .text:00405080 push 1Fh .text:00405082 movsx edx, ds:_KiFindFirstSetLeft[edx] //_KiFindFirstSetLeft这个全局变量不知道啥意思, 但结合上下文上看应该是从左边数寻找第一个下表为1的位 .text:00405089 add edx, ecx //加上0x10,相当于绕过了低全局调度表的低16个成员//得到最后的edx //就是把32位巧妙分成四段来找可调度线程// .text:0040508B pop ecx //ecx = 0x1f// .text:0040508C sub ecx, edx // .text:0040508E shl eax, cl //就是在判断KiReadySummary全局变量是否有一位是1// .text:00405090 lea esi, _KiDispatcherReadyListHead[edx*8] //_KiDispatcherReadyListHead,全局调度链表,edx对应优先级,寻找就绪链表// .text:00405097 test eax, eax .text:00405099 jz short loc_40503F //若无可调度线程,跳转后eax置0返回出去// .text:0040509B .text:0040509B loc_40509B: ; CODE XREF: KiFindReadyThread(x,x)-7↑j .text:0040509B test eax, eax //有可调度线程// .text:0040509D jge short loc_405043 // .text:0040509F mov eax, [esi] // .text:004050A1 mov ecx, [eax] .text:004050A3 sub eax, 60h .text:004050A6 push edi .text:004050A7 mov edi, [eax+64h] .text:004050AA mov [edi], ecx .text:004050AC mov [ecx+4], edi .text:004050AF cmp [esi], esi .text:004050B1 pop edi .text:004050B2 jz loc_401EEC .text:004050B8 .text:004050B8 loc_4050B8: ; CODE XREF: KiFindReadyThread(x,x)-11↑j .text:004050B8 pop esi .text:004050B9 retn .text:004050B9 @KiFindReadyThread@8 endp .text:004050B9 .text:004050B9 ; --------------------------------------------------------------------------- //从右到左线程优先级越来越低
四、
1、SwapContext 有几个参数,分别是什么?
3个参数,esi:下一个可用线程结构体;ebx:KPCR地址;edi:当前线程结构体;
2、SwapContext 在哪里实现了线程切换
以下两句
.text:00404955 mov [edi+28h], esp //CurrentThread.KernelStack = esp//
.text:00404995 mov esp, [esi+28h] //mov esp,NextThread.KernelStack//
线程的切换本质就是堆栈的切换!!!
3、线程切换的时候,会切换CR3吗?切换CR3的条件是什么?
会,不是同一个进程的线程切换
4、中断门提权时,CPU会从TSS得到ESP0和SS0,TSS中存储的一定是当前线程的ESP0和SS0吗?如何做到的?
是的,通过下一个线程的InitialStack...(还要减去浮点寄存器的大小以及0x10)
SS不变
5、FS:[0]在3环指向TEB,但是线程有很多,FS:[0]指向的是哪个线程的TEB,如何做到的?
当前线程下面在GDT的第七个段描述符的位置修改了含有Teb地址的段描述符,而fs在三环指向0x3B
6、0环的 ExceptionList 在哪里备份的?
在切换线程之前,被KPCR存放到了堆栈中
7、IdleThread 是什么?什么时候执行?找到这个函数.
空闲线程,当调度链表里没有可以调度的线程的时候,就会跑这个线程
8、如何找到下一个就绪线程?
通过_KiReadySummary全局变量,按照线程优先级在32个链表圈中找...
9、模拟线程切换与Windows线程切换有哪些区别?
太多了:
异常链表的处理
判断模式以及浮点寄存器的状态
构造teb段描述符,考虑三环0环堆栈
考虑不同进程
考虑线程优先级等等等等
五、
参考链接:https://www.cnblogs.com/LittleHann/p/3456697.html
https://blog.csdn.net/qq_18059143/article/details/103396737
https://blog.csdn.net/Kwansy/article/details/109702433
__EOF__

本文链接:https://www.cnblogs.com/lordtianqiyi/articles/15674141.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现