4. 进程(二) -> Windows核心编程【第五版】
1. CreateProcess
- 函数原型
BOOL CreateProcess(
PCTSTR pszApplicationName, // 新进程要使用的可执行文件的名称,一般为NULL,为了支持Windows的POSIX子系统
PTSTR pszCommandLine, // 新进程命令行字符串, 注意此值不是只读的,调用过程中,可能修改该值,在返回的时候会恢复为原来的值
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES psaThread,
BOOL bInheritHandles, // 决定是否允许被创建的进程操纵进程A能访问的一些内核对象
DWORD fdwCreate,
PVOID pvEnvironment,
PCTSTR pszCurDir,
PSTARTUPINFO psiStartInfo,
PPROCESS_INFORMATION ppiProcInfo);
pszApplicationName
和 pszCommandLine
参数的示例
-
pszApplicationName
参数是新进程要使用的可执行文件的名称,一般为NULL
, 实际是为了支持Windows的POSIX子系统 -
pszCommandLine
新进程命令行字符串, 注意此值不是只读的,调用过程中,可能修改该值,在返回的时候会恢复为原来的值;
#include <Windows.h>
#include <tchar.h>
#include <StrSafe.h>
int _tmain(int argc, TCHAR *argv[])
{
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
TCHAR szCommandLine[] = TEXT("NOTEPAD");
CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
return(0);
}
psaProcess
, psaThread
, bInheritHandles
参数示例
- 下面示例演示了内核对象句柄的继承。
- 假设现在由进程A来创建进程B,它调用
CreateProcess
,并为psaProcess
参数传入一个SECURITY_ATTRIBUTES
结构的地址(在这个结构中,bInheritHandle
成员被设为TRUE
). - 在同一个调用中,
psaThread
参数指向另外一个SECURITY_ATTRIBUTES
结构,该结构的bInheritHandle
成员被设为FALSE
. - 系统创建进程B时,会同时分配一个进程内核对象和一个线程内核对象,并在
ppiProcInfo
参数指向的一个结构中,将句柄返回给进程A。利用返回的句柄,进程A就可以操纵新建的进程对象和线程对象。
#include <Windows.h>
#include <tchar.h>
#include <StrSafe.h>
int WINAPI _tWinMain(HINSTANCE hInstanceExe, HINSTANCE, PTSTR pszCmdLine, int nCmdShow)
{
STARTUPINFO si = {sizeof(si)};
SECURITY_ATTRIBUTES saProcess, saThread;
PROCESS_INFORMATION piProcessB, piProcessC;
TCHAR szPath[MAX_PATH];
saProcess.nLength = sizeof(saProcess);
saProcess.lpSecurityDescriptor = NULL;
saProcess.bInheritHandle = TRUE;
saThread.nLength = sizeof(saThread);
saThread.lpSecurityDescriptor = NULL;
saThread.bInheritHandle = FALSE;
_tcscpy_s(szPath, _countof(szPath), TEXT("ProcessB")); // _countof 是计算静态分配的数组的元素个数
CreateProcess(NULL, szPath, &saProcess, &saThread, FALSE, 0, NULL, NULL, &si, &piProcessB);
_tcscpy_s(szPath, _countof(szPath), TEXT("ProcessC"));
CreateProcess(NULL, szPath, NULL, NULL, TRUE, 0, NULL, NULL, &si, &piProcessC);
return (0);
}
fdwCreate
参数
pvEnvironment参数
- 指向一块内存,其中包含新进程要是用的环境字符串
- 大多时候传入的是
NULL
值
psiStartInfo参数
- 指向一个
STARTUPINFO
结构或STARTUPINFOEX
结构 - 这个结构一般希望使用默认值,最起码需要将所有成员初始化为0。
typedef struct _STARTUPINFOA {
DWORD cb;
LPSTR lpReserved;
LPSTR lpDesktop;
LPSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFOA, *LPSTARTUPINFOA;
typedef struct _STARTUPINFOW {
DWORD cb;
LPWSTR lpReserved;
LPWSTR lpDesktop;
LPWSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFOW, *LPSTARTUPINFOW;
2. 终止进程的方式(4种)
- 主线程的入口函数点返回(强烈推荐的方式)
- 进程中的一个线程调用
ExitProcess
函数(要避免这种方式) - 另一个进程中的线程调用
TerminateProcess
函数(要避免这种方式) - 进程中所有的线程都“自然死亡”(这种情况几乎从来不会发生)
3. 子进程
- 为了执行复杂的任务而不让当前线程一直等待,可以创建一个新的进程来完成工作。
父进程和子进程之间可以进行一些数据共享。
(DDE dynamic Data Exchange) OLE, 管道, 邮件槽等。共享数据最方便的方式就是使用内存映像文件(
CreateFileMapping
)
以阻塞方式运行子进程
- 以下代码创建了一个子进程,并等待子进程完成相应的工作正常结束以后,再继续当前线程的执行。
PROCESS_INFORMATION pi;
DWORD dwExitCode;
//Spawn the child process.
BOOL fSuccess = CreateProcess(..., &pi);
if (fSuccess) {
// Close the thread handle as soon as it is no longer needed!
CloseHandle(pi.hThread);
// Suspend our execution until the child has terminated.
WaitForSingleObject(pi.hProcess, INFINITE);
// The child process terminated; get its exit code.
GetExitCodeProcess(pi.hProcess, &dwExitCode);
// Close the process handle as soon as it is no longer needed.
CloseHandle(pi.hProcess);
}
- 上面的例子使用了
WaitForSingleObject
函数,该函数会一直等待直到hObject
参数所表示的对象变为已触发。 进程对象在终止的时候会变为已触发。 -
WaitForSingleObject
将会挂起当前线程,直到子进程终止。
运行独立的子进程
- 大多数时候应用程序将另一个进程作为独立的进程(detached process)来启动。
- 这就意味着一旦子进程创建,父进程就不再与其通信,或者不必等他它完成工作之后再继续自己的工作(当前进程不必挂起等待)。
- 这时候只需要调用
CloseHandle
关闭子进程的进程句柄和主线程句柄。
PROCESS_INFORMATION pi;
//Spawn the child process.
BOOL fSuccess = CreateProcess(..., &pi);
if (fSuccess) {
// Allow the system to destroy the process & thread kernel
// objects as soon as the child process terminates.
// Close the thread handle as soon as it is no longer needed!
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
4. 管理员以标准用户权限运行时
每个用户登录windows以后会有一个安全令牌(Security token)其后该用户启动的进程都拥有此令牌的权限。
由于许多windows用户使用Administrator登录,此用户的权限太高(可以修改系统文件)可能导致操作系统以高权限执行而已软件而破坏操作系统。
在Vista以上用户以Administrator登录出来具有高特权的安全令牌,还会具有一个筛选过的令牌(filtered token) 只有普通的标准用户权限(standard user)。
之后从第一个进程开始所有启动的进程都和筛选过的令牌相关联。因此默认运行的应用程序将无法访问受限资源。
可以在进程启动之前(进程边界, 进程已经启动以后会与筛选过的令牌相关联并且运行时不可修改)让操作系统提示用户取得提升权限的同意。也可以在快捷菜单中选择以管理员身份运行。
可以在自己的应用程序上显示一个盾牌图,会弹出一个权限提升对话框。
Windows只允许进程边界上进行权限提升(未启动以前)。 可以用一个未提升权限的进程来生成另一个提升了权限的进程,后者将包含一个com服务器,这个新进程将保持活动状态。这样未提升权限的进程就可以向已经提升权限的进程发出IPC调用,而不必启动一个新的实例再终止它自身。
5. 自动提升进程的权限
windows每次启动应用程序都将自动弹框询问并提升应用程序的权限(比如安装程序)
在可执行文件中嵌入资源(RT_MANIFEST) 系统会检查段
一个例子 参考blog : http://blog.csdn.net/sesiria/article/details/51939231
6. 手动提升进程的权限
-
CreateProcess
函数没有提供提升应用程序权限的功能。 可以调用ShellExecuteEx
函数
SHSTDAPI_(BOOL) ShellExecuteExW(_Inout_ SHELLEXECUTEINFOW *pExecInfo);
typedef struct _SHELLEXECUTEINFOW
{
DWORD cbSize; // in, required, sizeof of this structure
ULONG fMask; // in, SEE_MASK_XXX values
HWND hwnd; // in, optional
LPCWSTR lpVerb; // in, optional when unspecified the default verb is choosen
LPCWSTR lpFile; // in, either this value or lpIDList must be specified
LPCWSTR lpParameters; // in, optional
LPCWSTR lpDirectory; // in, optional
int nShow; // in, required
HINSTANCE hInstApp; // out when SEE_MASK_NOCLOSEPROCESS is specified
void *lpIDList; // in, valid when SEE_MASK_IDLIST is specified, PCIDLIST_ABSOLUTE, for use with SEE_MASK_IDLIST & SEE_MASK_INVOKEIDLIST
LPCWSTR lpClass; // in, valid when SEE_MASK_CLASSNAME is specified
HKEY hkeyClass; // in, valid when SEE_MASK_CLASSKEY is specified
DWORD dwHotKey; // in, valid when SEE_MASK_HOTKEY is specified
union
{
HANDLE hIcon; // not used
#if (NTDDI_VERSION >= NTDDI_WIN2K)
HANDLE hMonitor; // in, valid when SEE_MASK_HMONITOR specified
#endif // (NTDDI_VERSION >= NTDDI_WIN2K)
} DUMMYUNIONNAME;
HANDLE hProcess; // out, valid when SEE_MASK_NOCLOSEPROCESS specified
} SHELLEXECUTEINFOW, *LPSHELLEXECUTEINFOW;
-
SHELLEXECUTEINFO
结构中lpVerb
和lpFile
为主要关注的参数。前者设定为 “runas” 后者包含可执行文件的路径。
// Initialize the structure.
SHELLEXECUTEINFO sei = { sizeof(SHELLEXECUTEINFO) };
// Ask for privileges elevation.
sei.lpVerb = TEXT("runas");
// Create a Command Prompt from which you will be able to start
// other elevated applications.
sei.lpFile = TEXT("cmd.exe");
// Don't forget this parameter; otherwise, the window will e hidden.
sei.nShow = SW_SHOWNORMAL;
if (!ShellExecuteEx(&sei)) {
DWORD dwStatus = GetLastError();
if (dwStatus == ERROR_CANCELLED) {
// the user refused to allow privileges elevation.
}
else if (dwStatus == ERROR_FILE_NOT_FOUND) {
// The file defined by lpFile was not found and
// an error message popped up.
}
}
但一个进程提升权限以后,每次他再调用CreateProcess来生成另一个子进程都会获得和它的父进程一样的提升后的权限。
某些任务需要高权限的时候应该在启动该任务的界面元素盘显示一个盾牌图标。由于任务管理器由另一个进程或者一个进程中的COM服务来执行,所以应该需要将需要管理员权限的所有任务集中到另一个应用程序中,并通过ShellExecuteEx(lpVerb 传送runas)来提升他的权限。 具体要执行的特权操作采用命令行参数传递。(SHELLEXECUTEINFO的lpParameters字段)
7. 权限上下文
如何判断应用程序是以管理与身份运行还是以筛选令牌权限运行的
以下代码的函数
GetProcessElevation
返回一个提升类型并指出进程是否以管理员身份运行的BOOL值。
BOOL GetProcessElevation(TOKEN_ELEVATION_TYPE * pElevationType, BOOL * pIsAdmin) {
HANDLE hToken = NULL;
DWORD dwSize;
// Get current process token
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
return FALSE;
BOOL bResult = FALSE;
// Retrive elevation type information
if (GetTokenInformation(hToken, TokenElevationType,
pElevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) {
// Create the SID corresponding to the Administrators group
BYTE adminSID[SECURITY_MAX_SID_SIZE];
dwSize = sizeof(adminSID);
CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL,
&adminSID, &dwSize);
if (*pElevationType == TokenElevationTypeLimited) {
// Get handle to linked token (will have one if we are lua)
HANDLE hUnfilteredToken = NULL;
GetTokenInformation(hToken, TokenLinkedToken, (VOID*)
&hUnfilteredToken, sizeof(HANDLE), &dwSize);
// Check if this original token contains admin SID
if (CheckTokenMembership(hUnfilteredToken, &adminSID, pIsAdmin)){
bResult = TRUE;
}
// Don't forget to close the unfiltered token
CloseHandle(hUnfilteredToken);
}
else {
*pIsAdmin = IsUserAnAdmin();
bResult = TRUE;
}
}
// Don't forget to close the process token
CloseHandle(hToken);
return bResult;
}
TOKEN_ELEVATION_TYPE枚举类型定义
首先获取这些值并判断使用的令牌是否被筛选过。
接下来判断用户是否是管理员。
如果没有被筛选过,直接调用
IsUserAnAdmin()
返回如果被筛选过,首先获取未筛选的令牌把
TokenLinkedToken
传给GetTokenInformation
)然后判断其是否包含管理员的Sid。(借助于CreateWellKnownSid
和CheckTokenMembership
)该函数可以用来获取当前进程的令牌和权限用以控制是否显示盾牌图标。
8. 枚举系统正在运行的进程
Windows在注册表中维护一个性能数据库,包含海量信息。例如
RegQueryValueEx
把注册表的根目录设为KEY_PERFORMANCE_DATA
但是该数据库在Win95和98上不可用,
没有自己的函数,使用注册表函数
数据库信息布局非常复杂
为了更方便访问此数据库借助Performance Data Helper(PDH.dll)
在(Win9X中 使用ToolHelp API的Process32First 和Process32Next函数)
在WinNT中使用EnumProcess函数。
在Win2000开始Tool Help函数支持2K以上的NT内核系统。
上一篇: 对MySQL配置参数 my.ini/my.cnf的详细解析
下一篇: PHP 摘引的调用