Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ include make/Makefile.tools
include make/Makefile.common
include make/Makefile.mettle

DOCKER_CONTAINER=rapid7/build:mettle
DOCKER_TARGET ?= x86_64-linux-musl

# Build mettle inside the official build container (no local toolchain or
# autotools required). Override the arch with DOCKER_TARGET=<triple>. Only the
# mettle checkout is mounted; build artifacts are chowned back to the invoking
# user. Output lands in build/$(DOCKER_TARGET)/bin/{mettle,mettle.bin}.
docker:
docker run --rm -v "$(CURDIR)":/mettle -w /mettle $(DOCKER_CONTAINER) \
bash -c "make TARGET=$(DOCKER_TARGET); rc=$$?; chown -R $(shell id -u):$(shell id -g) /mettle; exit $$rc"

distclean:
@rm -fr $(BUILD)

Expand Down
23 changes: 23 additions & 0 deletions lib/metasploit_payloads/mettle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class Mettle

CMDLINE_MAX = 2000
CMDLINE_SIG = 'DEFAULT_OPTS'.freeze
CONFIG_BLOCK_MAX = 8192
CONFIG_BLOCK_SIG = 'CONFIG_BLOCK'.freeze
#
# Config is a hash. Valid keys are:
# :uri to connect to
Expand All @@ -36,6 +38,9 @@ def initialize(triple, config={})
def to_binary(format=:process_image)
bin = self.class.read(@platform, format)
unless @config.empty?
if @config[:config_block]
bin = add_config_block(bin, @config[:config_block])
end
params = generate_argv
bin = add_args(bin, params)
end
Expand All @@ -47,6 +52,10 @@ def to_binary(format=:process_image)
def generate_argv
cmd_line = 'mettle '
@config.each do |opt, val|
# :config_block is embedded into the binary via add_config_block (a
# TLV blob parsed natively); it is not a command-line option.
next if opt == :config_block

cmd_line << "-#{short_opt(opt)} \"#{val}\" "
end
if cmd_line.length > CMDLINE_MAX
Expand Down Expand Up @@ -77,6 +86,20 @@ def short_opt(opt)
end
end

# Layout in the binary: [length:4 BE][config_bytes][zero padding] within
# the CONFIG_BLOCK_MAX-byte slot. Carrying the length explicitly avoids
# a trailing-null ambiguity — the XOR-encoded config can legitimately
# end in 0x00, which would otherwise be eaten by a backward scan.
def add_config_block(bin, config_bytes)
usable = CONFIG_BLOCK_MAX - 4
if config_bytes.length > usable
raise Mettle::Error, 'mettle config block too large', caller
end
payload = [config_bytes.length].pack('N') + config_bytes
padded = payload + "\x00" * (CONFIG_BLOCK_MAX - payload.length)
bin.sub(CONFIG_BLOCK_SIG + "\x00" * (CONFIG_BLOCK_MAX - CONFIG_BLOCK_SIG.length), padded)
end

def add_args(bin, params)
if params[8] != "\x00"
bin.sub(CMDLINE_SIG + ' ' * (CMDLINE_MAX - CMDLINE_SIG.length), params)
Expand Down
2 changes: 1 addition & 1 deletion mettle/src/base64.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ int base64encode(char *dest, const char *src, int l)
bits = 0;

for (rpos = 0; rpos < l; rpos++) {
c = src[rpos];
c = (unsigned char)src[rpos];

bits += c;
char_count++;
Expand Down
53 changes: 53 additions & 0 deletions mettle/src/c2.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ struct c2_transport {
struct c2 *c2;
struct c2_transport_type *type;
void *ctx;
struct c2_transport_config *config;
};

struct c2_transport_type {
Expand Down Expand Up @@ -109,6 +110,7 @@ c2_remove_transports(struct c2 *c2)
if (t->type->cbs.free) {
t->type->cbs.free(t);
}
c2_transport_config_free(t->config);
free(t->uri);
free(t);
}
Expand Down Expand Up @@ -158,6 +160,57 @@ int c2_add_transport_uri(struct c2 *c2, const char *uri)
return -1;
}

void c2_verb_config_free(struct c2_verb_config *vc)
{
if (vc) {
for (int j = 0; j < vc->uri_count; j++) {
free(vc->uris[j]);
}
free(vc->uris);
free(vc->prefix);
free(vc->suffix);
free(vc->uuid_prefix);
free(vc->uuid_suffix);
free(vc->uuid_get);
free(vc->uuid_header);
free(vc->uuid_cookie);
free(vc);
}
}

void c2_transport_config_free(struct c2_transport_config *tc)
{
if (tc) {
free(tc->proxy_url);
free(tc->proxy_user);
free(tc->proxy_pass);
free(tc->user_agent);
free(tc->custom_headers);
free(tc->cert_hash);
free(tc->c2_uuid);
c2_verb_config_free(tc->c2_get);
c2_verb_config_free(tc->c2_post);
free(tc);
}
}

struct c2_transport_config * c2_transport_get_config(struct c2_transport *t)
{
return t->config;
}

int c2_add_transport_uri_config(struct c2 *c2, const char *uri,
struct c2_transport_config *config)
{
int rc = c2_add_transport_uri(c2, uri);
if (rc == 0 && config) {
/* Find the last-added transport (tail of CDL) */
struct c2_transport *t = c2->transports->prev;
t->config = config;
}
return rc;
}

static struct c2_transport *
choose_next_transport(struct c2 *c2)
{
Expand Down
52 changes: 52 additions & 0 deletions mettle/src/c2.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,62 @@
#include <ev.h>
#include "buffer_queue.h"

/*
* C2 Profile configuration for GET/POST verbs
*/
struct c2_verb_config {
char **uris; /* candidate request URIs; one chosen at random per request */
int uri_count;
int enc_inbound;
int enc_outbound;
int enc_uuid;
void *prefix;
size_t prefix_len;
void *suffix;
size_t suffix_len;
char *uuid_prefix;
char *uuid_suffix;
int prefix_skip;
int suffix_skip;
char *uuid_get;
char *uuid_header;
char *uuid_cookie;
};

/*
* Per-transport configuration parsed from TLV config block
*/
struct c2_transport_config {
uint32_t comm_timeout;
uint32_t retry_total;
uint32_t retry_wait;
char *proxy_url;
char *proxy_user;
char *proxy_pass;
char *user_agent;
char *custom_headers;
void *cert_hash;
size_t cert_hash_len;
char *c2_uuid;
struct c2_verb_config *c2_get;
struct c2_verb_config *c2_post;
};

void c2_verb_config_free(struct c2_verb_config *vc);
void c2_transport_config_free(struct c2_transport_config *tc);

/*
* C2 Manager
*/
struct c2;

struct c2 * c2_new(struct ev_loop *loop);

int c2_add_transport_uri(struct c2 *c2, const char *uri);

int c2_add_transport_uri_config(struct c2 *c2, const char *uri,
struct c2_transport_config *config);

int c2_start(struct c2 *c2);

int c2_close(struct c2 *c2);
Expand Down Expand Up @@ -58,6 +108,8 @@ int c2_register_transport_type(struct c2 *c2, const char *proto,

struct c2_transport* c2_get_current_transport(struct c2 *c2);

struct c2_transport_config * c2_transport_get_config(struct c2_transport *t);

const char * c2_transport_uri(struct c2_transport *t);
const char * c2_transport_dest(struct c2_transport *t);
struct ev_loop * c2_transport_loop(struct c2_transport *loop);
Expand Down
Loading
Loading