Windows内核实验002 中断现场
文章目錄
- 如何獲取中斷現場環境
- 中段現場環境
- 觀察中斷現場堆棧環境
- 觀察中斷現場的寄存器環境
- 段選擇子
- 段寄存器結構
- 變化的段寄存器的具體含義
- 遺留問題:SS段寄存器和棧頂指針來自于哪?
- 什么是TSS
- TSS的工作細節
- 中斷提權的任務切換過程
- 實驗代碼
上一課我們已經實現了利用中斷提權的方式讓自己寫的函數擁有了零環的權限,但是為了更方便的寫零環代碼。我們還需要搞清楚中斷現場的上下文環境,什么資源可以被使用,什么資源不能被使用。如果不清楚現場的環境,就很容易寫出有問題的代碼,從而導致系統藍屏。
如何獲取中斷現場環境
比較方便的一個方式是在函數內部將當前的寄存器包括通用寄存器 段寄存器甚至是控制寄存器等等全部打印出來。
中段現場環境
觀察中斷現場堆棧環境
我們加上下面幾行代碼,來觀察一下中斷之前和之后的堆棧環境,看一下int 20這條指令對堆棧的影響
DWORD g_esp[2];void __declspec(naked) IdtEntry() {__asm{mov [g_esp+4], esp;iretd;} }void go() {__asm mov[g_esp], esp;__asm int 0x20; }運行程序,可以看到在中斷提權之前,地址是一個三環的esp,而中斷之后esp的地址則變成了零環的地址。
接下來我們在代碼中加入一條int3指令,在調試器中觀察一下堆棧內的數據情況
kd> dps esp a6e63c9c 0040114f a6e63ca0 0000001b a6e63ca4 00000246 a6e63ca8 0012ff44 a6e63cac 00000023 ......可以看到棧頂的數據是0x0040114f,接著用ub命令觀察這個地方的反匯編
kd> ub 0040114f 00401131 668cc0 mov ax,es 00401134 66a380334000 mov word ptr ds:[00403380h],ax 0040113a 668ce0 mov ax,fs 0040113d 66a37c334000 mov word ptr ds:[0040337Ch],ax 00401143 668ce8 mov ax,gs 00401146 66a378334000 mov word ptr ds:[00403378h],ax 0040114c 58 pop eax 0040114d cd20 int 20h可以發現,這個地方就是我之前寫的代碼,也就是三環的返回地址。堆棧中的其他數據含義如下:
- 0000001b是三環的cs
- 00000246是三環的eflags
- 0012ff44是三環的esp
- 00000023是三環的ss
總結:通過中斷門進入零環之前需要先保存三環的寄存器環境來達到再次返回三環的目的
觀察中斷現場的寄存器環境
接下來我們修改一下代碼,把所有的寄存器盡可能全部打印出來。接著運行我們寫的程序
可以看到中斷現場前后的寄存器環境中,ESP從三環棧切換到了零環棧,CS從0x1B變成了0x8,代碼提權也是從這里體現的,另外ss也發生了變化。
那么問題也就來了,這些寄存器改變的背后是一種什么樣的機制,另外這些變化的寄存器都是從哪來的。要想知道這個問題的答案,需要了解一個東西叫做段選擇子
段選擇子
段寄存器結構
段寄存器結構可以抽象成以下的結構
struct SegMent {WORD selector; //段選擇子WORD attribute; //屬性DWORD base; //基址DWORD limit; //段限長 }- selector: 首先可見部分16位對應上面SegMent的selector成員。在 OD 中,可以看到段寄存器后面就跟著一個數字,比如 ds 后面的 0023。而 0023 后面的部分就是,剩余部分不可見部分,不過 OD 也給我們展示出來了
- attribute: attribute 屬性記錄了該段是否有效,是否可讀寫等權限。如果往一個不可寫的段執行寫數據,會報異常。
- base 表示基地址
- limit 表示段界限,如果在超出了段界限進行讀寫,會報錯。
在段寄存器中,16位的段選擇子是可見的,其余80位的不可見的。我們主要關注段選擇子
例如上圖中CS的段選擇子位0x23,SS的段選擇子為0x2B。段選擇子就是一個數字,一共有16位,結構如下:
| 1 | 0 | 字節 |7654321076543 2 10| 比特 |-------------|-|--| 占位 | INDEX |T|R | 含義 | |I|P | | | |L |- INDEX:高13位表示的是在GDT數組或LDT數組的索引號
- TI:Table Indicator,這個值為0表示查找GDT,1則查找LDT
- RPL:請求特權級。以什么樣的權限去訪問段。
變化的段寄存器的具體含義
明白了段選擇子的結構,接下來就來拆解其中的具體含義,以CS從0x1B變成了0x8為例:
0x1B=?00011011?
- 索引號:00011=3,代表查找GTB表下標為3的描述符
- TI:0代表查GDT表
- RPL:11=3 代表特權等級為3 也就是三環權限
0x8=?00001000
- ?索引號:00001,代表查找GTB表下標為1的描述符
- TI:0代表查GDT表
- RPL:0 代表特權等級為0,也就是零環權限
總結:CS段寄存器從0x1B變成0x8,主要是代表特權等級從三環提升到了零環。
權限切換了,esp堆棧和相應的段寄存器ss自然也會跟著切換。因為棧數據是線程的核心資源,特權切換必須伴隨棧切換
遺留問題:SS段寄存器和棧頂指針來自于哪?
我們已經知道代碼提權的時候是需要切換棧的。那么,還剩下一個問題:就是SS段寄存器和棧頂指針是來自于哪?
答案是來自于TSS 任務狀態段
什么是TSS
TSS 全稱task state segment,是指在操作系統進程管理的過程中,任務(進程)切換時的任務現場信息。注意不要把TSS和任務切換關聯起來,它只是一塊用于保存任務現場的一些數據
Inter白皮書給出的TSS在內存中的圖是這樣的:
看的一臉懵逼對吧?我也看的一臉懵逼。抽象成結構體就是這樣的
typedef struct TSS {DWORD link; // 保存前一個 TSS 段選擇子,使用 call 指令切換寄存器的時候由CPU填寫。// 這 6 個值是固定不變的,用于提權,CPU 切換棧的時候用DWORD esp0; // 保存 0 環棧指針DWORD ss0; // 保存 0 環棧段選擇子DWORD esp1; // 保存 1 環棧指針DWORD ss1; // 保存 1 環棧段選擇子DWORD esp2; // 保存 2 環棧指針DWORD ss2; // 保存 2 環棧段選擇子// 下面這些都是用來做切換寄存器值用的,切換寄存器的時候由CPU自動填寫。DWORD cr3; DWORD eip; DWORD eflags;DWORD eax;DWORD ecx;DWORD edx;DWORD ebx;DWORD esp;DWORD ebp;DWORD esi;DWORD edi;DWORD es;DWORD cs;DWORD ss;DWORD ds;DWORD fs;DWORD gs;DWORD ldt;// 這個暫時忽略DWORD io_map; } TSS;TSS的工作細節
TSS在任務切換過程中起著重要作用。在任務切換的過程中,首先,處理器中的各寄存器的當前值被自動保存到TR(任務寄存器)所指定的TSS中;然后下一任務的TSS選擇子被裝入TR;最后,從TR所指定的TSS中取出各寄存器的值送到處理器的各寄存器中,從而實現任務切換。
中斷提權的任務切換過程
三環程序通過中斷門進入到零環時,首先會保存當前的寄存器環境到TR所指定的TSS中,這一點我們已經在中斷現場的堆棧環境中見過了。
然后將零環所需要的選擇子裝入TR。
最后從TSS中取出各寄存器送到當前環境下。這其中就包括了ss段寄存器和棧頂指針。我們也可以從TSS的結構體中看到0環的ss段寄存器和esp棧頂指針。
總結:ss段寄存器和esp棧頂指針來自于TSS
實驗代碼
最后 附上本次實驗所用到的代碼
#include "pch.h" #include <iostream> #include <stdio.h> #include <stdlib.h> #include <windows.h>DWORD g_eax[2], g_ecx[2], g_edx[2], g_ebx[2]; DWORD g_esp[2], g_ebp[2], g_esi[2], g_edi[2]; WORD g_cs[2], g_ds[2], g_ss[2], g_es[2], g_fs[2], g_gs[2]; WORD g_tr;void __declspec(naked) IdtEntry() {__asm{mov [g_eax + 4], eax;mov [g_ecx + 4], ecx;mov [g_edx + 4], edx;mov [g_ebx + 4], ebx;mov [g_esp + 4], esp;mov [g_ebp + 4], ebp;mov [g_esi + 4], esi;mov [g_edi + 4], edi;push eax;mov ax, cs;mov [g_cs + 2], ax;mov ax, ds;mov[g_ds + 2], ax;mov ax, ss;mov[g_ss + 2], ax;mov ax, es;mov[g_es + 2], ax;mov ax, fs;mov[g_fs + 2], ax;mov ax, gs;mov[g_gs + 2], ax;str ax;mov g_tr, ax;pop eax;int 3;iretd;} }void go() {__asm{mov[g_eax], eax;mov[g_ecx], ecx;mov[g_edx], edx;mov[g_ebx], ebx;mov[g_esp], esp;mov[g_ebp], ebp;mov[g_esi], esi;mov[g_edi], edi;push eax;mov ax, cs;mov[g_cs], ax;mov ax, ds;mov[g_ds], ax;mov ax, ss;mov[g_ss], ax;mov ax, es;mov[g_es], ax;mov ax, fs;mov[g_fs], ax;mov ax, gs;mov[g_gs], ax;pop eax;}__asm int 0x20; }//eq 80b95500 0040ee00`00081040 int main() {if ((DWORD)IdtEntry != 0x401040){printf("wrong addr:%p", IdtEntry);exit(-1);}go();printf("eax:%p,\tecx:%p\tedx:%p\tebx:%p\n", g_eax[0], g_ecx[0], g_edx[0], g_ebx[0]);printf("esp:%p,\tebp:%p\tesi:%p\tedi:%p\n", g_esp[0], g_ebp[0], g_esi[0], g_edi[0]);printf("cs:%p,\tds:%p\tss:%p\tes:%p\tfs:%p\tgs:%p\n", g_cs[0], g_ds[0], g_ss[0], g_es[0], g_fs[0], g_gs[0]);printf("eax:%p,\tecx:%p\tedx:%p\tebx:%p\n", g_eax[1], g_ecx[1], g_edx[1], g_ebx[1]);printf("esp:%p,\tebp:%p\tesi:%p\tedi:%p\n", g_esp[1], g_ebp[1], g_esi[1], g_edi[1]);printf("cs:%p,\tds:%p\tss:%p\tes:%p\tfs:%p\tgs:%p\n", g_cs[1], g_ds[1], g_ss[1], g_es[1], g_fs[1], g_gs[1]);printf("tr:%p\n", g_tr);system("pause"); }總結
以上是生活随笔為你收集整理的Windows内核实验002 中断现场的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows内核实验001 中断提权
- 下一篇: Windows内核实验003 再次回到中