Windows访问控制

Access Control Module,访问控制模型。是Windows用来约束线程对安全对象访问的控制模型。可以分为两部分,线程的访问令牌access token和安全对象的安全描述符security descriptor。

访问令牌access token

令牌也是一个内核对象,用于表示进程或线程的安全上下文。包括特权和一些标识,成功登陆系统的用户,将由系统分配一个访问令牌,该用户创建的所有进程继承该令牌。

access token主要包含这些信息:

  • 用户账户的Sid;
  • 用户所属组的Sid;
  • 登陆Sid标识当前登陆会话;
  • 用户和用户组特权列表;
  • 用户创建的进程的默认DACL;
  • access token类型和来源;
  • 其他信息;

我们用windbg的 dt !_token命令看一下:

dt _token
nt!_TOKEN
   +0x000 TokenSource      : _TOKEN_SOURCE
   +0x010 TokenId          : _LUID
   +0x018 AuthenticationId : _LUID
   +0x020 ParentTokenId    : _LUID
   +0x028 ExpirationTime   : _LARGE_INTEGER
   +0x030 TokenLock        : Ptr32 _ERESOURCE
   +0x038 AuditPolicy      : _SEP_AUDIT_POLICY
   +0x040 ModifiedId       : _LUID
   +0x048 SessionId        : Uint4B
   +0x04c UserAndGroupCount : Uint4B
   +0x050 RestrictedSidCount : Uint4B
   +0x054 PrivilegeCount   : Uint4B
   +0x058 VariableLength   : Uint4B
   +0x05c DynamicCharged   : Uint4B
   +0x060 DynamicAvailable : Uint4B
   +0x064 DefaultOwnerIndex : Uint4B
   +0x068 UserAndGroups    : Ptr32 _SID_AND_ATTRIBUTES
   +0x06c RestrictedSids   : Ptr32 _SID_AND_ATTRIBUTES
   +0x070 PrimaryGroup     : Ptr32 Void
   +0x074 Privileges       : Ptr32 _LUID_AND_ATTRIBUTES
   +0x078 DynamicPart      : Ptr32 Uint4B
   +0x07c DefaultDacl      : Ptr32 _ACL
   +0x080 TokenType        : _TOKEN_TYPE
   +0x084 ImpersonationLevel : _SECURITY_IMPERSONATION_LEVEL
   +0x088 TokenFlags       : Uint4B
   +0x08c TokenInUse       : UChar
   +0x090 ProxyData        : Ptr32 _SECURITY_TOKEN_PROXY_DATA
   +0x094 AuditData        : Ptr32 _SECURITY_TOKEN_AUDIT_DATA
   +0x098 OriginatingLogonSession : _LUID
   +0x0a0 VariablePart     : Uint4B

可以看到Token还是包含了很多信息的。刚才我们说到Token的种类,其实Token可以分为两种:主Token和模拟Token。主Token是进程默认Token,模拟Token往往是线程为了执行某些任务模拟其他线程的Token,得到某些权限。接下来看一个具体的Token,windows xp sp2下的notepad。

!process 0 1 notepad.exe
PROCESS 8237dc70  SessionId: 0  Cid: 0834    Peb: 7ffde000  ParentCid: 0754
    DirBase: 09c804a0  ObjectTable: e112b7f8  HandleCount:  44.
    Image: notepad.exe
    VadRoot 820e2460 Vads 66 Clone 0 Private 208. Modified 13. Locked 0.
    DeviceMap e1c51418
    Token                             e265a030
    ElapsedTime                       00:00:15.219
    UserTime                          00:00:00.015
    KernelTime                        00:00:00.062
    QuotaPoolUsage[PagedPool]         62772
    QuotaPoolUsage[NonPagedPool]      2640
    Working Set Sizes (now,min,max)  (948, 50, 345) (3792KB, 200KB, 1380KB)
    PeakWorkingSetSize                953
    VirtualSize                       31 Mb
    PeakVirtualSize                   36 Mb
    PageFaultCount                    1001
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      404

lkd-> !token e265a030
_TOKEN e265a030
TS Session ID: 0
User: S-1-5-21-790525478-1844823847-1801674531-500
Groups: 
 00 S-1-5-21-790525478-1844823847-1801674531-513
    Attributes - Mandatory Default Enabled 
 01 S-1-1-0
    Attributes - Mandatory Default Enabled 
 02 S-1-5-32-544
    Attributes - Mandatory Default Enabled Owner 
 03 S-1-5-32-545
    Attributes - Mandatory Default Enabled 
 04 S-1-5-4
    Attributes - Mandatory Default Enabled 
 05 S-1-5-11
    Attributes - Mandatory Default Enabled 
 06 S-1-5-5-0-64280
    Attributes - Mandatory Default Enabled LogonId 
 07 S-1-2-0
    Attributes - Mandatory Default Enabled 
Primary Group: S-1-5-21-790525478-1844823847-1801674531-513
Privs: 
 00 0x000000017 SeChangeNotifyPrivilege           Attributes - Enabled Default 
 01 0x000000008 SeSecurityPrivilege               Attributes - 
 02 0x000000011 SeBackupPrivilege                 Attributes - 
 03 0x000000012 SeRestorePrivilege                Attributes - 
 04 0x00000000c SeSystemtimePrivilege             Attributes - 
 05 0x000000013 SeShutdownPrivilege               Attributes - 
 06 0x000000018 SeRemoteShutdownPrivilege         Attributes - 
 07 0x000000009 SeTakeOwnershipPrivilege          Attributes - 
 08 0x000000014 SeDebugPrivilege                  Attributes - 
 09 0x000000016 SeSystemEnvironmentPrivilege      Attributes - 
 10 0x00000000b SeSystemProfilePrivilege          Attributes - 
 11 0x00000000d SeProfileSingleProcessPrivilege   Attributes - 
 12 0x00000000e SeIncreaseBasePriorityPrivilege   Attributes - 
 13 0x00000000a SeLoadDriverPrivilege             Attributes - Enabled 
 14 0x00000000f SeCreatePagefilePrivilege         Attributes - 
 15 0x000000005 SeIncreaseQuotaPrivilege          Attributes - 
 16 0x000000019 SeUndockPrivilege                 Attributes - Enabled 
 17 0x00000001c SeManageVolumePrivilege           Attributes - 
 18 0x00000001d SeImpersonatePrivilege            Attributes - Enabled Default 
 19 0x00000001e SeCreateGlobalPrivilege           Attributes - Enabled Default 
Authentication ID:         (0,10166)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: User32             TokenFlags: 0x89 ( Token in use )
Token ID: 4a319a           ParentToken ID: 0
Modified ID:               (0, 4a319c)
RestrictedSidCount: 0      RestrictedSids: 00000000

也可以看到notepad可能拥有的权限,Enabled表示被允许。如果有些特权不在列表中,那么是不可能被拥有的,除非采用一些特殊的手段。

安全描述符security descriptor

如果写过Windows程序的话会知道,在创建内核对象的时候需要一个安全描述符。安全描述符用于控制对象的安全访问。

安全描述符的结构可以用windbg dt _security_descriptor查看。

lkd-> dt _security_descriptor
nt!_SECURITY_DESCRIPTOR
   +0x000 Revision         : UChar
   +0x001 Sbz1             : UChar
   +0x002 Control          : Uint2B
   +0x004 Owner            : Ptr32 Void
   +0x008 Group            : Ptr32 Void
   +0x00c Sacl             : Ptr32 _ACL
   +0x010 Dacl             : Ptr32 _ACL

关键的成员是SACL和DACL。DACL我们在上文提到过。为了说清楚DLAC以及访问控制模型的精华部分,不得不多提一些东西。

SID

SID即security identifier,系统为每个account分配一个唯一的SID。这里的account包括用户、用户组等,并不是狭义的用户。你只要知道一个用户就对应一个SID,比如当用户登陆的时候会根据SID判断用户是否正确输入密码等。

ACL

ACL即Access Control List,访问控制列表,是由ACE即Access Control Entry访问控制入口组成的链表。ACE包含允许访问所属对象的所有账户的SID及该SID所有用的对该对象的操作权限。结构如下:

lkd-> dt _acl
nt!_ACL
   +0x000 AclRevision      : UChar
   +0x001 Sbz1             : UChar
   +0x002 AclSize          : Uint2B
   +0x004 AceCount         : Uint2B
   +0x006 Sbz2             : Uint2B

安全描述符有两个ACL结构,SACL用于审计audit,而DACL是我们关心的重点,用于控制线程对对象的访问。线程的SID继承自创建线程所属进程的用户。那么就有了如下的访问规则:

1.DACL==NULL,线程拥有对象的全部访问权限; 2.AceCount=0,即没有ACE,则拒绝所有访问; 3.遍历所有ACE,如果找不到与线程所属用户SID相关的ACE,拒绝访问; 4.找到ACE,进行权限判断。

这就是访问控制模型的大致理论。通过安全描述符和访问令牌,相互对应达到对内核对象的访问控制。

enable 权限,即提权

上面的notepad的token,可以看到有的特权是enabled,表示被允许。如果我们想让用户拥有某些默认不被允许的特权,就需要提权操作。其实就是几个API的使用。

OpenProcessToken打开进程的Token对象,三个参数含义显而易见。DesiredAccess表示期望的权限。对于添加特权,我们需要的权限至少是TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES。进程对象需要以PROCESS_QUERY_INFORMATION打开。

BOOL WINAPI OpenProcessToken(
  _In_   HANDLE ProcessHandle,
  _In_   DWORD DesiredAccess,
  _Out_  PHANDLE TokenHandle
);

LookupPrivilegeValue用于打开特定系统lpName权限的LUID,即locally unique identifier,用于标识系统中的某个特权。lpSystemName为Null表示本地系统。

BOOL WINAPI LookupPrivilegeValue(
  _In_opt_  LPCTSTR lpSystemName,
  _In_      LPCTSTR lpName,
  _Out_     PLUID lpLuid
);

AdjustTokenPrivileges用于调整token特权,第二个参数有点儿奇怪,如果是True的话表示禁止所有特权。PTOKEN_PRIVILEGES相当于一个已计数的LUID_AND_ATTRIBUTES数组。LUID_AND_ATTRIBUTES表示希望允许或禁止LUID代表的特权。

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
);

typedef struct _TOKEN_PRIVILEGES {
	DWORD               PrivilegeCount;
	LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

typedef struct _LUID_AND_ATTRIBUTES {
	LUID  Luid;
	DWORD Attributes;
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;
我们这里的attributes为SE_PRIVILEGE_ENABLED,表示允许特权

有这三个API就可以提权了,这里给出一个样例:

BOOL EnablePrivilege(PTCHAR Privilege)
{
	BOOL rc = FALSE;
    HANDLE hToken;
    LUID luid;
    TOKEN_PRIVILEGES tokenPrivilege;
	DWORD dwProcID = GetCurrentProcessId();
	HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcID);
	
    rc = OpenProcessToken(
            hProc,
            TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
            hToken);

    if (rc)
    {
        rc = LookupPrivilegeValue(NULL, Privilege, luid);
        if (rc)
        {
            tokenPrivilege.PrivilegeCount = 1;
            tokenPrivilege.Privileges[0].Luid = luid;
            tokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

            rc = AdjustTokenPrivileges(
                    hToken, 
                    FALSE, 
                    tokenPrivilege, 
                    sizeof(tokenPrivilege), 
                    NULL, 
                    NULL);
        }
    }
    if (hToken) CloseHandle(hToken);
	if(hProc)	CloseHandle(hProc);

    return rc;
}

理解访问控制模型,提权代码看着也舒服多了。

添加特权

在刚开始我们就提到,如果有些特权不在token列表中,那么需要一些额外手段,才能添加到列表。添加到列表才可以进行提权。添加特权还需要理解另外的概念。

本地安全策略

Local Security Authority,即LSA,是一个受保护的子系统,用于认证和登录用户以及调整本地权限信息。它通过一组LSA policy objects记录了本地安全策略信息。包括以下对象:

1.Policy对象保存全局策略信息; 2.TrustedDomain保存受信任域信息; 3.Account保存用户、组、本地账户信息; 4.Private Data保存密码等加密信息。

这里我们用到的时Policy对象,保存全局策略信息。LsaOpenPolicy建立与LSA的通信,得到Policy对象。

NTSTATUS LsaOpenPolicy(
	_In_ 	PLSA_UNICODE_STRING SystemName,
	_In_ 	PLSA_OBJECT_ATTRIBUTES ObjectAttributes,
	_In_ 	ACCESS_MASK DesiredAccess,
	_InOut_ PLSA_HANDLE PolicyHandle
);

typedef struct _LSA_UNICODE_STRING {
	USHORT Length;
	USHORT MaximumLength;
	PWSTR  Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING;

ObjectAttributes:	为0即可。
DesiredAccess : 	这里需要GENERIC_READ|GENERIC_WRITE|GENERIC_ALL GENERIC_EXECUTE

使用policy对象可以进一步使用操作本地安全策略的其他相关函数,注意这个函数只有Administrator用户才能调用。LsaClosePolicy用于关闭打开的Policy对象。MSDN有一个打开policy并关闭的简单程序:

#include <windows.h>
#define TARGET_SYSTEM_NAME L"mysystem"

LSA_HANDLE GetPolicyHandle()
{
  LSA_OBJECT_ATTRIBUTES ObjectAttributes;
  WCHAR SystemName[] = TARGET_SYSTEM_NAME;
  USHORT SystemNameLength;
  LSA_UNICODE_STRING lusSystemName;
  NTSTATUS ntsResult;
  LSA_HANDLE lsahPolicyHandle;

  // Object attributes are reserved, so initialize to zeros.
  ZeroMemory(ObjectAttributes, sizeof(ObjectAttributes));

  //Initialize an LSA_UNICODE_STRING to the server name.
  SystemNameLength = wcslen(SystemName);
  lusSystemName.Buffer = SystemName;
  lusSystemName.Length = SystemNameLength * sizeof(WCHAR);
  lusSystemName.MaximumLength = (SystemNameLength+1) * sizeof(WCHAR);

  // Get a handle to the Policy object.
  ntsResult = LsaOpenPolicy(
        lusSystemName,    //Name of the target system.
        ObjectAttributes, //Object attributes.
        POLICY_ALL_ACCESS, //Desired access permissions.
        lsahPolicyHandle  //Receives the policy handle.
    );

  if (ntsResult != STATUS_SUCCESS)
  {
    // An error occurred. Display it as a win32 error code.
    wprintf(L"OpenPolicy returned %lu\n",
      LsaNtStatusToWinError(ntsResult));
    return NULL;
  } 
  return lsahPolicyHandle;
}

查询用户SID并添加特权

查询用户SID需要用到LookupAccountName,查询MSDN后就会发现很简单的一个API。

BOOL WINAPI LookupAccountName(
  _In_opt_   LPCTSTR lpSystemName,
  _In_       LPCTSTR lpAccountName,
  _Out_opt_  PSID Sid,
  _Inout_    LPDWORD cbSid,
  _Out_opt_  LPTSTR ReferencedDomainName,
  _Inout_    LPDWORD cchReferencedDomainName,
  _Out_      PSID_NAME_USE peUse
);
typedef enum _SID_NAME_USE { 
  SidTypeUser            = 1,
  SidTypeGroup,
  SidTypeDomain,
  SidTypeAlias,
  SidTypeWellKnownGroup,
  SidTypeDeletedAccount,
  SidTypeInvalid,
  SidTypeUnknown,
  SidTypeComputer,
  SidTypeLabel
} SID_NAME_USE, *PSID_NAME_USE;
表明sid类型

添加特权需要用到LsaAddAccountRights:

NTSTATUS LsaAddAccountRights(
  _In_  LSA_HANDLE PolicyHandle,
  _In_  PSID AccountSid,
  _In_  PLSA_UNICODE_STRING UserRights,
  _In_  ULONG CountOfRights
);

typedef struct _LSA_UNICODE_STRING {
	USHORT Length;
	USHORT MaximumLength;
	PWSTR  Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING;

最后

本文提到的内容大部分都来源于MSDN,但是MSDN的信息相对太分散,所以看了很久很久之后写了这篇文章,有什么问题希望大家能提出来。对于Policy的那一部分说实话我也不是非常了解,因此描述上可能有不妥,请见谅。