现在的位置: 首页 > 综合 > 正文

Windows的进程创建

2013年12月12日 ⁄ 综合 ⁄ 共 9254字 ⁄ 字号 评论关闭

漫谈兼容内核之十七:

再谈Windows的进程创建

毛德操

 

 

在漫谈之十中。我根据“Microsoft Windows Internals 4e”一书第六章的叙述介绍了Windows的进程创建和映像装入的过程。但是,由于缺乏源代码的支撑,这样的叙述对于只是想对此有个大致了解的读者固然不无帮助,可是对于需要实际从事研发、特别是兼容内核开发的读者就显得过于抽象笼统了。不幸,Windows内核的代码是不公开的,我们无法通过Windows内核的代码来确切地了解和理解它的方方面面。虽说是“科学无禁区”,但是现实往往不那么理想。幸运的是我们有了ReactOS。当然,ReactOS不等于Windows,但是读者将会看到,至少就Windows进程的创建而言,它的代码和“Internals”书中的叙述还是相当吻合的。本文引用的代码均取自ReactOS0.2.6版,大致上是一年前的版本。

正如“Internals”所述,Windows进程的创建是个复杂的过程,分成好几个步骤,涉及到好几个系统调用。Win32 API函数CreateProcessW()就是这些步骤的整合。这是由动态连接库kernel32.dll导出的库函数,其主体就在这个DLL中。还有个CreateProcessA(),是与CreateProcessW()连在一起的,前者接受ASCII码的字符串,而后者要求使用“宽字符”、即Unicode的字符串。实际上CreateProcessA()只是把ASCII字符串转换成Unicode字符串,然后就调用CreateProcessW()

 

[CreateProcessW()]

 

BOOL STDCALL

CreateProcessW (LPCWSTR lpApplicationName,  LPWSTR lpCommandLine,

            LPSECURITY_ATTRIBUTES lpProcessAttributes,

            LPSECURITY_ATTRIBUTES lpThreadAttributes,

            BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment,

            LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo,

            LPPROCESS_INFORMATION lpProcessInformation)

{

   . . . . . .

 

   TidyCmdLine = GetFileName(lpCurrentDirectory, lpApplicationName, lpCommandLine,

                                    Name, sizeof(Name) / sizeof(WCHAR));

   . . . . . .

   if (lpApplicationName != NULL && lpApplicationName[0] != 0)

     {

        wcscpy (TempApplicationNameW, lpApplicationName);

        i = wcslen(TempApplicationNameW);

        if (TempApplicationNameW[i - 1] == L'.')

        {

            TempApplicationNameW[i - 1] = 0;

        }

        else

        {

            s = max(wcsrchr(TempApplicationNameW, L'//'),

                   wcsrchr(TempApplicationNameW, L'/'));

            if (s == NULL)

            {

                s = TempApplicationNameW;

            }

            else

            {

                s++;

            }

            e = wcsrchr(s, L'.');

            if (e == NULL)

            {

                wcscat(s, L".exe");

                e = wcsrchr(s, L'.');

            }

        }

   }

   else if (L'"' == TidyCmdLine[0])

   {

        wcscpy(TempApplicationNameW, TidyCmdLine + 1);

        s = wcschr(TempApplicationNameW, L'"');

        if (NULL == s)

        {

            return FALSE;

        }

        *s = L'/0';

   }

   else

   {

        wcscpy(TempApplicationNameW, TidyCmdLine);

        s = wcschr(TempApplicationNameW, L' ');

        if (NULL != s)

        {

            *s = L'/0';

        }

   }

   s = max(wcsrchr(TempApplicationNameW, L'//'), wcsrchr(TempApplicationNameW, L'/'));

   if (NULL == s)

   {

        s = TempApplicationNameW;

   }

   s = wcsrchr(s, L'.');

   if (NULL == s)

   {

        wcscat(TempApplicationNameW, L".exe");

   }

 

   if (!SearchPathW(NULL, TempApplicationNameW, NULL,

                        sizeof(ImagePathName)/sizeof(WCHAR), ImagePathName, &s))

   {

     return FALSE;

   }

 

   e = wcsrchr(s, L'.');

   if (e != NULL && (!_wcsicmp(e, L".bat") || !_wcsicmp(e, L".cmd")))

   {

       // the command is a batch file

       IsBatchFile = TRUE;

       if (lpApplicationName != NULL && lpApplicationName[0])

       {

         // FIXME: use COMSPEC for the command interpreter

         wcscpy(TempCommandLineNameW, L"cmd /c ");

         wcscat(TempCommandLineNameW, lpApplicationName);

         lpCommandLine = TempCommandLineNameW;

         wcscpy(TempApplicationNameW, L"cmd.exe");

      if (!SearchPathW(NULL, TempApplicationNameW, NULL,

                    sizeof(ImagePathName)/sizeof(WCHAR), ImagePathName, &s))

      {

         return FALSE;

      }

    }

    else

    {

         return FALSE;

    }

   }

 

   /* Process the application name and command line */

   RtlInitUnicodeString(&ImagePathName_U, ImagePathName);

   RtlInitUnicodeString(&CommandLine_U, IsBatchFile ? lpCommandLine : TidyCmdLine);

 

   . . . . . .

 

   /* Initialize the current directory string */

   if (lpCurrentDirectory != NULL)

   {

       RtlInitUnicodeString(&CurrentDirectory_U, lpCurrentDirectory);

   }

   else

   {

       GetCurrentDirectoryW(256, TempCurrentDirectoryW);

       RtlInitUnicodeString(&CurrentDirectory_U, TempCurrentDirectoryW);

   }

 

   /* Create a section for the executable */

  

   hSection = KlMapFile (ImagePathName);

 

因为这是个W32 API函数,对于调用参数这里就不作解释了。

代码中首先是对应用名和命令行的处理。这里要考虑许多不同的情况。例如:

l         调用参数lpApplicationName lpCommandLine可能是空指针。

l         命令行中的应用名、即可执行文件名可能是加引号的,也可能是不加引号的。

l         应用名可能是个完整的路径,也可能只是不带路径的应用名。

l         应用名可能带扩展名(例如.exe),也可能不带扩展名。

l         应用名后面可能带一个点,但是不带扩展名字符。

l         如果不带扩展名,那么应用名可能是指一个.exe文件,也可能是指.com.bat文件。

要是在文件系统的指定目录中发现应用名所代表的是.com.bat文件,那就要把应用名改成cmd.exe,而把原来的应用名和命令行作为传递给cmd.exe的参数。

具体的代码就留给读者自己阅读了。由于这里因篇幅的考虑而作了删节,读者最好还是阅读原始的ReactOS代码。代码中wcscpy()一类的函数相当于strcpy()一类,只不过所处理的是宽字符而不是普通的ASCII字符。

这里的最后一步操作是KlMapFile(),目的是为目标映像建立一个共享内存区、即Section对象。不过,对于CreateProcessW()而言,这其实还只能说是第一步。

 

[CreateProcessW() > KlMapFile()]

 

HANDLE KlMapFile(LPCWSTR lpApplicationName)

{

   . . . . . .

 

   InitializeObjectAttributes(&ObjectAttributes, &ApplicationNameString,

                        OBJ_CASE_INSENSITIVE, NULL, SecurityDescriptor);

 

   /* Try to open the executable */

 

   Status = NtOpenFile(&hFile, SYNCHRONIZE|FILE_EXECUTE|FILE_READ_DATA,

            &ObjectAttributes, &IoStatusBlock,

            FILE_SHARE_DELETE|FILE_SHARE_READ,

            FILE_SYNCHRONOUS_IO_NONALERT|FILE_NON_DIRECTORY_FILE);

 

   . . . . . .

   Status = NtCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, NULL,

                                       PAGE_EXECUTE, SEC_IMAGE,

                                       hFile);

   NtClose(hFile);

   . . . . . .

   return(hSection);

}

 

先打开目标映像文件,再通过系统调用NtCreateSection()为已经打开的映像文件创建一个共享内存区对象。注意NtCreateSection()只是创建了一个共享内存区对象,并将它与一个已打开文件挂上钩,而并未将其映射到任何进程的用户空间,所以函数名KlMapFile()不免误导。由于调用时使用了参数SEC_IMAGE,表明目标文件是个映像文件,NtCreateSection()会对目标文件的头部进行检验,以确认其为PE格式的可执行映像。如果发现并非PE格式映像,就会通过hSection返回0

回到CreateProcessW()的代码中,如果KlMapFile()的返回值非0就说明为目标映像文件创建的共享内存区对象已经成功,下面就可以用这个已打开对象(hSection为其Handle)去创建进程了。可是,如果返回值是0,那就说明目标映像文件并不是一个PE格式的文件。既然扩充名是.exe,却又不是PE格式的文件,那是怎么回事呢?原来,DOS格式的可执行文件也用.exe作为扩充名,这种文件的头部并非PE格式,但是也有DOS格式的“签名” IMAGE_DOS_SIGNATURE可供验证。

 

[CreateProcessW()]

 

   if (hSection == NULL)

   {

        . . . . . .

        DPRINT("Inspecting Image Header for image type id/n");

        . . . . . .

        InitializeObjectAttributes(&ObjectAttributes, &ApplicationNameString,

                       OBJ_CASE_INSENSITIVE, NULL, SecurityDescriptor);

 

        // Try to open the executable

        Status = NtOpenFile(&hFile, SYNCHRONIZE|FILE_EXECUTE|FILE_READ_DATA,

              &ObjectAttributes, &IoStatusBlock,

              FILE_SHARE_DELETE|FILE_SHARE_READ,

              FILE_SYNCHRONOUS_IO_NONALERT|FILE_NON_DIRECTORY_FILE);

        . . . . . .

 

        // Read the dos header

        Offset.QuadPart = 0;

        Status = ZwReadFile(hFile, NULL, NULL, NULL, &Iosb,

                      &DosHeader, sizeof(DosHeader), &Offset, 0);

        . . . . . .

        if (Iosb.Information != sizeof(DosHeader)) {

            DPRINT("Failed to read dos header from file/n");

            SetLastErrorByStatus(STATUS_INVALID_IMAGE_FORMAT);

            return FALSE;

        }

 

        // Check the DOS signature

        if (DosHeader.e_magic != IMAGE_DOS_SIGNATURE) {

            DPRINT("Failed dos magic check/n");

            SetLastErrorByStatus(STATUS_INVALID_IMAGE_FORMAT);

            return FALSE;

        }

        NtClose(hFile);

 

        DPRINT("Launching VDM.../n");

        return CreateProcessW(L"ntvdm.exe", (LPWSTR)lpApplicationName,

             lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags,

             lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation);

   }

 

DOS格式的可执行映像都是16位的,不能直接在32位的WinNT内核上运行,而得要借助系统工具软件vdm.exe的支持才能运行。为了在32x86结构的CPU芯片上兼容为16位芯片开发的软件,Intelx86芯片上提供了一种“虚拟机模式”。而VDM.、即“虚拟DOS机”,则是微软开发的一种系统软件,利用x86芯片的虚拟机模式为16位的DOS应用软件供一个虚拟的DOS环境,使DOS应用软件感觉到就好像是在DOS操作系统上运行一样。可想而知,ntvdm.exe就是实现于WinNT内核上的VDM。注意ntvdm.exe本身是32位软件,它所支持的目标软件才是16位的,ntvdm.exe连同其所支持的目标软件一起作为一个进程在WinNT内核上运行。所以,对于16位软件这里递归地调用CreateProcessW(),而以ntvdm.exe作为新的应用名,但是命令行则不变。

当然,16位软件只是少数,WinNT上运行的绝大多数软件都是32PE格式的,所以NtCreateSection()一般都会返回一个非0Handle,从而跳过上面这段代码。我们继续往下看:

 

[CreateProcessW()]

 

   /* Get some information about the executable */

   Status = ZwQuerySection(hSection, SectionImageInformation, &Sii, sizeof(Sii), &i);

   . . . . . .

   if (0 != (Sii.Characteristics & IMAGE_FILE_DLL))

   {

     NtClose(hSection);

     DPRINT("Can't execute a DLL/n");

     SetLastError(ERROR_BAD_EXE_FORMAT);

     return FALSE;

   }

 

   if (IMAGE_SUBSYSTEM_WINDOWS_GUI != Sii.Subsystem

       &&  IMAGE_SUBSYSTEM_WINDOWS_CUI != Sii.Subsystem)

   {

     NtClose(hSection);

     DPRINT("Invalid subsystem %d/n", Sii.Subsystem);

     SetLastError(ERROR_CHILD_NOT_COMPLETE);

     return FALSE;

   }

 

   /* Initialize the process object attributes */

   if(lpProcessAttributes != NULL)

   {

     if(lpProcessAttributes->bInheritHandle)

     {

       ProcAttributes |= OBJ_INHERIT;

     }

     ProcSecurity = lpProcessAttributes->lpSecurityDescriptor;

   }

 

   InitializeObjectAttributes(&ProcObjectAttributes, NULL,

                              ProcAttributes, NULL, ProcSecurity);

   /* initialize the process priority class structure */

   PriorityClass.Foreground = FALSE;

  

   if(dwCreationFlags & IDLE_PRIORITY_CLASS)

   {

     PriorityClass.PriorityClass = PROCESS_PRIORITY_CLASS_IDLE;

   }

   else if(dwCreationFlags & BELOW_NORMAL_PRIORITY_CLASS)

   {

     PriorityClass.PriorityClass = PROCESS_PRIORITY_CLASS_BELOW_NORMAL;

   }

   else if . . . . . .

   . . . . . .

 

   /* Create a new process */

   Status = NtCreateProcess(&hProcess, PROCESS_ALL_ACCESS,

                        &ProcObjectAttributes, NtCurrentProcess(),

                        bInheritHandles, hSection, NULL, NULL);

   . . . . . .

 

为目标映像创建的Section对象中含有许多来自目标映像PE头部的信息,可以通过ZwQuerySection()、即NtQuerySection()询问、获取这些信息。所获取的信息在数据结构Sii中,这是个SECTION_IMAGE_INFORMATION数据结构,其Subsystem字段表明了映像的模式。一个PE映像可以是GUI模式的、面向“视窗”和图像的应用,也可以是“控制台”、即CUI模式的面向命令行和字符的应用。但是二者必居其一,否则就错了。

然后,这里对一个局部量的数据结构PriorityClass进行了一些设置,设置的依据来自调用参数dwCreationFlags中的一些标志位

接着就是对NtCreateProcess()的调用了。不过刚才的PriorityClass

抱歉!评论已关闭.