windows建立程式的使用者態和核心態互動----小話windows(1)

Just4life發表於2013-04-25

作者:陳曦

日期:2012-6-19 12:28:30

環境:[win7旗艦版 SP1  ; Intel i3  x86, 支援64位  ;vs2010  ; wrk-v1.2  ; Virtual PC 2007   windows 2003 server sp1 standard edition映象 ;WinDbg 6.11.0001.404(x86) ]

轉載請註明出處


Q: 在windows下,呼叫CreateProcess這個API來建立程式,它內部究竟做了什麼?

A: 對於作業系統,一般肯定是分層的。核心將處理最終的建立程式操作,但是它的上層可能有一些模組,進行一些引數合法性判斷或者為了可移植考慮的判斷。windows同樣不例外。看看windows下面核心上面的模組:


不妨先寫一個CreateProcess的程式,通過逆向工程得到內部呼叫的東西。


Q: 如下程式碼:

  1. #include <windows.h> 
  2. #include <stdio.h> 
  3.  
  4. int main() 
  5.     PROCESS_INFORMATION processInfo; 
  6.     STARTUPINFOA    startupInfo; 
  7.     ZeroMemory(&processInfo, sizeof(processInfo)); 
  8.     ZeroMemory(&startupInfo, sizeof(startupInfo)); 
  9.     startupInfo.cb = sizeof(startupInfo); 
  10.  
  11.     BOOL ret = CreateProcessA(NULL, "c:\\windows\\system32\\cmd.exe", NULL, NULL, false,  
  12.                 0, NULL, NULL, &startupInfo, &processInfo); 
  13.     if(ret) 
  14.     printf("create process ok...\n"); 
  15.     else 
  16.     { 
  17.     printf("create process failed...\n"); 
  18.     printf("error is %d", GetLastError()); 
  19.     } 
  20.  
  21.     return 0; 
#include <windows.h>
#include <stdio.h>

int main()
{
    PROCESS_INFORMATION processInfo;
    STARTUPINFOA	startupInfo;
    ZeroMemory(&processInfo, sizeof(processInfo));
    ZeroMemory(&startupInfo, sizeof(startupInfo));
    startupInfo.cb = sizeof(startupInfo);

    BOOL ret = CreateProcessA(NULL, "c:\\windows\\system32\\cmd.exe", NULL, NULL, false, 
				0, NULL, NULL, &startupInfo, &processInfo);
    if(ret)
	printf("create process ok...\n");
    else
    {
	printf("create process failed...\n");
	printf("error is %d", GetLastError());
    }

    return 0;
}


編譯成CreateProcessDemo.exe, 執行:

可以看出,它正確地建立了程式。


A:  下面我們將找出哪個模組包含CreateProcessA函式。進入VS的命令列工具,

使用如下命令dumpbin.exe  /all  CreateProcessDemo.exe  >  d:\dumpbin_createprocessdemo.txt得到所有dump的資訊,找到如下資訊:

  1. KERNEL32.dll 
  2.                41819C Import Address Table 
  3.                41803C Import Name Table 
  4.                     0 time date stamp 
  5.                     0 Index of first forwarder reference 
  6.  
  7.                   A4 CreateProcessA 
  8.                  1C0 GetCurrentProcess 
  9.                  4C0 TerminateProcess 
  10.                  162 FreeLibrary 
  11.                  4F1 VirtualQuery 
  12.                  214 GetModuleFileNameW 
  13.                  24A GetProcessHeap 
  14.                  2CB HeapAlloc 
  15.                  2CF HeapFree 
  16.                  279 GetSystemTimeAsFileTime 
  17.                  1C1 GetCurrentProcessId 
  18.                  1C5 GetCurrentThreadId 
  19.                  293 GetTickCount 
  20.                  3A7 QueryPerformanceCounter 
  21.                   CA DecodePointer 
  22.                  4A5 SetUnhandledExceptionFilter 
 KERNEL32.dll
                41819C Import Address Table
                41803C Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                   A4 CreateProcessA
                  1C0 GetCurrentProcess
                  4C0 TerminateProcess
                  162 FreeLibrary
                  4F1 VirtualQuery
                  214 GetModuleFileNameW
                  24A GetProcessHeap
                  2CB HeapAlloc
                  2CF HeapFree
                  279 GetSystemTimeAsFileTime
                  1C1 GetCurrentProcessId
                  1C5 GetCurrentThreadId
                  293 GetTickCount
                  3A7 QueryPerformanceCounter
                   CA DecodePointer
                  4A5 SetUnhandledExceptionFilter


可以看出,CreateProcessA是在KERNEL32.DLL中被引用的。


Q: 現在我們可以在kernel32.dll中檢視CreateProcessA的呼叫關係了?

A: 是的。使用ida,開啟系統目錄下面的kernel32.dll, 並查詢CreateProcessA函式的位置:


可以在靠近最後的時候發現呼叫CreateProcessInternalA例程。


Q: 繼續查詢CreateProcessInternalA例程的內部實現,它最終會呼叫CreateProcessInternalW來實現。繼續查詢CreateProcessInternalW的實現,發現它最終會呼叫NtCreateUserProcess例程(在xp或者server 2003下,會呼叫NtCreateProcessEx).此例程不在kernel32.dll中,它在哪裡?

A: 正如上面的圖示描述,它以nt開頭,它在ntdll.dll中。同樣適用ida開啟ntdll.dll, 找到NtCreateUserProcess的實現:



Q: ZwCreateUserProcess是什麼,好像和NtCreateUserProcess是一樣的?

A: 僅僅在ntdll.dll中來說,依照上面的截圖,它們是一致的;可是在核心中,它們不完全一致。Nt開頭的例程會進行訪問許可權和引數合法性判斷,而Zw不會,Zw它可以由核心模式程式碼直接使用;同時,呼叫Zw開頭的例程會將先前的模式改變為核心模式,而使用Nt開頭的例程不能。

這裡,可以看下xp或2003 server下的nt核心對應的程式碼:

  1. // 
  2. //NtCreateProcess函式是呼叫它 的。 
  3. //此函式呼叫PspCreateProcess函式。 
  4. // 
  5.  
  6. NTSTATUS              // typedef  ULONG  NTSTATUS; 
  7. NtCreateProcessEx( 
  8.     __out PHANDLE ProcessHandle, 
  9.     __in ACCESS_MASK DesiredAccess, 
  10.     __in_opt POBJECT_ATTRIBUTES ObjectAttributes, 
  11.     __in HANDLE ParentProcess, 
  12.     __in ULONG Flags,        //建立標誌 
  13.     __in_opt HANDLE SectionHandle, 
  14.     __in_opt HANDLE DebugPort, 
  15.     __in_opt HANDLE ExceptionPort, 
  16.     __in ULONG JobMemberLevel 
  17.     ) 
  18.  
  19. /*++
  20. Routine Description: //例程描述
  21.     This routine creates a process object.    //建立一個程式物件
  22. Arguments:   //引數
  23.     ProcessHandle - Returns the handle for the new process. //返回新程式的控制程式碼指標
  24.     DesiredAccess - Supplies the desired access modes to the new process. //提供新程式的訪問許可權
  25.     ObjectAttributes - Supplies the object attributes of the new process. //提供新程式的物件屬性
  26.     .
  27.     .
  28.     .
  29. --*/ 
  30.  
  31.     NTSTATUS Status; 
  32.  
  33.     // 
  34.     //  在除錯模式下才有用;否則,被定義為空語句 
  35.     // 
  36.  
  37.     PAGED_CODE();    
  38.  
  39.     //如果執行緒之前執行的模式不是核心模式 
  40.     if (KeGetPreviousMode() != KernelMode) {  
  41.  
  42.         // 
  43.         // Probe all arguments    //檢查所有的引數 
  44.         // 
  45.  
  46.         try
  47.             ProbeForWriteHandle (ProcessHandle); //ProbeForWriteHandle巨集在ex.h檔案中定義 
  48.         } except (EXCEPTION_EXECUTE_HANDLER) { 
  49.             return GetExceptionCode (); 
  50.         } 
  51.     } 
  52.  
  53.     if (ARGUMENT_PRESENT (ParentProcess)) {  //如果引數--父程式控制程式碼存在 
  54.         Status = PspCreateProcess (ProcessHandle, //呼叫PspCreateProcess函式 
  55.                                    DesiredAccess, 
  56.                                    ObjectAttributes, 
  57.                                    ParentProcess, 
  58.                                    Flags, 
  59.                                    SectionHandle, 
  60.                                    DebugPort, 
  61.                                    ExceptionPort, 
  62.                                    JobMemberLevel); 
  63.     } else {    //否則,返回引數不合法的錯誤 
  64.         Status = STATUS_INVALID_PARAMETER; 
  65.     } 
  66.  
  67.     return Status; 
//
//NtCreateProcess函式是呼叫它 的。
//此函式呼叫PspCreateProcess函式。
//

NTSTATUS              // typedef  ULONG  NTSTATUS;
NtCreateProcessEx(
    __out PHANDLE ProcessHandle,
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in HANDLE ParentProcess,
    __in ULONG Flags,        //建立標誌
    __in_opt HANDLE SectionHandle,
    __in_opt HANDLE DebugPort,
    __in_opt HANDLE ExceptionPort,
    __in ULONG JobMemberLevel
    )

/*++ 

Routine Description: //例程描述

    This routine creates a process object.    //建立一個程式物件

Arguments:   //引數

    ProcessHandle - Returns the handle for the new process. //返回新程式的控制程式碼指標

    DesiredAccess - Supplies the desired access modes to the new process. //提供新程式的訪問許可權

    ObjectAttributes - Supplies the object attributes of the new process. //提供新程式的物件屬性
    .
    .
    .

--*/

{
    NTSTATUS Status;

	//
	//  在除錯模式下才有用;否則,被定義為空語句
	//

    PAGED_CODE();   

	//如果執行緒之前執行的模式不是核心模式
    if (KeGetPreviousMode() != KernelMode) { 

        //
        // Probe all arguments    //檢查所有的引數
        //

        try {
            ProbeForWriteHandle (ProcessHandle); //ProbeForWriteHandle巨集在ex.h檔案中定義
        } except (EXCEPTION_EXECUTE_HANDLER) {
            return GetExceptionCode ();
        }
    }

    if (ARGUMENT_PRESENT (ParentProcess)) {  //如果引數--父程式控制程式碼存在
        Status = PspCreateProcess (ProcessHandle, //呼叫PspCreateProcess函式
                                   DesiredAccess,
                                   ObjectAttributes,
                                   ParentProcess,
                                   Flags,
                                   SectionHandle,
                                   DebugPort,
                                   ExceptionPort,
                                   JobMemberLevel);
    } else {    //否則,返回引數不合法的錯誤
        Status = STATUS_INVALID_PARAMETER;
    }

    return Status;
}



 

Q: 剛剛所說的使用者模式是如何進入核心模式的?

A: 上面的ntdll.dll中執行依然是使用者模式,NtCreateUserProcess通過向eax傳入中斷號, edx傳入7FFE0300H為引數地址來進行系統呼叫,進入核心模式。它的內部實現為:

  1. NTSTATUS 
  2. NtCreateProcess(  //建立程式 
  3.     __out PHANDLE ProcessHandle,   //程式控制程式碼的指標 
  4.     __in ACCESS_MASK DesiredAccess, 
  5.     __in_opt POBJECT_ATTRIBUTES ObjectAttributes, 
  6.     __in HANDLE ParentProcess,    //父程式控制程式碼 
  7.     __in BOOLEAN InheritObjectTable,  //是否繼承控制程式碼表 
  8.     __in_opt HANDLE SectionHandle, 
  9.     __in_opt HANDLE DebugPort,         //除錯埠 
  10.     __in_opt HANDLE ExceptionPort     //異常埠 
  11.     ) 
  12.     ULONG Flags = 0; 
  13.  
  14.     if ((ULONG_PTR)SectionHandle & 1) { 
  15.         Flags |= PROCESS_CREATE_FLAGS_BREAKAWAY; 
  16.     } 
  17.  
  18.     if ((ULONG_PTR) DebugPort & 1) { 
  19.         Flags |= PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT; 
  20.     } 
  21.  
  22.     if (InheritObjectTable) {  //是否繼承控制程式碼表 
  23.         Flags |= PROCESS_CREATE_FLAGS_INHERIT_HANDLES; 
  24.     } 
  25.  
  26.     //呼叫NtCreateProcessEx函式 
  27.     return NtCreateProcessEx (ProcessHandle, 
  28.                               DesiredAccess, 
  29.                               ObjectAttributes OPTIONAL, //OPTIONAL只是表示可選引數的意思,並不會對編譯造成影響 
  30.                               ParentProcess, 
  31.                               Flags,   //上面計算得到的Flags 
  32.                               SectionHandle, 
  33.                               DebugPort, 
  34.                               ExceptionPort, 
  35.                               0); 
NTSTATUS
NtCreateProcess(  //建立程式
    __out PHANDLE ProcessHandle,   //程式控制程式碼的指標
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
    __in HANDLE ParentProcess,    //父程式控制程式碼
    __in BOOLEAN InheritObjectTable,  //是否繼承控制程式碼表
    __in_opt HANDLE SectionHandle,
    __in_opt HANDLE DebugPort,         //除錯埠
    __in_opt HANDLE ExceptionPort     //異常埠
    )
{
    ULONG Flags = 0;

    if ((ULONG_PTR)SectionHandle & 1) {
        Flags |= PROCESS_CREATE_FLAGS_BREAKAWAY;
    }

    if ((ULONG_PTR) DebugPort & 1) {
        Flags |= PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT;
    }

    if (InheritObjectTable) {  //是否繼承控制程式碼表
        Flags |= PROCESS_CREATE_FLAGS_INHERIT_HANDLES;
    }

	//呼叫NtCreateProcessEx函式
    return NtCreateProcessEx (ProcessHandle,
                              DesiredAccess,
                              ObjectAttributes OPTIONAL, //OPTIONAL只是表示可選引數的意思,並不會對編譯造成影響
                              ParentProcess,
                              Flags,   //上面計算得到的Flags
                              SectionHandle,
                              DebugPort,
                              ExceptionPort,
                              0);
}


因為未找到win7的WRK原始碼資訊,上面為nt核心的實現。注意,如上程式執行是在win7系統下的執行。


Q: 為了更清楚地得到內部呼叫細節,是否可以使用虛擬機器來測試一下?

A: 可以,使用虛擬機器進行核心模式除錯,來驗證上面的整個過程;當然,因為使用的虛擬機器系統是2003 server sp1, 它的核心和win7核心是不同的,所以會有不一致的地方。


Q: 當虛擬機器和偵錯程式啟動後(此具體過程略,關於wrk的配置可以在網上搜尋),然後做什麼?

A: 先在win7下面編寫一個可以在虛擬機器系統2003 server sp1下可以跑的程式.可以是任意的了。


Q: 如下程式碼:

  1. #include <stdio.h> 
  2.  
  3. int main() 
  4.     printf("hello\n"); 
  5.  
  6.     return 0; 
#include <stdio.h>

int main()
{
    printf("hello\n");

    return 0;
}

為了保證在低版本下可執行,編譯時將使用的庫設定成使用靜態庫。

編譯成hello.exe.放到虛擬機器系統桌面上。此時讓hello.exe執行嗎?

A: 不要著急,我們先在NtCreateProcessEx開始處加斷點。如下:


如上圖紅色地方。接著讓虛擬機器繼續執行,在虛擬機器的桌面雙擊hello.exe執行起來。


Q: 雙擊後,偵錯程式遇到斷點停頓下來:

如上,紫色位置為windbg跑到斷點時的截圖。此時,是否就可以看看呼叫堆疊了?

A: 是的。使用在kd提示符後輸入k命令得到堆疊資訊:

可以看出從kernel32中的CreateProcessW,到CreateProcessInternalW, 又到ntdll中的NtCreateProcessEx,接著調入陷入核心例程KiFastSystemCallRet進入nt模組(即為核心模組)。在核心中,它呼叫NtCreateProcessEx函式來完成具體的工作。


Q: ntdll中的NtCreateProcessEx與nt中的NtCreateProcessEx名稱一樣,會有衝突嗎?

A: 它們不是簡單的上層和下層的模組,中間又有了一些模組,所以名稱一樣不會直接造成衝突;並且ntdll呼叫核心例程也肯定不是把函式名稱傳進去的; 編譯器是可以正確地找到了地址,執行時也不會混淆。


Q: 在核心中,NtCreateProcessEx和ZwCreateProcessEx有什麼區別?

A: 使用x  nt!ZwCreateProcessEx先看看核心中是否存在ZwCreateProcessEx:

由上可以看出確實存在。

接著反彙編:

可以看出,它的實現很直接,呼叫系統呼叫具體處理。它和之前看到的NtCreateProcessEx顯然不一樣,NtCreateProcessEx先進行了一些引數和模式的判斷。所以說,ZwCreateProcessEx會將執行的模式轉變成核心模式,因為它通過系統呼叫被迫陷入核心模式。不過,NtCreateProcessEx內部的處理依然是它實際的實現。


Q: 那麼,呼叫結束時如何返回呢?

A: 如下,

在base\ntos\ke\i386\trap.asm中包含了返回時的處理:

  1. kss61: 
  2.  
  3. ; Upon return, (eax)= status code. This code may also be entered from a failed 
  4. ; KiCallbackReturn call. 
  5.  
  6.         mov     esp, ebp                ; deallocate stack space for arguments 
  7.  
  8. ; Restore old trap frame address from the current trap frame. 
  9.  
  10. kss70:  mov     ecx, PCR[PcPrcbData+PbCurrentThread] ; get current thread address 
  11.         mov     edx, [ebp].TsEdx        ; restore previous trap frame address 
  12.         mov     [ecx].ThTrapFrame, edx  ; 
  13.  
  14. ;   System service's private version of KiExceptionExit 
  15. ;   (Also used by KiDebugService) 
  16. ;   Check for pending APC interrupts, if found, dispatch to them 
  17. ;   (saving eax in frame first). 
  18.         public  _KiServiceExit 
  19. _KiServiceExit: 
  20.  
  21.         cli                                         ; disable interrupts 
  22.         DISPATCH_USER_APC   ebp, ReturnCurrentEax 
  23.  
  24. ; Exit from SystemService 
  25.  
  26.         EXIT_ALL    NoRestoreSegs, NoRestoreVolatile 
kss61:

;
; Upon return, (eax)= status code. This code may also be entered from a failed
; KiCallbackReturn call.
;

        mov     esp, ebp                ; deallocate stack space for arguments

;
; Restore old trap frame address from the current trap frame.
;

kss70:  mov     ecx, PCR[PcPrcbData+PbCurrentThread] ; get current thread address
        mov     edx, [ebp].TsEdx        ; restore previous trap frame address
        mov     [ecx].ThTrapFrame, edx  ;

;
;   System service's private version of KiExceptionExit
;   (Also used by KiDebugService)
;
;   Check for pending APC interrupts, if found, dispatch to them
;   (saving eax in frame first).
;
        public  _KiServiceExit
_KiServiceExit:

        cli                                         ; disable interrupts
        DISPATCH_USER_APC   ebp, ReturnCurrentEax

;
; Exit from SystemService
;

        EXIT_ALL    NoRestoreSegs, NoRestoreVolatile


主要就是恢復呼叫時的資訊,繼續執行。

相關文章