⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄

📁 兼容内核漫谈 适合想将Windows上的程序移植到其它平台上的朋友研究查看
💻
📖 第 1 页 / 共 5 页
字号:
漫谈兼容内核之十七:再谈Windows的进程创建

[i] 毛德操[/i]


    在漫谈之十中。我根据“Microsoft Windows Internals 4e”一书第六章的叙述介绍了Windows的进程创建和映像装入的过程。但是,由于缺乏源代码的支撑,这样的叙述对于只是想对此有个大致了解的读者固然不无帮助,可是对于需要实际从事研发、特别是兼容内核开发的读者就显得过于抽象笼统了。不幸,Windows内核的代码是不公开的,我们无法通过Windows内核的代码来确切地了解和理解它的方方面面。虽说是“科学无禁区”,但是现实往往不那么理想。幸运的是我们有了ReactOS。当然,ReactOS不等于Windows,但是读者将会看到,至少就Windows进程的创建而言,它的代码和“Internals”书中的叙述还是相当吻合的。本文引用的代码均取自ReactOS的0.2.6版,大致上是一年前的版本。
    正如“Internals”所述,Windows进程的创建是个复杂的过程,分成好几个步骤,涉及到好几个系统调用。Win32 API函数CreateProcessW()就是这些步骤的整合。这是由动态连接库kernel32.dll导出的库函数,其主体就在这个DLL中。还有个CreateProcessA(),是与CreateProcessW()连在一起的,前者接受ASCII码的字符串,而后者要求使用“宽字符”、即Unicode的字符串。实际上CreateProcessA()只是把ASCII字符串转换成Unicode字符串,然后就调用CreateProcessW()。

[code][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);[/code]

    因为这是个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()而言,这其实还只能说是第一步。

[code][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);
}[/code]

    先打开目标映像文件,再通过系统调用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可供验证。

[code][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);
   }[/code]
    DOS格式的可执行映像都是16位的,不能直接在32位的WinNT内核上运行,而得要借助系统工具软件vdm.exe的支持才能运行。为了在32位x86结构的CPU芯片上兼容为16位芯片开发的软件,Intel在x86芯片上提供了一种“虚拟机模式”。而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上运行的绝大多数软件都是32位PE格式的,所以NtCreateSection()一般都会返回一个非0的Handle,从而跳过上面这段代码。我们继续往下看:

[code][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;
   }

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -