Windows NT引导过程源代码分析(三)

 

1.3 建立用户登录会话

Windows 内核在阶段1 初始化的最后,启动了一个用户模式进程——会话管理器子系统(smss)。Smss 进程是Windows 操作系统的关键组成部分,它尽管是一个用户模式进程,但有其特殊性:首先,它是可信的,这意味着它可以做一些其他用户进程无法做的事情,比如创建安全令牌;其次,它直接建立在Windows 内核的基础上,只使用Windows内核提供的系统服务,而不依赖于任何一个环境子系统。这不难理解,因为smss 启动时,Windows 子系统尚未启动,而且,启动Windows 子系统本身正是smss 的任务之一。

Smss 进程启动以后,继续完成系统引导过程。Smss 做的工作有相当一部分依赖于注册表中的设置。在内核的阶段1 初始化过程中,配置管理器已经初始化,但是直到smss进程启动起来,只有SYSTEM 储巢已被加载到内存中。加载其他的储巢也是smss 的职责,它通过调用系统服务NtInitializeRegistry 来初始化注册表。在NtInitializeRegistry 系统服务中,除了用户轮廓储巢以外的所有其他储巢,均会被加载到系统中,并且注册表树也将建立起来。实际上,加载这些储巢的工作由CmpInitializeHiveList 函数来完成,它仅仅加载6 个储巢(包括SYSTEM 储巢),正好是表2.6 中的前6 个储巢。

Smss 在注册表中的主键是HKLM\SYSTEM\CurrentControlSet\Control\Session Manager,用于指示smss 在系统初始化阶段做一些必要的工作。图2.17 显示了在Windows Server2003 SP1 系统中该键的一个典型设置。

按照Session Manager 键中的指示,smss 主线程完成以下事项:

运行在启动时执行的程序,这些程序由BootExecute 值指定。

执行启动时的文件删除或重命名任务,这由FileRenameOperations 子键来指定。

打开已知的DLL,并且在对象管理器名字空间的\KnownDlls 目录下创建相应的内存区对象,这些已知DLL 的列表位于KnownDLLs 子键中。已知DLL 是系统全局共享DLL,包括子系统DLL 等,各种应用程序通常都会用到这些DLL。

创建页面文件,页面文件列表由Memory Management 子键中的PagingFiles 值指定。

建立系统的全局环境变量,这些环境变量由Environment 键下的值指定。

加载Windows 子系统的内核模式模块( win32k.sys ) , 这是通过系统服务NtSetSystemInformation 来完成的,该函数也会调用win32k.sys 中的初始化例程(即入口函数)。子系统内核模块的文件路径由SubSytstems 子键的Kmode 值指定。关于Windows 子系统的初始化过程,请参考9.2 节。

启动Windows 子系统进程(csrss.exe)。子系统进程的命令行字符串由SubSystems子键的Windows 值指定。

代码摘自\private\sm\server\sminit.c

NTSTATUS

SmpLoadDataFromRegistry(

    OUT PUNICODE_STRING InitialCommand

    )

/*++

函数功能:

该函数为NT会话管理器从注册表中加载所有的可配置的数据

--*/

{

    NTSTATUS Status;

    PLIST_ENTRY Head, Next;

    PSMP_REGISTRY_VALUE p;

    PVOID OriginalEnvironment;

    RtlInitUnicodeString( &SmpDebugKeyword, L"debug" );

    RtlInitUnicodeString( &SmpASyncKeyword, L"async" );

    RtlInitUnicodeString( &SmpAutoChkKeyword, L"autocheck" );

    InitializeListHead( &SmpBootExecuteList );

    InitializeListHead( &SmpPagingFileList );

    InitializeListHead( &SmpDosDevicesList );

    InitializeListHead( &SmpFileRenameList );

    InitializeListHead( &SmpKnownDllsList );

    InitializeListHead( &SmpExcludeKnownDllsList );

    InitializeListHead( &SmpSubSystemList );

    InitializeListHead( &SmpSubSystemsToLoad );

    InitializeListHead( &SmpSubSystemsToDefer );

    InitializeListHead( &SmpExecuteList );      //初始化相关数据列表

    Status = RtlCreateEnvironment( TRUE, &SmpDefaultEnvironment );   //创建默认的环境块

    if (!NT_SUCCESS( Status )) {

        KdPrint(("SMSS: Unable to allocate default environment - Status == %X\n", Status ));

        return( Status );

        }

    // In order to track growth in smpdefaultenvironment, make it sm's environment while doing the registry groveling and then restore it

    OriginalEnvironment = NtCurrentPeb()->ProcessParameters->Environment;

    NtCurrentPeb()->ProcessParameters->Environment = SmpDefaultEnvironment;

    Status = RtlQueryRegistryValues( RTL_REGISTRY_CONTROL,

                                     L"Session Manager",

                                     SmpRegistryConfigurationTable,

                                     NULL,

                                     NULL

                                   );

    SmpDefaultEnvironment = NtCurrentPeb()->ProcessParameters->Environment;

    NtCurrentPeb()->ProcessParameters->Environment = OriginalEnvironment;

    if (!NT_SUCCESS( Status )) {

        KdPrint(( "SMSS: RtlQueryRegistryValues failed - Status == %lx\n", Status ));

        return( Status );

        }

    Status = SmpInitializeDosDevices();  //初始化DOS设备

    if (!NT_SUCCESS( Status )) {

        KdPrint(( "SMSS: Unable to initialize DosDevices configuration - Status == %lx\n", Status ));

        return( Status );

        }

    Head = &SmpBootExecuteList;

    while (!IsListEmpty( Head )) {

        Next = RemoveHeadList( Head );

        p = CONTAINING_RECORD( Next,

                               SMP_REGISTRY_VALUE,

                               Entry

                             );

#ifdef SMP_SHOW_REGISTRY_DATA

        DbgPrint( "SMSS: BootExecute( %wZ )\n", &p->Name );

#endif

        SmpExecuteCommand( &p->Name, 0 );

        RtlFreeHeap( RtlProcessHeap(), 0, p );

        }     //运行在启动时执行的程序,这些程序由BootExecute值来指定

    SmpProcessFileRenames();   //执行启动时的文件删除或重命名任务,这由FileRenameOperations子键来指定

    Status = SmpInitializeKnownDlls();  //初始化已知的dlls

    if (!NT_SUCCESS( Status )) {

        KdPrint(( "SMSS: Unable to initialize KnownDll configuration - Status == %lx\n", Status ));

        return( Status );

        }

    // 处理页面文件列表.

    Head = &SmpPagingFileList;

    while (!IsListEmpty( Head )) {

        Next = RemoveHeadList( Head );

        p = CONTAINING_RECORD( Next,

                               SMP_REGISTRY_VALUE,

                               Entry

                             );

#ifdef SMP_SHOW_REGISTRY_DATA

        DbgPrint( "SMSS: PagingFile( %wZ )\n", &p->Name );

#endif

        SmpAddPagingFile( &p->Name );

        RtlFreeHeap( RtlProcessHeap(), 0, p );

        }

    // 创建页面文件

    SmpCreatePagingFiles();

    // 完成注册表初始化

    NtInitializeRegistry(FALSE);

    Status = SmpCreateDynamicEnvironmentVariables( );

    if (!NT_SUCCESS( Status )) {

        return Status;

        }

    // Translate the system partition information stored during IoInitSystem into a DOS path and store in Win32-standard location.

    SmpTranslateSystemPartitionInformation();

    Head = &SmpSubSystemList;

    while (!IsListEmpty( Head )) {

        Next = RemoveHeadList( Head );

        p = CONTAINING_RECORD( Next,

                               SMP_REGISTRY_VALUE,

                               Entry

                             );

        if ( !_wcsicmp( p->Name.Buffer, L"Kmode" )) {

            BOOLEAN TranslationStatus;

            UNICODE_STRING FileName;   //"Kmode"指定子系统内核模块的文件路径

            TranslationStatus = RtlDosPathNameToNtPathName_U(

                                    p->Value.Buffer,

                                    &FileName,

                                    NULL,

                                    NULL

                                    );

            if ( TranslationStatus ) {

                PVOID State;

                Status = SmpAcquirePrivilege( SE_LOAD_DRIVER_PRIVILEGE, &State );

                if (NT_SUCCESS( Status )) {

                    Status = NtSetSystemInformation(

                                SystemExtendServiceTableInformation,

                                (PVOID)&FileName,

                                sizeof(FileName)

                                );

                    RtlFreeHeap(RtlProcessHeap(), 0, FileName.Buffer);

                    SmpReleasePrivilege( State );

                    if ( !NT_SUCCESS(Status) ) {

                        Status = STATUS_SUCCESS;

                        }

                    }

                }

            else {

                Status = STATUS_OBJECT_PATH_SYNTAX_BAD;

                }

            }

#ifdef SMP_SHOW_REGISTRY_DATA

        DbgPrint( "SMSS: Unused SubSystem( %wZ = %wZ )\n", &p->Name, &p->Value );

#endif

        RtlFreeHeap( RtlProcessHeap(), 0, p );

        }

    Head = &SmpSubSystemsToLoad;

    while (!IsListEmpty( Head )) {

        Next = RemoveHeadList( Head );

        p = CONTAINING_RECORD( Next,

                               SMP_REGISTRY_VALUE,

                               Entry

                             );

#ifdef SMP_SHOW_REGISTRY_DATA

        DbgPrint( "SMSS: Loaded SubSystem( %wZ = %wZ )\n", &p->Name, &p->Value );

#endif

        if (!_wcsicmp( p->Name.Buffer, L"debug" )) {

            SmpExecuteCommand( &p->Value, SMP_SUBSYSTEM_FLAG | SMP_DEBUG_FLAG );

            }

        else {

            SmpExecuteCommand( &p->Value, SMP_SUBSYSTEM_FLAG );

            }

        RtlFreeHeap( RtlProcessHeap(), 0, p );

        }

    Head = &SmpExecuteList;

    if (!IsListEmpty( Head )) {

        Next = Head->Blink;

        p = CONTAINING_RECORD( Next,

                               SMP_REGISTRY_VALUE,

                               Entry

                             );

        RemoveEntryList( &p->Entry );

        *InitialCommand = p->Name;

        // This path is only taken when people want to run ntsd -p -1 winlogon

        // This is nearly impossible to do in a race free manner.

// In some cases, we can get in a state where we can not properly fail a debug API. 

// This is due to the subsystem switch that occurs when ntsd is invoked on csr. If csr is relatively idle, this does not occur. 

// If it is active when you attach, then we can get into a potential race. The slimy fix is to do a 5 second delay

        // if the command line is anything other that the default.

            {

                LARGE_INTEGER DelayTime;

                DelayTime.QuadPart = Int32x32To64( 5000, -10000 );

                NtDelayExecution(

                    FALSE,

                    &DelayTime

                    );

            }

        }

    else {

        RtlInitUnicodeString( InitialCommand, L"winlogon.exe" );

        InitialCommandBuffer[ 0 ] = UNICODE_NULL;

        LdrQueryImageFileExecutionOptions( InitialCommand,

                                           L"Debugger",

                                           REG_SZ,

                                           InitialCommandBuffer,

                                           sizeof( InitialCommandBuffer ),

                                           NULL

                                         );

        if (InitialCommandBuffer[ 0 ] != UNICODE_NULL) {

            wcscat( InitialCommandBuffer, L" " );

            wcscat( InitialCommandBuffer, InitialCommand->Buffer );

            RtlInitUnicodeString( InitialCommand, InitialCommandBuffer );

            KdPrint(( "SMSS: InitialCommand == '%wZ'\n", InitialCommand ));

            }

        }

    while (!IsListEmpty( Head )) {

        Next = RemoveHeadList( Head );

        p = CONTAINING_RECORD( Next,

                               SMP_REGISTRY_VALUE,

                               Entry

                             );

#ifdef SMP_SHOW_REGISTRY_DATA

        DbgPrint( "SMSS: Execute( %wZ )\n", &p->Name );

#endif

        SmpExecuteCommand( &p->Name, 0 );

        RtlFreeHeap( RtlProcessHeap(), 0, p );

        }

#ifdef SMP_SHOW_REGISTRY_DATA

    DbgPrint( "SMSS: InitialCommand( %wZ )\n", InitialCommand );

#endif

    return( Status );

}

如果利用Microsoft 提供的符号信息来调试WRK 系统,则可以观察到以上这些行为均在smss 主模块(smss.exe)的SmpLoadDataFromRegistry 函数中完成。由此也可以看出,Windows 子系统作为会话(session)的一部分,它的实例由smss 来启动。Smss 除了依据注册表中的设置来完成必要的系统引导工作以外,还执行以下的步骤,以进一步提供多会话和本地登录服务:

创建LPC 端口对象(\SmApiPort),以接收“加载子系统”或“创建会话”的请求。

启动登录进程(winlogon.exe),登录进程将会承担起与用户登录有关的事项。

NTSTATUS

SmpInit(

    OUT PUNICODE_STRING InitialCommand,

    OUT PHANDLE WindowsSubSystem

    )

{

    NTSTATUS st;

    OBJECT_ATTRIBUTES ObjA;

    HANDLE SmpApiConnectionPort;

    UNICODE_STRING Unicode;

    NTSTATUS Status;

    ULONG HardErrorMode;

    SmBaseTag = RtlCreateTagHeap( RtlProcessHeap(),

                                  0,

                                  L"SMSS!",

                                  L"INIT\0"

                                  L"DBG\0"

                                  L"SM\0"

                                );

    // Make sure we specify hard error popups

    HardErrorMode = 1;

    NtSetInformationProcess( NtCurrentProcess(),

                             ProcessDefaultHardErrorMode,

                             (PVOID) &HardErrorMode,

                             sizeof( HardErrorMode )

                           );

    RtlInitUnicodeString( &SmpSubsystemName, L"NT-Session Manager" );

    RtlInitializeCriticalSection(&SmpKnownSubSysLock);

    InitializeListHead(&SmpKnownSubSysHead);

    RtlInitializeCriticalSection(&SmpSessionListLock);

    InitializeListHead(&SmpSessionListHead);

    SmpNextSessionId = 1;

    SmpNextSessionIdScanMode = FALSE;

    SmpDbgSsLoaded = FALSE;

    // 初始化安全描述符以获取更多的特权

    // (保护模式尚未从注册表中读取).

    st = SmpCreateSecurityDescriptors( TRUE );

    if ( !NT_SUCCESS(st) ) {

        return(st);

        }

    InitializeListHead(&NativeProcessList);

    SmpHeap = RtlProcessHeap();

    RtlInitUnicodeString( &Unicode, L"\\SmApiPort" );

    InitializeObjectAttributes( &ObjA, &Unicode, 0, NULL, SmpApiPortSecurityDescriptor);

    st = NtCreatePort(

            &SmpApiConnectionPort,

            &ObjA,

            sizeof(SBCONNECTINFO),

            sizeof(SMMESSAGE_SIZE),

            sizeof(SBAPIMSG) * 32

            );    //此处创建LPC端口对象

    ASSERT( NT_SUCCESS(st) );

    SmpDebugPort = SmpApiConnectionPort;

    st = RtlCreateUserThread(

            NtCurrentProcess(),

            NULL,

            FALSE,

            0L,

            0L,

            0L,

            SmpApiLoop,

            (PVOID) SmpApiConnectionPort,

            NULL,

            NULL

            );

    ASSERT( NT_SUCCESS(st) );

    st = RtlCreateUserThread(

            NtCurrentProcess(),

            NULL,

            FALSE,

            0L,

            0L,

            0L,

            SmpApiLoop,

            (PVOID) SmpApiConnectionPort,

            NULL,

            NULL

            );

    ASSERT( NT_SUCCESS(st) );

    // Configure the system

    Status = SmpLoadDataFromRegistry( InitialCommand );

    if (NT_SUCCESS( Status )) {

        *WindowsSubSystem = SmpWindowsSubSysProcess;

        }

    return( Status );

}

加载win32k.sys的代码位于WRK中base\ntos\ex\sysinfo.c

//省略部分代码

case SystemExtendServiceTableInformation:

            {

                ULONG_PTR EntryPoint;

                UNICODE_STRING Image;

                PVOID ImageBaseAddress;

                PDRIVER_INITIALIZE InitRoutine;

                PIMAGE_NT_HEADERS NtHeaders;

                PVOID SectionPointer;

                DRIVER_OBJECT Win32KDevice;

  // 判断系统信息缓冲区长度是否正确

                if (SystemInformationLength != sizeof(UNICODE_STRING)) {

                    return STATUS_INFO_LENGTH_MISMATCH;

                }

// 如果先前的模式不是内核模式,就验证是否能够加载win32k子系统

                if (PreviousMode != KernelMode) {

// 检测是否为会话首进程

                    if (MmIsSessionLeaderProcess(PsGetCurrentProcess()) == FALSE) {

                        return STATUS_PRIVILEGE_NOT_HELD;

                    }

                    

// 检测是否有权限加载驱动

                    if (SeSinglePrivilegeCheck(SeLoadDriverPrivilege, UserMode) == FALSE) {

                        return STATUS_PRIVILEGE_NOT_HELD;

                    }

                   

// 检测win32k子系统是否被加载

                    try {

                       

     // 测试并读取unicode字符串描述符

                        ProbeAndReadUnicodeStringEx(&Image,(PUNICODE_STRING)SystemInformation);

// 检测unicode字符串的长度是否正确

                        if (Image.Length != WIN32K_PATH_SIZE) {

                            return STATUS_PRIVILEGE_NOT_HELD;

                        }

                        

// 测试unicode字符串的缓冲区并检测路径名称

                        ProbeForReadSmallStructure(Image.Buffer,

                                                   WIN32K_PATH_SIZE,

                                                   sizeof(UCHAR));

                        if (memcmp(Image.Buffer, &Win32kFullPath[0], WIN32K_PATH_SIZE) != 0) {

                            return STATUS_PRIVILEGE_NOT_HELD;

                        }

    

                        

// 初始化win32全路径名称的字符串描述符

                        Image.Buffer = &Win32kFullPath[0];

                        Image.MaximumLength = WIN32K_PATH_SIZE;

                    } except(EXCEPTION_EXECUTE_HANDLER) {

                        return GetExceptionCode();

                    }

// 递归调用这个内核服务,迫使先前的模式进入内核

                    Status = ZwSetSystemInformation(SystemExtendServiceTableInformation,

                                                    (PVOID)&Image,

                                                    sizeof(Image));

                    return Status;

                }

                

// 先前的模式即为内核-加载指定的驱动

                Image = *(PUNICODE_STRING)SystemInformation;

                Status = MmLoadSystemImage(&Image,

                                           NULL,

                                           NULL,

                                           MM_LOAD_IMAGE_IN_SESSION,

                                           &SectionPointer,

                                           (PVOID *)&ImageBaseAddress);

                if (NT_SUCCESS(Status) == FALSE) {

                    return Status;

                }

               

// 获取映像基地址

                NtHeaders = RtlImageNtHeader(ImageBaseAddress);

                if (NtHeaders == NULL) {

                    MmUnloadSystemImage(SectionPointer);

                    return STATUS_INVALID_IMAGE_FORMAT;

                }

                

// 调用win32k.sys的PE头中指定的入口函数EntryPoint

                EntryPoint = NtHeaders->OptionalHeader.AddressOfEntryPoint;

                EntryPoint += (ULONG_PTR)ImageBaseAddress;

                InitRoutine = (PDRIVER_INITIALIZE)EntryPoint;

                RtlZeroMemory(&Win32KDevice, sizeof(Win32KDevice));

                ASSERT(KeGetCurrentIrql() == 0);

                Win32KDevice.DriverStart = (PVOID)ImageBaseAddress;

                Status = (InitRoutine)(&Win32KDevice, NULL);

                ASSERT(KeGetCurrentIrql() == 0);

                

// 不成功的话就卸载

                if (NT_SUCCESS(Status) == FALSE) {

                    MmUnloadSystemImage(SectionPointer);

                } else {

                    MmSessionSetUnloadAddress(&Win32KDevice);

                }

            }

            break;

Smss 的主线程在完成了以上描述的初始化工作以后,将在csrss 进程和winlogon 进程的句柄上等待。一旦等待成功,则意味着这两个进程中至少有一个退出了,于是系统崩溃。Windows 操作系统依赖于这两个进程,所以,它们也是保持Windows 操作系统正常运行不可缺少的组成部分。

接下来引导过程转到了winlogon 进程。它的职责包括:

创建初始的窗口站(WinSta0),并且为该窗口站创建一个桌面线程和RIT(Raw InputThread)以便接收标准输入。

创建登录桌面和默认桌面。登录桌面只有winlogon 进程才可以访问,因而也称为winlogon 桌面;而默认桌面允许其他进程访问。因此,当登录桌面活动时,其他进程无法访问与该桌面关联的代码或数据。Windows 用这种方式来保护与口令相关的操作,以及锁定桌面或解除桌面锁定这样的安全操作。

启动服务控制管理器(SCM,Service Control Manager)进程(services.exe)。在启动Windows 服务的过程中,会有更多的窗口站被创建。SCM 进程加载所有“自动-启动”类型的服务和设备驱动程序。

启动本地安全权威子系统( lsass ) 进程。然后与它建立一个LPC 连接(LsaAuthenticationPort 端口),以便在登录、注销和口令操作过程中交换信息。

关于这里提到的窗口站、桌面、桌面线程和RIT ,它们是Windows 窗口管理中的重要组成部分,由Windows 子系统内核模块win32k.sys 实现。Winlogon 的登录是通过一种称为GINA(图形化识别和认证,Graphical Identification andAuthentication)的可扩展机制来完成的,例如,默认的GINA 为msgina,可显示标准的Windows 登录对话框,指示用户输入用户名和口令。Winlogon 对于Ctrl+Alt+Del 按键序列做了特殊的保护,以防止其他程序截取此按键序列。第三方厂商可以利用像指纹或虹膜这样的生物特征来识别用户,然后从一个加密的数据库中提取出他们的口令,这样就不需要用户再输入口令了。

当winlogon 通过GINA 获得了用户名和口令以后,它首先调用lsass 函数LsaLookupAuthenticationPackage 以获得一个认证包的句柄,然后调用LsaLogonUser 将登录信息传递给认证包。一旦认证包认证了当前用户,则winlogon 继续该用户的登录过程;否则认证失败。因此,认证过程是由lsass 来完成的。

在登录过程的最后,winlogon 检查注册表HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Winlogon\Userinit 的值,并创建一个进程来运行该值字符串。该值串的默认值为userinit.exe 程序的路径。Userinit 进程加载当前登录用户的轮廓,然后检查HKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell 的值,并创建一个进程来运行该值字符串;如果该值不存在,则运行HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Winlogon\Shell 的值,其默认值为explorer.exe。然后,userinit 进程退出。由于当前登录会话的Shell 程序(explorer.exe)已经启动,因此用户可以在桌面上操作了。

至此,引导过程结束,用户登录到系统中,并可通过explorer.exe 程序的用户界面操作系统中的资源,例如文件系统中的目录和文件;也可以启动各种应用程序。下图显示了从会话管理器启动一直到Shell 程序启动之间发生的事项。

在系统引导过程中,有多个进程被创建,包括smss 、csrss 、winlogon 、SCM(services.exe)、lsass、userinit(登录完成后自动退出)、explorer 等。这些进程都是操作系统的一部分,而且大多数还是可信的。实际上,在系统启动以后,当用户开始在Shell程序中操作时,他们通常可以看到更多的进程,这其中有些进程是由SCM 启动并初始化的。在Windows 中,SCM 提供了在系统启动时启动进程的机制,这些进程被称为服务(service)进程,它们类似于UNIX 中的守护进程。例如,Web 服务器或者数据库服务器显然应该以这样的方式来启动,因为无论是否有人登录到系统中,它们都必须被启动。在每个服务进程中,它可以宿纳一个或多个Windows 服务(Windows service)。

Smss和Winlogon进程的初始化过程代码不完整,这里给出两个文件的idb,借助IDA的graph view可以很清晰地看出流程.

Smss:主要的函数有SmConnectToSm,SmpLoadDataFromRegistry以及SmpLoadSubSystemForMuSession
--------------------- 
作者:cosmoslife 
来源:CSDN 
原文:https://blog.csdn.net/cosmoslife/article/details/7855425 
版权声明:本文为博主原创文章,转载请附上博文链接!

你可能感兴趣的:(C/C++)