天天看點

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

作者:Loong716@Amulab

0x00 前言

Mimikatz是法國安全研究員Benjamin Delpy開發的一款安全工具。ST測試人員對mimikatz印象最深的肯定就是抓取Windows憑證,但作者對它的描述是“a tool I’ve made to learn C and make somes experiments with Windows security.”,其實它的功能不僅僅是抓取憑證,還包含了很多Windows安全相關的技術和知識

這裡借用@daiker師傅的思維導圖,mimikatz的子產品大緻可分為幾個部分:

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

是以文章也會大緻分為windows 通路控制模型,windows 憑據以及加解密,windows AD 安全,windows 程序以及服務,mimikatz 其他子產品五個小系列。之前自己一直想分析mimikatz的相關功能,主要是出于以下原因:

  • mimikatz中有許多功能利用了Windows的一些機制和特性,以changentlm為例,其利用MS-SAMR協定修改使用者的密碼,我們再根據MS-SAMR或RPC進行知識延伸,肯定也有不少收獲
  • mimikatz中涉及大量記憶體的操作,其中運用的記憶體Patch技術也被經常應用于一些安全機制的繞過(如繞過AMSI、Credential Guard等),于是自己想在分析過程中通過windbg學到一些調試的技巧
  • mimikatz在實戰中被殺的很厲害,了解相應原理可以自己實作相應功能
  • 學習/練習C語言 ????

mimikatz中與Windows通路控制模型相關的有privilege、token、sid三個子產品,其分别對應特權、通路令牌、安全辨別符三個知識,本文主要分析token子產品,并簡要介紹Windows通路控制模型

由于mimikatz代碼邏輯較為複雜,涉及大量回調,是以文中代碼都是經過簡化的。文章可能也會有一些技術上或者邏輯上的錯誤,還請師傅們指正

0x01 通路控制模型簡介

Windows通路控制模型有兩個基本組成部分:

  • 通路令牌(Access Token):包含有關登入使用者的資訊
  • 安全描述符(Security Descriptor):包含用于保護安全對象的安全資訊

1. 通路令牌(Access Token)

通路令牌(Access Token)被用來描述一個程序或線程的安全上下文,使用者每次登入成功後,系統會為其建立通路令牌,該使用者的所有程序也将擁有此通路令牌的副本

當線程與安全對象進行互動或嘗試執行需要特權的系統任務時,系統使用通路令牌來辨別使用者。使用windbg檢視程序的token,其包含資訊如下圖所示:

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)
【安全研究】從mimikatz學習Windows安全之通路控制模型(一)
【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

2. 安全描述符(Security Descriptor)

安全描述符(Security Descriptor)包含與安全對象有關的安全資訊,這些資訊規定了哪些使用者/組可以對這個對象執行哪些操作,安全描述符主要由以下部分構成:

  • 所有者的SID
  • 組SID
  • 自主通路控制清單(DACL),規定哪些使用者/組可以對這個對象執行哪些操作
  • 系統通路控制清單(SACL),規定哪些使用者/組的哪些操作将被記錄到安全審計日志中

在windbg中檢視一個安全對象的安全描述符,可以清晰的看到安全描述符的組成:

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

可以看到該安全描述符的DACL中有三條ACE,ACE的類型都是

ACCESS_ALLOWED_ACE_TYPE

Mask

是權限掩碼,用來指定對應的權限。以第一條ACE為例,其表示允許SID為S-1-5-32-544的對象能夠對該安全對象做0x001fffff對應的操作

3. 權限檢查的過程

當某個線程嘗試通路一個安全對象時,系統根據安全對象的ACE對照線程的通路令牌來判斷該線程是否能夠對該安全對象進行通路。通常,系統使用請求通路的線程的主通路令牌。但是,如果線程正在模拟其他使用者,則系統會使用線程的模拟令牌

此時将在該安全對象的DACL中按順序檢查ACE,直到發生以下事件:

  • 某一條拒絕類型的ACE顯式拒絕令牌中某個受信者的所有通路權限
  • 一條或多條允許類型的ACE允許令牌中列出的受信者的所有通路權限
  • 檢查完所有ACE但沒有一個權限顯式允許,那麼系統會隐式拒絕該通路

我們以微軟文檔中的圖檔為例,描述一下整個過程:

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)
  1. 線程A請求通路安全對象,系統讀取ACE1,發現拒絕Andrew使用者的所有通路權限,而線程A的通路令牌是Andrew,是以拒絕通路,并不再檢查ACE2、ACE3
  2. 線程A請求通路,系統按順序讀取ACE,ACE1不适用,讀取到ACE2發現适用,再讀取到ACE3也适用,是以最終該使用者擁有對該安全對象的讀、寫、執行權限

0x02 Mimikatz的Token子產品

Mimikatz的token子產品共有5個功能:

  • token::whoami:列出目前程序/線程的token資訊
  • token::list:列出目前系統中存在的token
  • token::elevate:竊取其他使用者的token
  • token::run:利用某使用者權限運作指定程式
  • token::revert:恢複為原來的token

1. token::whoami

該功能用于列出目前程序/線程的token資訊

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

隻有一個可選參數

/full

,當指定該參數時會列印出目前token的組資訊和特權資訊:

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

該功能的原理大緻如下:

  1. 通過

    OpenProcess()

    擷取目前程序/線程的句柄
  2. 調用

    GetTokenInformation()

    擷取token的各種資訊并輸出

其核心為調用

GetTokenInformation()

來擷取token的各種資訊,我們先來看這個API定義

BOOL GetTokenInformation(
  HANDLE                  TokenHandle,
  TOKEN_INFORMATION_CLASS TokenInformationClass,
  LPVOID                  TokenInformation,
  DWORD                   TokenInformationLength,
  PDWORD                  ReturnLength
);
           

其中第二個參數是一個

TOKEN_INFORMATION_CLASS

枚舉類型,我們可以通過指定它的值來擷取token指定的資訊

typedef enum _TOKEN_INFORMATION_CLASS {
  TokenUser,
  TokenGroups,
  TokenPrivileges,
  TokenOwner,
  TokenPrimaryGroup,
  TokenDefaultDacl,
  TokenSource,
  ...
} TOKEN_INFORMATION_CLASS, *PTOKEN_INFORMATION_CLASS;
           

例如擷取token的SessionID并輸出,可以使用以下代碼:

if (!GetTokenInformation(hToken, TokenSessionId, &sessionId, sizeof(TokenSessionId), &dwSize))
{
    wprintf(L"[!] GetTokenInformation error: %u\n", GetLastError());
}

wprintf(L"\t%-21s: %u\n", L"Session ID", sessionId);
           

2. token::list

該功能是擷取目前系統中所有的token,注意使用前需要先擷取

SeDebugPrivilege

,否則列出的token不全

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

該功能原理大緻如下:

  1. NtQuerySystemInformation()

    擷取系統程序資訊(如程序PID等)
  2. 循環周遊所有程序的PID,使用

    token::whoami

    功能中的方法對指定token資訊進行輸出

NtQuerySystemInformation()

用來檢索指定的系統資訊:

__kernel_entry NTSTATUS NtQuerySystemInformation(
  SYSTEM_INFORMATION_CLASS SystemInformationClass,
  PVOID                    SystemInformation,
  ULONG                    SystemInformationLength,
  PULONG                   ReturnLength
);
           

其第一個參數是一個

SYSTEM_INFORMATION_CLASS

枚舉類型,我們同樣可以指定不同參數來擷取不同的系統資訊

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

以擷取系統程序名和PID為例,代碼如下:

PSYSTEM_PROCESS_INFORMATION pProcessInfo = NULL;
DWORD flag = TRUE;

pProcessInfo = (PSYSTEM_PROCESS_INFORMATION)malloc(dwSize);
ntReturn = NtQuerySystemInformation(SystemProcessInformation, pProcessInfo, dwSize, &dwSize);

while (ntReturn == STATUS_INFO_LENGTH_MISMATCH) {
    free(pProcessInfo);
    pProcessInfo = (PSYSTEM_PROCESS_INFORMATION)malloc(dwSize);
    ntReturn = NtQuerySystemInformation(SystemProcessInformation, pProcessInfo, dwSize, &dwSize);
}

while (flag)
{
    if (pProcessInfo->NextEntryOffset == 0)
        flag = FALSE;

    wprintf(L"%-15d", (DWORD)pProcessInfo->UniqueProcessId);
    wprintf(L"%-50s", (wchar_t*)pProcessInfo->ImageName.Buffer);

    pProcessInfo = (PSYSTEM_PROCESS_INFORMATION)((BYTE*)pProcessInfo + pProcessInfo->NextEntryOffset);
}
           

PS:按照該思路,理論上利用

CreateToolhelp32Snapshot()

 + 

Process32First()

周遊程序PID也可以實作該功能

3. token::elevate

該子產品用于竊取指定使用者的token,共有7個可選參數,這些參數主要用來指定要竊取的token,如果不指定參數則預設竊取

NT AUTHORITY\SYSTEM

的token

  • /id:指定目标token的TokenID
  • /domainadmin:竊取域管的token
  • /enterpriseadmin:竊取企業管理者的token
  • /admin:竊取本地管理者的token
  • /localservice:竊取Local Service權限的token
  • /networkservice:竊取Network Service權限的token
  • /system:竊取SYSTEM權限的token

假設我們現在在目标機器上發現的域管權限的token

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

我們可以指定目标TokenID,或者使用

/domainadmin

來竊取域管的token,執行成功後可以看到目前線程已經擁有域管的模拟令牌:

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

然後我們就可以在目前mimikatz上下文中使用域管身份執行操作了,如DCSync

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

該功能大緻過程如下:

  1. OpenProcess()

  2. OpenProcessToken()

    打開與程序相關的token句柄
  3. 使用

    DuplicateTokenEx()

    使用目标程序token建立一個新的模拟token
  4. SetThreadToken()

    設定目前線程的token為上一步建立的新的模拟token

由于竊取token是Access Token利用的重點,該過程放在本文後面分析

4. token::run

該功能是使用指定的token來運作程式,也可以使用

token::elevate

中的幾個參數來指定運作程式的token,除此之外還有一個參數:

  • /process:指定要運作的程式,預設值為whoami.exe
【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

其原理前三步與

token::elevate

大緻相同,差別在于使用

DuplicateTokenEx()

竊取token後,該功能使用

CreateProcessAsUser()

來使用新的primary token建立一個程序

BOOL CreateProcessAsUserA(
  HANDLE                hToken,
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);
           

建立程序後,利用匿名管道做程序間通信,将新建立程序的标準輸出寫入到匿名管道的write端,從管道read端讀取資料進行回顯(在webshell等非互動場景下很有用)

if (CreatePipe(&hStdoutR, &hStdoutW, &saAttr, 0))
{
    SetHandleInformation(hStdoutR, HANDLE_FLAG_INHERIT, 0);
    si.cb = sizeof(STARTUPINFO);
    si.hStdOutput = hStdoutW;
    si.hStdError = si.hStdOutput;
    si.dwFlags |= STARTF_USESTDHANDLES;

    if (CreateProcessWithTokenW(hDupToken, LOGON_WITH_PROFILE, NULL, cmd, CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &si, &pi))
    {
        CloseHandle(si.hStdOutput);
        si.hStdOutput = si.hStdError = NULL;
        while (ReadFile(hStdoutR, resultBuf, sizeof(resultBuf), &dwRead, NULL) && dwRead)
        {
            for (i = 0; i < dwRead; i++)
                wprintf(L"%c", resultBuf[i]);
        }

        WaitForSingleObject(pi.hProcess, INFINITE);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
    }
    else wprintf(L"CreateProcessWithTokenW error 0x%08X\n", GetLastError());

}
else wprintf(L"CreatePipe error! 0x%08X\n", GetLastError());
           

5. token::revert

該子產品用來清除線程的模拟令牌:

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

原理很簡單,直接使用

SetThreadToken(NULL, NULL)

即可将目前線程的token清除

0x03 令牌竊取

在ST測試中,竊取token是administrator -> system的常見手法之一,還經常被用于降級等使用者切換操作

1. 原理

竊取token主要涉及以下幾個API:

  1. OpenProcess
HANDLE OpenProcess(
  DWORD dwDesiredAccess,
  BOOL  bInheritHandle,
  DWORD dwProcessId
);
           

該函數打開指定PID的程序的句柄,需要注意的是第一個參數dwDesiredAccess,主要會用到的是以下三個權限

  • PROCESS_ALL_ACCESS
  • PROCESS_QUERY_INFORMATION (0x0400)
  • PROCESS_QUERY_LIMITED_INFORMATION (0x1000)

我在編寫竊取Token的代碼時,發現對部分程序(如smss.exe、csrss.exe等)調用OpenProcess會出現拒絕通路的情況,查閱網上資料後發現這些程序存在保護,需要使用

PROCESS_QUERY_LIMITED_INFORMATION

權限打開句柄,詳情請參考這篇文章

  1. OpenProcessToken
BOOL OpenProcessToken(
  HANDLE  ProcessHandle,
  DWORD   DesiredAccess,
  PHANDLE TokenHandle
);
           

該函數打開與程序相關聯的令牌的句柄,其中第二個參數DesiredAccess同樣用來指定令牌的通路權限,需要以下幾個:

  • TOKEN_DUPLICATE:複制令牌需要的權限
  • TOKEN_QUERY:查詢令牌需要的權限

如果要調用

DuplicateTokenEx

需要指定TOKEN_DUPLICATE,如果調用

ImpersonatedLoggedOnUser

則需要指定TOKEN_DUPLICATE和TOKEN_QUERY

  1. DuplicateTokenEx
BOOL DuplicateTokenEx(
  HANDLE                       hExistingToken,
  DWORD                        dwDesiredAccess,
  LPSECURITY_ATTRIBUTES        lpTokenAttributes,
  SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
  TOKEN_TYPE                   TokenType,
  PHANDLE                      phNewToken
);
           

DuplicateTokenEx

用來複制現有的令牌來生成一張新令牌,該函數可以選擇生成主令牌還是模拟令牌

  • hExistingToken:指定現有的令牌句柄,可以使用

    OpenProcessToken

    獲得
  • dwDesiredAccess:用來指定令牌通路權限,需要指定以下幾個來支援後面調用

    CreateProcessWithToken

    • TOKEN_DUPLICATE:需要複制通路令牌
    • TOKEN_QUERY:需要查詢通路令牌
    • TOKEN_ASSIGN_PRIMARY:将令牌附加到主程序的權限
    • TOKEN_ADJUST_DEFAULT:需要更改通路令牌的預設所有者、主要組或 DACL
    • TOKEN_ADJUST_SESSIONID:需要調整通路令牌的會話 ID,需要 SE_TCB_NAME 權限
  • lpTokenAttributes:指向SECURITY_ATTRIBUTES結構的指針,該 結構指定新令牌的安全描述符并确定子程序是否可以繼承該令牌
  • ImpersonationLevel:指定令牌的模拟級别,當進行複制令牌時,主令牌被複制為模拟令牌是始終被允許的,而模拟令牌複制為主令牌則需要模拟級别 >= Impersonate
  • TokenType:指定新令牌的類型,是主令牌(Primary Token)還是模拟令牌(Impersonate Token)
  • phNewToken:傳回令牌句柄的位址

複制完一張新令牌後,我們就可以利用這張新令牌來運作我們指定的程序了

  1. CreateProcessWithTokenW
BOOL CreateProcessWithTokenW(
  HANDLE                hToken,
  DWORD                 dwLogonFlags,
  LPCWSTR               lpApplicationName,
  LPWSTR                lpCommandLine,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCWSTR               lpCurrentDirectory,
  LPSTARTUPINFOW        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);
           

該函數建立一個新程序及其主線程,新程序在指定令牌的安全上下文中運作。我們直接指定前面複制出來的新令牌,使用該令牌建立我們指定的程序即可

2. 利用

根據mimikatz的token子產品的原理,簡單實作了一個demo,也有許多token相關的工具如incognito等

當擷取管理者權限後,我們可以列出系統中程序對應的token:

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

然後竊取指定程序的token來運作我們的程式,如直接運作上線馬

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

如果想要拿回程式輸出的話,則可以通過管道等程序間通信的方法來回顯輸出

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

如果拿到一台機器有域管的程序,那麼我們可以直接竊取域管程序的token來進行DCSync *GJ*

【安全研究】從mimikatz學習Windows安全之通路控制模型(一)

0x04 參考連結

https://docs.microsoft.com/

https://github.com/gentilkiwi/mimikatz/

繼續閱讀