任务段提权实验

TSS结构

TSS段描述符

实验流程

  1. 构建TSS段描述符,写入到GDT表的空白区
c 复制代码
0000e912`9e6c20ac
base = 0x00129e6c

eq 80b99048 0000e912`9e6c20ac 
  1. 把cr3填入我们的程序
c 复制代码
!process 0 0

3 . 最终成功断在了我们的test函数里:

完整代码:

c 复制代码
#include <iostream>
#include <Windows.h>
using namespace std;
struct _KiIoAccessMap
{
    UCHAR DirectionMap[32];                                                 //0x0
    UCHAR IoMap[8196];                                                      //0x20
};

typedef struct _KTSS
{
    USHORT Backlink;                                                        //0x0
    USHORT Reserved0;                                                       //0x2
    ULONG Esp0;                                                             //0x4
    USHORT Ss0;                                                             //0x8
    USHORT Reserved1;                                                       //0xa
    ULONG NotUsed1[4];                                                      //0xc
    ULONG CR3;                                                              //0x1c
    ULONG Eip;                                                              //0x20
    ULONG EFlags;                                                           //0x24
    ULONG Eax;                                                              //0x28
    ULONG Ecx;                                                              //0x2c
    ULONG Edx;                                                              //0x30
    ULONG Ebx;                                                              //0x34
    ULONG Esp;                                                              //0x38
    ULONG Ebp;                                                              //0x3c
    ULONG Esi;                                                              //0x40
    ULONG Edi;                                                              //0x44
    USHORT Es;                                                              //0x48
    USHORT Reserved2;                                                       //0x4a
    USHORT Cs;                                                              //0x4c
    USHORT Reserved3;                                                       //0x4e
    USHORT Ss;                                                              //0x50
    USHORT Reserved4;                                                       //0x52
    USHORT Ds;                                                              //0x54
    USHORT Reserved5;                                                       //0x56
    USHORT Fs;                                                              //0x58
    USHORT Reserved6;                                                       //0x5a
    USHORT Gs;                                                              //0x5c
    USHORT Reserved7;                                                       //0x5e
    USHORT LDT;                                                             //0x60
    USHORT Reserved8;                                                       //0x62
    USHORT Flags;                                                           //0x64
    USHORT IoMapBase;                                                       //0x66
    struct _KiIoAccessMap IoMaps[1];                                        //0x68
    UCHAR IntDirectionMap[32];                                              //0x208c
}KTSS;

void __declspec(naked) test()
{


    __asm
    {
        int 3;

        
        //win7 32位 29912分页模式有个bug:
        // 上个任务段保存的cr0=0,导致返回3环出错
        // 此段代码就是为了修复cr3
        sub esp, 8; 借栈 8 字节给 sgdt 用
        // ------------------------------
        // Step 1: 取 TR 和 GDT Base
        // ------------------------------
        str bx; BX = 当前 TSS 段选择子
        sgdt fword ptr[esp]; [esp + 0..1] = limit, [esp + 2..5] = GDT base
        mov edx, [esp + 2]; EDX = GDT Base

        // ------------------------------
        // Step 2: 通过 TR 找到"当前 TSS 描述符"
        // selector->index->描述符地址
        // ------------------------------
        movzx ecx, bx; ECX = selector
        and ecx, 0FFF8h; 清 RPL / TI
        shr   ecx, 3; index = selector >> 3
        lea   esi, [edx + ecx * 8]; ESI = 当前 TSS 段描述符地址(每个 8 字节)

        // ------------------------------
        // Step 3: 从描述符拼当前 TSS 基址
        // 描述符布局:
        //   +2 WORD Base 0..15
        //   +4 BYTE Base 16..23
        //   +7 BYTE Base 24..31
        // ------------------------------
        movzx eax, word ptr[esi + 2]; EAX = BaseLow
        movzx ecx, byte ptr[esi + 4]; ECX = BaseMid
        shl   ecx, 16
        or eax, ecx
        movzx ecx, byte ptr[esi + 7]; ECX = BaseHigh
        shl   ecx, 24
        or eax, ecx; EAX = 当前 TSS 线性基址

        mov   edi, eax; EDI = CurrentTssBase

        // ------------------------------
        // Step 4: 从当前 TSS 里取 Backlink(上一个 TSS 的 selector)
        // TSS.Backlink 偏移 = 0x00
        // ------------------------------
        movzx cx, word ptr[edi + 0]; CX = Backlink selector

        // 如果 Backlink 为 0 或 index 为 0,认为不存在上一个任务,直接退出
        test  cx, 0FFF8h
        jz    NoPrevTask

        // ------------------------------
        // Step 5: 备份当前 TSS 中的 CR3
        // TSS.CR3 偏移 = 0x1C
        // ------------------------------
        mov   eax, [edi + 1Ch]; EAX = CurrentTss.CR3 (备份)

        // ------------------------------
        // Step 6: 通过 Backlink selector 找到"上一个 TSS 描述符"
        // ------------------------------
        movzx ebx, cx; EBX = prev selector
        and ebx, 0FFF8h
        shr   ebx, 3; index = selector >> 3
        lea   esi, [edx + ebx * 8]; ESI = 上一个 TSS 的段描述符

        // ------------------------------
        // Step 7: 拼出"上一个 TSS"基址
        // ------------------------------
        movzx edx, word ptr[esi + 2]; EDX = BaseLow
        movzx ecx, byte ptr[esi + 4]; ECX = BaseMid
        shl   ecx, 16
        or edx, ecx
        movzx ecx, byte ptr[esi + 7]; ECX = BaseHigh
        shl   ecx, 24
        or edx, ecx; EDX = PrevTssBase

        // ------------------------------
        // Step 8: 把当前 TSS 的 CR3 写入上一个 TSS
        // PrevTss.CR3 偏移同样是 0x1C
        // ------------------------------
        mov[edx + 1Ch], eax; PrevTss.CR3 = CurrentTss.CR3

        NoPrevTask :
        add esp, 8; 还原栈
         
        
    }

    __asm
    {
        pushfd;
        pop eax;
        or eax, 0x4000;   //int 3会把efl的NT位置0,所有这里需要手动把Eflags的NT位置1
        push eax;
        popfd;
        iret

    }

}



int main(void)
{
    //申请栈空间
    char bufEsp0[0x2000] = { 0 };
    char bufEsp3[0x2000] = { 0 };

    memset(bufEsp0, 0, 0x2000);
    memset(bufEsp3, 0, 0x2000); //测试发现不这样操作,进0环后物理页没映射

    KTSS tss = { 0 };

    tss.Esp0 = (ULONG)bufEsp0 + 0x2000;
    tss.Esp = (ULONG)bufEsp3 + 0x2000;
    tss.Ss0 = 0x10;
    tss.Ss = 0x10;
    tss.Cs = 0x8;
    tss.Ds = 0x23;
    tss.Es = 0x23;
    tss.Fs = 0x30;
    tss.EFlags = 2;
    tss.Eip = (ULONG)test;
    tss.IoMapBase = 0x20ac;

    cout << "test:" << test << endl;
    cout << "TSS:" << &tss << endl;
    cout << "请输入CR3:";
    cin >> hex >> tss.CR3;

    char bufcode[] = { 0,0,0,0,0x48,0 };   //执行构建的任务段,执行完需要手动修复cr3
    __asm
    {
        call fword ptr bufcode;
    }
    system("pause");


    system("pause");

    return 0;
}

实验中遇到的一些问题

一、call 任务段选择子 会发生什么?

  1. 权限检查等操作
  2. CPU自动保存以下寄存器到当前 TSS:
c 复制代码
	•	EIP
	•	EFLAGS
	•	ESP + SS
	•	CS
	•	DS/ES/FS/GS
	•	CR3(地址空间基础)
	•	LDT selector
	•	I/O位图位置等
  1. CPU将目标TSS设置为 Busy 状态
  2. 加载目标TSS内容到寄存器(执行上下文切换)
c 复制代码
CR3 → 切换页目录(
EIP → 新任务入口地址
ESP/SS → 新任务栈
EFLAGS
CS/DS/ES/FS/GS
  1. 设置 NT =1
  2. 执行 TSS 指向的任务代码,从恢复的 EIP 继续执行

二、任务段是如何返回的?

任务段返回使用 IRET,CPU 会检测当前任务 TSS 中的 NT标志位,如果 NT = 1 且 IRET 被执行,则:

CPU 不是简单地弹栈,而是执行:

  1. 从当前 TSS 中读取 BackLink 字段,找到上一个任务的 TSS
  2. 自动切换任务到 BackLink 指向的 TSS,恢复任务寄存器、段寄存器、CR3、EFLAGS 等
  3. 清理 Busy 标志并恢复先前任务状态,返回到前一个任务

三、为什么要在test函数里把NT位置1?

int 3中断会发生:

c 复制代码
IF = 0
TF = 0
NT = 0

如果NT位=0,那么任务段在iret时,会执行普通中断返回(从栈恢复 CS,EIP,EFLAGS,SS,ESP),导致程序返回失败蓝屏!

相关推荐
阿富软件园1 小时前
文档搜索利器——“搜索文本”全能版 支持多格式 加 内容搜索
windows·电脑·开源软件
2301_793069822 小时前
Linux Ubuntu/Windows 双系统 分区挂载指南
linux·windows·ubuntu
道路与代码之旅2 小时前
Windows 10 中以 WSL 驱 Ubuntu 记
linux·windows·ubuntu
星空椰2 小时前
Windows 使用nvm多版本管理node.js
windows·node.js
人工智能训练4 小时前
openEuler系统中home文件夹下huawei、HwHiAiUser、lost+found 文件夹的区别和作用
linux·运维·服务器·人工智能·windows·华为·openeuler
先生沉默先4 小时前
windows顺序程序启动器
windows
Adellle4 小时前
windows安装ES(8.14.x版本)
大数据·windows·elasticsearch
UNbuff_016 小时前
重复文件查找工具(AllDup)
windows·开源软件
Irene199117 小时前
VSCode 内置终端 和 系统自带终端 的主要区别
windows·vscode·终端