Skip to content
Draft
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
23 changes: 13 additions & 10 deletions .github/workflows/command_shell_acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ on:
metasploitPayloadsCommit:
description: 'metasploit-payloads branch you want to test'
required: true
default: 'master'
default: '6.5'
mettleCommit:
description: 'mettle branch you want to test'
required: true
Expand Down Expand Up @@ -109,19 +109,22 @@ jobs:
tools: none

- name: Install system dependencies (Windows)
shell: cmd
shell: pwsh
if: runner.os == 'Windows'
run: |
REM pcap dependencies
powershell -Command "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} ; [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; (New-Object System.Net.WebClient).DownloadFile('https://www.winpcap.org/install/bin/WpdPack_4_1_2.zip', 'C:\Windows\Temp\WpdPack_4_1_2.zip')"

choco install 7zip.installServerCertificateValidationCallback
7z x "C:\Windows\Temp\WpdPack_4_1_2.zip" -o"C:\"
$ErrorActionPreference = 'Stop'
# pcap dependencies
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri 'https://www.winpcap.org/install/bin/WpdPack_4_1_2.zip' -OutFile 'C:\Windows\Temp\WpdPack_4_1_2.zip' -SkipCertificateCheck
$expected = 'ea799cf2f26e4afb1892938070fd2b1ca37ce5cf75fec4349247df12b784edbd'
$actual = (Get-FileHash 'C:\Windows\Temp\WpdPack_4_1_2.zip' -Algorithm SHA256).Hash.ToLower()
if ($actual -ne $expected) { throw "WpdPack_4_1_2.zip SHA256 mismatch: expected $expected, got $actual" }

dir C:\\
choco install 7zip -y --no-progress
if ($LASTEXITCODE -ne 0) { throw "choco install 7zip failed with exit code $LASTEXITCODE" }

dir %WINDIR%
type %WINDIR%\\system32\\drivers\\etc\\hosts
7z x "C:\Windows\Temp\WpdPack_4_1_2.zip" -o"C:\"
if ($LASTEXITCODE -ne 0) { throw "7z extraction failed with exit code $LASTEXITCODE" }

# The job checkout structure is:
# .
Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/meterpreter_acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ on:
metasploit_payloads_commit:
description: 'metasploit-payloads branch you want to test'
required: true
default: 'master'
default: '6.5'
mettle_commit:
description: 'mettle branch you want to test'
required: true
default: 'master'
default: '6.5'
push:
branches-ignore:
- gh-pages
Expand Down Expand Up @@ -61,7 +61,8 @@ jobs:
build:
uses: ./.github/workflows/shared_meterpreter_acceptance.yml
with:
metasploit_payloads_commit: ${{ github.event.inputs.metasploit_payloads_commit }}
mettle_commit: ${{ github.event.inputs.mettle_commit }}
build_metasploit_payloads: ${{ contains(github.event.pull_request.labels.*.name, 'payload-testing-branch') }}
build_mettle: ${{ contains(github.event.pull_request.labels.*.name, 'payload-testing-mettle-branch') }}
metasploit_payloads_repo: adfoster-r7/metasploit-payloads
metasploit_payloads_commit: update-php-meterpreter-errors
mettle_commit: ${{ github.event.inputs.mettle_commit || '6.5' }}
build_metasploit_payloads: true
build_mettle: true
38 changes: 23 additions & 15 deletions .github/workflows/shared_meterpreter_acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ on:
default: ''
required: false
type: string
metasploit_payloads_repo:
description: "metasploit-payloads repository to build with (e.g. a fork: myuser/metasploit-payloads)"
default: 'rapid7/metasploit-payloads'
required: false
type: string
mettle_commit:
description: "mettle commit to build with"
default: ''
Expand All @@ -30,19 +35,19 @@ on:
type: boolean

jobs:
# Compile the Meterpreter payloads via docker if required, we can't always do this on the
# host environment (i.e. for macos). So it instead gets compiled first on a linux
# host, then the artifacts are copied back to the host later
# Compile Java Meterpreter and prepare PHP/Python payloads via docker if required, we can't
# always do this on the host environment (i.e. for macos). So it instead gets compiled first
# on a linux host, then the artifacts are copied back to the host later
meterpreter_compilation:
name: Compile Meterpreter
name: Build Meterpreter Gem (optional)
runs-on: ubuntu-latest
if: ${{ inputs.build_metasploit_payloads }}

steps:
- name: Checkout metasploit-payloads
uses: actions/checkout@v4
with:
repository: rapid7/metasploit-payloads
repository: ${{ inputs.metasploit_payloads_repo }}
path: metasploit-payloads
ref: ${{ inputs.metasploit_payloads_commit }}

Expand Down Expand Up @@ -141,19 +146,22 @@ jobs:
java-version: ${{ matrix.meterpreter.runtime_version }}

- name: Install system dependencies (Windows)
shell: cmd
shell: pwsh
if: runner.os == 'Windows'
run: |
REM pcap dependencies
powershell -Command "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} ; [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; (New-Object System.Net.WebClient).DownloadFile('https://www.winpcap.org/install/bin/WpdPack_4_1_2.zip', 'C:\Windows\Temp\WpdPack_4_1_2.zip')"
$ErrorActionPreference = 'Stop'
# pcap dependencies
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri 'https://www.winpcap.org/install/bin/WpdPack_4_1_2.zip' -OutFile 'C:\Windows\Temp\WpdPack_4_1_2.zip' -SkipCertificateCheck
$expected = 'ea799cf2f26e4afb1892938070fd2b1ca37ce5cf75fec4349247df12b784edbd'
$actual = (Get-FileHash 'C:\Windows\Temp\WpdPack_4_1_2.zip' -Algorithm SHA256).Hash.ToLower()
if ($actual -ne $expected) { throw "WpdPack_4_1_2.zip SHA256 mismatch: expected $expected, got $actual" }

choco install 7zip.installServerCertificateValidationCallback
7z x "C:\Windows\Temp\WpdPack_4_1_2.zip" -o"C:\"

dir C:\\
choco install 7zip -y --no-progress
if ($LASTEXITCODE -ne 0) { throw "choco install 7zip failed with exit code $LASTEXITCODE" }

dir %WINDIR%
type %WINDIR%\\system32\\drivers\\etc\\hosts
7z x "C:\Windows\Temp\WpdPack_4_1_2.zip" -o"C:\"
if ($LASTEXITCODE -ne 0) { throw "7z extraction failed with exit code $LASTEXITCODE" }

# The job checkout structure is:
# .
Expand Down Expand Up @@ -265,7 +273,7 @@ jobs:
if: ${{ inputs.build_metasploit_payloads && matrix.meterpreter.name != 'mettle' }}
uses: actions/checkout@v4
with:
repository: rapid7/metasploit-payloads
repository: ${{ inputs.metasploit_payloads_repo }}
path: metasploit-payloads
ref: ${{ inputs.metasploit_payloads_commit }}

Expand Down
2 changes: 1 addition & 1 deletion lib/msf/base/sessions/mettle_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def generate_config(opts = {})
# Mettle reserves a fixed 8 KB slot in the binary for the config
# block. Catch the overflow here with a useful message instead of
# letting the gem's `to_binary` raise a generic "config block too
# large" baked-in EXTENSIONS= is the usual culprit.
# large" -- baked-in EXTENSIONS= is the usual culprit.
if opts[:config_block].length > MetasploitPayloads::Mettle::CONFIG_BLOCK_MAX
raise ArgumentError, "Mettle config block (#{opts[:config_block].length} bytes) exceeds the #{MetasploitPayloads::Mettle::CONFIG_BLOCK_MAX}-byte embedded slot. " \
"Drop EXTENSIONS= and `load <ext>` once the session is up."
Expand Down
10 changes: 5 additions & 5 deletions lib/msf/core/handler/reverse_http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,14 @@ def setup_handler
#
# * query parameter (profile: `parameter "name";`)
# * request header (profile: `header "name";`)
# * trailing path seg (default no placement directive)
# * trailing path seg (default -- no placement directive)
#
# For the path case, the URI looks like `<base>/<id>`, where `<base>`
# is the profile's per-verb `set uri` if defined, otherwise `LURI`
# (each is registered as a separate mount point in `all_uris`).
# Taking the last `/`-separated segment skips the base regardless of
# which one was used. Profile authors should not put `/` in
# prepend/append directives that would split the id across segments
# prepend/append directives -- that would split the id across segments
# and defeat this scheme.
#
# If the profile applied `prepend` / `append` / `base64` / `base64url`
Expand Down Expand Up @@ -325,7 +325,7 @@ def find_resource_id(cli, request)

# Reverse the prepend/append + base64 transforms the profile applied
# to the id on the payload side. If a declared wrapper is missing from
# the candidate, leave the candidate alone this is not a payload
# the candidate, leave the candidate alone -- this is not a payload
# request, and `process_uri_resource` will return nil for it.
def unwrap_profile_uuid(candidate, placement)
prefix = placement.prepend.map{|d| d.args[0]}.join('')
Expand Down Expand Up @@ -357,8 +357,8 @@ def add_response_headers(req, resp)

# Return the live meterpreter session whose passive dispatcher is
# registered for this bare conn_id, or nil if none matches. Used so
# MC2 traffic which Rex routes to on_request because the profile
# URI prefix outranks the session's /<conn_id> mount can still be
# MC2 traffic -- which Rex routes to on_request because the profile
# URI prefix outranks the session's /<conn_id> mount -- can still be
# delivered to the right session instead of triggering a fresh
# "orphaned attach" on every poll.
def session_for_conn_id(bare_conn_id)
Expand Down
4 changes: 2 additions & 2 deletions lib/msf/core/payload/java/meterpreter_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module Payload::Java::MeterpreterLoader
include Msf::Sessions::MeterpreterOptions::Java

# Resource path the stageless StagelessMain bootstrap reads. Deliberately
# innocuous no meterpreter/metasploit markers in the name.
# innocuous -- no meterpreter/metasploit markers in the name.
STAGELESS_CONFIG_RESOURCE = 'META-INF/data'.freeze
STAGELESS_MAIN_CLASS = 'com.metasploit.meterpreter.StagelessMain'.freeze

Expand All @@ -44,7 +44,7 @@ def stage_payload(opts={})
#
# When opts[:stageless] is set, returns a self-contained jar with the
# TLV config embedded as a resource and Main-Class pinned to
# StagelessMain ready to run under `java -jar`.
# StagelessMain -- ready to run under `java -jar`.
#
def stage_meterpreter(opts={})
met = MetasploitPayloads.read('meterpreter', 'meterpreter.jar')
Expand Down
8 changes: 7 additions & 1 deletion lib/msf/core/payload/macho.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
class Msf::Payload::MachO

def initialize(data)
@raw_data = data.dup
@macho = MachO::MachOFile.new_from_bin(data)
end

Expand Down Expand Up @@ -59,7 +60,12 @@ def to_dylib(name)
# See: https://developer.apple.com/forums/thread/702351
# See: https://github.com/apple-oss-distributions/Security/blob/e4ea024c9bbd3bfda30ec6df270bfb4c7438d1a9/SecurityTool/sharedTool/codesign.c#L323
def sign
raw_data = @macho.serialize
# Use the original binary data rather than @macho.serialize. On Linux,
# ruby-macho's serialize can reorder or drop sections for newer macOS
# load commands (LC_DYLD_CHAINED_FIXUPS, LC_DYLD_EXPORTS_TRIE), which
# causes the LC_CODE_SIGNATURE.dataoff to point to the wrong location
# in the reconstructed output.
raw_data = @raw_data.dup
code_signature_index = @macho[:LC_CODE_SIGNATURE][0].dataoff
code_signature = raw_data[code_signature_index..]
s_magic, s_length, s_count, code_indexes = code_signature.unpack("N3a*")
Expand Down
4 changes: 2 additions & 2 deletions lib/msf/core/payload/malleable_c2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def tokenize(text)
# blank line
next
elsif scanner.scan(/#[^\n]*/)
# comment anchorless so it matches indented comments too
# comment -- anchorless so it matches indented comments too
# (the prior `\s+` rule has already eaten any leading
# whitespace by the time we get here)
next
Expand Down Expand Up @@ -450,7 +450,7 @@ def parse(file)
profile.sets << parse_set
elsif block_ahead?
# Any `name { ... }` is a block. We keep the ones we understand
# (http-get/http-post/etc.) and harmlessly retain the rest
# (http-get/http-post/etc.) and harmlessly retain the rest --
# unsupported CS blocks like dns-beacon/process-inject/post-ex
# are parsed for structure and simply ignored by to_tlv.
profile.sections << parse_section
Expand Down
2 changes: 1 addition & 1 deletion lib/rex/proto/http/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def to_s

#
# Returns a hijacked version of the body that shoves the request's query string in as a
# replacement in cases where there is no body. YOLO! ¯\_(ツ)_/¯
# replacement in cases where there is no body. YOLO! (shrug)
#
def body
str = super || ''
Expand Down
2 changes: 1 addition & 1 deletion lib/rex/proto/http/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ def dispatch_request(cli, request)
p = resources[request.resource]
# This branch is reached when resource_id is nil (e.g. fetch-payload
# adapters have no find_resource_id), so the matched resource is the
# request resource itself not resource_id, which would nil-deref.
# request resource itself -- not resource_id, which would nil-deref.
len = request.resource.length
root = request.resource
else
Expand Down
2 changes: 1 addition & 1 deletion modules/payloads/singles/php/meterpreter_reverse_http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module MetasploitModule
include Msf::Payload::Php::ReverseHttp
include Msf::Payload::TransportConfig
include Msf::Payload::UUID::Options
include Msf::Sessions::MeterpreterOptions
include Msf::Sessions::MeterpreterOptions::Php

def initialize(info = {})
super(
Expand Down
2 changes: 1 addition & 1 deletion modules/payloads/singles/php/meterpreter_reverse_https.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module MetasploitModule
include Msf::Payload::Php::ReverseHttp
include Msf::Payload::TransportConfig
include Msf::Payload::UUID::Options
include Msf::Sessions::MeterpreterOptions
include Msf::Sessions::MeterpreterOptions::Php

def initialize(info = {})
super(
Expand Down
54 changes: 54 additions & 0 deletions spec/file_fixtures/malleable_c2/base64_transforms.profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# base64_transforms.profile
# Purpose: exercises base64 encoding on server->client GET responses and
# prepend/append transforms on client->server POST bodies.
# Used by acceptance tests to confirm wrap/unwrap encoding round-trips correctly.

set useragent "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)";

http-get {
set uri "/updates/check";

client {
header "Accept" "application/json";

metadata {
parameter "v";
}
}

server {
header "Content-Type" "application/octet-stream";

output {
base64;
prepend "START_";
append "_END";
print;
}
}
}

http-post {
set uri "/updates/report";

client {
header "Content-Type" "application/octet-stream";

id {
parameter "uid";
}

output {
base64;
print;
}
}

server {
header "Content-Type" "text/plain";

output {
print;
}
}
}
52 changes: 52 additions & 0 deletions spec/file_fixtures/malleable_c2/minimal_uris_headers.profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# minimal_uris_headers.profile
# Purpose: exercises custom URI routing + response headers; no encoding directives.
# Used by acceptance tests to confirm the handler registers profile URIs and that
# the payload reaches a session over those URIs.

set useragent "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)";

http-get {
set uri "/jquery-3.3.1.min.js";

client {
header "Accept" "text/javascript, application/javascript";
header "Referer" "https://www.example.com/";

metadata {
parameter "callback";
}
}

server {
header "Content-Type" "application/javascript; charset=utf-8";
header "Cache-Control" "max-age=604800";

output {
print;
}
}
}

http-post {
set uri "/jquery-3.3.1.min.js/save";

client {
header "Content-Type" "application/octet-stream";

id {
parameter "id";
}

output {
print;
}
}

server {
header "Content-Type" "text/plain; charset=utf-8";

output {
print;
}
}
}
Loading
Loading