九.Windows内核保护机制--TSS

一.什么是TSS:

  TSS全称Task State Segment,中文名任务状态段,储存在内存中,大小104个字节,结构如下:(每个CPU,就是每个核,都有一个TSS,但是每个线程都有自己的ESP0Ktrap_frame 结构体)

  

 二.TSS原本的作用:

  TSS在任务(线程)切换时起着重要的作用,通过它保存CPU中各寄存器的值,实现任务的挂起和恢复。

  比如说,当CPU执行A进程的时间片用完,要切换到B进程时,CPU会先把当前寄存器里的值保存到A进程的TSS里(任务寄存器TR指向当前进程的TSS),比如CS,EIP,ESP,标志寄存器等等,然后挂起A进程。执行B进程。这样,在CPU下次执行A进程的时候,就可以从其TSS中取出,CPU就知道上一次A进程执行到了什么位置,执行状态等等,这样就恢复了上次A进程的执行现场。(但是Windows和Linux并没有使用TSS进行任务切换的工作,是使用的堆栈来实现的任务(也就是线程)的切换.但是在执行调用门操作的时候,ESPSS的值就是通过TSS来自动进行切换的.)

三.CPU如何调用TSS:

  CPU中有一个TR段寄存器,存出一个段描述符,存在于GDT段描述符表中,属于系统段描述符的一种,base指向TSS表的地址,limit表示他的偏移范围.

四.我们对于TSS的调用:

  1.申请内存空间,自己写一个TSS表(104字节),并为表中的寄存器赋值.

  2.修改GDT表中的段描述符,让其Base指向我们自己的TSS表

  3.CALL FAR 或者 JMP FAR,跳到段描述符,使用我们的TSS表.(会先自动改写TR寄存器,不需要LTR在改了.)

    1)也可以通过LTR指令:修改TR寄存器的段选择子,让TR寄存器指向GDT表中我们改写的TR段描述符.(LTR是系统指令,只有在R0层,才能使用)

        mov ax,4B(GDT中,自己写的段描述符)

        ltr ax

  *加载后TSS段描述符状态会更改(就是上图的B位),TYPE的值由1001变为1011.

  *如果要读TR寄存器的值,也就是段选择子,可以用STR指令.

  上述操作完成,可以一次性改写一堆寄存器,在指向TSS表的时候,我们可以使用CALL FAR 和JMP FAR指令来进行,需要注意的是二者的区别,  CALL FAR后NT位为1,返回时,EIP会取Previous Task Link的值,JMP FAR后NT位为0,返回时,EIP从堆栈中取值.

五.Intel设计TSS的目的是为了任务切换(线程切换),但Windows与Linux , 并没有使用。而是采用堆栈来保存线程的各种寄存器。

  一个CPU只有一个TSS.但是线程很多,如何用一个TSS来保存所有线程的ESP0呢?

  下面来看看SwapContext代码分析:

SwapContext     proc near               ; CODE XREF: KiUnlockDispatcherDatabase(x)+72p
                                        ; KiSwapContext(x)+29p ...
                or      cl, cl
                mov es:[esi+_ETHREAD.Tcb.State],2 ; _KTHREAD(+0x2d state)改为2
                pushf                   ; 存储当前的EFLAGS寄存器
 
loc_40492C:                             ; CODE XREF: KiIdleLoop()+5Aj
                mov ecx,[ebx+KPCR+NtTib.ExceptionList] ; ebx存储KPCR的地址 此处是读出异常链表 3环的FS:[0]
                cmp [ebx+KPCR.PrcbData.DpcRoutineActive],0 ; 是否有DPC有就蓝屏
                push    ecx
                jnz     loc_404A70
                cmp     ds:_PPerfGlobalGroupMask, 0 ; LOG用的 Windows自己调试用的别的地方没用
                jnz     loc_404A47
 
loc_404949:                             ; CODE XREF: SwapContext+12Bj
                                        ; SwapContext+13Cj ...
                mov     ebp, cr0        ; CR0中的保护控制位
                mov     edx, ebp
                mov     cl, [esi+2Ch]
                mov     [ebx+50h], cl
                cli
                mov [edi+_ETHREAD.Tcb.KernelStack],esp ; 将当前的esp存储到原线程结构中
                mov eax,[_ETHREAD.Tcb.InitialStack] ; 目标线程栈底
                mov ecx,[esi+_ETHREAD.Tcb.StackLimt]
                sub     eax, 210h       ; -210h 浮点寄存器
                mov [ebx+KPCR.NtTib.StackLimit],ecx
                mov [ebx+KPCR.NtTib.StackBase],eax
                xor     ecx, ecx
                mov cl,[esi+_ETHREAD.Tcb.NpxState] ; NpxState 浮点寄存器 运行浮点用这个 没运行就不用
                and     edx, 0FFFFFFF1h ; 判断NpxState有没有浮点支持
                                        ; 如果上一个线程和要替换的线程对浮点支持是一样的那就不用换CP0 如果不一样
                or      ecx, edx
                or      ecx, [eax+20Ch]
                cmp     ebp, ecx
                jnz     loc_404A3F
                lea     ecx, [ecx]
 
loc_404983: 
                test    dword ptr [eax-1Ch], 20000h
                jnz     short loc_40498F ; 通过KPCR取出TSS
                sub     eax, 10h        ; 再减去10h(虚拟8086模式下使用 TrapFrame结构)
 
loc_40498F:
                mov ecx,[ebx+KPCR.TSS]  ; 通过KPCR取出TSS
                mov     [ecx+4], eax    ; 将修正后的栈底储存到TSS中
                mov esp,[_ETHREAD.Tcb.KernelStatck] ; 将目标线程的ESP存储到ESP中
                mov eax,[esi+_ETHREAD.Tcb.Teb] ; 当前线程有很多状态一份在ETHREAD里面
                                        ; 还有一个备份在FS中
                mov ecx,[ebx+KPCR.TSS]  ; 通过KPCR取TSS
                sti
                mov eax,[edi+_ETHREAD.Tcb.ApcState.Process]
                cmp eax,[esi+_ETHREAD.Tcb.ApcState.Process]
                mov [edi+_ETHREAD.Tcb.IdleSwapBlock],0
                jz      short loc_4049D7 ; 如果是一个进程内的线程切换 跳转
                mov edi,[esi+_ETHREAD.Tcb.ApcState.Process] ; 如果不是一个进程 取出目标线程_KPROCESS
                test [edi+_ETHREAD.pcb.LdtDescriptor.LimitLow],0FFFFh ; 判断LdtDescriptor是否为-1
                jnz     short loc_404A11
                xor     eax, eax
 
loc_4049B8: 
                lldt    ax
                xor     eax, eax
                mov     gs, eax         ; GS段寄存器清0 这个能解释清除 位什么在3环单步执行 GS会清0
                assume gs:GAP
                mov eax,[edi+_EPROCESS.Pcb.DirectoryTableBase] ; 得到目标线程CR3
                mov ebp,[ebx+KPCR.TSS]  ; TSS寄存器
                mov ecx,dword ptr[edi+_EPROCESS.Pcb.IopmOffset]
                mov     [ebp+1Ch], eax  ; 将当前TSS中的CR3修改为目标进程的CR3
                mov     cr3, eax        ; 切换CR3
                mov     [ebp+66h], cx   ; 存储IO权限位图到TSS 当前线程的IO权限位图 Windows2000以后不用了
                jmp     short loc_4049D7
; ---------------------------------------------------------------------------
                db 8Dh, 49h, 0
; ---------------------------------------------------------------------------
 
loc_4049D7: 
                mov     eax, [ebx+18h]
                mov     ecx, [ebx+3Ch]
                mov     [ecx+3Ah], ax
                shr     eax, 10h
                mov     [ecx+3Ch], al
                mov     [ecx+3Fh], ah
                inc     dword ptr [esi+4Ch]
                inc     dword ptr [ebx+61Ch]
                pop     ecx
                mov     [ebx], ecx
                cmp     byte ptr [esi+49h], 0
                jnz     short loc_404A00
                popf
                xor     eax, eax
                retn
原文地址:https://www.cnblogs.com/jszyx/p/12431773.html