保護模式篇——段寄存器


寫在前面

  此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的復雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章后面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統內核——簡述 ,方便學習本教程。

  看此教程之前,問幾個問題,基礎知識儲備好了嗎?搭建好環境了嗎?沒有的話就不要繼續了。


🔒 華麗的分割線 🔒


什么是段寄存器

  當我們用匯編讀寫某一個地址時,比如用下面的代碼:

mov dword ptr ds:[0x123456], eax

  其實我們真正讀寫的地址是:ds.base + 0x123456。並不是0x123456,不過正好的是ds段寄存器的基址是0而已。

一些段寄存器

  段寄存器有這幾個:ES、CS、SS、DS、FS、GS、LDTR、TR,它們各有自己特殊的用途。

段寄存器的結構

  段寄存器的結構可用下圖表示:

  段寄存器具有96位,但我們可見的只有16位。我們可以用OD隨意加載一個程序,如下圖所示:

段寄存器的讀寫

  既然是寄存器了,那就可以進行讀寫操作,如下將介紹讀寫段寄存器的操作:

  • Mov指令:MOV AX,ES,但只能讀16位的可見部分;MOV DS,AX寫段寄存器,寫的是96位。
  • 讀寫LDTR的指令為:SLDT/LLDT
  • 讀寫TR的指令為:STR/LTR

段寄存器屬性探測

  我介紹過段寄存器有96位,但我們只能看見16位,那如果證明AttributeBaseLimit的存在呢?我們將在下面進行初步探測。

段寄存器成員簡介

  既然討論段寄存器屬性,首先要知道它們存着啥,下面表格的內容是我從虛擬機里查詢到的值,可能和我的不一樣,但無所謂。它們的屬性我已查詢並把它們的權限寫到表格中,之所以為什么我之后將會介紹。

Windows操作系統並不會使用GS寄存器,故用-表示。

段寄存器 Selector Attribute Base Limit
ES 0023 可讀、可寫 0 0xFFFFFFFF
CS 001B 可讀、可執行 0 0xFFFFFFFF
SS 0023 可讀、可寫 0 0xFFFFFFFF
DS 0023 可讀、可寫 0 0xFFFFFFFF
FS 003B 可讀、可寫 0x7FFDE000 0xFFF
GS - - - -

探測Attribute

  如果你沒有使用Visual C++6.0的話,我先將如何建立工程並寫代碼簡單介紹一下,但只介紹一遍。打開Visual C++6.0,通過File->New打開新建項目,選擇Win32 Console Application,輸入你的Project name,如下圖所示:

  選中帶有 Hello World 的工程,這樣基本上IDE就幫我們建好要做實驗的工程了。

  然后IDE會展示幫我們新建的內容信息,如下圖所示,直接點確定即可,工程新建完畢。

  按照下圖指示打開源代碼文件,刪掉用不到的printf("Hello World!\n");。就能開始做實驗了。

  怎么建工程我已詳細說明,那就正式進入正題,我們將用以下代碼進行驗證Attribute

#include "stdafx.h"

int a=0;
int main(int argc, char* argv[])
{
    _asm
    {
        mov ax,cs;
        mov dword ptr ds:[a],10;
        mov ds,ax;
        mov dword ptr ds:[a],20;
    }
    return 0;
}

  然后在main函數的第一句代碼下斷點,然后單步運行,運行過第一句給變量a賦值的匯編代碼時,成功通過,如下圖所示:

  運行到第二個給變量a賦值的匯編代碼時,彈出一個錯誤信息框,如下圖所示

  翻譯過來就是地址訪問沖突,這是什么原因呢?這就是由於cs段寄存器是可讀的,而不是可寫的。原來的ds是可讀可寫的,但將cs通過ax賦值給ds時候,ds不再是原來的ds,而是cs,故會引發此錯誤。

探測Base

  老生常談程序的零地址無法訪問。但零地址一定是無法訪問嗎?我們將用以下代碼進行驗證Base

#include "stdafx.h"

int a=0;
int main(int argc, char* argv[])
{
    _asm
    {
        mov ax,fs;
        mov es,ax;
        mov eax,es:[0];        //不要用DS,否則編譯不過去
        mov dword ptr ds:[a],eax;
    }
    return 0;
}

  編譯運行通過,變量a被正常賦值,如下圖所示:

探測Limit

  我們將用以下代碼進行驗證Limit

#include "stdafx.h"

int a=0;
int main(int argc, char* argv[])
{
    _asm
    {
        mov eax,fs:[0x1000];
        mov dword ptr ds:[a],eax;
    }
    return 0;
}

  執行第一句匯編就發生內存訪問沖突錯誤,就是因為0x1000超出了它的Limit的值0xFFF,如下圖所示:

踩坑問題

  里面有一些坑我還沒讓你踩,你踩一踩看看。請你思考出答案或者百思不得其解的時候再來看答案。

  1. 在驗證屬性的時候,用下面的代碼,結果運行mov dword ptr ds:[a],10正常通過,放開程序跑后內存訪問沖突。
#include "stdafx.h"

int main(int argc, char* argv[])
{
    int a=0;
    _asm
    {
        mov ax,cs;
        mov ds,ax;
        mov dword ptr ds:[a],10;
    }
    return 0;
}

🎉 踩坑解決方案 🎉


  為什么會出現這個問題呢?我明明代碼很正常但就不行呢,難道只是因為局部變量的問題呢。但全局變量和局部變量在內存上根本沒有區別。全局變量只是一個死地址,局部變量是該變量所在函數臨時的地址,但訪問上根本沒有區別。我們看一看編譯器到底把咱們的內聯匯編到底翻譯成了什么?為什么會出現這個問題呢?我明明代碼很正常但就不行呢,難道只是因為局部變量的問題呢。但全局變量和局部變量在內存上根本沒有區別。全局變量只是一個死地址,局部變量是該變量所在函數臨時的地址,但訪問上根本沒有區別。我們看一看編譯器到底把咱們的內聯匯編到底翻譯成了什么?

  前面的內斂匯編編譯器給我一五一十的直接翻譯了,但這個mov dword ptr ds:[a],10;內聯匯編給我翻譯成了啥,過分了!結果壓根沒有用ds的權限來訪問,而是默認的ss來訪問,這和預期結果一樣才怪。


2.在探測Base屬性的時候,使用gs作為試驗寄存器,單步執行到mov eax,gs:[0],出現內存訪問沖突錯誤。

#include "stdafx.h"

int a=0;
int main(int argc, char* argv[])
{
    _asm
    {
        mov ax,fs;
        mov gs,ax;
        mov eax,gs:[0];
        mov dword ptr ds:[a],eax;
    }
    return 0;
}
🎉 踩坑解決方案 🎉


  是因為每次單步調試,就會觸發單步調試異常,進入內核,內核會把gs清零了,故導致實驗無法成功。


下一篇

  保護模式篇——段描述符與段選擇子


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM