C++ AdjustTokenPrivileges 提升访问令牌权限

背景

在我们编程实现一些系统操作的时候,往往要求我们执行操作的进程拥有足够的权限方可成功操作。比如,我们使用 ExitWindows 函数实现关机或重启操作的时候,就要求我们的进程要有 SE_SHUTDOWN_NAME 的权限,否则,会忽视不执行操作。这时,我们唯一能够做的,就是按照要求,提升我们进程的权限。

函数介绍

/* 
	打开与进程关联的访问令牌。
	如果函数成功,则返回值不为零。
*/ 
    BOOL WINAPI OpenProcessToken(
         _In_  HANDLE  ProcessHandle,			// 打开与进程关联的访问令牌。
         _In_  DWORD   DesiredAccess,			// 指定一个访问掩码,指定访问令牌的请求类型。
         _Out_ PHANDLE TokenHandle				// 指向一个句柄的指针,用于标识当函数返回时新打开的访问令牌。
    );


/*
	查看系统权限的特权值,返回信息到一个LUID结构体里。
	如果函数成功,函数将返回非零值
*/
    BOOL WINAPI LookupPrivilegeValue(
         _In_opt_ LPCTSTR lpSystemName,		// 指向以NULL结尾的字符串的指针,该字符串是指向要获取特权值的系统名称
         _In_     LPCTSTR lpName,			// 指向空终止字符串的指针,指定特权的名称
         _Out_    PLUID   lpLuid			// 指向LUID变量的指针,该变量接收由lpSystemName参数指定的系统上已知权限的LUID。
    );

/*
	启用或禁用指定的访问令牌中的权限
	如果函数成功,则返回值不为零
*/
    BOOL WINAPI AdjustTokenPrivileges(
         _In_      HANDLE            TokenHandle,				// 访问令牌的句柄,其中包含要修改的权限
         _In_      BOOL              DisableAllPrivileges,		// 指定该功能是否禁用所有令牌的权限
         _In_opt_  PTOKEN_PRIVILEGES NewState,					// 指向TOKEN_PRIVILEGES结构的指针,该结构指定特权数组及其属性
         _In_      DWORD             BufferLength,				// 指定由PreviousState参数指向的缓冲区的大小
         _Out_opt_ PTOKEN_PRIVILEGES PreviousState,				// 接收修改权限的完整列表
         _Out_opt_ PDWORD            ReturnLength				// 接收由PreviousState参数指向的缓冲区所需的大小
    );

实现过程

首先,我们需要调用 OpenProcessToken 函数打开指定进程令牌,并获取 TOKEN_ADJUST_PRIVILEGES 权限的令牌句柄。之所以要获取进程令牌权限为 TOKEN_ADJUST_PRIVILEGES,是因为 AdjustTokenPrivileges 函数,要求要有此权限,方可修改进程令牌的访问权限。

其中,第 1 个参数表示要打开进程令牌的进程句柄;第 2 个参数表示我们对进程令牌具有的权限,TOKEN_ADJUST_PRIVILEGES就表示,我们有修改进程令牌的权限;第 3 个参数表示返回的进程令牌句柄。

    //打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄
    bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
    if (FALSE == bRet)
    {
        ShowError("OpenProcessToken");
        return FALSE;
    }

然后,我们调用 LookupPrivilegeValue 函数,获取本地系统指定特权名称的LUID值,这个LUID值就相当于该特权的身份标号。

其中,第 1 个参数表示系统,NULL表示本地系统,即要获取本地系统的指定特权的LUID值;第 2 个参数表示特权名称;第 3 个参数表示获取到的LUID返回值。

    // 获取本地系统的 pszPrivilegesName 特权的LUID值
    bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
    if (FALSE == bRet)
    {
        ShowError("LookupPrivilegeValue");
        return FALSE;
    }

接着,我们就开始对 TOKEN_PRIVILEGES 进程令牌特权结构体进行赋值设置,设置设置新特权的数量、特权对应的LUID值以及特权的属性状态。其中,tokenPrivileges.PrivilegeCount表示设置新特权的特权数量;tokenPrivileges.Privileges[i].Luid表示第 i 个特权对应的LUID值;tokenPrivileges.Privileges[0].Attributes表示特权的属性;SE_PRIVILEGE_ENABLED就表示启用该特权。

    // 设置提升权限信息
    tokenPrivileges.PrivilegeCount = 1;
    tokenPrivileges.Privileges[0].Luid = luidValue;
    tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

最后,我们调用 AdjustTokenPrivileges 函数对进程令牌的特权进行修改,将上面设置好的新特权设置到进程令牌中。

其中,第 1个参数表示进程令牌;第 2 个参数表示能是否禁用所有令牌的权限,FALSE则不禁用;第 3个参数是新设置的特权,指向设置好的令牌特权结构体;第 4 个参数表示返回上一个特权数据缓冲区的大小,不获取,则可以设为 0;第 5 个参数表示返回上一个特权数据缓冲区,不接收返回数据,可以设为 NULL;第 6 个参数表示接收返回上一个特权数据缓冲区应该有的大小。

    // 提升进程令牌访问权限
    bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
    if (FALSE == bRet)
    {
        ShowError("AdjustTokenPrivileges");
        return FALSE;
    }

但是,需要注意的是,AdjustTokenPrivileges 返回 TRUE,并不代表特权就设置成功,还需要使用 GetLastError 来判断错误吗返回值。若错误码返回值为ERROR_SUCCESS,则所有特权设置成功;若为 ERROR_NOT_ALL_ASSIGNED,则表示并不是所有特权都设置成功。

    dwRet = ::GetLastError();
    if (ERROR_SUCCESS == dwRet)
    {
        return TRUE;
    }
    else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
    {
        ShowError("ERROR_NOT_ALL_ASSIGNED");
        return FALSE;
    }

换句话说,如果你只提升了一个特权,且错误码为ERROR_NOT_ALL_ASSIGNED,那么这就是说明提升失败了。如果程序运行在 Win7 或者 Win7 以上版本的操作系统,可以试着以管理员身份运行程序,这样就可以成功提升进程令牌的访问权限。

编码实现

    BOOL EnbalePrivileges(HANDLE hProcess, char *pszPrivilegesName)
    {
        HANDLE hToken = NULL;
        LUID luidValue = {0};
        TOKEN_PRIVILEGES tokenPrivileges = {0};
        BOOL bRet = FALSE;
        DWORD dwRet = 0;
        // 打开进程令牌并获取进程令牌句柄
        bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
        if (FALSE == bRet)
        {
            ShowError("OpenProcessToken");
            return FALSE;
        }
        // 获取本地系统的 pszPrivilegesName 特权的LUID值
        bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
        if (FALSE == bRet)
        {
            ShowError("LookupPrivilegeValue");
            return FALSE;
        }
        // 设置提升权限信息
        tokenPrivileges.PrivilegeCount = 1;
        tokenPrivileges.Privileges[0].Luid = luidValue;
        tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        // 提升进程令牌访问权限
        bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
        if (FALSE == bRet)
        {
            ShowError("AdjustTokenPrivileges");
            return FALSE;
        }
        else
        {
            // 根据错误码判断是否特权都设置成功
            dwRet = ::GetLastError();
            if (ERROR_SUCCESS == dwRet)
            {
                return TRUE;
            }
            else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
            {
                ShowError("ERROR_NOT_ALL_ASSIGNED");
                return FALSE;
            }
        }
        return FALSE;
    }
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
program Project1; //{$APPTYPE CONSOLE} uses windows, SysUtils, tlhelp32, accctrl, aclapi; procedure SetPrivilege; var OldTokenPrivileges, TokenPrivileges: TTokenPrivileges; ReturnLength: dword; hToken: THandle; Luid: int64; begin OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES, hToken); LookupPrivilegeValue(nil, 'SeDebugPrivilege', Luid); TokenPrivileges.Privileges[0].luid := Luid; TokenPrivileges.PrivilegeCount := 1; TokenPrivileges.Privileges[0].Attributes := 0; AdjustTokenPrivileges(hToken, False, TokenPrivileges, SizeOf(TTokenPrivileges), OldTokenPrivileges, ReturnLength); OldTokenPrivileges.Privileges[0].luid := Luid; OldTokenPrivileges.PrivilegeCount := 1; OldTokenPrivileges.Privileges[0].Attributes := TokenPrivileges.Privileges[0].Attributes or SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, False, OldTokenPrivileges, ReturnLength, PTokenPrivileges(nil)^, ReturnLength); end; function GetProcessID(EXE_Name: PChar): THandle; var s: string; ok: Bool; ProcessListHandle: THandle; ProcessStruct: TProcessEntry32; begin Result := 0; //获得进程列表句柄 ProcessListHandle := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0); try ProcessStruct.dwSize := SizeOf(ProcessStruct); //获得第一个进程句柄 ok := Process32First(ProcessListHandle, ProcessStruct); while ok do begin s := ExtractFileName(ProcessStruct.szExeFile);//获取进程的可执行文件名称 if AnsiCompareText(Trim(s), EXE_Name)=0 then//如果是HL程序名,表示找到游戏进程。 begin Result := ProcessStruct.th32ProcessID;//保留游戏进程句柄 break; end; ok := Process32Next(ProcessListHandle, ProcessStruct);//获取下一个进程信息。 end; finally CloseHandle(ProcessListHandle);//关闭进程列表句柄 end; end; ///////////////////////////////////////////////////////////////// Function CreateSystemProcess(szProcessName: LPTSTR): BOOL; Var hProcess: THANDLE; hToken, hNewToken: THANDLE; dwPid: DWORD; pOldDAcl: PACL; pNewDAcl: PACL; bDAcl: BOOL; bDefDAcl: BOOL; dwRet: DWORD; pSacl: PACL; pSidOwner: PSID; pSidPrimary: PSID; dwAclSize: DWORD; dwSaclSize: DWORD; dwSidOwnLen: DWORD; dwSidPrimLen: DWORD; dwSDLen: DWORD; ea: EXPLICIT_ACCESS; pOrigSd: PSECURITY_DESCRIPTOR; pNewSd: PSECURITY_DESCRIPTOR; si: STARTUPINFO; pi: PROCESS_INFORMATION; bError: BOOL; Label Cleanup; begin pOldDAcl:= nil; pNewDAcl:= nil; pSacl:= nil; pSidOwner:= nil; pSidPrimary:= nil; dwAclSize:= 0; dwSaclSize:= 0; dwSidOwnLen:= 0; dwSidPrimLen:= 0; pOrigSd:= nil; pNewSd:= nil; SetPrivilege; // 选择 WINLOGON 进程 dwPid := GetProcessId('WINLOGON.EXE'); If dwPid = High(Cardinal) Then begin bError := TRUE; Goto Cleanup; end; hProcess := OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,dwPid); If hProcess = 0 Then begin bError := TRUE; Goto Cleanup; end; If not OpenProcessToken(hProcess,READ_CONTROL or WRITE_DAC,hToken) Then begin bError := TRUE; Goto Cleanup; end; // 设置 ACE 具有所有访问权限 ZeroMemory(@ea, Sizeof(EXPLICIT_ACCESS)); BuildExplicitAccessWithName(@ea, 'Everyone', TOKEN_ALL_ACCESS, GRANT_ACCESS, 0); If not GetKernelObjectSecurity(hToken, DACL_SECURITY_INFORMATION, pOrigSd, 0, dwSDLen) Then begin {第一次调用给出的参数肯定返回这个错误,这样做的目的是 为了得到原安全描述符 pOrigSd 的长度} // HEAP_ZERO_MEMORY = 8;HEAP_GENERATE_EXCEPTIONS = &H4 If GetLastError = ERROR_INSUFFICIENT_BUFFER Then begin pOrigSd := HeapAlloc(GetProcessHeap(), $00000008, dwSDLen); If pOrigSd = nil Then begin bError := TRUE; Goto Cleanup; end; // 再次调用才正确得到安全描述符 pOrigSd If not GetKernelObjectSecurity(hToken, DACL_SECURITY_INFORMATION, pOrigSd, dwSDLen, dwSDLen) Then begin bError := TRUE; Goto Cleanup; end; end Else begin bError := TRUE; Goto Cleanup; end; end;//GetKernelObjectSecurity() // 得到原安全描述符的访问控制列表 ACL If not GetSecurityDescriptorDacl(pOrigSd,bDAcl,pOldDAcl,bDefDAcl) Then begin bError := TRUE; goto Cleanup; end; // 生成新 ACE 权限访问控制列表 ACL dwRet := SetEntriesInAcl(1,@ea,pOldDAcl,pNewDAcl); If dwRet ERROR_SUCCESS Then begin pNewDAcl := nil; bError := TRUE; goto Cleanup; end; If not MakeAbsoluteSD(pOrigSd, pNewSd, dwSDLen, pOldDAcl^, dwAclSize, pSacl^, dwSaclSize, pSidOwner, dwSidOwnLen, pSidPrimary, dwSidPrimLen) Then begin {第一次调用给出的参数肯定返回这个错误,这样做的目的是 为了创建新的安全描述符 pNewSd 而得到各项的长度} If GetLastError = ERROR_INSUFFICIENT_BUFFER Then begin pOldDAcl := HeapAlloc(GetProcessHeap(), $00000008, dwAclSize); pSacl := HeapAlloc(GetProcessHeap(), $00000008, dwSaclSize); pSidOwner := HeapAlloc(GetProcessHeap(), $00000008, dwSidOwnLen); pSidPrimary := HeapAlloc(GetProcessHeap(), $00000008, dwSidPrimLen); pNewSd := HeapAlloc(GetProcessHeap(), $00000008, dwSDLen); If (pOldDAcl = nil) or (pSacl = nil) or (pSidOwner = nil) or (pSidPrimary = nil) or (pNewSd = nil) Then begin bError := TRUE; goto Cleanup; end; {再次调用才可以成功创建新的安全描述符 pNewSd 但新的安全描述符仍然是原访问控制列表 ACL} If not MakeAbsoluteSD(pOrigSd, pNewSd, dwSDLen, pOldDAcl^, dwAclSize, pSacl^, dwSaclSize, pSidOwner, dwSidOwnLen, pSidPrimary, dwSidPrimLen) Then begin bError := TRUE; goto Cleanup; end; end Else begin bError := TRUE; goto Cleanup; end; end; {将具有所有访问权限访问控制列表 pNewDAcl 加入到新的 安全描述符 pNewSd 中} If not SetSecurityDescriptorDacl(pNewSd,bDAcl,pNewDAcl,bDefDAcl) Then begin bError := TRUE; goto Cleanup; end; // 将新的安全描述符加到 TOKEN 中 If not SetKernelObjectSecurity(hToken,DACL_SECURITY_INFORMATION,pNewSd) Then begin bError := TRUE; goto Cleanup; end; // 再次打开 WINLOGON 进程的 TOKEN,这时已经具有所有访问权限 If not OpenProcessToken(hProcess,TOKEN_ALL_ACCESS,hToken) Then begin bError := TRUE; goto Cleanup; end; // 复制一份具有相同访问权限TOKEN If not DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, nil, SecurityImpersonation, TokenPrimary, hNewToken) Then begin bError := TRUE; goto Cleanup; end; ZeroMemory(@si,Sizeof(STARTUPINFO)); si.cb := Sizeof(STARTUPINFO); {不虚拟登陆用户的话,创建新进程会提示 1314 客户没有所需的特权错误} ImpersonateLoggedOnUser(hNewToken); {我们仅仅是需要建立高权限进程,不用切换用户 所以也无需设置相关桌面,有了新 TOKEN 足够} // 利用具有所有权限TOKEN,创建高权限进程 If not CreateProcessAsUser(hNewToken, nil, szProcessName, nil, nil, FALSE, 0, nil, nil, si, pi) Then begin bError := TRUE; goto Cleanup; end; bError := FALSE; Cleanup: If pOrigSd = nil Then HeapFree(GetProcessHeap(),0,pOrigSd); If pNewSd = nil Then HeapFree(GetProcessHeap(),0,pNewSd); If pSidPrimary = nil Then HeapFree(GetProcessHeap(),0,pSidPrimary); If pSidOwner = nil Then HeapFree(GetProcessHeap(),0,pSidOwner); If pSacl = nil Then HeapFree(GetProcessHeap(),0,pSacl); If pOldDAcl = nil Then HeapFree(GetProcessHeap(),0,pOldDAcl); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hToken); CloseHandle(hNewToken); //CloseHandle(hProcess); If bError Then Result := FALSE Else Result := True; end; begin CreateSystemProcess('test.exe'); { TODO -oUser -cConsole Main : Insert code here } end.
C++ 服务中打开更高权限的进程,可以通过 Windows API 的 `CreateProcessAsUser()` 函数实现。该函数可以在指定用户的上下文中创建一个新进程,因此可以使用它来创建一个拥有更高权限的进程。下面是示例代码: ```c++ #include <windows.h> #include <userenv.h> // 需要链接 Userenv.lib 库 int main() { HANDLE hToken = NULL; HANDLE hTokenDup = NULL; HANDLE hProcess = NULL; TOKEN_PRIVILEGES tp; PROCESS_INFORMATION pi; STARTUPINFO si = { sizeof(si) }; DWORD dwSessionId; WCHAR cmd[] = L"C:\\Program Files\\MyApp\\MyApp.exe"; WCHAR params[] = L""; // 获取当前会话的会话 ID ProcessIdToSessionId(GetCurrentProcessId(), &dwSessionId); // 获取当前用户的访问令牌 if (!WTSQueryUserToken(dwSessionId, &hToken)) { DWORD err = GetLastError(); printf("WTSQueryUserToken failed with error %d\n", err); return 1; } // 复制访问令牌,以便在新进程中使用 if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hTokenDup)) { DWORD err = GetLastError(); printf("DuplicateTokenEx failed with error %d\n", err); CloseHandle(hToken); return 1; } // 启用权限 tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid)) { DWORD err = GetLastError(); printf("LookupPrivilegeValue failed with error %d\n", err); CloseHandle(hToken); CloseHandle(hTokenDup); return 1; } if (!AdjustTokenPrivileges(hTokenDup, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) { DWORD err = GetLastError(); printf("AdjustTokenPrivileges failed with error %d\n", err); CloseHandle(hToken); CloseHandle(hTokenDup); return 1; } // 切换到新进程的访问令牌 si.lpDesktop = L"winsta0\\default"; si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOW; if (!CreateProcessAsUser(hTokenDup, NULL, cmd, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &si, &pi)) { DWORD err = GetLastError(); printf("CreateProcessAsUser failed with error %d\n", err); CloseHandle(hToken); CloseHandle(hTokenDup); return 1; } // 等待进程退出 WaitForSingleObject(pi.hProcess, INFINITE); // 关闭进程句柄 CloseHandle(pi.hProcess); CloseHandle(pi.hThread); // 关闭访问令牌 CloseHandle(hToken); CloseHandle(hTokenDup); return 0; } ``` 在上面的示例中,首先获取当前会话的会话 ID,然后使用 `WTSQueryUserToken()` 函数获取当前用户的访问令牌。接着使用 `DuplicateTokenEx()` 函数复制访问令牌,以便在新进程中使用。然后启用 `SE_DEBUG_NAME` 权限,以便在新进程中打开更高权限的进程。接着切换到新进程的访问令牌,使用 `CreateProcessAsUser()` 函数创建新进程。最后等待进程退出,关闭进程句柄和访问令牌。 注意,使用 `CreateProcessAsUser()` 函数时,需要指定新进程的可执行文件路径和命令行参数,并使用 `STARTF_USESHOWWINDOW` 标志指定窗口的显示方式。另外,需要链接 Userenv.lib 库。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值