域管理员权限下获取登录用户用户名及OpenProcessToken错误6解决
问题描述
我们需要运行一个获取登录用户用户名的程序,该程序通过WinRM推送并以管理员权限运行。普通用户身份运行时可正常返回用户名,但以管理员身份运行时触发OpenProcessToken错误6(无效句柄),现寻求有效解决方法。
现有代码
#include <windows.h> #include <stdio.h> #include <tlhelp32.h> #pragma comment(lib, "advapi32.lib") #define MAX_NAME 256 BOOL SearchTokenGroupsForSID (VOID) { DWORD i, dwSize = 0, dwResult = 0; HANDLE hToken; PTOKEN_USER pUserInfo; SID_NAME_USE SidType; char lpName[MAX_NAME]; char lpDomain[MAX_NAME]; PSID pSID = NULL; char *procname = "explorer.exe"; int pid = 0; PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (Process32First(snapshot, &entry) == TRUE) { while (Process32Next(snapshot, &entry) == TRUE) { if (stricmp(entry.szExeFile, procname) == 0) { HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID); pid = entry.th32ProcessID; if (!OpenProcessToken( hProcess, TOKEN_QUERY, &hToken )) { printf( "OpenProcessToken Error %u\n", GetLastError() ); return FALSE; } CloseHandle(hProcess); } } } printf("pid of %s: %d\n", procname, pid); if(!GetTokenInformation(hToken, TokenUser, NULL, dwSize, &dwSize)) { dwResult = GetLastError(); if( dwResult != ERROR_INSUFFICIENT_BUFFER ) { printf( "GetTokenInformation Error %u\n", dwResult ); return FALSE; } } pUserInfo = (PTOKEN_USER) GlobalAlloc( GPTR, dwSize ); if(! GetTokenInformation(hToken, TokenUser, pUserInfo, dwSize, &dwSize ) ) { printf( "GetTokenInformation Error %u\n", GetLastError() ); return FALSE; } dwSize = MAX_NAME; if( !LookupAccountSid( NULL, pUserInfo->User.Sid, lpName, &dwSize, lpDomain, &dwSize, &SidType ) ) { dwResult = GetLastError(); if( dwResult == ERROR_NONE_MAPPED ) strcpy_s (lpName, dwSize, "NONE_MAPPED" ); else { printf("LookupAccountSid Error %u\n", GetLastError()); return FALSE; } } printf( "Current user is a member of the %s\\%s group\n", lpDomain, lpName ); if (pSID) FreeSid(pSID); if ( pUserInfo ) GlobalFree( pUserInfo ); return TRUE; } int main(void) { SearchTokenGroupsForSID(); return 0; }
错误原因与解决方案
错误根源
错误6对应ERROR_INVALID_HANDLE,核心问题有两点:
- 权限请求过度:代码使用
PROCESS_ALL_ACCESS打开explorer.exe,该权限包含大量不必要的操作权限,即使是管理员身份,也可能因UAC隔离或跨会话限制导致进程打开失败,返回无效句柄,后续OpenProcessToken调用自然报错。 - 未校验句柄有效性:代码未检查
OpenProcess的返回值,若进程打开失败,直接用无效句柄调用OpenProcessToken必然触发错误。
修复方案
方案1:优化原进程遍历逻辑
修改原代码,降低权限请求并添加有效性校验:
#include <windows.h> #include <stdio.h> #include <tlhelp32.h> #pragma comment(lib, "advapi32.lib") #define MAX_NAME 256 BOOL SearchTokenGroupsForSID (VOID) { DWORD dwSize = 0, dwResult = 0; HANDLE hToken = NULL; PTOKEN_USER pUserInfo = NULL; SID_NAME_USE SidType; char lpName[MAX_NAME]; char lpDomain[MAX_NAME]; char *procname = "explorer.exe"; DWORD pid = 0; PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot == INVALID_HANDLE_VALUE) { printf("CreateToolhelp32Snapshot Error %u\n", GetLastError()); return FALSE; } if (Process32First(snapshot, &entry) == TRUE) { while (Process32Next(snapshot, &entry) == TRUE) { if (stricmp(entry.szExeFile, procname) == 0) { // 仅请求获取令牌所需的最小权限 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, entry.th32ProcessID); if (hProcess == NULL) { printf("OpenProcess Error %u\n", GetLastError()); continue; } pid = entry.th32ProcessID; if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { CloseHandle(hProcess); break; // 找到有效令牌后退出循环 } else { printf("OpenProcessToken Error %u\n", GetLastError()); CloseHandle(hProcess); } } } } CloseHandle(snapshot); printf("pid of %s: %lu\n", procname, pid); if (hToken == NULL) { printf("Failed to get valid process token\n"); return FALSE; } if(!GetTokenInformation(hToken, TokenUser, NULL, dwSize, &dwSize)) { dwResult = GetLastError(); if( dwResult != ERROR_INSUFFICIENT_BUFFER ) { printf( "GetTokenInformation Error %u\n", dwResult ); CloseHandle(hToken); return FALSE; } } pUserInfo = (PTOKEN_USER) GlobalAlloc(GPTR, dwSize); if (pUserInfo == NULL) { printf("GlobalAlloc failed\n"); CloseHandle(hToken); return FALSE; } if(!GetTokenInformation(hToken, TokenUser, pUserInfo, dwSize, &dwSize)) { printf( "GetTokenInformation Error %u\n", GetLastError() ); GlobalFree(pUserInfo); CloseHandle(hToken); return FALSE; } dwSize = MAX_NAME; DWORD dwDomainSize = MAX_NAME; if( !LookupAccountSid(NULL, pUserInfo->User.Sid, lpName, &dwSize, lpDomain, &dwDomainSize, &SidType) ) { dwResult = GetLastError(); if( dwResult == ERROR_NONE_MAPPED ) strcpy_s(lpName, MAX_NAME, "NONE_MAPPED"); else { printf("LookupAccountSid Error %u\n", GetLastError()); GlobalFree(pUserInfo); CloseHandle(hToken); return FALSE; } } printf( "登录用户:%s\\%s\n", lpDomain, lpName ); GlobalFree(pUserInfo); CloseHandle(hToken); return TRUE; } int main(void) { SearchTokenGroupsForSID(); return 0; }
方案2:使用WTS API直接获取活跃会话用户(推荐)
通过WTS接口跳过进程遍历,直接获取当前活跃登录用户的令牌,避免跨会话权限问题:
#include <windows.h> #include <stdio.h> #include <wtsapi32.h> #pragma comment(lib, "advapi32.lib") #pragma comment(lib, "wtsapi32.lib") #define MAX_NAME 256 BOOL GetInteractiveUserInfo(char* lpDomain, DWORD dwDomainSize, char* lpName, DWORD dwNameSize) { DWORD dwSessionCount = 0; PWTS_SESSION_INFO pSessionInfo = NULL; // 枚举所有会话 if (!WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &dwSessionCount)) { printf("WTSEnumerateSessions Error %u\n", GetLastError()); return FALSE; } BOOL bSuccess = FALSE; for (DWORD i = 0; i < dwSessionCount; i++) { // 找到活跃的用户会话 if (pSessionInfo[i].State == WTSActive) { HANDLE hUserToken = NULL; if (WTSQueryUserToken(pSessionInfo[i].SessionId, &hUserToken)) { DWORD dwTokenInfoSize = 0; GetTokenInformation(hUserToken, TokenUser, NULL, 0, &dwTokenInfoSize); PTOKEN_USER pTokenUser = (PTOKEN_USER)LocalAlloc(LPTR, dwTokenInfoSize); if (GetTokenInformation(hUserToken, TokenUser, pTokenUser, dwTokenInfoSize, &dwTokenInfoSize)) { SID_NAME_USE sidType; if (LookupAccountSid(NULL, pTokenUser->User.Sid, lpName, &dwNameSize, lpDomain, &dwDomainSize, &sidType)) { bSuccess = TRUE; } else { printf("LookupAccountSid Error %u\n", GetLastError()); } } else { printf("GetTokenInformation Error %u\n", GetLastError()); } LocalFree(pTokenUser); CloseHandle(hUserToken); break; } else { printf("WTSQueryUserToken Error %u\n", GetLastError()); } } } LocalFree(pSessionInfo); return bSuccess; } int main(void) { char lpDomain[MAX_NAME] = {0}; char lpName[MAX_NAME] = {0}; DWORD dwDomainSize = MAX_NAME; DWORD dwNameSize = MAX_NAME; if (GetInteractiveUserInfo(lpDomain, dwDomainSize, lpName, dwNameSize)) { printf("登录用户:%s\\%s\n", lpDomain, lpName); } else { printf("获取登录用户信息失败\n"); } return 0; }
方案说明
- 方案1优化了原逻辑,降低权限请求并添加校验,适用于依赖进程遍历的场景,但仍可能受跨会话限制影响。
- 方案2通过WTS API直接获取活跃会话的用户令牌,管理员权限下可稳定获取登录用户信息,无需依赖
explorer.exe进程,是更可靠的方案。
内容的提问来源于stack exchange,提问作者shawnixer
相关产品推荐
相关产品推荐

