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

2.1 Windows核心编程-进程UAC下以管理员权限运行

程序员文章站 2022-07-14 14:38:16
...

从NT6.0开始,微软为了提高安全防护增加了非常多的新机制,其中对普通用户层开发人员影响最大的就是”用户账户控制系统(User Account Control , UAC)”

如果想让自己的程序默认以指定权限运行,可以通过修改VS工程的属性来达到目地:[属性]>[链接器]>[清单文件]>[UAC执行级别]

说明
requireAdministrator 应用程序须以管理员权限运行否则拒绝运行
highestAvailable 应用程序以当前可用的最高权限运行
如果当前用户为管理员,则弹出提示框
如果当前用户为普通用户,则不会提示
asInvoker 应用程序以与主调应用程序相同的权限启动

方法一

以管理员权限启动程序:

SHELLEXECUTEINFO sei = {sizeof(SHELLEXECUTEINFO)};
//请求提高权限
sei.lpVerb = TEXT("runas");
//需要提升权限的应用程序
sei.lpFile = TEXT("cmd.exe");

sei.nShow = SW_SHOWNORMAL;

if (!ShellExecuteEx(&sei)){
    DWORD dwStatus = GetLastError();
    if (dwStatus == ERROR_CANCELLED) {
        printf("ShellExecute Cancel..");
    }
    else if(dwStatus == ERROR_FILE_NOT_FOUND)
    {
        printf("File Not Found");
    }
}

以管理员权限启动进程步骤(注:进程启动的权限,在进程启动前已经设置好,所以程序启动之后,已经有启动权限了):
1. 打开当前进程的令牌
2. 查看当前进程是否具有管理员权限
3. 如果无管理员权限,则以管理员权限重新启动本进程

#include <Windows.h>
#include <ShlObj.h>
bool runAsAdmin()
{
    //1. 获取进程令牌
    HANDLE hToken = NULL;
    if ( !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken) )
        return false;
    //2. 获取提升类型
    TOKEN_ELEVATION_TYPE ElevationType = TokenElevationTypeDefault;
    BOOL  bIsAdmin = false;
    DWORD dwSize   = 0;
    if (GetTokenInformation(hToken, TokenElevationType, &ElevationType,
            sizeof(TOKEN_ELEVATION_TYPE), &dwSize))
    {
        //2.1 创建管理员组对应的SID
        BYTE adminSID(SECURITY_MAX_SID_SIZE);
        dwSize = sizeof(adminSID);
        CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID, &dwSize);
        //2.2 判断当前进程运行角色是否为管理员
        if (ElevationType == TokenElevationTypeLimited){
            //a. 获取连接令牌的句柄
            HANDLE hUnfilteredToken = NULL;
            GetTokenInformation(hToken, TokenLinkedToken, (PVOID)&hUnfilteredToken,
                sizeof(HANDLE), &dwSize);
            //b. 检查这个原始的令牌是否包含管理员的SID
            if (!CheckTokenMembership(hUnfilteredToken, &adminSID, &bIsAdmin))
                return false;
            CloseHandle(hUnfilteredToken);
        } else {
            bIsAdmin = IsUserAnAdmin();
        }
        CloseHandle(hToken);
    }

    // 3. 判断具体的权限状况
    BOOL bFullToken = false;
    switch(ElevationType)
    {
        case TokenElevationTypeDefault:   /*默认的用户或UAC被禁用*/
            if (IsUserAnAdmin())    bFullToken = true;  //默认用户有管理员权限
            else                    bFullToken = false; //默认用户不是管理员组
            break;
        case TokenElevationTypeFull:      /*已经成功提高进程权限*/
            if (IsUserAnAdmin())    bFullToken = true;  //当前以管理员权限运行
            else                    bFullToken = false; //当前未以管理员权限运行
            break;
        case TokenElevationTypeLimited:     /*进程在以有限的权限运行*/
            if (bIsAdmin)           bFullToken = false; //用户有管理员权限,但进程权限有限
            else                    bFullToken = false; //用户不是管理员组,且进程权限有限
    }

    //4. 根据权限的不同控制按钮的显示
    if (!bFullToken)
        Button_SetElevationRequiredState(GetDlgItem(hWnd, 控件ID), !bFullToken);
    else
        ShowWindow(GetDlgItem(hWnd, 控件ID), SW_HIDE);

    // 1. 隐藏当前窗口
    ShowWindow(hWnd, SW_HIDE);
    // 2. 获取当前程序路径
    WCHAR szApplication[MAX_PATH] = {0};
    DWORD cchLength               = _countof(szApplication);
    QueryFullProcessImageName(GetCurrentProcess(), 0,
                                szApplication, &cchLength);
    // 3. 以管理员权限重新打开进程
    SHELIEXECUTEINFO    sei = { sizeof(SHELIEXECUTEINFO) };
    sei.lpVerb          = L"runas";      //请求提升权限
    sei.lpFile          = szApplication; // 可执行文件路径
    sei.lpParameters    = NULL;          // 不需要参数
    sei.nShow           = SW_SHOWNORMAL; // 正常显示窗口

    if (ShellExecuteEx(&sei))
        ExitProcess(0);
    else
        ShowWindow(hWnd, SW_SHOWNORMAL);
}
  • 关键Windows API:
    • OpenProcessToken
    • GetTokenInformation
    • CreateWellKnownSid
    • CheckTokenMembership
    • IsUserAnAdmin
    • ShellExecuteEx

TOKEN_ELEVATION_TYPE值:

描述
TokenElevationTypeDefault 进程以默认用户运行,或UAC被禁止
TokenElevationTypeFull 进程的权限被成功提升,而且令牌没有被筛选过
TokenElevationTypeLimited 进程使用和一个筛选过的令牌对应的受限的权限运行

方法二

  • 相关Windows API:
    • OpenProcessToken
    • LookupPrivilegeValue
    • AdjustTokenPrivileges
LookupPrivilegeValue(
    _In_opt_    LPCTSTR lpSystemName,  //系统的名字,如果为NULL,则是本地系统
    _In_        LPCTSTR lpName, //权限的名字
    _Out_       LPUID   lpLuid  //返回LUID表示
);

lpName:
#define SE_BACKUP_NAMETEXT("SeBackupPrivilege") 
#define SE_RESTORE_NAMETEXT("SeRestorePrivilege") 
#define SE_SHUTDOWN_NAMETEXT("SeShutdownPrivilege") 
#define SE_DEBUG_NAMETEXT("SeDebugPrivilege") 

typedef struct _LUID_AND_ATTRIBUTES
{
    LUID    Luid;           //不同的权限类型
    DWORD   Attributes;     //权限描述
} LUID_AND_ATTRIBUTES;

BOOL WINAPI AdjustTokenPrivileges(
  _In_      HANDLE            TokenHandle,           //进程令牌
  _In_      BOOL              DisableAllPrivileges,  //是否禁用所有的权限
  _In_opt_  PTOKEN_PRIVILEGES NewState,             //新权限
  _In_      DWORD             BufferLength,         //第三个参数的字节长度
  _Out_opt_ PTOKEN_PRIVILEGES PreviousState,        //原本的权限
  _Out_opt_ PDWORD            ReturnLength
);
#include <stdio.h>
#include <windows.h>

void main()
{
    BOOL retn;
    HANDLE hToken;
    retn = OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken);
    if(retn != TRUE)
    {
        printf("获取令牌句柄失败!\n");
        return;
    }

    TOKEN_PRIVILEGES tp; //新特权结构体
    LUID Luid;
    retn = LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&Luid);

    if(retn != TRUE)
    {
        printf("获取Luid失败\n");
        return;
    }
            //给TP和TP里的LUID结构体赋值
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    tp.Privileges[0].Luid = Luid;      

    AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),NULL,NULL);
    if(GetLastError() != ERROR_SUCCESS)
    {
        printf("修改特权不完全或失败!\n");
    }
    else
    {
        printf("修改成功!\n");
    }
}

方法二提升的权限,需要在访问令牌中具有的权限,如果本身没有关联该权限,调用AdjustTokenPrivileges函数时会出现 ERROR_NOT_ALL_ASSIGNED错误码。

进程具有管理员权限之后,在创建其他进程时,默认是以管理员权限启动。