再談Windows NT/2000內部資料結構(http://webcrazy.yeah.net)

來源:互聯網
上載者:User
再談Windows NT/2000內部資料結構
               WebCrazy(tsu00@263.net)
                        注:本文最初見於www.nsfocus.com
    現在我們結合Regmon(http://www.sysinternals.com/)在NT中的實現方法再來談談Windows NT/2000內部資料結構。

    Regmon是監視應用程式訪問系統註冊表的公用程式。大家都知道在應用程式中使用註冊表一般都調用WinAPI Regxxx,而Regxxx最終會調用Native API Zwxxx!(參閱Windows NT/2000 DDK Documentation)。Regmon正是通過改變這些常式以達到監視註冊表的目的。Zwxxx的實現方式如下:

    mov eax, ServiceId
    lea edx, ParameterTable
    int 2eh
    ret ParamTableBytes

    這就是所說的NT System Services,是不是與Linux有點相似(只不過Linux使用的是80h中斷而已,它也有ServiceID,如fork系統調用ServiceID為2)。

    System Services在DDK Documentation是如下定義的:

    The set of native, user-mode routines exported by the executive for use only by protected subsystems. Each system service has a name of the form TwoLettersXxxYyy where:
    TwoLetters is the prefix for all system services.
    Xxx is usually a verb, describing the operation of a given service.
    Yyy is generally the object type the service operates on.

    System Services在系統中由兩部分組成,一部分由win32k.sys匯出,另一部分由ntoskrnl.exe提供服務。前者主要完成NT中win32、Posix與Os/2等子系統(subsystems)與核心的通訊,僅能由使用者態的應用程式調用,如user32!WaitMessage等。由於Regmon只涉及後者,所以本文將對其進行討論,以下所有關於System Service的討論均適合兩者!

    上次(Nsfocus Magazine 10)我曾經提及KeServiceDescriptorTable,也說過它的結構如下:

    struct _ServiceDescriptorEntry {
        unsigned int *ServiceTableBase;
        unsigned int *ServiceCounterTableBase;
        unsigned int NumberOfServices;
        unsigned char *ParamTableBase;
    }ServiceDescriptorTableEntry

    ntoskrnl.exe匯出全域變數KeServiceDescriptorTable指向ServiceDescriptorTableEntry(由win32k.sys匯出的System Services也有自己的ServiceDescriptorTable,在Win2000 Server中其Service ID從1000h始,由KeServiceDescriptorTable以下位移50h處指向,其結構與ntosrknl.exe匯出的基本一致,本文不作討論,SoftICE中的ntcall命令在特定情況下可以列出所有的System Service)。

    下面我們先用SoftICE 4.05 For Windows NT/2000來分析分析x86平台Windows 2000 Server Build 2195的情況(以下僅摘錄部分,不同版本不同時刻可能得到的資料未必一樣)

    :dd KeServiceDescriptorTable l 4*4
    //如果看win32k.sys匯出的表請使用dd KeServiceDescriptorTable+50 l 4*4
    //即將KeServiceDescriptorTable向下位移50h處下同
    0008:8046AB80 804704D8             00000000 000000F8 804708BC ..G...........G.
              |      |_ServiceTableBase值 |           |     |_ParamTableBase值
              |                           |_似乎總為0 |
              |_KeServiceDescriptorTable地址          |_NumberOfService

    :dd @KeServiceDescriptorTable l byte(@(KeServiceDescriptorTable+08))*4
    // dd ServiceDescriptorTableEntry->ServiceTableBase l NumberOfService*4
    0008:804704D8 804AB3BF 804AE86B 804BDEF3 8050B034 ..J.k.J...K.4.P.
            |
            |_ServiceID=0的System Service入口地址(依次類推)

    0008:804704E8 804C11F4 80459214 8050C2FF 8050C33F ..L...E...P.?.P.
    0008:804704F8 804B581C 80508874 8049860A 804FC7E2 .XK.t.P...I...O.
    0008:80470508 804955F7 8049C8A6 80448472 804A8D50 .UI...I.r.D.P.J.
    0008:80470518 804B6BFB 804F0CEF 804FCB95 8040189A .kK...O...O...@.
    0008:80470528 804D06CB 80418F66 804F69D4 8049E0CC ..M.f.A..iO...I.
    ...(略)

    :db @(KeServiceDescriptorTable+0c) l byte(@(KeServiceDescriptorTable+08))
    // db ServiceDescriptorTableEntry->ParamTableBase
    0008:804708BC 18 20 2C 2C 40 2C 40 44-0C 18 18 08 04 04 0C 10 . ,,@,@D........
                  |
                  |_ServiceID=0的System Service參數個數*4(即參數個數為18h/4=6)

    0008:804708CC 18 08 08 0C 08 08 04 04-04 0C 04 20 08 0C 14 0C ........... ....
    ...(略)

    要獲得哪個應用程式對系統註冊表有過操作,只要在對其有操作的System Service中注入自己的代碼,也就是改變這些System Service的執行流程,先執行自己的代碼(Regmon中用於記錄供GUI部分使用),接著返回至原先處繼續執行即可。通過以上分析,我們知道只要修改ServiceTableBase到ServiceTableBase+NumberOfService*4範圍的資料就可以改變System Service的執行流程,而只要知道System Service的ServiceID就可以改變這一System Service入口地址在這一地區的位置,那麼又如何得到System Service的Service ID呢!我們可以隨便以ZwOpenKey作個例子:

    :u ZwOpenKey
    ntoskrnl!ZwOpenKey
    0008:80400E2A B867000000 MOV EAX,00000067
                      |                 |_ServiceID
                      |_機器碼(其中第二位元組即ZwOpenKey線性地址加一處就是ServiceID)
    0008:80400E2F 8D542404 LEA EDX,[ESP+04]
    0008:80400E33 CD2E INT 2E
    0008:80400E35 C20C00 RET 000C

    這樣只要知道Zwxxx常式名(即System Service在記憶體中的線性地址),是不是就可以實現我們的目的了呢?來看看Regmon的具體實現代碼吧:
        .
        .
        .
    // 儲存ZwOpenKey原先入口,在HookRegOpenKey中使用
    RealRegOpenKey = SYSCALL( ZwOpenKey );

    // 修改ZwOpenKey流程,指向新的入口,即調用ZwOpenKey時轉向執行HookRegOpenKey
    SYSCALL( ZwOpenKey ) = (PVOID) HookRegOpenKey;
        .
        .
        .

    SYSCALL在intel平台是如下定義的:
    #define SYSCALL(_function) ServiceTable->ServiceTable[ *(PULONG)((PUCHAR)_function+1)]
    ServiceTable->ServiceTable就是我們上面所述的 ServiceDescriptorTableEntry->ServiceTableBase(為了便於描述)。_function+1即ServiceID所在地址。整個運算式即取得_function對應的System Service的入口地址線上性記憶體中的位置。其它定義請參閱Regsys.c與Regsys.h!
    可以使用SoftICE對比一下Regsys.sys裝載前後ServiceTable中System Service入口地址的變化,加深對System Service攔截的理解.

    好了現在我們知道Regmon的基本實現方法了(當然真正要實現此功能還要考慮很多問題,如保護態應用程式與核心驅動程式之間的通訊、線程同步等等)。

    讓我們再來看看KeServiceDescriptorTable的另一個應用吧!如果我們重新分配段記憶體池,構造自己的ServiceTable與ParamTable數組(必須複製系統原有的System Services,否則...),然後修改結構中ServiceTableBase與ParamTableBase,使其指向自己的ServiceTable與ParamTable,再修改一下NumberOfServices,是不是可以增加自個兒的System Service呢!如果你有興趣的話可以參閱<<UNDOCUMENTED NT>>。這書我也沒見過,只知道網上它名聲在外。哦,還要感謝James Shatlyk給我提供隨書配套例子代碼。如果您見過此書(不知道有沒有Chinese 版,E文也可),能不能與我聯絡聯絡?

    談完System Service後,再讓我們來看看Regmon是如何在Driver中取得系統進程名的。
首先談談KTEB(Kernel Thread Environment Block)與KPEB(Kernel Process Environment Block),與TEB(其實應該是User-TEB)一樣,KPEB/KTEB則紀錄著系統核心進程/線程資訊。要瞭解KTEB、KPEB,首先要知道如何得到當前進程/線程中它們的基址,可以先看看Native API IoGetCurrentProcess。在Windows 2000 DDK Document中它是如下定義的:
    PEPROCESS IoGetCurrentProcess();
    使用IDA Pro或SoftICE,可知其在ntoskrnl.exe僅是由幾條彙編指令實現的:

    mov eax,fs:[00000124]
    mov eax,[eax+00000044] //NT 4.0以下這個值應為[eax+40]
    ret

    這個Native API很有典型性,它的第一條指令取得當前線程的KTEB,而整個API剛好將相對於KTEB始68(即16進位44)位元組處取得當前進程的KPEB返回給使用者。你可以使用SoftICE驗證一下。

    讓我們再來看看其具體是如何?的:

    //----------------------------------------------------------------------
    //
    // GetProcessNameOffset
    //
    // In an effort to remain version-independent, rather than using a
    // hard-coded into the KPEB (Kernel Process Environment Block), we
    // scan the KPEB looking for the name, which should match that
    // of the GUI process
    //
    //----------------------------------------------------------------------
    ULONG GetProcessNameOffset()
    {
        PEPROCESS curproc;
        int i;
        DbgPrint(("GetProcessNameOffset/n"));
        curproc = PsGetCurrentProcess();

        //
        // Scan for 12KB, hopping the KPEB never grows that big!
        //
        for( i = 0; i < 3*PAGE_SIZE; i++ ) {

            if( !strncmp( SYSNAME, (PCHAR) curproc + i, strlen(SYSNAME) )) {

                return i;
            }
        }

        //
        // Name not found - oh, well
        //
        return 0;
    }

    //----------------------------------------------------------------------
    //
    // GetProcess
    //
    // Uses undocumented data structure offsets to obtain the name of the
    // currently executing process.
    //
    //----------------------------------------------------------------------
    FILTERSTATUS GetProcess( PCHAR Name )
    {
        PEPROCESS curproc;
        char *nameptr;
        ULONG i;

        //
        // We only try and get the name if we located the name offset
        //
        if( ProcessNameOffset ) {

            curproc = PsGetCurrentProcess();
            nameptr = (PCHAR) curproc + ProcessNameOffset;
            strncpy( Name, nameptr, 16 );

        } else {

            strcpy( Name, "???");
        }
        .
        .
        .
    }

    這段代碼從Regmon中NT Driver部分摘錄,詳細可參閱Regsys.c。
    這兩函數主要功能是取得進程名稱,供程式使用。大家都知道在Driver部分不能簡單的調用WIN32 API,而NT執行體提供的NtQuerySystemInformation主要針對所有進程、線程或其他NT內部資訊等,所以我們必須尋找其它方法(一般方法是跟蹤相應的Win32 API用Debugger對其進行艱苦但充滿挑戰充滿樂趣的逆向工程,然後找出其在NT執行體中的具體實現過程,你也可以使用此方法對本文所提及的進行驗證)。

    Regmon中這兩個函數通過尋找KPEB取得進程名,GetProcessNameOffset主要是調用PsGetCurrentProcess取得KPEB基址,然後搜尋KPEB,得到ProcessName相對KPEB的位移量,存放在全域變數ProcessNameOffset中。在NT/2000 DDK中如下定義PsGetCurrentProcess:
    #define PsGetCurrentProcess() IoGetCurrentProcess()
而IoGetCurrentProcess已經在前面討論過了。
    作者在3頁記憶體地區(x86中一頁為4k)尋找,從程式中注釋可知他也不知道是否會超出此範圍,還有程式段中SYSNAME被定義為system,因為調用Driver中DriverEntry入口正是由system進程調度(GetProcessNameOffset在DriverEntry中調用)。你也可以使用SoftICE查出特定Windows NT/2000版本中ProcessNameOffset的值。在x86平台Windows 2000 Server Build 2195中它為1fch(NT 4.0與3.51中為1dch),然後根據這個值找幾個進程核對核對。

    GetProcess將當前進程的KPEB基址加上ProcessNameOffset值取得當前進程(Regmon中即叫用作業Registry的Native API進程)的名稱。

  至於KPEB/KTEB等的具體結構,各位元組的具體含義,由於其所謂的Undocument,我查MSDN,到各新聞群組,追蹤NT核心,也沒找到其中的一小部分,這也是我著手寫此篇的用意,希望懂得的高手,朋友能互相交流交流,還有本文有誤之處,還望您能指出並與我說說,謝謝!

參考資料:
    1.Regmon 4.22原始碼
    2.Windows 2000 DDK Documentation

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.