Skip to content
Merged
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
4 changes: 3 additions & 1 deletion lib/msf/core/exploit/exe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ def generate_payload_exe_service(opts = {})
#Ensure opts[:arch] is an array
opts[:arch] = [opts[:arch]] unless opts[:arch].kind_of? Array

if opts[:arch] && opts[:arch].index(ARCH_X64)
if opts[:arch] && opts[:arch].index(ARCH_AARCH64)
exe = Msf::Util::EXE.to_winaarch64pe(framework, pl, opts)
elsif opts[:arch] && opts[:arch].index(ARCH_X64)
exe = Msf::Util::EXE.to_win64pe_service(framework, pl, opts)
else
exe = Msf::Util::EXE.to_win32pe_service(framework, pl, opts)
Expand Down
11 changes: 11 additions & 0 deletions lib/msf/util/exe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,20 @@ def to_executable_fmt(framework, arch, plat, code, fmt, exeopts)
to_win32pe_service(framework, code, exeopts)
when ARCH_X64
to_win64pe_service(framework, code, exeopts)
when ARCH_AARCH64
# No dedicated AArch64 service template exists yet; the loader
# template still runs when dropped as a "service" binary (the SCM
# start request just times out, as with any non-service exe).
to_winaarch64pe(framework, code, exeopts)
Comment thread
dledda-r7 marked this conversation as resolved.
end
when 'exe-small'
case arch
when ARCH_X86, nil
to_win32pe_old(framework, code, exeopts)
when ARCH_X64
to_win64pe(framework, code, exeopts)
when ARCH_AARCH64
to_winaarch64pe(framework, code, exeopts)
end
when 'exe-only'
case arch
Expand All @@ -165,6 +172,8 @@ def to_executable_fmt(framework, arch, plat, code, fmt, exeopts)
exe = to_win32pe(framework, code, exeopts)
when ARCH_X64
exe = to_win64pe(framework, code, exeopts)
when ARCH_AARCH64
exe = to_winaarch64pe(framework, code, exeopts)
end
exeopts[:uac] = true
Msf::Util::EXE.to_exe_msi(framework, exe, exeopts)
Expand All @@ -174,6 +183,8 @@ def to_executable_fmt(framework, arch, plat, code, fmt, exeopts)
exe = to_win32pe(framework, code, exeopts)
when ARCH_X64
exe = to_win64pe(framework, code, exeopts)
when ARCH_AARCH64
exe = to_winaarch64pe(framework, code, exeopts)
end
Msf::Util::EXE.to_exe_msi(framework, exe, exeopts)
when 'elf'
Expand Down
19 changes: 19 additions & 0 deletions lib/msf/util/exe/windows/aarch64.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,20 @@ def self.included(base)
end

module ClassMethods
# The size, in bytes, of the fixed `payload[]` buffer declared in
# data/templates/src/pe/exe/template_aarch64_windows.c (SCSIZE). Shellcode
# longer than this would overwrite adjacent bytes in the compiled template.
WINAARCH64_PAYLOAD_SPACE = 8192

# Construct a Windows AArch64 PE executable with the given shellcode.
#
# Unlike the x86/x64 templates, there is currently no dedicated "service"
# or "dll" AArch64 template, so this loader-style template (which copies
# the payload into RWX memory and runs it in a new thread) is reused
# wherever an AArch64 PE is requested, including when a caller asked for
# an exe-service. That is safe for psexec-style delivery: Windows still
# spawns the process when the SCM start request times out because the
# binary doesn't speak the service control protocol.
# to_winaarch64pe
#
# @param framework [Msf::Framework] The Metasploit framework instance.
Expand All @@ -26,6 +39,12 @@ def to_winaarch64pe(framework, code, opts = {})

# Find the tag and inject the payload
bo = find_payload_tag(pe, 'Invalid Windows AArch64 template: missing "PAYLOAD:" tag')

if code.length > WINAARCH64_PAYLOAD_SPACE
raise RuntimeError, "The Windows AArch64 EXE generator has a max size of " \
"#{WINAARCH64_PAYLOAD_SPACE} bytes, please fix the calling module"
end

pe[bo, code.length] = code.dup
pe
end
Expand Down
29 changes: 24 additions & 5 deletions modules/exploits/windows/smb/psexec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,20 @@ def initialize(info = {})
},
'Platform' => 'win',
'Targets' => [
[ 'Automatic', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
# PowerShell isn't offered for AArch64: the PowerShell shellcode-injection
# wrapper (rex-powershell) only knows how to spawn x86/x64 powershell.exe,
# so it can't be used to run AArch64 shellcode.
[ 'Automatic', { 'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64] } ],
[ 'PowerShell', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
[ 'Native upload', { # upload a service executable
'Arch' => [ARCH_X86, ARCH_X64],
'Payload' => { 'Space' => 2 ** 30 } # service executables place the payload within a segment, 1GiB is a practical max in many cases
'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64],
Comment thread
dledda-r7 marked this conversation as resolved.
# service executables place the payload within a segment, 1GiB is a practical max in many cases.
# AArch64 is a notable exception: it has no dedicated service template yet, so it reuses the
# loader template's fixed 8192-byte buffer (see Msf::Util::EXE::Windows::Aarch64::WINAARCH64_PAYLOAD_SPACE).
# native_upload_with_workaround rescues the resulting RuntimeError if an AArch64 payload is too big.
'Payload' => { 'Space' => 2 ** 30 }
} ],
[ 'MOF upload', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
[ 'MOF upload', { 'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64] } ],
Comment on lines 74 to +82
[ 'Command', { 'Arch' => [ARCH_CMD], 'Payload' => { 'Space' => 8191 } } ]
],
'DefaultTarget' => 0,
Expand Down Expand Up @@ -113,6 +120,13 @@ def native_upload_with_workaround(smbshare)
smb_login
end
native_upload(smbshare, service_filename, service_encoder)
rescue RuntimeError => e
# generate_payload_exe_service can raise a plain RuntimeError when the
# encoded payload doesn't fit the target architecture's generator (e.g.
# AArch64's loader template is capped at 8192 bytes, well under the
# 1GiB this target advertises for 'Native upload'). Surface that as a
# normal exploit failure instead of an unhandled exception.
fail_with(Msf::Exploit::Failure::PayloadFailed, "#{peer} - Failed to generate the service executable: #{e.message}")
end

def validate_service_stub_encoder!
Expand Down Expand Up @@ -145,7 +159,12 @@ def exploit

case target.name
when 'Automatic'
if powershell_installed?(smbshare, datastore['PSH_PATH'])
# The PowerShell delivery path can only launch x86/x64 powershell.exe, so
# an AArch64 payload has to go straight to the native upload technique.
if payload_instance.arch.include?(ARCH_AARCH64)
print_status('Selecting native target')
native_upload_with_workaround(smbshare)
elsif powershell_installed?(smbshare, datastore['PSH_PATH'])
print_status('Selecting PowerShell target')
execute_powershell_payload
else
Expand Down
51 changes: 51 additions & 0 deletions spec/lib/msf/util/exe/windows/aarch64_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# -*- coding: binary -*-
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Msf::Util::EXE::Windows::Aarch64 do
let(:template) do
File.expand_path('../../../../../../data/templates/template_aarch64_windows.exe', __dir__)
end

# The generator only tolerates payloads up to WINAARCH64_PAYLOAD_SPACE
# (currently 8192) bytes because that is the size of the fixed payload[]
# buffer compiled into template_aarch64_windows.exe. The oversize test uses
# 8193 bytes so any change to WINAARCH64_PAYLOAD_SPACE that widens the
# buffer will surface here.
let(:max_payload_space) { Msf::Util::EXE::Windows::Aarch64::ClassMethods::WINAARCH64_PAYLOAD_SPACE }

describe '.to_winaarch64pe' do
let(:payload) { 'A'.b * 32 }

let(:generated_exe) do
Msf::Util::EXE.to_winaarch64pe(nil, payload, template: template)
end

let(:template_bytes) { File.binread(template) }
let(:payload_offset) { template_bytes.index('PAYLOAD:') }

it 'returns a Windows PE the same size as the template' do
expect(generated_exe.bytesize).to eq(template_bytes.bytesize)
expect(generated_exe.byteslice(0, 2)).to eq('MZ')
end

it 'injects the shellcode at the PAYLOAD: tag offset' do
expect(payload_offset).not_to be_nil
expect(generated_exe.byteslice(payload_offset, payload.bytesize)).to eq(payload)
end

it 'leaves bytes before and after the payload buffer unchanged' do
expect(generated_exe.byteslice(0, payload_offset)).to eq(template_bytes.byteslice(0, payload_offset))

tail_offset = payload_offset + max_payload_space
expect(generated_exe.byteslice(tail_offset..)).to eq(template_bytes.byteslice(tail_offset..))
end

it 'raises when the payload exceeds the template payload buffer' do
oversized = 'B'.b * (max_payload_space + 1)
expect { Msf::Util::EXE.to_winaarch64pe(nil, oversized, template: template) }
.to raise_error(RuntimeError, /max size of #{max_payload_space} bytes/)
end
end
end
Loading