一丶段描述符
1.1 GDT與LDT
1.1.1 段描述符之GDT表 與 LDT表的概述
-
GDT表
查詢inter手冊可以得知. 當我們在保護模式下. 進行內存訪問的時候 所有的內存訪問. 要么通過
全局描述符表(GDT) 要么就通過局部描述符表(LDT) 進行訪問的. 而 這些描述符表中.記錄的都是
段描述符 段描述符里面包含了 段的基地址 訪問特權 類型 和用法信息. 每個段描述符都會有一個與之相關的段選擇子,也叫做段選擇符 段選擇子 包含的是 GDT 與LDT(與它相關的的段描述符)里面的一個索引. 一個全局或者局部標志 決定了 段選擇子 是指向GDT 還是 LDT. 想要訪問GDT 或者LDT 要提供 段選擇子以及偏移地址. 段選擇子只是提供了一個索引.來進行訪問GDT 或者LDT中的段描述符的. 而段描述符 包含了線性地址 空間的基地址 , 偏移量 確定了相對與基地址的字節地址.
GDT表或者LDT表的線性地址都存儲在 GDTR 寄存器與 LDTR寄存器 中.
通過上面我們可以得出幾點概要
-
- 保護模式下.我們的內存訪問其實都是查表. 查的是GDT 或者 LDT. 如何確定查詢的是GDT 還是LDT 取決於段選擇子的 全局或者局部的標志位 而查表其實就是 段選擇當索引去GDT表中查詢. 查到哪一項. 這一項保存的是 段描述符結構
- GDT或者LDT表中.保存的是段描述符結構 段描述符里面才真正的 描述了 段的基地址 訪問特權 類型 和用法信息
- 訪問GDT或者LDT 就要提供段選擇子以及偏移地址.
以上就是對GDT表或者 LDT表的描述 總結來說 GDT或者LDT 就是一塊內存. 也可以看成一個數組. 數組的每一項其實保存的都是段描述符 段選擇子就是下標
3.1.2 GDTR寄存器與GDT表了解.
根據Inter手冊所屬. GDTR寄存器 保存了 GDT的 32位基地址 和16位表界限
基地址指的就是GDT從0字節開始的線性地址.可以理解為就是數組首地址.
表界限.可以理解為就是數組的大小. 所以說GDTR 寄存器是一個48位寄存器
按照C語言結構來來表是就如下
struct GDTR { DWORD *GdtBase, SHORT limit; }
LGDT 與SGDT 匯編指令 分別是用來獲取和保存 GDTR寄存器的.
電腦開機之后,通電之后.GDT就開始初始化了.
總結:
-
1.GDTR是一個寄存器.保存的就是GDT的首地址以及一個長度. 根據長度可以確定一個GDT表示的內存有多大
-
2.GDT是一個數組.數組里面保存的是段描述符結構 請不要搞混概念
-
在這里我們就可以用驅動程序來讀取 GDT了.並且我們進行打印輸出即可.
3.1.3 LDTR寄存器與LDT
LDTR寄存器 保存了 16位的段選擇子 32位的基地址 16位的段界限(長度) LDT描述符的屬性.
LLDT 和 SLDT 指令分別是用來 獲取 和設置 LDTR寄存器的.
結構如下:
struct LDTR
{
WORD select,
DWORD base,
WORD Limit,
WORD Attribute,
}
LDTR沒有使用.所以簡單了解下.
3.2 段選擇子
3.2.1 段選擇子介紹
上面了解了LDT表. GDT表. 以及分段的概念. 那么就該說一下如何查表. 段選擇子是什么.
其實我們說了怎么多. 都是在了解 當匯編指令訪問內存的時候 是怎么走的.
比如:
mov ebx,30h
mov fs,bx
官方的說法如下:
段選擇子 是一個 16位 的段標識符. 他不是直接指向該段.而是作為一個索引.指向該
段的 段描述符 其實就是下標. 去GDT表查詢. 查到段描述符 段描述符才描述了信息.
30就是段選擇子. 這里的30賦值給了ebx中的bx位. bx賦值給fs. 可見部分是段選擇子.所以會查表
看圖了解
分為兩個域 一個是 index(索引 3 - 15位表示) 一個是 標記以及特權位
根據官方手冊所說. 索引位. 來表示 GDT表中的8192個描述符的某一項. 由此可以得知.GDT表不會超過 8192項 索引值 * 8 來索引 段描述符 由此可以得知. 段描述符是8個字節大小 且GDT表中的每一項是8個字節
手冊還是 GDIT 第一項是不可用的. 也就是段選擇子為0. TI 位設置為0 則被視為空選擇子
當我們查詢GDT表的時候如果為索引為0 那么CPU不會產生異常的. 但是如果我們以空選擇子來查詢的時候得出的段描述符來進行內存訪問的時候.就會產生異常. 就會產生一個 通用保護異常.(GP)
還說了 對CS 以及SS賦予一個空選擇副是的時候也會產生GP異常.
-
TI標記位
進行內存訪問的時候,為 0 表示查詢GDT. 為1則訪問LDT表.
-
RPL 位
特權級別位. 總共三個等級 0 - 3 0為最高特權級. 描述了 RPL 與 CPL 之間的關系. 以及
段選擇子所指向的段描述符
看到這里我們可以進行總結了
- 1.段段選擇子其實也是一個結構.表達了 GDT表的索引. 一個標志.查詢GDT還是LDT. 一個特權級別.
所以我們可以進行手工解析了.
- 2.GDT表中的第一項為不可用的.也就是空選擇子
比如我電腦上的段選擇子. 也就是段的可見部分. 以DS段為例子 段選擇子為 0x0023
拆分成二進制
0000 0000 0010 0011 第一次拆分
0000 0000 0000 0 索引 = 0
0 查詢的GDT表
11 RPL 特權級別 3
總結一下, 查詢的索引 = 0; 查詢的是GDT表 . RPL 特權級別 = 3;
2. 段描述符
2.1 段描述符介紹
段描述符是一個結構. 占用了8個字節大小 是GDT表或者LDT表中的一個數據結構
其實上面也說了當進行內存訪問的時候,段選擇子 當索引 查詢GDT表.來得出段描述符表.
段描述符表示了 段基地址 段的大小 訪問權限. 以及狀態等信息.
看一下這個結構體信息
2.2 段描述符屬性詳解.
2.2.1 段寄存器與段描述符 一一的對應關系
-
段寄存器中的段屬性 與 段描述符中的段屬性的對應關系
段寄存器我們知道其結構為
struct set { WORD Selector, //段選擇子 16位 WORD Attribute,//段屬性 16位 DWORD Base, //段基地址 32位 DWORD Limit, //段限長 32位 }
那么段屬性與段描述符 是一種怎么樣的對應關系?
請不要把 段寄存器結構 與 段描述符搞混
看下圖
對應着段描述符的 高4個字節中的 第八位 到 第23位
-
段寄存器中的 32位的基地址 與 段描述符中的基地址對應關系
段描述符的基地址由三部分組成. 原因是CPU實在16位上擴展的.要兼容16位.32位 64位.所以只能不斷擴展
看下圖:
分別是由 高4字節的 24 - 31位 4-7位 低四個字節的 16 - 31位來組成的.
所以我們通過位運算.可以拼接處地址. 並且賦值給段寄存器的基地址域
-
Limit對應
這個就不用說了.看上圖就知道. 第四個字節的 0-15位來做成的limit
高地址的 16 - 19位 也是. 合計20個字節
-
2.2.2 段屬性中的位詳解
2.2.2.1P位 高四個字節的第15位
p = 1 代表這個段描述符是有效的
p = 0 代表這個段描述符是無效的
2.2.2.2G位 粒度位 高四個字節的第23位
G位 根據inter手冊所說如下
G = 0: 代表段limit(限長)是以字節為單位的. 根據段描述符我們知道段限長 為20位組成.
也就是0xFFFFF 大小. 如果G = 0; 那么是以字節為單位. 在 0xFFFFF最大表示了0xFFFFF個大小
意思就是說以字節為單位. 字節 * limit 來表示一個 一個段界限值.
G = 1: 段限長就是表示4kb大小. 4096-1 = 4095 也就是0xFFF 大小. 也就是一個頁大小
段限長可以表示為 4kb * limit 來表示一個limit有多大.
2.2.2.3 S位
在系統中段描述符有很多形式.一種是系統段描述符 一種是數據或者代碼段描述符
S = 1 表示代碼段或者數據段描述符
s = 0 表示系統段描述符
根據G位 DPL位 S位 可以枚舉出什么是代碼段.什么是數據段.
DPL要么都為1 要么都為0
所以如果是代碼段 G DPL S 組合起來 十六進制要么為9 要么為F
- S = 1 代碼段與數據段中的Type域講解
TYPE 域
TYPE域 跟S位相關聯. 看下TYPE域的4個位表示
S = 1的時候Type如下.是針對 代碼或者數據段表示的. S= 0那么則是針對內核.
TYpe 域中的第 11位 = 0 確定了這是數據段
11位 = 1 確定了這是代碼段
意思就是說. S位只是確定了你是代碼段還是數據段,但是肯定不會是系統段描述符.
而Type域則真正決定了.你是代碼段還是數據段.
所以我們可以在確定代碼段或者數據段的前提下.精確的遍歷出那些是代碼段.那些是數據段.
在段描述符的第6個十六進制位 可以看. 入如果 > 9 就是代碼段. 否則就是數據段
對於數段而言 控制位如下:
控制位了. E W A
W 表示可寫
A 訪問位
E 擴展位
A 位表示了你這個段描述符是否訪問過.如果訪問過.那么就會置1 否則就是0
W 表示可寫位. 如果針對這個段描述符寫過.則W位置1. 否則如果為0.那么表示這個數據段是不可寫的.
E擴展位
擴展位 分為向下擴展 和向上擴展. 具體意思是啥.
首先了解下堆棧段. 堆棧比如是可讀寫的數據段. 而且大小需要動態變化. 所以我們使用向下擴展.擴展方向 = 1 就是向下擴展的意思. 意思就是ss.base + limit 這塊表示的空間. 其余的空間 是有效的. 比如我們讀取堆棧.那么除了ss.base可以讀.其余空間也是有效的.也是可以讀的.
E = 0 向上擴展: 那么意思就是有效空間只有 ss.base + limt空間大小.
如下圖:
圖是以fs為原型. 第一個表示向上擴展. 有效空間只有 fs.base + limit E = 0
E = 1 向下擴展. 表示有效空間除了fs.base + limit 之外都是可以訪問的.
對於代碼段而言 控制位如下:
控制位: C R A
A = 訪問位. 同數據段一樣.
R = 可讀位 表示當前這個段是否是可讀的.
C = 一致位
C = 1 一致代碼段
C = 0 非一致代碼段
-
S = 0 Type域講解
上面我們說過S = 1 表示代碼段域數據段.而且Type域也不同. 那么S = 0則表示是系統段描述符.那么看下如果是系統段描述符
根據Inter手冊所屬 當S位 = 0; 這個描述符位系統描述符 處理可以識別以下類型的系統描述符
局部描述符 LDT
任務段描述符 TSS
調用們描述符
中斷門描述符
陷阱門描述
任務們描述
這些描述又可以分為兩大類 一類是系統段描述符 另一個是門描述符.
系統段(LDT TSS) 門就是本身
看下Type解析
如果是系統段.那么Type域就進行組合解析了. 比如序號為11的一項 二進制為 1011 那么它則表示這個描述符為 32位的TSS 並且處於繁忙狀態(Busy)
其實也是第五個字節.可以直接看第五個字節的數據表示形式.圖片如下
-
db位
db位主要是影響段寄存器的操作
-
CS代碼段的影響
D = 1 那么我們的匯編就采用32位的尋址方式
D = 0 那么我們的匯編就采用16位的尋址方式.
D = 0 匯編偽代碼
```
mov esp,dword ptr [di - 18] 操作的di 也就是16位尋址 可以用硬編碼0x67進行修改來實現這種尋址
```D = 1
mov esp,dword ptr [ebp - 18] 操作的都是32位的寄存器
SS段的影響
堆棧段影響也是分為16位與32位. 32位下.我們 push pop call 都會影響ESP. 也就是D = 1的情況下
D = 0的話. 那么影響的就是SP 而不是ESP
向下擴展的數據段
如果D = 1 那么數據段的limit尋址可以到達4GB.
如果D = 0; 那么就只能到達64kb
其實就是 2^32 與 2^16影響. d = 1 那么都按照32位來算. 否則就是按照16位尋址來算.
-
總結與實驗
總結1 GDT的操作
1.1 獲取GDT數組表.輸出所有段描述符
遍歷獲取GDT表 只是獲取GDT表的首地址. 地址里面的內容則是段描述符
#include <ntddk.h>
//讀取GDT表並且遍歷解析,
void DriverUnLoad(PDRIVER_OBJECT pDriverObj)
{
UNREFERENCED_PARAMETER(pDriverObj);
}
NTSTATUS DriverEntry(
PDRIVER_OBJECT pDriverObj,
PUNICODE_STRING pReg)
{
UNREFERENCED_PARAMETER(pDriverObj);
UNREFERENCED_PARAMETER(pReg);
pDriverObj->DriverUnload = DriverUnLoad;
char gdt[6];
short index = 0;
int* pBase = NULL;
short limit = 0;
__asm
{
sgdt gdt;
}
//遍歷GDT表
limit = *(short*)gdt; //獲取limit
//獲取base
pBase = (int *)*(int *)((char *)gdt + 2);
/*DbgPrint("base = %p\r\n", index, gdt.BaseAddress);*/
for (index = 0; index < limit; index++)
{
//每個GDT表存儲的是 8個字節的段描述符.這里不解析段描述符.只是遍歷內存
if (index == 5)
{
break;
}
DbgPrint("GDT Array %d base = %x\r\n", index,(char *)pBase + index * 8);
//輸出結果 GDT Array 0 base = 80b95000
//輸出結果 GDT Array 1 base = 80b95008
}
return STATUS_SUCCESS;
}
封裝成函數如下
PVOID GetGdtBaseByIndex(ULONG uIndex)
{
/*
利用匯編 sgdt 讀取GDTR內容. 並且進行解析
*/
char gdt[6];
short index = 0;
int* pBase = NULL;
short limit = 0;
__asm
{
sgdt gdt;
}
limit = *(short*)gdt; //獲取limit
//獲取base
pBase = (int*)*(int*)((char*)gdt + 2);
for (index = 0; index < limit; index++)
{
//每個GDT表存儲的是 8個字節的段描述符.這里不解析段描述符.只是遍歷內存
if (index == uIndex)
{
return (PVOID)((char*)pBase + (index * 8));
}
}
return NULL;
}
1.2解析GDT表中的段描述符
總結2
2.1輸出所有有效的段描述符 (P位)
原理: 根據P位來判斷段描述符表是否是有效還是無效.解析P位進行輸出即可.
偽代碼
//我們要獲取P位.就要建立段描述符結構體.來表示GDT中的內容.這樣就很方便獲取值了.用結構體位域表示
typedef struct _Segmentdescriptor
{
ULONG limit0_15 : 16;
ULONG LowBase16_31 : 16;
ULONG LowHighBase0_7 : 8;
ULONG Type : 4;
ULONG sBit : 1;
ULONG DplBit : 2;
ULONG pBit : 1;
ULONG HightLimit16_19 : 4;
ULONG Avl : 1;
ULONG Resever : 1;
ULONG DbBit : 1;
ULONG Gbit : 1;
ULONG HightBase : 8;
}SEGMENTDESCRIPTOR, * PSEGMENTDESCRIPTOR;
BOOLEAN CheckSegmentdescriptorEffective(PVOID GDTValue)
{
/*
根據段描述符來判斷P位是否= 1 = 1則是有效位
*/
KdBreakPoint();
PSEGMENTDESCRIPTOR pSecDescr = NULL;
if (GDTValue == NULL)
{
return 0;
}
//解析GDTVALUE 進行解析
pSecDescr = (PSEGMENTDESCRIPTOR)GDTValue;
ULONG isEffective = 0;
isEffective = pSecDescr->pBit;
if (isEffective&0x00000001)
{
return TRUE;
}
return FALSE;
}
2.2 獲取所有數據段描述符 並且輸出其Type域表示 (S = 1,Type 1 = 0)
原理: 根據S位. 來解析對應的Type位. S= 1代表可能是數據段或者代碼段 解析其Type域的高第一位.來肯定是代碼段還是數據段 並且根據不同段.對type域不同的解釋.來解釋type域
這里只是獲取特定的一個段來判斷是否是數據段.其實可以進行遍歷. 這里邏輯就不寫了.貼出簡單代碼
總共實現三步
1.獲取是s 判斷是系統段. 還是代碼以及數據段
2.獲取Type值
3.解析Type值 確定是代碼 還是數據段
4.輸出
//獲取s位
INT CheckSegmentdescriptorIsSystem(PVOID GDTValue)
{
/*
根據段描述符來判斷S位是否= 1 = 1則是代碼數據段 0就是系統段
*/
KdBreakPoint();
PSEGMENTDESCRIPTOR pSecDescr = NULL;
if (GDTValue == NULL)
{
return -1;
}
//解析GDTVALUE 進行解析
pSecDescr = (PSEGMENTDESCRIPTOR)GDTValue;
ULONG isSystemSegmentdescriptor = 0;
isSystemSegmentdescriptor = pSecDescr->sBit;
if (isSystemSegmentdescriptor & 0x00000001)
{
return TRUE;
}
return FALSE;
}
//根據GDT數組的內容(也就是段描述符) 來獲取對應的Type域
INT GetSegmentdescriptorTypeValue(PVOID GDTValue)
{
/*
根據段描述符來獲取Type域的值即可.
*/
KdBreakPoint();
PSEGMENTDESCRIPTOR pSecDescr = NULL;
if (GDTValue == NULL)
{
return -1;
}
//解析GDTVALUE 進行解析
pSecDescr = (PSEGMENTDESCRIPTOR)GDTValue;
int TypeValue = -1;
TypeValue = pSecDescr->Type;
return TypeValue;
}
//根據Type值進行校驗 是數據段還是代碼段
INT CheckCodeAnDataByTypeValue(INT TypeValue)
{
//獲取TypeValue的高位.來判斷高位是否是1
if (TypeValue & 0x00000008)
{
// 高位位1 是代碼段
return TRUE;
}
else if (TypeValue & 0x00000007)
{
return FALSE;
}
else
{
return -1;
}
}
//輸出
NTSTATUS PrintTypeBlock(INT isSystem, INT uTypeValue)
{
if (isSystem == 0)
{
//Type直接按照系統段解析
switch (uTypeValue)
{
case 0x0:
DbgPrint("Inter公司保留未定義\r\n");
break;
case 0x1:
DbgPrint("有效的286TSS\r\n");
break;
case 0x2:
DbgPrint("LDT \r\n");
break;
case 0x3:
DbgPrint("286TSS忙碌\r\n");
break;
case 0x4:
DbgPrint("286調用門\r\n");
break;
case 0x5:
DbgPrint("任務門\r\n");
break;
case 0x6:
DbgPrint("286中斷門\r\n");
break;
case 0x7:
DbgPrint("286陷阱門\r\n");
break;
case 0x8:
DbgPrint("未定義(Inter保留)\r\n");
break;
case 0x9:
DbgPrint("有效的386TSS\r\n");
break;
case 0xA:
DbgPrint("有效的386TSS忙\r\n");
break;
case 0xB:
DbgPrint("未定義(Inter保留)\r\n");
break;
case 0xC:
DbgPrint("386調用門\r\n");
break;
case 0xD:
DbgPrint("未定義(Inter保留)\r\n");
break;
case 0xE:
DbgPrint("386中斷門\r\n");
break;
case 0xF:
DbgPrint("386陷阱門\r\n");
break;
default:
DbgPrint("解析Type未找到相關定義\r\n");
break;
}
}
else
{
//Type按照數據段 以及代碼段進行解析
switch (uTypeValue)
{
case 0x0:
DbgPrint("Index = 0 DType = 0 Data Seg E = 0 W = 0 A = 0 Read-Only\r\n");
break;
case 0x1:
DbgPrint("Index = 1 DType = 0 Data Seg E = 0 W = 0 A = 1 Read-Only,accessed\r\n");
break;
case 0x2:
DbgPrint("Index = 2 DType = 0 Data Seg E = 0 W = 1 A = 0 Read/Write\r\n");
break;
case 0x3:
DbgPrint("Index = 3 DType = 0 Data Seg E = 0 W = 1 A = 1 Read/Write,accessed\r\n");
break;
case 0x4:
DbgPrint("Index = 4 DType = 0 Data Seg E = 1 W = 0 A = 0 Read-Only,expand-down\r\n");
break;
case 0x5:
DbgPrint("Index = 5 DType = 0 Data Seg E = 1 W = 0 A = 1 Read-Only,expand-down,accessed\r\n");
break;
case 0x6:
DbgPrint("Index = 6 DType = 0 Data Seg E = 1 W = 1 A = 0 Read/Write,expand-down\r\n");
break;
case 0x7:
DbgPrint("Index = 7 DType = 0 Data Seg E = 1 W = 1 A = 1 Read/Write,expand-down,accessed\r\n");
break;
case 0x8:
DbgPrint("Index = 8 CType = 1 Code Seg C = 0 R = 0 A = 0 Execute-Only\r\n");
break;
case 0x9:
DbgPrint("Index = 9 CType = 1 Code Seg C = 0 R = 0 A = 1 Execute-Only,accessed\r\n");
break;
case 0xA:
DbgPrint("Index = A CType = 1 Code Seg C = 0 R = 1 A = 0 Execute/Read\r\n");
break;
case 0xB:
DbgPrint("Index = B CType = 1 Code Seg C = 0 R = 1 A = 1 Execute/Read,accessed\r\n");
break;
case 0xC:
DbgPrint("Index = C CType = 1 Code Seg C = 1 R = 0 A = 0 Execute-Only,conforming\r\n");
break;
case 0xD:
DbgPrint("Index = D CType = 1 Code Seg C = 1 R = 0 A = 1 Execute-Only,conforming,accessed\r\n");
break;
case 0xE:
DbgPrint("Index = E CType = 1 Code Seg C = 1 R = 1 A = 0 Execute/Read-Only,conforming\r\n");
break;
case 0xF:
DbgPrint("Index = F CType = 1 Code Seg C = 1 R = 1 A = 1 Execute/Read-Only,conforming,accessed\r\n");
break;
default:
DbgPrint("解析Type未找到相關定義\r\n");
break;
}
}
return STATUS_SUCCESS;
}
//調用代碼
GetGdtBaseByIndex 函數就是獲取GDT表數組中記錄的地址.6就是下標.表示要獲取 GDT表中哪一項
uIsSytem = CheckSegmentdescriptorIsSystem(GetGdtBaseByIndex(6));
if (uIsSytem != -1)
{
if (uIsSytem == FALSE)
{
DbgPrint("當前段描述符是系統段\r\n");
}
else
{
INT TypeVaue = -1;
INT IsCodeAnDataSeg = -1;
TypeVaue = GetSegmentdescriptorTypeValue(GetGdtBaseByIndex(6));
if (TypeVaue == -1)
{
DbgPrint("Type類型數據獲取錯誤");
return STATUS_SUCCESS;
}
IsCodeAnDataSeg = CheckCodeAnDataByTypeValue(TypeVaue);
if (IsCodeAnDataSeg != -1)
{
if (IsCodeAnDataSeg == TRUE)
{
DbgPrint("當前段描述符是代碼段\r\n");
PrintTypeBlock(uIsSytem, TypeVaue);
}
else
{
DbgPrint("當前段描述符是數據段\r\n");
PrintTypeBlock(uIsSytem, TypeVaue);
}
}
}
}
2.3 獲取所有代碼段描述符 並且輸出其Type域表示(S = 1,Type 1 = 1)
原理: 根據S位. 來解析對應的Type位. S= 1代表可能是數據段或者代碼段 解析其Type域的高第一位.來肯定是代碼段還是數據段 並且根據不同段.對type域不同的解釋.來解釋type域
同上.只要進行遍歷即可.
2.4 獲取所有系統段描述符,並且輸出其Type域表示(S = 0)
原理: 根據S位. 來解析對應的Type位. S= 0代表是系統段. 然后直接解析Type域來看看表示的是什么.
同上.只要進行遍歷即可.
2.5 代碼工程下載
代碼工程放到百度盤里面.也不大.可以直接下載下來學習.是針對32位的.
鏈接:https://pan.baidu.com/s/1tHLBuus91fgYkjKTjiZcsw
提取碼:rfbv
2.6 總結
1.總結GDT 以及如何用代碼獲取GDT
2.根據段描述符建立對應結構體.來進行解析GDT中數組的內容. 其實定義好了怎么解析都可以了.就很簡單了.