欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

4. 进程(二) -> Windows核心编程【第五版】

程序员文章站 2022-03-13 23:53:02
...

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);
pszApplicationNamepszCommandLine参数的示例
  • 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参数

4. 进程(二) -> Windows核心编程【第五版】
4. 进程(二) -> Windows核心编程【第五版】
4. 进程(二) -> Windows核心编程【第五版】
4. 进程(二) -> Windows核心编程【第五版】

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. 自动提升进程的权限

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结构中 lpVerblpFile 为主要关注的参数。前者设定为 “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枚举类型定义
    4. 进程(二) -> Windows核心编程【第五版】

  • 首先获取这些值并判断使用的令牌是否被筛选过。

  • 接下来判断用户是否是管理员。

  • 如果没有被筛选过,直接调用IsUserAnAdmin()返回

  • 如果被筛选过,首先获取未筛选的令牌把TokenLinkedToken传给GetTokenInformation)然后判断其是否包含管理员的Sid。(借助于CreateWellKnownSidCheckTokenMembership

  • 该函数可以用来获取当前进程的令牌和权限用以控制是否显示盾牌图标。

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内核系统。

相关标签: windows