windows核心编程---进程

进程

-进程组成
1.一个内核对象,保存进程统计信息,操作系统用它来管理进程。
2.一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据。还报错动态分配内存。

-两类应用程序
GUI,CUI

用Microsoft Visual Studio创建一个应用程序项目时,集成开发环境会设置各种链接器开关,使链接器将子系统的正确类型嵌入最终生成的可执行文件中。对CUI,链接器开关是 /SUBSYSTEM:CONSOLE,对GUI,则是/SUBSYSTEM:WINDOWS。
操作系统加载程序会检查可执行文件映像的文件头,获取这个子系统值,如此值表明是一个CUI程序,加载程序会自动确保有一个可用的文本控制台窗口。

-入口点函数
1.Windows c/c++的入口点函数

Int WINAPI _tWinMain
(
HINSTANCE hInstanceExe,
HINSTANCE,
PTSTR pszCmdLine,
int nCmdShow
);

int _tmain
(
int argc,
TCHAR *argv[],
TCHAR *envp[]
);

2.操作系统实际不调用我们的入口点函数,它调用由c/c++运行库实现并在链接时使用-entry:命令行选项来设置的一个c/c++运行时启动函数。
该函数将初始化c/c++运行库。
确保我们的代码执行前,我们声明的全局和静态c++对象都被正确地构造。

3.

应用类型 入口点函数 嵌入可执行文件的启动函数
ANSI+GUI _tWinMain WinMainCRTStartup
Unicode+GUI _tWinMain WinMainCRTStartup
ANSI+CUI _tmain mainCRTStartup
Unicode+CUI _tmain wmainCRTStartup

4.Visual C++自带c运行库源代码。
启动函数用途:
获取 指向新进程完整命令行指针。
获取 指向新进程环境遍历的指针。
初始化 c/c++运行库的全局变量。
初始化 c运行库内存分配函数和其它底层I/O例程使用的堆。
调用所有全局和静态c++类对象的构造函数。
调用 应用程序的入口点函数
入口点函数返回后,启动函数将调用c运行库函数exit,向其传递返回值nMainRetVal。

exit:
调用_onexit函数调用注册的任何一个函数
调用所有全局和静态c++类对象的析构函数
在DEBUG生成中,如设置了 _CRTDBG_LEAK_CHECK_DF标志,就通过调用_CrtDumpMemoryLeaks来生成内存泄漏报告。
调用操作系统的ExitProcess函数,向其传入nMainRetVal。这导致系统杀死进程,设置它的退出代码。

-进程实例句柄–HINSTANCE
1.加载到进程地址空间的每一个可执行文件或DLL文件都被赋予了一个独一无二的实例句柄。
2.可执行文件的实例被当作(w)WinMain的第一个参数hInstanceExe传入。
3.HMODULE和HINSTANCE是一回事。两者可以互用。
4.(w)WinMain的hInstanceExe参数实际值是一个内存基地址,系统将可执行文件的映像加载到进程地址空间中的这个位置。
5.可执行文件的映像具体加载到那个基地址由链接器决定。默认地址,用/BASE:address显示指定的。
6.获取可执行文件或DLL文件被加载到进程地址空间的位置

HMODULE GetModuleHandle
(
// 传递以0为终止符的字符串,
// 指定在主调进程地址空间中加载的一个可执行文件或DLL文件的名称
// 传入NULL时返回主调进程可执行文件的基地址
PCTSTR pszModule
);

7.了解代码正在什么模块中运行
7.1.链接器伪变量__ImageBase,指向当前正在运行模块基地址。
7.2.调用GetModuleHandleEx,将GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS作为参数1。
将当前函数地址作为参数二。
最后一个是指向HMODULE的指针。

-进程的命令行
1.系统在创建一个新进程时,会传一个命令行给它。
2.C运行库的启动代码开始执行一个GUI应用时,用GetCommandLine获取完整命令行,忽略可执行文件名称。将剩余部分传给pszCmdLine。
3.

// 对命令行进行分解
PWSTR* CommandLineToArgvW
(
// 命令行
PWSTR pszCmdLine,
// 执行后被设置为命令行中实参的数目
int *pNumArgs
);

-进程的环境变量
1.每个进程都有一个与它关联的环境块。

// 举例
=::=\...
VarName1=VarValue1\0
...

2.

2.1.
// 
PTSTR GetEnvironmentStrings();

//
BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock);

2.2.
main入口点函数的 TCHAR *env[];

3.空格有意义

// 两者值不同
XYZ= Windows
ABC=Windows
// 两者变量名不同
XYZ =Home
XYZ=Work

4.用户登陆Windows时,系统会创建外壳进程,并将一组环境字符串与其关联。
系统通过检查注册表中的两个注册表项来获得初始的环境字符串。

// 应用于系统的所有环境变量
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
// 应用于当前登陆用户的所有环境变量
HKEY_CURRENT_USER\Environment

5.更新了环境变量后,会给应用的窗口发送WM_SETTINGCHANGE消息。
6.生成子进程时,父进程可控制哪些环境变量允许在子进程中创建相同的副本。
7.

DWORD GetEnvironmentVariable
(
// 指向环境变量名
PCTSTR pszName,
// 保存环境变量值缓冲区
PTSTR pszValue,
// 缓冲区大小。
// 传递0时,返回需要的缓冲区所需字符数(包含终止符)
DWORD cchValue
);

8.字符串替换

// 两个%之间部分的内容就是一个可替换字符串
%USERPROFILE%\Documents
// 假设USERPROFILE环境变量值为C:\User,替换后为
C:\User\Documents

//
DWORD ExpandEnvironmentStrings
(
// 包含可替换环境变量字符串的一个字符串地址
PCTSTR pszSrc,
// 接收扩展后字符串的一个缓冲区的地址
PTSTR pszDst,
// 缓冲区大小。字符数表示。
// 传入0时,返回所需 字符数(包含终止符)
DWORD chSize
);

9.字符串修改

BOOl SetEnvironmentVariable
(
// 变量名存在,则修改其值
// 变量名不存在,则添加
PCTSTR pszName,
// 为NULL,从环境块删除该变量
PCTSTR pszValue
);

-进程的关联性
进程中的线程可以在主机的任何CPU上执行,也可强迫线程在可用CPU的一个 子集上运行。

-进程的错误模式
每个进程都关联了一组标志,这些标志的作用是让系统知道进程如何响应严重错误。进程可以调用SetErrorMode来告诉系统如何处理这些错误。

UINT SetErrorMode(UINT fuErrorMode);
标志 描述
SEM_FAILCRITICALERRORS 不显示严重错误处理程序消息框
SEM_NOGPFAULTERRORBOX 不显示常规保护错误消息框
SEM_NOOPENFILEERRORBOX 查找文件失败时,不显示消息框
SEM_NOALIGNMENTFAULTEXCEPT 自动修复内存对齐错误

默认下,子进程会继承父进程错误模式标志。

-进程当前所在驱动器和目录
1.如不提供完整的路径名,Windows函数会在当前驱动器的当前目录查找文件和目录。
2.系统在内部跟踪记录着一个进程的当前驱动器和目录。
3.

// 获取所在进程的当前驱动器和目录
// 缓冲区不够大时,不会向缓冲区复制内容,返回 所需字符数(包含终止符'\0')
// 调用成功,返回字符串字符数(不含终止符)
DWORD GetCurrentDirectory
(
DWORD cchCurDir,// 一般传递 MAX_PATH是安全的
PTSTR pszCurDir
);

// 设置所在进程的当前驱动器和目录
BOOL SetCurrentDirectory
(
PCTSTR pszCurDir
);

-进程的当前目录
1.系统记录着进程的当前驱动器和目录,但没有记录每个驱动器的当前目录。
2.利用操作系统支持,可以处理多个驱动器的当前目录。这个支持通过环境字符串来提供。
3.

// 举例
=C:=C:\Utility\Bin
=D:=D:\Program Files

指出进程在C驱动器的当前目录为 \Utility\Bin
4.如调用一个函数,且传入的路径名限定的是当前驱动器以外的驱动器。系统会在进程的环境块中查找与指定驱动器号关联的变量。
如找到与指定驱动器号关联的变量,系统就将变量的值作为当前目录使用。如没找到,系统就假定指定驱动器的当前目录是它的根目录。
5.
假定进程的当前目录为C:\Utility\Bin,而我们调用CreateFile来打开D:ReadMe.txt,则系统会查找环境变量=D:。由于=D:变量是存在的,所以尝试从D:\Program Files目录打开ReadMe.txt文件。如果=:D变量不存在,系统就会试着从D盘的根目录打开ReadMe.txt文件。

Windows的文件函数,不会添加或更改驱动器号环境变量。
6.
C运行库函数 _chdir来更改当前目录时,同时会调用 SetCurrentDirectory和 SetEnvironmentVariable。使不同驱动器的当前目录得以保留。
7.
如父进程创建了一个希望传给子进程的环境块。子进程的环境块就不会自动继承父进程的当前目录。子进程的当前目录默认为每个驱动器的根目录。如希望子进程继承父进程的当前目录,父进程须在生成子进程前,创建这些驱动器号环境变量,并把它们添加到环境块。
8.

// 获得进程当前目录
DWORD GetFullPathName
(
PCTSTR pszFile,
DWORD cchPath,
PTSTR pszPath,
PTSTR *ppszFilePart
);

TCHAR szCurDir[MAX_PATH];
DWORD cchLength = GetFullPathName(TEXT("C:"), MAX_PATH, szCurDir, NULL);

-系统版本
1.很多时候,应用需判断所运行的Windows系统的版本。
2.

// 高位字节:次版本号
// 低位字节:主版本号
DWORD GetVersion();

BOOL GetVersionEx(POSVERSIONINFOEX pVersionInformation);

typedef struct
{
    DWORD dwOSVersionInfoSize;// 结构尺寸
    DWORD dwMajorVersion;// 系统主版本号
    DWORD dwMinorVersion;// 系统次版本号
    DWORD dwBuildNumber;// 系统构建版本号
    DWORD dwPlatformId;// 系统支持的套件
    TCHAR szCSDVersion[128];// 已安装操作系统额外信息
    WORD wServicePackMajor;// OSVERSIONINFO结构无 最新安装的Service Pack主版本号
    WORD wServicePackMinor;// OSVERSIONINFO结构无 最新安装的Service Pack次版本号
    WORD wSuiteMask;// 系统可以的suite(s)
    BYTE wProductType;// OSVERSIONINFO结构无
    BYTE wReserved;// OSVERSIONINFO结构无 将来使用
}
OSVERSIONINFOEX, *POSVERSIONINFOEX
;

// 验证通过,非0
// 0,验证失败或调用错误
BOOL VerifyVersionInfo
(
// 需设置dwOSVersionInfoSize为结构尺寸
// 需设置结构内想验证字段目标值
POSVERSIONINFOEX pVersionInformation,
// 要验证字段掩码组合
// VER_MINORVERSION
// VER_MAJORVERSION
// VER_BUILDNUMBER
// VER_PLATFORMID
// VER_SERVICEPACKMINOR
// VER_SERVICEPACKMAJOR
// VER_SUITENAME
// VER_PRODUCT_TYPE
DWORD dwTypeMask,
// 决定函数如何将系统的版本信息与我们希望的版本信息比较
DWORDLONG dwlConditionMask
);

// 创建合适的 dwlConditionMask
// 需对每个要验证字段调用一次此函数
VER_SET_CONDITION
(
DWORDLONG dwlConditionMask,
ULONG dwTypeBitMask,
// 比较方式
// VER_EQUAL
// VER_GREATER
// VER_GREATER_EQUAL
// VER__LESS
// VER_LESS_EUQAL
ULONG dwConditionMask
);

-CreateProcess函数

BOOL CreateProcess
(
PCTSTR pszApplicationName,
// 在CreateProcess内部会修改pszCommandLine。
// 在CreateProcess返回前,会将这个字符串还原为原来的形式。
// 如把命令行字符串放在文件映像的只读部分,会引起访问违规
PTSTR pszCommandLine,
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES psaThread,
BOOL bInheritHandles,
DWORD fdwCreate,
PVOID pvEnvironment,
PCTSTR pszCurDir,
PSTARTUPINFO psiStartInfo,
PPROCESS_INFORMATION ppiProcInfo
);

1.一个线程调用CreateProcess时,系统将创建一个进程内核对象,其初始计数为1。
2.系统为新进程创建一个虚拟地址空间,并将可执行文件(和所有必要的DLL)的代码及数据加载到进程的地址空间。
3.系统为新进程的主线程创建一个线程内核对象。这个主线程一开始就会执行C/C++运行时的启动例程,它是由链接器设为应用程序入口的,最终调用WinMain,wWinMain,main,wmain。如系统成功创建了 新进程和主线程,函数返回TRUE。
4.CreateProcess在进程完全初始化好之前就返回TRUE,这意味着操作系统加载程序尚未尝试定位所有必要的DLL。如一个DLL找不到或不能正确初始化,进程会终止。

5.参数解释
5.1.pszCommandLine
pszApplicationName为NULL时,
CreateProcess解析pszCommandLine字符串时,会检查字符串中的第一个标记,并假定此标记是我们想运行的可执行文件的名称。如可执行文件的名称没有扩展名,会默认是exe扩展名。

CreateProcess按以下顺序搜索可执行文件:
主调进程EXE文件所在目录
主调进程当前目录
Windows系统目录
Windows目录
PATH环境变量中列出的目录

如包含了完整路径,则使用完整路径。

如系统找到了 可执行文件,就创建新进程,并将可执行文件的代码和数据映射到新进程的地址空间。系统然后调用由链接器设为应用程序入口点的C/C++运行时启动例程。[C/C++运行时启动例程会检查进程的命令行,将可执行文件名后的第一个实参地址传给wWinMain的pszCmdLine]

5.2.pszApplication不为NULL
指定的文件名须包含拓展名后缀。
文件含完整路径时,使用完整路径。
不含路径时,假定文件位于当前目录。

即使在pszApplicationName中指定了文件名,CreateProcess也会将pszCommandLine参数中的内容作为新进程的命令行传递。

5.3.psaProcess,psaThread
允许通过上述两个参数,为进程对象和线程对象指定安全性。为NULL时,相应对象采用默认的安全描述符。同时,还可指定进程对象,线程对象 对于父进程后续生成的子进程是否是可继承的。

5.4.bInheritHandles
决定新生成的子进程是否要继承父进程中可继承的内核对象。

5.5.fdwCreate—创建标识
CREATE_SUSPENDED 让系统在创建新进程的同时挂起其主线程。
CREATE_DEFAULT_ERROR_MODE 新进程采用默认的错误模式。
CREATE_BREAKAWAY_FROM_JOB 允许一个作业中的进程生成一个和作业无关的进程。
EXTENDED_STARTUPINFO_PRESENT 向操作系统表明传给psiStartInfo参数的是一个STARTUPINFOEX结构。
优先级类标志 优先级类决定了相对于其它进程的线程,这个进程中的线程的调度方式。

5.5.pvEnvironment
指向一块内存,其中包含新进程要使用的环境字符串。
大多时候,为此参数传入NULL。这将导致子进程继承其父进程使用的一组环境字符串。

PVOID GetEnvironmentStrings();

此函数获取主调进程正在使用的环境字符串数据块的地址。
可将这函数返回的地址用作CreateProcess函数的pvEnvironment参数的值。如为pvEnvironment传入NULL,CreateProcess就会这样做。

// 释放前面获取的环境字符串数据块
BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock);

5.6.pszCurDir
允许父进程设置子进程的当前驱动器和目录。如这个参数为NULL,则新进程的工作目录与生成新进程的应用一样。如此参数不为NULL,则pszCurDir须指向一个用0为终止符的字符串。包含我们想要的工作驱动器和目录。

5.7.psiStartInfo

typedef struct _STARTUPINFO
{
    // 结构尺寸信息
    DWORD cb;
    // 保留
    PSTR lpReserved;
    // 标识一个名称,表明要在那个桌面上启动应用程序。
    // 如桌面已存在,则新进程与指定桌面关联。
    // 如桌面不存在,则用指定名称和默认的属性为新进程创建一个桌面。
    // 如为NULL,进程与当前桌面关联。
    PSTR lpDesktop;
    // 控制台窗口的窗口标题。
    // 如为NULL,以可执行文件名称作为窗口标题。
    PSTR lpTitle;
    // 子过程用CW_USEDEFAULT作为CreateWindow参数创建窗口时,使用
    DWORD dwX;
    DWORD dwY;
    // 子过程用CW_USEDEFAULT作为CreateWindow参数创建窗口时,使用
    DWORD dwXSize;
    DWORD dwYSize;
    // 子进程控制台--尺寸,属性
    DWORD dwXCountChars;
    DWORD dwYCountChars;
    DWORD dwFillAttribute;
    // STARTF_USESIZE
    // STARTF_USESHOWWINDOW
    // STARTF_USEPOSITION
    // STARTF_USECOUNTCHARS
    // STARTF_USEFILEATTRIBUTE
    // STARTF_USESTDHANDLES
    // STARTF_RUNFULLSCREEN
    // STARTF_FORCEONFEEDBACK
    // STARTF_FORCEOFFFEEDBACK
    DWORD dwFlags;
    // 
    WORD wShowWindow;
    // 保留
    WORD cbReserved2;
    // NULL
    PBYTE lpReserved2;
    // 控制台
    HANDLE hStdInput;
    HANDLE hStdOutput;
    HANDLE hStdError;
}
STARTUPINFO, *LPSTARTUPINFO
;

typedef struct _STARTUPINFOEX
{
    STARTUPINFO StartupInfo;
    struct _PROC_THREAD_ATTRIBUTE_LIST *lpAttributeList;
}
STARTUPINFOEX, *LPSTARTUPINFOEX
;

不明确为此成员各个子成员指定值时,需要让未指定子成员内容清零。

typedef struct _STARTUPINFOEX
{
    STARTUPINFO StartupInfo;
    // 属性列表
    // 举例
    // 键:PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
    // 值:进程句柄,指定的进程将正在创建进程的父进程
    struct _PROC_THREAD_ATTRIBUTE_LIST *lpAttributeList;
};

// 创建空白属性列表
// 调用两次
BOOL InitializeProcThreadAttributeList
(
PPROC_THREAD_ATTRIBUTE_LIST pAttributeList,
DWORD dwAttributeCount,
// 0
DWORD dwFlags,
PSIZE_T pSize
);

SIZE_T cbAttributeListSize = 0;
BOOL bReturn = InitializeProcThreadAttributeList
(
NULL,
// 属性数目
1,
0,
&cbAttributeListSize
);

pAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc
(
GetProcessHeap(),
0,
cbAttributeListSize
);

// 初始化属性列表
bReturn = InitializeProcThreadAttributeList
(
pAttributeList,
1,
0,
&cbAttributeListSize
);

// 添加 键/值对
BOOL UpdateProcThreadAttribute
(
PPROC_THREAD_ATTRIBUTE_LIST pAttributeList,
// 0
DWORD dwFlags,
// 属性
// 可取值
// PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
// PROC_THREAD_ATTRIBUTE_HANDLE_LIST
DWORD_PTR Attribute,
// 值
PVOID pValue,
// 值大小
SIZE_T cbSize,
// NULL
PVOID pPreviousValue,
// NULL
PSIZE_T pReturnSize
);

// 不再需要参数的时候,需要在释放已分配的内存前,
// 先用以下方法来清除不透明的属性列表
VOID DeleteProcThreadAttributeList
(
PPROC_THREAD_ATTRIBUTE_LIST pAttributeList
);

// 应用可用来获取STARTUPINFO结构的一个副本
// 总是获取 非扩展版本,因为属性列表在父进程分配
// 属性只在父进程地址空间有效
VOID GetStartupInfo(LPSTARTUPINFO pStartupInfo);

5.8.ppiProcInfo
指向一个PROCESS_INFORMATION。
CreateProcess返回前,会初始化这个结构的成员。

typedef struct _PROCESS_INFOMATION
{
    HANDLE hProcess;
    HANDLE hThread;
    DWORD dwProcessId;
    DWORD dwThreadId;
}
PROCESS_INFOMATION
;

创建一个新进程,会导致创建一个进程内核对象,一个线程内核对象。

创建时,系统会为每个对象指定一个初始的使用计数1。
然后,在CreateProcess返回前,会使用完全访问权限来打开进程对象和线程对象。
并将各自的与进程相关联的句柄放入PROCESS_INFORMATION结构的hProcess和hThread成员中。
当CreateProcess在内部打开这个对象时,每个对象的使用计数变为2。

创建一个进程内核对象或线程内核对象时,系统会为此对象分配一个独一无二的标识符。
进程ID和线程ID分享同一个号码池。这意味这进程和线程不可能有相同的ID。
一个对象分配到的ID绝对不会是0。Windows任务管理器将进程ID 0 与”System Idle Process”关联。但实际上并没有System Idle Process这样的东西。任务管理器创建这个虚拟进程的目的是将其作为Idle线程的占位符:在没有别的线程正在运行时,系统就运行这个Idle进程。

System Idle Process中线程数量始终等于计算机CPU的数量。它始终代表未被真实进程使用的CPU使用率。

一旦一个进程或线程对象被释放,那么用于它们的标识Id重新进入可分配状态。

// 得到当前进程的ID
GetCurrentProcessId

// 得到当前正在运行的线程的ID
GetCurrentThreadId

// 获得与指定句柄对应的一个进程的ID
GetProcessId

// 获得与指定句柄对应的一个线程的ID
GetThreadID

// 根据线程句柄获得其所在进程的ID
GetProcessIdOfThread

-终止进程
1.主线程的入口点函数返回
2.进程中的一个线程调用ExitProcess
3.另一个进程中的线程调用TerminateProcess
4.进程中的所有线程都自然死亡

-主线程的入口点函数返回
让主线程的入口点函数返回,可以保证以下操作会被执行。

1.该线程创建的任何C++对象都将由这些对象的析构函数正确销毁。
2.操作系统将正确释放线程栈使用的内存。
3.系统将进程的退出代码(在进程内核对象中维护)设为入口点函数的返回值。
4.系统递减进程内核对象的使用计数。

-ExitProcess

// 进程在该进程的一个线程调用 ExitProcess时终止
// 调用后,进程终止,ExitProcess后代码,不会执行。
VOID ExitProcess(UINT fuExitCode);

如果在入口点函数中调用ExitThread,而不是ExitProcess或入口点函数直接返回,应用程序的主线程将停止执行,但只要进程中还有其它线程正在运行,进程就不会终止。

调用ExitProcess或ExitThread会导致进程或线程直接终止运行–再也不会返回当前函数调用。

这时,进程或线程的所有操作系统资源都会被正确清理。C/C++运行库也许不能执行正确清理工作。

-TerminateProcess

BOOL TerminateProcess
(
// 要终止进程的句柄
HANDLE hProcess,
// 进程退出码
UINT fuExitCode
);

被终止的进程得不到自己要被终止的通知。
进程没有机会执行自己的清理工作,但操作系统会在进程终止后,彻底进行清理,确保不会泄露任何操作系统资源。

TerminateProcess是异步的,它告诉系统我们希望进程终止,但函数返回时,不能保证进程已被强行终止了。

-进程中所有线程终止时
如一个进程中的所有线程都终止了,一旦系统检测到一个进程中没有任何线程在运行,就会终止这个进程。进程的退出代码会被设为最后一个终止的那个线程的退出代码。

-进程终止
一个进程终止时,会依次执行以下操作:
1.终止进程中遗留的线程
2.释放进程分配的所有用户对象和GDI对象,关闭所有内核对象。
3.进程的退出代码从STILL_ACTIVE变为传给ExitProcess或TerminateProcess函数的代码。
4.进程内核对象的状态变成已触发状态。
5.进程内核对象的使用计数减1。

在进程已经退出时,如果系统中另一个进程还拥有此进程的内核对象的句柄,则,可利用此句柄获取进程信息。

// 获得进程退出代码
BOOL GetExitCodeProcess
(
HANDLE hProcess,
PDWORD pdwExitCode
);

-子进程
进程间传递数据:
动态数据交换
OLE
管道
邮件槽

共享数据最方便的方式之一就是使用内存映射文件。

// 创建进程,等候执行结果
PROCESS_INFORMATION pi;
DWORD dwExitCode;
BOOL fSuccess = CreateProcess(..., &pi);
if(fSuccess)
{
    CloseHandle(pi.hThread);
    WaitForSingleObject(pi.hProcess, INFINITE);
    GetExitCodeProcess(pi.hProcess, &dwExitCode);
    CloseHandle(pi.hProcess);
}

-管理员以标准用户权限运行时
1.大多数用户都用一个管理员账户来登录Windows。利用这个账户,用户几乎能没有任何限制地访问重要的系统资源。

2.一旦用户用这样的一个特权账户来登陆Vista前的某个Windows操作系统,就会创建一个安全令牌。每当有代码试图访问一个受保护的安全资源时,系统就会使用这个安全令牌。

这个令牌会与新建的所有进程关联。这样从Internet下载的恶意程序也将获得很高的权限。

3.在Windows Vista中,如用户使用管理员这样的一个被授予高特权的账户登陆,那么除了与这个账户对应的安全令牌外,还会创建一个经过刷选的令牌,后者将只被授予标准用户权限。此后,从包括Windows资源管理器在内的第一个进程开始,这个筛选的令牌会与系统代表最终用户启动的所有新进程关联。

4.权限低的进程如何执行需要高权限的操作。
4.1.要求操作系统提升权限
但只能在进程边界上提升。默认下,一个进程启动时,会与当前登陆用户的刷选后的令牌关联。要为进程授予更多权限,须指示Windows:在进程启动前,先友好地征得最终用户得同一。
右键–以管理员身份运行
+本身以管理员身份登陆,
系统会在一个“安全Windows桌面”中显示一个确认对话框,要求用户批准将权限提升到未刷选的安全令牌的级别。
+ 非管理员身份登陆
需要进行管理员权限验证

进程启动后,再要求更多的权限就已经迟了。

4.2.自动提升进程的权限
假如应用的可执行文件已经嵌入了一种特殊的资源,系统就会检查段,并解析其内容。

<trustInfo xmlns="urn:schemas-micorsoft-com:asm.v2">
    <security>
        <requestedPrivileges>
            <requestedExecutionLevel
                level="requireAdministrator"
            />
        requestedPrivileges>
    security>
trustInfo>

level的3个可能值:
requireAdministrator: 应用必须以管理员权限启动
highestAvailable:
应用以当前可用的最高权限运行
如使用管理员账户登陆,会出现一个要求批准提升权限的对话框
如使用普通账户登陆,会用标准权限启动
asInvoker:应用用与主调程序一样的权限启动。

也可选择不将清单嵌入可执行文件资源中。可将清单保存到可执行文件所在的目录中,名称和可执行文件相同,但扩展名使用.mainfest。

可执行文件中嵌入了一个清单时,外部清单会被忽略。

4.3.手动提升进程的权限

BOOL ShellExecuteEx(LPSHELLEXECUTEINFO pExecInfo);

typedef struct _SHELLEXECUTEINFO
{
    DWORD cbSize;
    ULONG fMask;
    HWND hwnd;
    // 须被设为 "runas"
    PCTSTR lpVerb;
    // 须包含使用提升后权限来启动的一个可执行文件的路径
    PCTSTR lpFile;
    PCTSTR lpParameters;
    PCTSTR lpDirectory;
    int nShow;
    HINSTANCE hInstApp;
    PVOID lpIDList;
    PCTSTR lpClass;
    HKEY hkeyClass;
    DWORD dwHotKey;
    union
    {
        HANDLE hIcon;
        HANDLE hMonitor;
    }DUMMYUNIONNAME;
    HANDLE hProcess;
}
SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO
;

当一个进程使用提升后的权限启动时,它每次用CreateProcess来生成另一个进程时,子进程会获得和它的父进程一样的提升后的权限。
一个使用刷选的令牌运行的应用,采用CreateProcess生成一个要求权限提升的可执行文件,会失败。

–何为当前权限上下文

BOOL GetProcessElevation
(
TOKEN_ELEVATION_TYPE *pElevationType,
BOOL *pIsAdmin
)
{
    HANDLE hToken = NULL;
    DWORD dwSize;
    // 获得进程的安全令牌
    if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
    {
        return FALSE;
    }
    BOOL bResult = FALSE;
    // 获得安全令牌类型信息:未筛选令牌,筛选令牌,默认
    if(GetTokenInformation(hToken, TokenElevationType, pElevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize))
    {
        BYTE adminSID[SECURITY_MAX_SID_SIZE];
        dwSize = sizeof(adminSID);
        // 管理员SID
        CreateWellKnownSid(WinBuiltinAdministratorSid, NULL, &adminSID, &dwSize);
        // 筛选令牌
        if(*pElevationType == TokenElevationTypeLimited)
        {
            HANDLE hUnfilteredToken = NULL;
            // 获得筛选令牌所关联的未筛选令牌
            GetTokenInformation(hToken, TokenLinkedToken, (VOID*)&hUnfilteredToken, sizeof(HANDLE), &dwSize);
            // 检查未筛选令牌中是否包含一个管理员SID
            if(CheckTokenMembership(hUnfilteredToken, &adminSID, pIsAdmin))
            {
                bResult = TRUE;
            }
            CloseHandle(hUnfilteredToken);
        }
        else
        {
            *pIsAdmin = IsUseAdmin();
            bResult = TRUE;
        }
    }
    CloseHandle(hToken);
    return bResult;
}

-枚举系统中正在运行的进程

// 参数1:进程ID
// 参数2:进程内一个模块的地址
// 处理:查看那个进程的地址空间,定位那个模块,读取模块的文件头信息以确定这个模块的首选基地址。
PVOID GetModulePreferredBaseAddr(DWORD dwProcessId, PVOID pvModuleRemote);

获取进程的命令行
1.在WinDbg中,用于获得一个内核风格的PEB结构相关详情的命令已发生了变化。现在用
dt nt! PEB

+0x000 InheritedAddressSpace : UChar
...
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
...

dt nt! _RTL_USER_PROCESS_PARAMETERS

+0x000 MaximumLength : Uint4B
...
+0x040 CommandLine : _UNICODE_STRING
...
typedef struct
{
    DWORD Filter[4];
    DWORD InfoBlockAddress;
}__PEB;

typedef struct
{
    DWORD Filter[17];
    DWORD wszCmdLineAddress;
}__INFOBLOCK;

远程进程不能访问受保护进程的虚拟内存。
权限低的进程不能访问权限高的进程。

安全描述符
访问控制列表
系统访问控制列表(访问控制项,为受保护资源分配一个完整性级别)
默认完整性级别:中
每个进程有一个基于其安全令牌的完整性级别。

低:
中:
高:
系统:

试图访问一个内核对象时,系统会将主调进程的完整性级别与内核对象的完整性级别比较。如后者高,则不能对内核对象进行修改删除。

知道一个进程的令牌的完整性级别,
及它要访问的一个内核对象的完整性级别,
系统就可根据令牌和资源中都存储有的代码策略来核实具体能执行的操作。

GetTokenInformation + TokenMandatoryPolicy + 进程的安全令牌句柄

可能的代码策略:
POLICY_NO_WRITE_UP 进程不能向高完整性级别资源写
POLICY_NEW_PROCESS_MIN 进程启动一个子进程,子进程采用 父进程/清单中优先级较低者。

没有策略 TOKEN_MANDATORY_POLICY_OFF
TOKEN_MANDATORY_POLICY_VALID_MASK

设置资源策略:
SYSTEM_MANDATORY_LABEL_NO_WRITE_UP  较低完整性级别进程不能写入或删除较高完整性级别资源
SYSTEM_MANDATORY_LABEL_NO_READ_UP 不允许低完整性级别进程读高完整性资源

进程被授予Debug权限时,可以无视完整性级别。

完整性级别低的进程,无法通过 PostMessage,SendMessage,Windows挂钩来和完整性级别高的进程互动。

你可能感兴趣的:(3.2.系统-Windows,进程,windows)