Windows内核中的CPU架构-8-任务段TSS(task state segment)

任务段tss(task state segment)是针对于CPU的一个概念。

举一个简单的例子,你一个电脑,肯定是同时会运行多个程序把,比如说QQ,微信,LOL。哪我们知道每个进程的内容是不一样的,那么,这个时候如果说只有一块CPU,这个CPU肯定不能只执行一个进程吧,不然这么多个程序就得卡死了,等LOL打完才能进行微信视频聊天,这样对很多和女朋友视频的男生非常不友好吧。就CPU肯定是会切换的,这里的task是一个比较广的概念,不仅仅局限于什么进程线程这样子,可以抽象为当前做的事情。

就比如说一个人是CPU,他穿了一套打篮球的装备在打篮球,那么突然要去踢足球,是不是得换一套踢足球的装备再去踢足球啊。

这里的TSS就可以类比为人身上穿的东西,然后人做的事就可以类比为任务。就你切换任务的时候,根据任务不同你还得穿不同的TSS。

再通过OS和CPU的内容来描述,就是TSS是任务的状态,每个任务有每个状体。当切换成task时,tss就会赋值为别的tss。

所以我们就可以利用这个机制,把我们的代码作为一种切换,让CPU切换的我们的代码,并且TSS采用我们写的TSS进行赋值,这样来作为一种提权,修改RPL和CPL。

TSS结构体:

这个结构体一共有104个字节。

字段 内容
灰色字段 保留字段,用0填充。
Previous Task Link 上一个TSS的段选择子。
ESP0 SS0到 ESP2 SS2 ESP0 SS0到ESP2 SS2,这个就是CPU内部的设置了,因为intel有3中特权级,0 1 2 3,所以这个esp0 ss0就是对应的0环的栈空间。然后根据特权级,来使用不同特权级的栈空间到ss和esp寄存器来使用。
GS-EIP 普通的段寄存器和寄存器。
其它字段 其它字段暂时用不到,感兴趣的可以去看intel手册,这里只是为了一段提权代码。

如何查找tss

tss就是一个结构体,肯定是保存在这个内存里面的,那么我们如何拿到它呢。

这里需要引入一个寄存器,叫tr寄存器,它是一个段寄存器,和CS,ES等等一样,存放的也是段选择子的内容。然后可以拿到段描述符的内容。

这里我们再从头到尾解析一下:

首先输出tr寄存器:

kd> r tr
tr=00000028 

得到的段选择子为0x28。然后解析它:

   0x28  = 0000 0000 0010 1 0   00

   0x28  = 0000 0000 00101(GDT的第5个)   0(GDT)   00(RPL)

然后通过段选择子结构体,找到对应的段描述符:

kd> dq gdtr
80b93000 00000000`00000000 00cf9b00`0000ffff
80b93010 00cf9300`0000ffff 00cffa00`0000ffff
80b93020 00cff300`0000ffff 80008b1e`400020ab
80b93030 834093f6`dd003748 0040f200`00000fff
80b93040 0000f200`0400ffff 00000000`00000000
80b93050 830089f6`b0000068 830089f6`b0680068
80b93060 00000000`00000000 00000000`00000000
80b93070 800092b9`300003ff 00000000`00000000

tr段描述符对应的就是gdt表中的第五个值的内容:

80008b1e`400020ab

然后通过tss的段描述符结构进行解析:

TSS的地址就是几个base address加起来的结果。

这里通过前面的段描述符来找内容:

80 00 8b 1e `40 00 20 ab
地址为:801e4000

然后查看一下当前tss的内容:

手动添加一个任务切换

自己构造一个tr,然后把tr寄存器赋值为我们自己的tr,再进行任务切换,这样就会把tr结构体里的值赋值给切换后的环境的值,从而达到一个提权的作用。

那么首先我们得构造一个tss结构体:

typedef struct _KTSS
{
   USHORT link;                                                       //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
}TSS;

然后开始构造这个段描述符:

构建段描述符

由于tss是我们自己构建的,所以首先得获取我们的tss首地址

获取tss首地址

首先要注意是这个tss的首地址的问题,我采用的方式是在vs里面创建一个tss,并把地址写死,然后把这个程序里的tss的地址作为这个段描述符中的地址来处理。修改了代码这个值还是会改变。

需要进行两个设置:

首先是随机基址改为否:

然后是这个启用增量链接改为否:

//获取构建的tss地址
#include<iostream>
#include<Windows.h>
using namespace std;
typedef struct _KTSS
{
   USHORT link;                                                       //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
}TSS;
TSS tss{ 0 };


int main()
{
   printf("tss的地址为: %x\n", &tss);

system("pause");
return 0;
}

然后我这里的结果是00403378:

构建tss段描述符:

高32位:
Base 31:24 00

G:0(0表示tss大小以字节为单位,1表示以页为单位)

AVL:这里暂时没找到解释,采用0就好

Limit 19:16 采用0,因为104个字节用不到这么高位

p: 1表示可用,0为不可用

DPL:特权级,这里采用3

Type中的B表示这个段是否在被使用中,这里用0 表示空闲

Base23:16: 40



低32位:
前16位地址:3378

Segment Limit(15-0):0068


总和下来就是

0000e940 33780068

安装tss段描述符

方式tss段描述符需要根据段选择子来确定,因为需要确定放置在gdt表的位置。这里段描述符我采用之前用的 0x48来处理。

这里的0x48就表明这个段描述符要在gdt表里的第10个,这个不会的建议回头看看前面的知识。

对结构体进行赋值

我们在切换任务的时候,会把tss的值赋值给当前的环境,所以我们的tss肯定是要赋值的。

//申请0环栈空间
BYTE esp0[0x2000];
//申请普通栈空间
BYTE esp[0x2000];

   memset(esp0, 0, 0x2000);//清空0环栈空间
   memset(esp, 0, 0x2000);//情况普通栈空间
   tss.eip = (ULONG)test;//执行地址
   tss.cs = 0x08;  //赋予0环的RPL,别的不用管
   tss.ss = 0x10;  //赋予0环的RPL,别的不用管
   tss.ds = 0x23;  //数据段,不用管
   tss.es = 0x23;  //这个也不用管
   tss.fs = 0x30;//0环是30,三环是3B
   tss.esp0 = (ULONG)esp0+2000;   //赋值esp给前面开辟的空间
   tss.esp = (ULONG)esp + 2000;//赋值给前面开辟的esp空间

这里还需要一个关键值CR3,别的值可以直接忽略掉了。但是这个CR3比较麻烦,就直接先说一下怎么找吧,这里采用一个直接输入的方式来添加cr3:

(完整代码)

#include<iostream>
#include<Windows.h>
using namespace std;
typedef struct _KTSS
{
   USHORT link;                                                       //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
}TSS;
TSS tss{ 0 };
//申请0环栈空间
BYTE esp0[0x2000];
//申请普通栈空间
BYTE esp[0x2000];

void _declspec(naked)test()
{

   _asm
{
int 3
iretd
}

}

int main()
{
BYTE code[6] = {0,0,0,0,0x48,0 };
   printf("tss的地址为: %x\n", &tss);
   system("pause");
   memset(esp0, 0, 0x2000);//清空0环栈空间
   memset(esp, 0, 0x2000);//情况普通栈空间
   tss.eip = (ULONG)test;//执行地址
   tss.cs = 0x08;  //赋予0环的RPL,别的不用管
   tss.ss = 0x10;  //赋予0环的RPL,别的不用管
   tss.ds = 0x23;  //数据段,不用管
   tss.es = 0x23;  //这个也不用管
   tss.fs = 0x30;//0环是30,三环是3B
   tss.ss0 = 0x10;//赋予0环的RPL,别的不用管
   tss.esp0 = (ULONG)esp0+0x1600;   //赋值esp给前面开辟的空间
   tss.esp = (ULONG)esp + 0x1600;//赋值给前面开辟的esp空间
   DWORD cr3 = 0;
   printf("please input cr3\n");
   scanf_s("%x", &cr3);
   _asm
{
call far fword ptr code
}
system("pause");
return 0;
}

这里建议查看前面的tss全局变量是否地址改变,如果改变了需要重新配置段描述符。

使用TSS

前面我们还有个坑cr3没有处理。

先让程序在虚拟机里跑起来,然后WinDbg把虚拟机断下来,来找cr3:

!process 0 0    //查看所有进程,找到我们的程

这里的DirBase就是cr3。具体原因就先不解释了。

正常运行首先得先安装段描述符,但是需要注意的是如果我们代码修改了,前面的tss全局变量的地址可能是会改变的,需要重新编写段描述符,然后这个cr3也是会改变的,每次都得重新添加才行。

这样再运行就会跑到我们写好的int 3中断里:

同时再查看一下寄存器的内容:

就成功了。

这里有一些补充知识:这里的esp就是给写好的代码段进行正常使用的esp,然后esp0就是给你代码要执行0环的代码的时候采用的。

然后这个previou task link会自动赋值为上一个的tss段选择子。

还有就是iretd这个指令,它会根据EFLAG寄存器的nt位来不同的选择返回方式:

如果nt为0,就会通过esp来返回,和retf差不多,如果为1就会通过tss来返回。但是如果执行了int 3就会把eflag寄存器的nt位清零。

小结

tss作为x86架构中的一个段,主要作用是用来保存环境。我感觉这个x86我其实没学明白,准备后面几天把x86架构阅读一遍,再来重新修改这些内容,不好意思各位,可能这里没讲清楚,等我后面再来修改这系列x86 CPU架构的博客。

Windows内核中的CPU架构-8-任务段TSS(task state segment)的更多相关文章

  1. Windows内核中的CPU架构-6-中断门(32-Bit Interrupt Gate)

    Windows内核中的CPU架构-6-中断门(32-Bit Interrupt Gate) 中断门和调用门类似,也是一种系统段.同样的它也可以用来提权. 中断门: 虽然中断门的段描述符如下: 但是中断 ...

  2. Windows内核中的CPU架构-7-陷阱门(32-Bit Trap Gate)

    Windows内核中的CPU架构-7-陷阱门(32-Bit Trap Gate) 陷阱门和中断门几乎是一模一样的: (注:图里高32位中的第11位的值为D,其实是1) 除了高32位中的type字段的内 ...

  3. 02全志r58平台Android4.4.4下关闭内核中的CPU的开启关闭提示

    02全志r58平台Android4.4.4下关闭内核中的CPU的开启关闭提示 2017/8/18 13:53 版本:V1.0 开发板:SC5806(全志R58平台) SDK:android4.4.4 ...

  4. Windows内核中的内存管理

    内存管理的要点 内核内存是在虚拟地址空间的高2GB位置,且由所有进程所共享,进程进行切换时改变的只是进程的用户分区的内存 驱动程序就像一个特殊的DLL,这个DLL被加载到内核的地址空间中,Driver ...

  5. Linux内核中的中断栈与内核栈的补充说明【转】

    转自:http://blog.chinaunix.net/uid-12461657-id-3487463.html 原文地址:Linux内核中的中断栈与内核栈的补充说明 作者:MagicBoy2010 ...

  6. Windows内核 基本汇编指令

    1)用VS2010新建Win32 Console Application,工程名为ACECore,工程建立完成后得到打开文件ACECore.cpp,代码如下: #include "stdaf ...

  7. Windows环境下多线程编程原理与应用读书笔记(3)————Windows环境中的多线程实现(3)

    纤程 纤程(fiber): 相当于用户级别的线程或轻进程.纤程由Win32库函数支持,对核心是不可见的.纤程可以通过SwitchToFiber显示至另一合作纤程,以实现合作纤程之间的协同.线程是在Wi ...

  8. windows内核代码之进程操作

    [toc] 一丶简介 整理一下windows内核中.常用的代码.这里只整理下进程的相关代码. 二丶 windows内核之遍历进程 内核中记录进程的结构体是EPROCESS结构.所以只需要遍历这个结构即 ...

  9. Windows内核开发-6-内核机制 Kernel Mechanisms

    Windows内核开发-6-内核机制 Kernel Mechanisms 一部分Windows的内核机制对于驱动开发很有帮助,还有一部分对于内核理解和调试也很有帮助. Interrupt Reques ...

随机推荐

  1. P6076-[JSOI2015]染色问题【组合数学,容斥】

    正题 题目链接:https://www.luogu.com.cn/problem/P6076 题目大意 给出\(n*m\)的网格,\(c\)种颜色涂色要求 每个格子可以染色也可以不染 每一行每一列至少 ...

  2. 巧用优先队列:重载运算符在STL优先队列中的应用

    前言 写优先队列优化dijkstra的时候,需要放进优先队列的常常有数值和编号两类,以下介绍让编号捆绑数值放入优先队列的几种方法. 由于过程比较简单,记住代码即可,下面不再讲解,只附上代码,请读者自行 ...

  3. Python:安装opencv出现错误Could not find a version that satisfies the requirement numpy==1.13.3 (from versions: 1.14.5, 1.14.6, 1.15.0rc2, 1.15.0, 1.15.1, 1.15.2, 1.15.3, 1.15.4, 1.16.0rc1, 1.16.0rc2,

    安装opencv的时候,出现numpy的版本不匹配,卸载了不匹配的版本,重新安装却是一点用都没有,后面尝试了一下这里的提示pip更新,居然安装成功了,看来pip的版本过低真是误事啊. 报错是: Cou ...

  4. Java到底怎么学?

    你现在是不是想学Java,但很迷茫不知该从何下手,那么请认真看完这篇文章,希望对你有所帮助! 作为零基础刚刚接触Java的朋友们来说,我的建议还是先看视频,虽然有很多人说看视频学习慢,建议直接买优秀的 ...

  5. 基本的bash shell命令

    目录 基本的bash shell命令 启动shell shell提示符 基本的bash shell命令 启动shell GNU bash shell 能提供对Linux系统的交互式访问.它是作为普通程 ...

  6. 深入理解Java虚拟机之垃圾回收篇

    垃圾回收简介 ​ Java 会对内存进行自动分配与回收管理,使上层业务更加安全,方便地使用内存实现程序逻辑.在不同的 JVM 实现及不同的回收机制中,堆内存的划分方式是不一样的. ​ 简要地介绍下垃圾 ...

  7. 无法获取指向控制台的文件描述符 (couldn't get a file descriptor referring to the console)

    背景 最近收拾东西,从一堆杂物里翻出来尘封四年多的树莓派 3B 主机来,打扫打扫灰尘,接上电源,居然还能通过之前设置好的 VNC 连上.欣慰之余,开始 clone 我的 git 项目,为它们拓展一个新 ...

  8. Frida高级逆向-Hook Native(Java So)2

    Frida Hook So 一些操作说明 Native方法第一个参数是 JNIEnv *env 如何在Frida中获取 JNIEnv 对象呢? Java.vm.getEnv(); 如何将string类 ...

  9. python收集参数与解包

    收集任意数量的实参 def make_pizza(*toppings): """打印顾客点的所有配料""" print(toppings) ...

  10. 【数据结构与算法Python版学习笔记】目录索引

    引言 算法分析 基本数据结构 概览 栈 stack 队列 Queue 双端队列 Deque 列表 List,链表实现 递归(Recursion) 定义及应用:分形树.谢尔宾斯基三角.汉诺塔.迷宫 优化 ...