-
-
[原创]Windows 借助服务实现无UAC提示创建管理员权限的进程
-
发表于: 2025-10-27 08:52 758
-
上次写完Windows访问控制机制文章之后,想起之前的一个需求,如何让一个GUI程序以管理员权限实现自启动。有的朋友可能会说,方法有很多,比如:注册表、计划任务、启动文件夹和组策略设置启动脚本。上述这些方法只有一种可以生效,那就是计划任务,其他的方式会因为权限的问题导致目标程序启动失败,如下图所示:
而计划任务有可能会被杀软进行清除,导致目标程序启动失败。本篇文章介绍如何从服务进程启动一个GUI程序,新进程拥有管理员权限。
原理
User Account Control
UAC(User Account Control,用户账户控制)是从Windows Vista和Windows Server 2008版本开始引入的一项安全机制,旨在提高操作系统的安全性,防止未经授权的系统更改。在UAC机制下,如果隶属于管理员组的用户登录到系统之后,它得到的是一个经过过滤的标准权限token(filtered token)来运行程序。如果程序运行需要管理员权限,则filter token会被提升至unfiltered token,这个token拥有管理员应有的全部权限。
如果程序需要以管理员权限进行启动,可以在VS项目的Properties->Linker->Manifest File->UAC Execution Level中设置为requireAdministrator表示程序启动需要管理员权限。这样编译之后的程序,会显示一个小盾牌,该程序启动的时候会弹出一个安全提示框,表示当前程序将以管理员权限启动。经过签名和未签名的程序,弹出的提示框颜色不同,未经过签名的提示窗是黄色的,经过签名的程序的提示窗是蓝色的。

启动之后的注册表编辑器进程是管理员进程:
该进程对应的token自然是一个具有管理员权限的token。这需要你当前登录的Windows账户本身就属于管理员组,如果不属于管理员组,那么就需要输入一个隶属于管理员组的成员的账号和密码进行验证。
根据Windows System Internals 7th中的描述,UAC机制是在可以设置中关闭的,关闭之后管理员组成员创建的任何进程都拥有管理权限。
实际工作中是存在这种需求的,比如:在云沙箱的环境下自动化运行恶意程序,需要充分暴露恶意程序的行为,那么就需要进行这样的设置。
LinkedToken
上文提到了用户登录到系统中之后,拿到的是一个filtered token,而LinkedToken对于filtered token就是拥有管理员权限的token(unfiltered token),这两种类型的token互为linked token。
1 2 3 4 5 6 7 | BOOL GetTokenInformation( [in] HANDLE TokenHandle, [in] TOKEN_INFORMATION_CLASS TokenInformationClass, [out, optional] LPVOID TokenInformation, [in] DWORD TokenInformationLength, [out] PDWORD ReturnLength); |
使用该Win32 API函数,第二个参数传入一个TokenLinkedToken,就可以获取一个token的Linked Token。
那么此时就有一个问题,假如一个恶意程序通过filtered token拿到一个拥有管理员权限的unfiltered token,然后再调用CreateProcessAsUser函数就可以实现提权了。显然,这是不可能的。我们从内核中开始分析,GetTokenInformation调用的底层API是NtQueryInformationToken,

上图中的591行有"v97 = -(SeSinglePrivilegeCheck(SeTcbPrivilege, v10) != 0);",这是检查当前进程是否具有SeTcbPrivilege权限,他的英文介绍是"Act as part of the operating system",普通进程不可能拥有这个权限,服务进程拥有这个权限。
此时SepDuplicateToken的第四个参数v99应该是2,TokenType对应是TokenImpersonation,第五个参数是1,ImpersonationLevel是SecurityIdentification。获取的token无法用于进程和线程的创建,原因如下:
- CreateProcessAsUser的第一个参数hToken要求指向的token对象是一个Primary token而不是一个impersonate token。token的类型不符合。
- SetThreadToken为特定线程设置impersonate token,要求token的ImpersonationLevel至少是SecurityImpersonation。token的level不符合。
普通进程获取的linked token只能用来进行信息查询。比如:查询当前用户是否属于管理员组以及完整的token拥有哪些权限等。
具体实现
方法一
使用服务的token作为GUI进程的token,服务的账户一般是Local System,它的token权限大于管理员权限。步骤如下:
- OpenProcess当前进程,获取进程句柄,复制当前进程的token为dupToken。
- 设置dupToken的参数信息,包括:session id和窗口工作站
- 根据dupToken创建环境变量,调用CreateProcesssAsUser创建新的进程
该方法有一个缺点,有些环境变量信息是不正确的,比如:访问注册表HKEY_CURRENT_USER,该方法访问的路径则是,HKEY_USERS\S-1-5-18。使用该方法的话需要修改对应的代码才能正常使用。
相关链接为:898K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2U0L8X3u0D9L8$3N6K6i4K6u0W2j5$3!0E0i4K6u0r3k6X3!0G2K9r3q4U0K9#2)9J5c8Y4m8Q4x3V1j5$3x3U0R3J5z5o6x3H3i4K6u0W2K9s2c8E0L8l9`.`.
方法二
使用会话的LinkedToken来作为GUI进程的token,如果当前会话是管理员组成员,则获取的LinkedToken就是拥有管理员权限的token。步骤如下:
- 调用WTSGetActiveConsoleSessionId和WTSQueryUserToken来获取用户的会话token
- GetTokenInformation获取会话token的linked token
- 根据linked token创建环境变量,调用CreateProcessAsUser创建GUI进程。
总体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | BOOL StartGameAssistantProcess(WCHAR* cmdline){ int res = 0; DWORD dwSessionId = 0; HANDLE hTokenLinked = NULL; HANDLE hToken = NULL; HANDLE hTokenToUse = NULL; LPVOID pEnv = NULL; STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION processInfo = { 0 }; WCHAR lpDesktop[] = L"WinSta0\\Default"; TOKEN_ELEVATION_TYPE tokenElvType = TokenElevationTypeDefault; DWORD dwSize = 0; dwSessionId = WTSGetActiveConsoleSessionId(); if (dwSessionId == 0xFFFFFFFF) { return FALSE; } BOOL bRet = WTSQueryUserToken(dwSessionId, &hToken); if (bRet == FALSE) { goto Clean; } dwSize = sizeof(tokenElvType); if (!GetTokenInformation(hToken, TokenElevationType, &tokenElvType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) { goto Clean; } if (tokenElvType == TokenElevationTypeLimited) { dwSize = sizeof(hTokenLinked); bRet = GetTokenInformation(hToken, TokenLinkedToken, &hTokenLinked, dwSize, &dwSize); if (bRet == FALSE) { goto Clean; } hTokenToUse = hTokenLinked; } else { hTokenToUse = hToken; } ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(si); si.lpDesktop = lpDesktop; si.wShowWindow = SW_SHOW; si.dwFlags = STARTF_USESHOWWINDOW; bRet = CreateEnvironmentBlock(&pEnv, hTokenToUse, FALSE); if (!bRet) { goto Clean; } if (pEnv == NULL) { goto Clean; } if (!CreateProcessAsUser(hTokenToUse, NULL, cmdline, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, pEnv, NULL, &si, &processInfo)) { res = FALSE; } else { CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); res = TRUE; }Clean: if (pEnv) { DestroyEnvironmentBlock(pEnv); } if (hTokenLinked) { CloseHandle(hTokenLinked); } if (hToken) { CloseHandle(hToken); } return res;} |
上面这种方法解决方法一的缺陷。但假如当前机器被多个用户使用RDP登录,则上述代码无法生效,上述代码仅适用于一般用户的PC机器。
总结
本文首先分析了UAC和LinkedToken机制,随后介绍了两种使用服务来实现GUI程序自启动的方式,启动的进程拥有管理员权限。
参考链接
b44K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3#2A6j5%4u0G2M7$3!0X3N6q4)9J5k6h3y4G2L8g2)9J5c8X3g2F1i4K6u0V1N6i4y4Q4x3V1k6%4K9h3&6V1L8%4N6K6i4K6u0r3M7$3g2U0N6i4u0A6N6s2W2Q4x3V1k6S2M7s2m8D9K9h3y4S2N6r3W2G2L8W2)9J5k6s2y4W2j5%4g2J5K9i4c8&6i4K6u0r3j5i4m8H3L8r3W2U0j5i4c8A6L8$3&6Q4x3X3c8U0L8$3&6@1M7X3!0D9i4K6u0r3N6i4y4W2M7W2)9J5k6r3q4U0j5$3!0#2L8Y4c8Q4x3X3c8U0L8$3&6@1M7X3!0D9i4K6u0r3
69fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1k6i4k6T1N6h3N6Q4x3X3g2S2M7Y4c8Q4x3V1k6H3L8%4y4@1M7#2)9J5c8Y4N6A6L8X3c8G2N6%4y4Q4x3X3c8@1L8$3E0W2L8W2)9J5k6s2N6W2K9i4u0W2k6q4)9J5k6r3u0#2k6%4y4Q4x3V1j5`.
5d8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6B7L8$3&6F1P5g2)9J5k6r3A6G2K9r3&6K6L8$3&6Q4x3X3g2E0k6h3c8A6N6h3#2Q4x3X3g2U0L8$3#2Q4x3V1k6W2P5s2m8D9L8%4u0A6L8X3N6Q4x3X3c8@1L8$3E0W2L8W2)9J5k6r3#2W2L8h3u0W2M7Y4y4Q4x3X3c8H3j5i4u0@1i4K6u0V1x3g2)9J5k6o6b7^5j5X3y4W2z5o6l9H3y4r3x3$3j5b7`.`.
470K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4c8#2M7$3!0F1k6K6R3$3i4K6u0r3j5i4u0@1K9h3y4D9k6g2)9J5c8X3c8W2N6r3q4A6L8s2y4Q4x3V1j5I4x3o6j5#2y4e0j5&6z5o6R3`.