Ⅰ:前言

​ 当我们需要从运行在Session 0的服务进程创建一个具有管理员权限,且运行在登录用户Session的进程时,一般的流程代码应该如:

 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
std::tuple<HANDLE, uint32_t> CreateSessionProcess(uint32_t sessionID, const std::wstring& cmd)
{
    // 获取特定session的用户token
    HANDLE userToken = nullptr;
	if (!WTSQueryUserToken(sessionID, &userToken))
	{
		return {};
	}

    // 获取linked token
    TOKEN_LINKED_TOKEN adminTokenInfo = { 0 };
	DWORD infoLen = sizeof(adminTokenInfo);
	if (!GetTokenInformation(userToken, TokenLinkedToken, &adminTokenInfo, sizeof(TOKEN_LINKED_TOKEN), &infoLen))
	{
		CloseHandle(userToken);
		return {};
	}
    
    //
    // ...做一些其他操作
    //
    
    // 使用Token创建进程
    CreateProcessAsUser(...)
}

​ 这流程看起来很普通,但是暗藏几个细节坑,下面将详细解释

Ⅱ:驱动保护与WTSQueryUserToken

  • 前置条件

    在Windows 7系统上,当需要对进程及线程进行保护时,一般我们使用ObRegisterCallbacks进行权限过滤来实现,并且一般而言,我们需要放过对部分系统关键进程的过滤,如csrss.exe

  • 问题描述

    WTSQueryUserToken失败,错误代码ERROR_ACCESS_DENIED

  • 原因分析

    1. 根据经验判断,开关进程保护,WTSQueryUserToken会表现出不同的行为(成功/失败)

    2. 猜测在Windows 7上WTSQueryUserToken的实现方式为RPC调用,最后经由RPC服务进程DuplicateTokenEx来实现最后的Token覆写,且RPC服务进程没有在驱动白名单内,导致无权限执行DuplicateTokenEx

    3. 掏出IDA一通马操作,得到如下的执行流程图

      如图所示(可以在新标签页中查看大图)

      wtsqueryusertoken

    4. 证实猜测

      再次证明写代码得靠猜

  • 解决办法

    将系统进程lsm.exe加入驱动保护过滤白名单即可

Ⅲ:系统UAC与GetTokenInformation

  • 前置条件

    在session 0进程下,我们默认拥有 SeTcbPrivilege权限,此时获取一个普通权限的用户Token的LinkedToken时,则能自动提权并得到具有管理员权限的Token。

  • 问题描述

    在Windows 10系统上,若禁用系统UAC后,调用GetTokenInformation且第二个参数为TokenLinkedToken时,会失败且错误码为ERROR_NO_SUCH_LOGON_SESSION

  • 原因分析

    Windows系统的Token具有三种提权类型,分别是

    • TokenElevationTypeDefault:此用户使用了Single Token

      换而言之,没有使用Split Token,比如说UAC禁用的条件下。

      此时当前Token已经提权

    • TokenElevationTypeFull:此用户使用的了Split Token,且当前Token已经提权

      此时的LinkedToken为受限权限Token

    • TokenElevationTypeLimited:此用户使用的了Split Token,且当前Token为受限权限Token

      此时的LinkedToken为提权Token

    那么当UAC禁用的条件下,由于没有使用Split Token,自然无法获取到相应的LinkedToken

  • 解决办法

    判断当前Token的提权类型,当且仅当其权限类型为TokenElevationTypeLimited才获取其LinkedToken

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    // ...前置代码
    
    // 获取token类型
    TOKEN_ELEVATION_TYPE tokenElvType;
    DWORD typeSize = sizeof(tokenElvType);
    if (!GetTokenInformation(userToken, TokenElevationType, &tokenElvType, sizeof(TOKEN_ELEVATION_TYPE), &typeSize))
    {
        CloseHandle(userToken);
        return {};
    }
    
    if (tokenElvType == TokenElevationTypeLimited)
    {
        // 获取linked token
        TOKEN_LINKED_TOKEN adminTokenInfo = { 0 };
        DWORD infoLen = sizeof(adminTokenInfo);
        if (!GetTokenInformation(userToken, TokenLinkedToken, &adminTokenInfo, sizeof(TOKEN_LINKED_TOKEN), &infoLen))
        {
            CloseHandle(userToken);
            return {};
        }
    }
    
    // ...后接代码
    

Ⅳ:后话

  • 如何验证系统UAC是否处于启用状态

    执行命令

    1
    
    REG QUERY HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ /v EnableLUA
    

    得到

    EnableLUA如果为1表示启用,0则反之