今天要來介紹的是 Token & Object,我會盡量在這系列將 Windows 的權限管理模型解釋清楚。
我想藉由 James Forshaw 演講中常見到的這張圖來解釋在 Windows 中 Process 如何用自身的 Token 去取得 Resource 的存取權限。
(ref: James Forshaw - Social Engineering in Windows Kernel)
整個流程大致上分成以下步驟:
接下來我要來介紹一些重要的物件!
Token 就像是一張身分證,去超商取貨時,店員會以身分證來辨識取貨人的身分。Token 是一樣的概念,可以用來驗證持有者對於其他 Resource 的存取權限。
在 Windbg 可以下指令 dt nt!_token
看到 Token 的結構
nt!_TOKEN
+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER
+0x030 TokenLock : Ptr64 _ERESOURCE
+0x038 ModifiedId : _LUID
+0x040 Privileges : _SEP_TOKEN_PRIVILEGES
+0x058 AuditPolicy : _SEP_AUDIT_POLICY
+0x078 SessionId : Uint4B
+0x07c UserAndGroupCount : Uint4B
+0x080 RestrictedSidCount : Uint4B
+0x084 VariableLength : Uint4B
+0x088 DynamicCharged : Uint4B
+0x08c DynamicAvailable : Uint4B
+0x090 DefaultOwnerIndex : Uint4B
+0x098 UserAndGroups : Ptr64 _SID_AND_ATTRIBUTES
+0x0a0 RestrictedSids : Ptr64 _SID_AND_ATTRIBUTES
+0x0a8 PrimaryGroup : Ptr64 Void
+0x0b0 DynamicPart : Ptr64 Uint4B
+0x0b8 DefaultDacl : Ptr64 _ACL
+0x0c0 TokenType : _TOKEN_TYPE
+0x0c4 ImpersonationLevel : _SECURITY_IMPERSONATION_LEVEL
+0x0c8 TokenFlags : Uint4B
+0x0cc TokenInUse : UChar
+0x0d0 IntegrityLevelIndex : Uint4B
+0x0d4 MandatoryPolicy : Uint4B
+0x0d8 LogonSession : Ptr64 _SEP_LOGON_SESSION_REFERENCES
+0x0e0 OriginatingLogonSession : _LUID
+0x0e8 SidHash : _SID_AND_ATTRIBUTES_HASH
+0x1f8 RestrictedSidHash : _SID_AND_ATTRIBUTES_HASH
+0x308 pSecurityAttributes : Ptr64 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
+0x310 Package : Ptr64 Void
+0x318 Capabilities : Ptr64 _SID_AND_ATTRIBUTES
+0x320 CapabilityCount : Uint4B
+0x328 CapabilitiesHash : _SID_AND_ATTRIBUTES_HASH
+0x438 LowboxNumberEntry : Ptr64 _SEP_LOWBOX_NUMBER_ENTRY
+0x440 LowboxHandlesEntry : Ptr64 _SEP_CACHED_HANDLES_ENTRY
+0x448 pClaimAttributes : Ptr64 _AUTHZBASEP_CLAIM_ATTRIBUTES_COLLECTION
+0x450 TrustLevelSid : Ptr64 Void
+0x458 TrustLinkedToken : Ptr64 _TOKEN
+0x460 IntegrityLevelSidValue : Ptr64 Void
+0x468 TokenSidValues : Ptr64 _SEP_SID_VALUES_BLOCK
+0x470 IndexEntry : Ptr64 _SEP_LUID_TO_INDEX_MAP_ENTRY
+0x478 DiagnosticInfo : Ptr64 _SEP_TOKEN_DIAG_TRACK_ENTRY
+0x480 BnoIsolationHandlesEntry : Ptr64 _SEP_CACHED_HANDLES_ENTRY
+0x488 SessionObject : Ptr64 Void
+0x490 VariablePart : Uint8B
也可以使用 James Forshaw 開發的 TokenViewer.exe 觀察各個 token 的細節
根據 0xcsandker 的文章,實際上只會有兩種 Token Type:Primary Token 和 Impersonation Token。
根據 James Forshaw 的演講 Token 也可以依照功能分成 Normal Token、Linked Token、Filter Token、LowBox Token 等(應該還有更多種以功能命名的 token),但這只是為了方便區分功能,在此的 token 都是 Impersonate Token,除了 Normal Token 有可能是 Primary Token。
(ref: James Forshaw - Social Engineering in Windows Kernel)
[TODO] 這邊會出現一堆 token,但我目前還沒整理好,沒辦法有條理地寫出來@@
Windows 是由各式各樣 Object Types 的 Object 為基礎所建構。所有的 Object 都是可以被取得的 Resources,連 Token 本身都是 Object。而在 Windows 10 中,可以在 Windbg 中統計 nt!ObTypeIndexTable 的 entry,會發現有多達 70 種左右的 Object Types。
在 SysInternal Suite 的 winobj64.exe 也可以看到各式各樣的 Object Type
在 User mode 中,不能直接操控 Object,必須透過建立 Handle 的方式,透過 Handle 來操控 Object。Kernel mode 中也會維護一張 Handle Table,用來管理該 Object 建立的多個 Handle。
Object 本身除了代表可以被取得的資源外,也會包含 Security Descriptor 決定該 Object 可以被誰存取,唯有符合條件的 Token 才能存取。 另外,Handle Table 中也會記錄每個 Handle 當初被開啟時賦予的權限。
根據 Rayanfam 的 blog,OpenProcess 會有以下流程:
user-mode | OpenProcess
| NtOpenProcess
---------------------------------
kernel-mode | NtOpenProcess
| PsOpenProcess
| ObOpenObjectByPointer
| ObpCreateHandle
最終回傳 Process Handle
在正式開始介紹 Security Descriptor 前,需要先介紹 Security Identifier 和 Access Mask。
我們先來認識 Security Identifier 的組成
(ref: James Forshaw - Introduction to Local Privilege Escalation on Windows)
根據 James Forshaw 的投影片,其實 SID 和 Linux 中的 UID/GID 類似,但是在結構上有所不同。記得一些常見的 SIDs 也對滲透測試蠻有幫助的。
(ref: James Forshaw - Introduction to Local Privilege Escalation on Windows)
在許多 Windows APIs 都可以看到需要填入想要取得的權限 (e.g., CreateFile) ,之後才能對這個 Object 開啟 Handle,原理就是在 Kernel Mode 中會檢查 DesiredAccess 是不是允許的
根據 MSDN
至於 Security Descripter 就是檢查存取權限的清單,上面會記載這些資訊:
Security Descriptor 可以在 _OBJECT_HEADER→SecurityDescriptor 中找到
在 Windbg,可以下指令 !sd address 0
就可以看到 Security Descriptor 中的 Owner SID, Group SID, DACL, SACL
(其中 address 的部分要注意後3個bit為0,e.g., _OBJECT_HEADER→SecurityDescriptor & ~0xf == address)
lkd> !sd 0xffffce04`250f7c20 0
->Revision: 0x1
->Sbz1 : 0x0
->Control : 0x8814
SE_DACL_PRESENT
SE_SACL_PRESENT
SE_SACL_AUTO_INHERITED
SE_SELF_RELATIVE
->Owner : S-1-5-32-544
->Group : S-1-5-21-2232672516-4162811975-572094402-513
->Dacl :
->Dacl : ->AclRevision: 0x2
->Dacl : ->Sbz1 : 0x0
->Dacl : ->AclSize : 0x50
->Dacl : ->AceCount : 0x3
->Dacl : ->Sbz2 : 0x0
->Dacl : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl : ->Ace[0]: ->AceFlags: 0x0
->Dacl : ->Ace[0]: ->AceSize: 0x18
->Dacl : ->Ace[0]: ->Mask : 0x000f01ff
->Dacl : ->Ace[0]: ->SID: S-1-5-32-544
->Dacl : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl : ->Ace[1]: ->AceFlags: 0x0
->Dacl : ->Ace[1]: ->AceSize: 0x14
->Dacl : ->Ace[1]: ->Mask : 0x000f01ff
->Dacl : ->Ace[1]: ->SID: S-1-5-18
->Dacl : ->Ace[2]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl : ->Ace[2]: ->AceFlags: 0x0
->Dacl : ->Ace[2]: ->AceSize: 0x1c
->Dacl : ->Ace[2]: ->Mask : 0x0002001f
->Dacl : ->Ace[2]: ->SID: S-1-5-5-0-1516497
->Sacl :
->Sacl : ->AclRevision: 0x2
->Sacl : ->Sbz1 : 0x0
->Sacl : ->AclSize : 0x1c
->Sacl : ->AceCount : 0x1
->Sacl : ->Sbz2 : 0x0
->Sacl : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
->Sacl : ->Ace[0]: ->AceFlags: 0x0
->Sacl : ->Ace[0]: ->AceSize: 0x14
->Sacl : ->Ace[0]: ->Mask : 0x00000001
->Sacl : ->Ace[0]: ->SID: S-1-16-12288
Integrity Level 是定義 SID 被記錄在 _TOKEN→IntegrityLevelIndex
根據 Yarden Shafir 的 Exploit Writeup,可以照著 SepLocateTokenIntegrity 的邏輯在 Token 裡面找到 Integrity Level
找到的 Integrity Level 會是一組 SID,還需要去查表才可以確認是哪種 Integrity Level。Integrity Level 的 SID 會是 S-1-16-xxxx,xxxx 的部分會填入以下的 RID。
(ref: https://learn.microsoft.com/en-us/windows/win32/secauthz/well-known-sids)
以 Yarden Shafir 的 Exploit Writeup 為例
根據 _TOKEN→IntegrityLevelIndex 是 0xe,以此為 index 可以在 _TOKEN→UserAndrGroups 中找到 S-1-16-8192,很明顯的這是 Mandatory Label (Integrity Level),並且 8192 == 0x2000 == Medium Integrity Level。
在 windbg 中可以下指令 !token address
就可以看到所有 SIDs。
其實,最簡單方式是直接用 Process Explorer 就可以看到全部的 SIDs。
Privilege 會存在 _TOKEN→Privileges 中,並且有以下結構
lkd> dt _sep_token_privileges
nt!_SEP_TOKEN_PRIVILEGES
+0x000 Present : Uint8B
+0x008 Enabled : Uint8B
+0x010 EnabledByDefault : Uint8B
Present 表示 token 能夠啟用的 privilege,沒有的 bit flag 也就無法啟用。
Enabled 表示 token 現在啟用的 privilege
此外,結構中的每個成員都是 8 bytes 的 bitmask,每個 bit flag 的定義可以參考 ReactOS 的程式碼
/* If the first isn't defined, assume none is */
#ifndef SE_MIN_WELL_KNOWN_PRIVILEGE
#define SE_MIN_WELL_KNOWN_PRIVILEGE 2L
#define SE_CREATE_TOKEN_PRIVILEGE 2L
#define SE_ASSIGNPRIMARYTOKEN_PRIVILEGE 3L
#define SE_LOCK_MEMORY_PRIVILEGE 4L
#define SE_INCREASE_QUOTA_PRIVILEGE 5L
#define SE_MACHINE_ACCOUNT_PRIVILEGE 6L
#define SE_TCB_PRIVILEGE 7L
#define SE_SECURITY_PRIVILEGE 8L
#define SE_TAKE_OWNERSHIP_PRIVILEGE 9L
#define SE_LOAD_DRIVER_PRIVILEGE 10L
#define SE_SYSTEM_PROFILE_PRIVILEGE 11L
#define SE_SYSTEMTIME_PRIVILEGE 12L
#define SE_PROF_SINGLE_PROCESS_PRIVILEGE 13L
#define SE_INC_BASE_PRIORITY_PRIVILEGE 14L
#define SE_CREATE_PAGEFILE_PRIVILEGE 15L
#define SE_CREATE_PERMANENT_PRIVILEGE 16L
#define SE_BACKUP_PRIVILEGE 17L
#define SE_RESTORE_PRIVILEGE 18L
#define SE_SHUTDOWN_PRIVILEGE 19L
#define SE_DEBUG_PRIVILEGE 20L
#define SE_AUDIT_PRIVILEGE 21L
#define SE_SYSTEM_ENVIRONMENT_PRIVILEGE 22L
#define SE_CHANGE_NOTIFY_PRIVILEGE 23L
#define SE_REMOTE_SHUTDOWN_PRIVILEGE 24L
#define SE_UNDOCK_PRIVILEGE 25L
#define SE_SYNC_AGENT_PRIVILEGE 26L
#define SE_ENABLE_DELEGATION_PRIVILEGE 27L
#define SE_MANAGE_VOLUME_PRIVILEGE 28L
#define SE_IMPERSONATE_PRIVILEGE 29L
#define SE_CREATE_GLOBAL_PRIVILEGE 30L
#define SE_MAX_WELL_KNOWN_PRIVILEGE SE_CREATE_GLOBAL_PRIVILEGE
#endif /* ndef SE_MIN_WELL_KNOWN_PRIVILEGE */
各個 bit flag 掌握的 privilege 都不太一樣,這篇文章 有寫到各個 Privilege 的作用:
可以用 whoami /priv
檢視所有的 privileges
最後,在這篇文章發布前我覺得還有還是有許多物件的細節不夠完整,因此我在之後還是會陸續編輯這篇文章。
下一篇,我要介紹的是本系列的第二篇 Access Check!