<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • C# 讀寫文件從用戶態切到內核態,到底是個什么流程?

    一:背景

    1. 一個很好奇的問題

    我們在學習 C# 的過程中,總會聽到一個詞叫做 內核態 ,比如說用 C# 讀寫文件,會涉及到代碼從 用戶態內核態 的切換,用 HttpClient 獲取遠端的數據,也會涉及到 用戶態內核態 的切換,那到底這是個什么樣的交互流程?畢竟我們的程序是無法操控 內核態 ,今天我們就一起探索下。

    二:探究兩態的交互流程

    1. 兩個態的交界在哪里

    我們知道人間和地府的交界處在 鬼門關,同樣的道理 用戶態內核態 的交界處在 ntdll.dll 層,畫個圖就像下面這樣:

    操作系統為了保護 內核態 的代碼,在用戶態直接用指針肯定是不行的,畢竟一個在 ring 3,一個在 ring 0,而且 cpu 還做了硬件保護兜底,那怎么進入呢? 為了方便研究,先上一個小例子。

    2. 一個簡單的文件讀取

    我們使用 File.ReadAllLines() 實現文件讀取,代碼如下:

    
        internal class Program
        {
            public static object lockMe = new object();
    
            static void Main(string[] args)
            {
                var txt= File.ReadAllLines(@"D:\1.txt");
    
                Console.WriteLine(txt);
    
                Console.ReadLine();
            }
        }
    
    

    在 Windows 平臺上,所有內核功能對外的入口就是 Win32 Api ,言外之意,這個文件讀取也需要使用它,可以在 WinDbg 中使用 bp ntdll!NtReadFile 在 鬼門關 處進行攔截。

    
    0:000> bp ntdll!NtReadFile
    breakpoint 0 redefined
    0:000> g
    ModLoad: 00007ffe`fdb20000 00007ffe`fdb50000   C:\Windows\System32\IMM32.DLL
    ModLoad: 00007ffe`e2660000 00007ffe`e26bf000   C:\Program Files\dotnet\host\fxr\6.0.5\hostfxr.dll
    Breakpoint 0 hit
    ntdll!NtReadFile:
    00007ffe`fe24c060 4c8bd1          mov     r10,rcx
    
    

    哈哈,很順利的攔截到了,接下來用 uf ntdll!NtReadFile 把這個方法體的匯編代碼給顯示出來。

    
    0:000> uf ntdll!NtReadFile
    ntdll!NtReadFile:
    00007ffe`fe24c060  mov     r10,rcx
    00007ffe`fe24c063  mov     eax,6
    00007ffe`fe24c068  test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
    00007ffe`fe24c070  jne     ntdll!NtReadFile+0x15 (00007ffe`fe24c075) 
    00007ffe`fe24c072  syscall
    00007ffe`fe24c074  ret
    00007ffe`fe24c075  int     2Eh
    00007ffe`fe24c077  ret
    
    

    從匯編代碼看,邏輯非常簡單,就是一個 if 判斷,決定到底是走 syscall 還是 int 2Eh,很顯然不管走哪條路都可以進入到 內核態,接下來逐一聊一下。

    3. int 2Eh 入關走法

    相信在調試界沒有人不知道 int 是干嘛的,畢竟也看過無數次的 int 3,本質上來說,在內核層維護著一張 中斷向量表,每一個數字都映射著一段函數代碼,當你打開電腦電源而后被 windows 接管同樣借助了 中斷向量表 ,好了,接下來簡單看看如何尋找 3 對應的函數代碼。

    windbg 中有一個 !idt 命令就是用來尋找數字對應的函數代碼。

    
    lkd> !idt 3
    
    Dumping IDT: fffff804347e1000
    
    03:	fffff80438000f00 nt!KiBreakpointTrap
    
    

    可以看到,它對應的內核層面的 nt!KiBreakpointTrap 函數,同樣的道理我們看下 2E

    
    lkd> !idt 2E
    
    Dumping IDT: fffff804347e1000
    
    2e:	fffff804380065c0 nt!KiSystemService
    
    

    現在終于搞清楚了,進入內核態的第一個方法就是 KiSystemService,從名字看,它是一個類似的通用方法,接下來就是怎么進去到內核態相關的 讀取文件 方法中呢?

    要想找到這個答案,可以回頭看下剛才的匯編代碼 mov eax,6 ,這里的 6 就是內核態需要路由到的方法編號,哈哈,那它對應著哪一個方法呢? 由于 windows 的閉源,我們無法知道,幸好在 github 上有人列了一個清單:https://j00ru.vexillium.org/syscalls/nt/64/ ,對應著我的機器上就是。

    從圖中可以看到其實就是 nt!NtReadFile ,到這里我想應該真相大白了,接下來我們聊下 syscall

    4. syscall 的走法

    syscall 是 CPU 特別提供的一個功能,叫做 系統快速調用,言外之意,它借助了一組 MSR寄存器 幫助代碼快速從 用戶態 切到 內核態, 效率遠比走 中斷路由表 要快得多,這也就是為什么代碼會有 if 判斷,其實就是判斷 cpu 是否支持這個功能。

    剛才說到它借助了 MSR寄存器,其中一個寄存器 MSR_LSTAR 存放的是內核態入口函數地址,我們可以用 rdmsr c0000082 來看一下。

    
    lkd> rdmsr c0000082
    msr[c0000082] = fffff804`38006cc0
    
    lkd> uf fffff804`38006cc0
    nt!KiSystemCall64:
    fffff804`38006cc0 0f01f8          swapgs
    fffff804`38006cc3 654889242510000000 mov   qword ptr gs:[10h],rsp
    fffff804`38006ccc 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]
    ...
    
    

    從代碼中可以看到,它進入的是 nt!KiSystemCall64 函數,然后再執行后續的 6 對應的 nt!NtReadFile 完成業務邏輯,最終也由 nt!KiSystemCall64 完成 內核態 到 用戶態 的切換。

    知道了這兩種方式,接下來可以把圖稍微修補一下,增加 syscallint xxx 兩種入關途徑。

    三:總結

    通過匯編代碼分析,我們終于知道了 用戶態內核態 的切換原理,原來有兩種途徑,一個是 int 2e,一個是 syscall ,加深了我們對 C# 讀取文件 的更深層理解。

    圖片名稱
    posted @ 2022-06-20 09:08  一線碼農  閱讀(2354)  評論(5編輯  收藏  舉報
    国产美女a做受大片观看