Skip to content

Commit

Permalink
Merge pull request rapid7#3 from smcintyre-r7/pr/collab/16995
Browse files Browse the repository at this point in the history
Bofloader Updates
  • Loading branch information
zeroSteiner committed Sep 12, 2022
2 parents 660dae6 + 5656d44 commit 098c49f
Show file tree
Hide file tree
Showing 5 changed files with 464 additions and 311 deletions.
69 changes: 69 additions & 0 deletions data/headers/windows/c_payload_util/beacon.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Beacon Object Files (BOF)
* -------------------------
* A Beacon Object File is a light-weight post exploitation tool that runs
* with Beacon's inline-execute command.
*
* Additional BOF resources are available here:
* - https://github.com/Cobalt-Strike/bof_template
*
* Cobalt Strike 4.x
* ChangeLog:
* 1/25/2022: updated for 4.5
*/

/* data API */
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} datap;

DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size);
DECLSPEC_IMPORT char * BeaconDataPtr(datap * parser, int size);
DECLSPEC_IMPORT int BeaconDataInt(datap * parser);
DECLSPEC_IMPORT short BeaconDataShort(datap * parser);
DECLSPEC_IMPORT int BeaconDataLength(datap * parser);
DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size);

/* format API */
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} formatp;

DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz);
DECLSPEC_IMPORT void BeaconFormatReset(formatp * format);
DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, char * text, int len);
DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, char * fmt, ...);
DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size);
DECLSPEC_IMPORT void BeaconFormatFree(formatp * format);
DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value);

/* Output Functions */
#define CALLBACK_OUTPUT 0x0
#define CALLBACK_OUTPUT_OEM 0x1e
#define CALLBACK_OUTPUT_UTF8 0x20
#define CALLBACK_ERROR 0x0d

DECLSPEC_IMPORT void BeaconOutput(int type, char * data, int len);
DECLSPEC_IMPORT void BeaconPrintf(int type, char * fmt, ...);


/* Token Functions */
DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token);
DECLSPEC_IMPORT void BeaconRevertToken();
DECLSPEC_IMPORT BOOL BeaconIsAdmin();

/* Spawn+Inject Functions */
DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length);
DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len);
DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len);
DECLSPEC_IMPORT BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO * si, PROCESS_INFORMATION * pInfo);
DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo);

/* Utility Functions */
DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max);
314 changes: 159 additions & 155 deletions lib/rex/post/meterpreter/extensions/bofloader/bofloader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,162 +6,166 @@
require 'set'

module Rex
module Post
module Meterpreter
module Extensions
module Bofloader

###
#
# Bofloader extension - Executes a beacon object file in
# the current meterpreter session.
#
# Kevin Haubris (@kev169)
# Kevin Clark (@GuhnooPlusLinux)
# TrustedSec (@TrustedSec)
#
###

class BofPack
# Code referenced from: https://github.com/trustedsec/COFFLoader/blob/main/beacon_generate.py
# Emulates the native Cobalt Strike bof_pack() function.
# Documented here: https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#bof_pack
#
# Type Description Unpack With (C)
# --------|---------------------------------------|------------------------------
# b | binary data | BeaconDataExtract
# i | 4-byte integer | BeaconDataInt
# s | 2-byte short integer | BeaconDataShort
# z | zero-terminated+encoded string | BeaconDataExtract
# Z | zero-terminated wide-char string | (wchar_t *)BeaconDataExtract

def initialize()
@buffer = ''
@size = 0
end

def addshort(short)
@buffer << [short.to_i].pack("<s")
@size += 2
end

def addint(dint)
@buffer << [dint.to_i].pack("<I")
@size += 4
end

def addstr(s)
s = s.encode("utf-8").bytes
s << 0x00 # Null terminated strings...
s_length = s.length
s = [s_length] + s
buf = s.pack("<Ic#{s_length}")
@size += buf.length
@buffer << buf
end

def addWstr(s)
s = s.encode("utf-16le").bytes
s << 0x00 << 0x00 # Null terminated wide string
s_length = s.length
s = [s_length] + s
buf = s.pack("<Ic#{s_length}")
@size += buf.length
@buffer << buf
end

def addbinary(b)
# Add binary data to the buffer
if b.class != "Array"
b = b.bytes
end
b << 0x00 # Null terminated binary data
b_length = b.length
b = [b_length] + b
buf = b.pack("<Ic#{b_length}")
@size += buf.length
@buffer << buf
end

def finalize_buffer()
output = [@size].pack("<I") + @buffer
initialize() # Reset the class' buffer for another round
return output
end

def bof_pack(fstring, args)
# Wrapper function to pack an entire bof command line into a buffer
if fstring.nil? or args.nil?
return finalize_buffer()
end

fstring.each_char.each_with_index do |c,i|
if c == "b"
addbinary(args[i])
elsif c == "i"
addint(args[i])
elsif c == "s"
addshort(args[i])
elsif c == "z"
addstr(args[i])
elsif c == "Z"
addWstr(args[i])
else
raise "Invalid character in format string: #{c}. Must be one of \"b, i, s, z, Z\""
module Post
module Meterpreter
module Extensions
module Bofloader
###
#
# Bofloader extension - Executes a beacon object file in
# the current meterpreter session.
#
# Kevin Haubris (@kev169)
# Kevin Clark (@GuhnooPlusLinux)
# TrustedSec (@TrustedSec)
#
###

class BofPackingError < RuntimeError
end

class BofPack
# Code referenced from: https://github.com/trustedsec/COFFLoader/blob/main/beacon_generate.py
# Emulates the native Cobalt Strike bof_pack() function.
# Documented here: https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#bof_pack
#
# Type Description Unpack With (C)
# --------|---------------------------------------|------------------------------
# b | binary data | BeaconDataExtract
# i | 4-byte integer | BeaconDataInt
# s | 2-byte short integer | BeaconDataShort
# z | zero-terminated+encoded string | BeaconDataExtract
# Z | zero-terminated wide-char string | (wchar_t *)BeaconDataExtract

def initialize
reset
end

def add_binary(b)
# Add binary data to the buffer
b = b.bytes if b.is_a? String
b << 0x00 # Null terminated binary data
b_length = b.length
b = [b_length] + b
buf = b.pack("<Ic#{b_length}")
@size += buf.length
@buffer << buf
end

def add_int(dint)
@buffer << [dint.to_i].pack('<I')
@size += 4
end

def add_short(short)
@buffer << [short.to_i].pack('<s')
@size += 2
end

def add_str(s)
s = s.encode('utf-8').bytes
s << 0x00 # Null terminated strings...
s_length = s.length
s = [s_length] + s
buf = s.pack("<Ic#{s_length}")
@size += buf.length
@buffer << buf
end

def add_wstr(s)
s = s.encode('utf-16le').bytes
s << 0x00 << 0x00 # Null terminated wide string
s_length = s.length
s = [s_length] + s
buf = s.pack("<Ic#{s_length}")
@size += buf.length
@buffer << buf
end

def finalize_buffer
output = [@size].pack('<I') + @buffer
reset
output
end

def reset
@buffer = ''
@size = 0
end

def bof_pack(fstring, args)
# Wrapper function to pack an entire bof command line into a buffer
if fstring.nil? || args.nil?
return finalize_buffer
end

if fstring.length != args.length
raise BofPackingError, 'Mismatched format and argument lengths'
end

fstring.chars.zip(args).each do |c, arg|
case c
when 'b'
add_binary(arg)
when 'i'
add_int(arg)
when 's'
add_short(arg)
when 'z'
add_str(arg)
when 'Z'
add_wstr(arg)
else
raise BofPackingError, "Invalid character in format string: #{c}. Must be one of \"b, i, s, z, Z\""
end
end

# return the packed bof_string
finalize_buffer
end
end

class Bofloader < Extension

def self.extension_id
EXTENSION_ID_BOFLOADER
end

# Typical extension initialization routine.
#
# @param client (see Extension#initialize)
def initialize(client)
super(client, 'bofloader')

client.register_extension_aliases(
[
{
'name' => 'bofloader',
'ext' => self
},
]
)
end

def execute(bof_data, args_format: nil, args: nil, entry: 'go')
request = Packet.create_request(COMMAND_ID_BOFLOADER_EXECUTE)

# Pack up beacon object file data and arguments into one single binary blob
# Hardcode the entrypoint to "go" (CobaltStrike approved)
bof = BofPack.new
packed_args = bof.bof_pack(args_format, args)

# Send the meterpreter TLV packet and get the output back
request.add_tlv(TLV_TYPE_BOFLOADER_EXECUTE_BUFFER, bof_data)
request.add_tlv(TLV_TYPE_BOFLOADER_EXECUTE_BUFFER_ENTRY, entry)
request.add_tlv(TLV_TYPE_BOFLOADER_EXECUTE_ARGUMENTS, packed_args)
response = client.send_request(request)
return response.get_tlv_value(TLV_TYPE_BOFLOADER_EXECUTE_RESULT)
end

end
end
end
end

# return the packed bof_string
return finalize_buffer()
end

def coff_pack_pack(entrypoint, coff_data, argument_data)
# Create packed data containing:
# functionname | coff_data | args_data
# which can be passed directly to the LoadAndRun() function
fmt_pack = "zbb" # string, binary, binary
return bof_pack(fmt_pack, [entrypoint, coff_data, argument_data])
end

end

class Bofloader < Extension

def self.extension_id
EXTENSION_ID_BOFLOADER
end

# Typical extension initialization routine.
#
# @param client (see Extension#initialize)
def initialize(client)
super(client, 'bofloader')

client.register_extension_aliases(
[
{
'name' => 'bofloader',
'ext' => self
},
])

end

def execute(bof_data, args_format: nil, args: nil, entry: 'go')
request = Packet.create_request(COMMAND_ID_BOFLOADER_EXECUTE)

# Pack up beacon object file data and arguments into one single binary blob
# Hardcode the entrypoint to "go" (CobaltStrike approved)
bof = BofPack.new
packed_args = bof.bof_pack(args_format, args)
packed_coff_data = bof.coff_pack_pack(entry, bof_data, packed_args)

# Send the meterpreter TLV packet and get the output back
request.add_tlv(TLV_TYPE_BOFLOADER_EXECUTE_BUFFER, packed_coff_data)
response = client.send_request(request)
return response.get_tlv_value(TLV_TYPE_BOFLOADER_EXECUTE_RESULT)
end

end

end; end; end; end; end
Loading

0 comments on commit 098c49f

Please sign in to comment.