Merge pull request #96 from chao-mu/master
Updates to Railgun [Fixes #6128] among other things.
This commit is contained in:
commit
9e78eff968
|
@ -0,0 +1,60 @@
|
|||
require 'rex/post/meterpreter/extensions/stdapi/railgun/railgun'
|
||||
|
||||
module Msf
|
||||
class Post
|
||||
module Windows
|
||||
module Railgun
|
||||
|
||||
# Go through each dll and add a corresponding convenience method of the same name
|
||||
Rex::Post::Meterpreter::Extensions::Stdapi::Railgun::Railgun::BUILTIN_DLLS.each do |api|
|
||||
# We will be interpolating within an eval. We exercise due paranoia.
|
||||
unless api.to_s =~ /^\w+$/
|
||||
print_error 'Something is seriously wrong with Railgun.BUILTIN_DLLS list'
|
||||
next
|
||||
end
|
||||
|
||||
# don't override existing methods
|
||||
if method_defined? api.to_sym
|
||||
# We don't warn as the override may have been intentional
|
||||
next
|
||||
end
|
||||
|
||||
# evaling a String is faster than calling define_method
|
||||
eval "def #{api.to_s}; railgun.#{api.to_s}; end"
|
||||
end
|
||||
|
||||
#
|
||||
# Return an array of windows constants names matching +winconst+
|
||||
#
|
||||
def select_const_names(winconst, filter_regex=nil)
|
||||
railgun.constant_manager.select_const_names(winconst, filter_regex)
|
||||
end
|
||||
|
||||
#
|
||||
# Returns an array of windows error code names for a given windows error code matching +err_code+
|
||||
#
|
||||
def lookup_error (err_code, filter_regex=nil)
|
||||
select_const_names(err_code, /^ERROR_/).select do |name|
|
||||
name =~ filter_regex
|
||||
end
|
||||
end
|
||||
|
||||
def memread(address, length)
|
||||
railgun.memread(address, length)
|
||||
end
|
||||
|
||||
def memwrite(address, length)
|
||||
railgun.memwrite(address, length)
|
||||
end
|
||||
|
||||
def railgun
|
||||
client.railgun
|
||||
end
|
||||
|
||||
def pointer_size
|
||||
railgun.util.pointer_size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,6 +5,8 @@ require 'test/unit'
|
|||
require 'rex'
|
||||
|
||||
require 'railgun/api_constants.rb.ut'
|
||||
require 'railgun/type/pointer_util.rb.ut'
|
||||
require 'railgun/platform_util.rb.ut'
|
||||
require 'railgun/buffer_item.rb.ut'
|
||||
require 'railgun/dll_function.rb.ut'
|
||||
require 'railgun/dll_helper.rb.ut'
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Extensions
|
||||
module Stdapi
|
||||
module Railgun
|
||||
module PlatformUtil
|
||||
|
||||
X86_64 = :x86_64
|
||||
X86_32 = :x86_32
|
||||
|
||||
def self.parse_client_platform(meterp_client_platform)
|
||||
meterp_client_platform =~ /win64/ ? X86_64 : X86_32
|
||||
end
|
||||
|
||||
end # PlatformUtil
|
||||
end # Railgun
|
||||
end # Stdapi
|
||||
end # Extensions
|
||||
end # Meterpreter
|
||||
end # Post
|
||||
end # Rex
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift(File.join(File.dirname(__FILE__), '..','..','..','..','..', '..', '..', 'lib'))
|
||||
|
||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/platform_util'
|
||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/mock_magic'
|
||||
require 'test/unit'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Extensions
|
||||
module Stdapi
|
||||
module Railgun
|
||||
class PlatformUtil::UnitTest < Test::Unit::TestCase
|
||||
def test_parse_client_platform
|
||||
assert_equal(PlatformUtil.parse_client_platform('x86/win32'), PlatformUtil::X86_32,
|
||||
'parse_client_platform should translate Win32 client platforms')
|
||||
assert_equal(PlatformUtil.parse_client_platform('x86/win64'), PlatformUtil::X86_64,
|
||||
'parse_client_platform should translate Win64 client platforms')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -61,9 +61,9 @@ class Railgun
|
|||
# definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'.
|
||||
# Naming is important and should follow convention. For example, if your
|
||||
# dll's name was "my_dll"
|
||||
# file name:: def_my_dll.rb
|
||||
# class name:: Def_my_dll
|
||||
# entry below:: 'my_dll'
|
||||
# file name: def_my_dll.rb
|
||||
# class name: Def_my_dll
|
||||
# entry below: 'my_dll'
|
||||
#
|
||||
BUILTIN_DLLS = [
|
||||
'kernel32',
|
||||
|
@ -104,6 +104,10 @@ class Railgun
|
|||
self.dlls = {}
|
||||
end
|
||||
|
||||
def self.builtin_dlls
|
||||
BUILTIN_DLLS
|
||||
end
|
||||
|
||||
#
|
||||
# Return this Railgun's Util instance.
|
||||
#
|
||||
|
@ -184,8 +188,8 @@ class Railgun
|
|||
|
||||
# For backwards compatibility, we ensure the dll is thawed
|
||||
if dll.frozen?
|
||||
# dup will copy values, but not the frozen status
|
||||
dll = dll.dup
|
||||
# Duplicate not only the dll, but its functions as well. Frozen status will be lost
|
||||
dll = Marshal.load(Marshal.dump(dll))
|
||||
|
||||
# Update local dlls with the modifiable duplicate
|
||||
dlls[dll_name] = dll
|
||||
|
@ -277,22 +281,6 @@ class Railgun
|
|||
def const(str)
|
||||
return constant_manager.parse(str)
|
||||
end
|
||||
|
||||
#
|
||||
# Return an array of windows constants names matching +winconst+
|
||||
#
|
||||
|
||||
def const_reverse_lookup(winconst,filter_regex=nil)
|
||||
return constant_manager.rev_lookup(winconst,filter_regex)
|
||||
end
|
||||
|
||||
#
|
||||
# Returns an array of windows error code names for a given windows error code matching +err_code+
|
||||
#
|
||||
|
||||
def error_lookup (err_code,filter_regex=/^ERROR_/)
|
||||
return constant_manager.rev_lookup(err_code,filter_regex)
|
||||
end
|
||||
|
||||
#
|
||||
# The multi-call shorthand (["kernel32", "ExitProcess", [0]])
|
||||
|
|
|
@ -120,6 +120,14 @@ class Railgun::UnitTest < Test::Unit::TestCase
|
|||
|
||||
assert(!unfrozen_dll.frozen?,
|
||||
"add_function should create a local unfrozen instance that get_dll can then access")
|
||||
|
||||
railgun2 = Railgun.new(make_mock_client())
|
||||
|
||||
assert(!railgun2.get_dll(dll_name).functions.has_key?('__lolz'),
|
||||
"functions added to one instance of railgun should not be accessible to others")
|
||||
|
||||
assert_not_same(!railgun2.get_dll(dll_name).functions, unfrozen_dll.functions,
|
||||
"function hash should have been duplicated during unfreeze")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
require 'rex/post/meterpreter/extensions/stdapi/railgun/platform_util'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Extensions
|
||||
module Stdapi
|
||||
module Railgun
|
||||
module Type
|
||||
module PointerUtil
|
||||
|
||||
ARCH_POINTER_SIZE = {
|
||||
PlatformUtil::X86_64 => 8,
|
||||
PlatformUtil::X86_32 => 4
|
||||
}.freeze
|
||||
|
||||
# Returns the pointer size for this architecture. Should accept client or platform or arch
|
||||
def self.pointer_size(platform)
|
||||
ARCH_POINTER_SIZE[platform]
|
||||
end
|
||||
|
||||
def self.pack_pointer(pointer, platform)
|
||||
if pointer.nil?
|
||||
return pack_pointer(0, platform)
|
||||
end
|
||||
|
||||
case platform
|
||||
when PlatformUtil::X86_64
|
||||
# XXX: Only works if attacker and victim are like-endianed
|
||||
[pointer].pack('Q')
|
||||
when PlatformUtil::X86_32
|
||||
[pointer].pack('V')
|
||||
else
|
||||
raise "platform symbol #{platform.to_s} not supported"
|
||||
end
|
||||
end
|
||||
|
||||
# Given a packed pointer, unpack it according to architecture
|
||||
def self.unpack_pointer(packed_pointer, platform)
|
||||
case platform
|
||||
when PlatformUtil::X86_64
|
||||
# XXX: Only works if attacker and victim are like-endianed
|
||||
packed_pointer.unpack('Q').first
|
||||
when PlatformUtil::X86_32
|
||||
packed_pointer.unpack('V').first
|
||||
else
|
||||
raise "platform symbol #{platform.to_s} not supported"
|
||||
end
|
||||
end
|
||||
|
||||
def self.null_pointer(pointer, platform)
|
||||
pack_pointer(0, platform)
|
||||
end
|
||||
|
||||
###
|
||||
# Summary: Returns true if pointer will be considered a 'null' pointer
|
||||
#
|
||||
# If given nil, returns true
|
||||
# If given 0, returns true
|
||||
# If given a string, if 0 after unpacking, returns true
|
||||
# false otherwise
|
||||
##
|
||||
def self.is_null_pointer?(pointer, platform)
|
||||
if pointer.kind_of?(String)
|
||||
pointer = unpack_pointer(pointer, platform)
|
||||
end
|
||||
|
||||
return pointer.nil? || pointer == 0
|
||||
end
|
||||
#
|
||||
# def self.is_unpacked_pointer?(pointer, platform)
|
||||
# # TODO also check that the integer size is appropriate for the platform
|
||||
# unless pointer.kind_of?(Fixnum) and pointer > 0 # and pointer <
|
||||
# return false
|
||||
# end
|
||||
#
|
||||
# packed_pointer = pack_pointer(pointer, platform)
|
||||
# if !packed_pointer.nil? and packed_pointer.length == pointer_size(platform)
|
||||
# return true
|
||||
# end
|
||||
#
|
||||
# return false
|
||||
# end
|
||||
#
|
||||
# Returns true if the data type is a pointer, false otherwise
|
||||
def self.is_pointer_type?(type)
|
||||
if type == :pointer
|
||||
return true
|
||||
end
|
||||
|
||||
if type.kind_of?(String) && type =~ /^L?P/
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
end # PointerUtil
|
||||
end # Type
|
||||
end # Railgun
|
||||
end # Stdapi
|
||||
end # Extensions
|
||||
end # Meterpreter
|
||||
end # Post
|
||||
end # Rex
|
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift(File.join(File.dirname(__FILE__), '..', '..','..','..','..','..', '..', '..', 'lib'))
|
||||
|
||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util'
|
||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/platform_util'
|
||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/mock_magic'
|
||||
require 'test/unit'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module Meterpreter
|
||||
module Extensions
|
||||
module Stdapi
|
||||
module Railgun
|
||||
module Type
|
||||
class PlatformUtil::UnitTest < Test::Unit::TestCase
|
||||
|
||||
include Rex::Post::Meterpreter::Extensions::Stdapi::Railgun::MockMagic
|
||||
|
||||
# memread value of win x86 pointer mapped to target unpack value
|
||||
X86_32_POINTERS = {
|
||||
"8D\x15\x00" => 1393720,
|
||||
"\x1C\x84\x15\x00" => 1410076,
|
||||
"\x0E\x84\x15\x00" => 1410062,
|
||||
"\x02\x84\x15\x00" => 1410050,
|
||||
"\xE6\x83\x15\x00" => 1410022,
|
||||
"\xC4\x83\x15\x00" => 1409988,
|
||||
"\x00\x00\x00\x00" => 0,
|
||||
}
|
||||
X86_64_POINTERS = {
|
||||
"\x10^ \x00\x00\x00\x00\x00" => 2121232,
|
||||
"\xCA\x9D \x00\x00\x00\x00\x00" => 2137546,
|
||||
"\xC8\x9D \x00\x00\x00\x00\x00" => 2137544,
|
||||
"Z\x9D \x00\x00\x00\x00\x00" => 2137434,
|
||||
"X\x9D \x00\x00\x00\x00\x00" => 2137432,
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" => 0,
|
||||
}
|
||||
|
||||
X86_64_NULL_POINTER = "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
X86_32_NULL_POINTER = "\x00\x00\x00\x00"
|
||||
|
||||
X86_64 = PlatformUtil::X86_64
|
||||
X86_32 = PlatformUtil::X86_32
|
||||
|
||||
def test_pack_pointer
|
||||
X86_64_POINTERS.invert.each_pair do |unpacked, packed|
|
||||
assert_equal(packed, PointerUtil.pack_pointer(unpacked.to_i, X86_64),
|
||||
"pack_pointer should pack 64-bit numberic pointers")
|
||||
end
|
||||
|
||||
X86_32_POINTERS.invert.each_pair do |unpacked, packed|
|
||||
assert_equal(packed, PointerUtil.pack_pointer(unpacked.to_i, X86_32),
|
||||
"pack_pointer should pack 32-bit numberic pointers")
|
||||
end
|
||||
|
||||
assert_equal(X86_64_NULL_POINTER, PointerUtil.pack_pointer(nil, X86_64),
|
||||
'pack_pointer should pack "nil" as a null pointer for x86_64')
|
||||
|
||||
assert_equal(X86_32_NULL_POINTER, PointerUtil.pack_pointer(nil, X86_32),
|
||||
'pack_pointer should pack "nil" as a null pointer for x86_32')
|
||||
|
||||
assert_equal(X86_64_NULL_POINTER, PointerUtil.pack_pointer(0, X86_64),
|
||||
'pack_pointer should pack numeric 0 as a null pointer for x86_64')
|
||||
|
||||
assert_equal(X86_32_NULL_POINTER, PointerUtil.pack_pointer(0, X86_32),
|
||||
'pack_pointer should pack numeric 9 as a null pointer for x86_32')
|
||||
end
|
||||
|
||||
def test_unpack_pointer
|
||||
X86_64_POINTERS.each_pair do |packed, unpacked|
|
||||
assert_equal(unpacked, PointerUtil.unpack_pointer(packed, X86_64),
|
||||
"unpack_pointer should unpack 64-bit pointers")
|
||||
end
|
||||
|
||||
X86_32_POINTERS.each_pair do |packed, unpacked|
|
||||
assert_equal(unpacked, PointerUtil.unpack_pointer(packed, X86_32),
|
||||
"unpack_pointer should unpack 32-bit pointers")
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
def test_is_null_pointer
|
||||
[X86_32, X86_64].each do |platform|
|
||||
assert(PointerUtil.is_null_pointer?(nil, platform), 'nil should be a null pointer')
|
||||
assert(PointerUtil.is_null_pointer?(0, platform), 'numeric 0 should be a null pointer')
|
||||
end
|
||||
|
||||
assert_equal(true, PointerUtil.is_null_pointer?(X86_32_NULL_POINTER, X86_32),
|
||||
'is_null_pointer? should return true for packed 32-bit null pointers')
|
||||
|
||||
assert_equal(true, PointerUtil.is_null_pointer?(X86_64_NULL_POINTER, X86_64),
|
||||
'is_null_pointer? should return true for packed 64-bit null pointers')
|
||||
|
||||
end
|
||||
|
||||
def test_pointer_size
|
||||
assert_equal(8, PointerUtil.pointer_size(X86_64),
|
||||
'pointer_size should report X86_64 arch as 8 (bytes)')
|
||||
|
||||
assert_equal(4, PointerUtil.pointer_size(X86_32),
|
||||
'pointer_size should report X86_32 arch as 4 (bytes)')
|
||||
end
|
||||
|
||||
def test_is_pointer_type
|
||||
assert_equal(true, PointerUtil.is_pointer_type?(:pointer),
|
||||
'pointer_type should return true for the symbol :pointer')
|
||||
|
||||
assert_equal(true, PointerUtil.is_pointer_type?('LPVOID'),
|
||||
'pointer_type should return true if string begins with LP')
|
||||
|
||||
assert_equal(true, PointerUtil.is_pointer_type?('PDWORD'),
|
||||
'pointer_type should return true if string begins with P')
|
||||
|
||||
assert_equal(false, PointerUtil.is_pointer_type?('LOLZ'),
|
||||
'pointer_type should return false if not a pointer type')
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -33,6 +33,7 @@ module Railgun
|
|||
# Manages our library of windows constants
|
||||
#
|
||||
class WinConstManager
|
||||
attr_reader :consts
|
||||
|
||||
def initialize(initial_consts = {})
|
||||
@consts = {}
|
||||
|
@ -40,12 +41,10 @@ class WinConstManager
|
|||
initial_consts.each_pair do |name, value|
|
||||
add_const(name, value)
|
||||
end
|
||||
|
||||
# Load utility
|
||||
end
|
||||
|
||||
def add_const(name, value)
|
||||
@consts[name] = value
|
||||
consts[name] = value
|
||||
end
|
||||
|
||||
# parses a string constaining constants and returns an integer
|
||||
|
@ -59,41 +58,38 @@ class WinConstManager
|
|||
return_value = 0
|
||||
for one_const in s.split('|')
|
||||
one_const = one_const.strip()
|
||||
if not @consts.has_key? one_const
|
||||
if not consts.has_key? one_const
|
||||
return nil # at least one "Constant" is unknown to us
|
||||
end
|
||||
return_value |= @consts[one_const]
|
||||
return_value |= consts[one_const]
|
||||
end
|
||||
return return_value
|
||||
end
|
||||
|
||||
def is_parseable(s)
|
||||
return parse(s) != nil
|
||||
end
|
||||
|
||||
# looks up a windows constant (integer or hex) and returns an array of matching winconstant names
|
||||
#
|
||||
# this function will NOT throw an exception but return "nil" if it can't find an error code
|
||||
def rev_lookup(winconst, filter_regex=nil)
|
||||
c = winconst.to_i # this is what we're gonna reverse lookup
|
||||
arr = [] # results array
|
||||
@consts.each_pair do |k,v|
|
||||
arr << k if v == c
|
||||
end
|
||||
if filter_regex # this is how we're going to filter the results
|
||||
# in case we get passed a string instead of a Regexp
|
||||
filter_regex = Regexp.new(filter_regex) unless filter_regex.class == Regexp
|
||||
# do the actual filtering
|
||||
arr.select! do |item|
|
||||
item if item =~ filter_regex
|
||||
end
|
||||
end
|
||||
return arr
|
||||
return !parse(s).nil?
|
||||
end
|
||||
|
||||
def is_parseable(s)
|
||||
return parse(s) != nil
|
||||
end
|
||||
#
|
||||
# Returns an array of constant names that have a value matching "winconst"
|
||||
# and (optionally) a name that matches "filter_regex"
|
||||
#
|
||||
def select_const_names(winconst, filter_regex=nil)
|
||||
matches = []
|
||||
|
||||
consts.each_pair do |name, value|
|
||||
matches << name if value == winconst
|
||||
end
|
||||
|
||||
# Filter matches by name if a filter has been provided
|
||||
unless filter_regex.nil?
|
||||
matches.reject! do |name|
|
||||
name !~ filter_regex
|
||||
end
|
||||
end
|
||||
|
||||
return matches
|
||||
end
|
||||
end
|
||||
|
||||
end; end; end; end; end; end
|
||||
|
|
|
@ -12,6 +12,26 @@ module Extensions
|
|||
module Stdapi
|
||||
module Railgun
|
||||
class WinConstManager::UnitTest < Test::Unit::TestCase
|
||||
|
||||
def test_select_const_names
|
||||
const_manager = WinConstManager.new
|
||||
|
||||
names = %w(W WW WWW)
|
||||
|
||||
names.each do |name|
|
||||
const_manager.add_const(name, 23)
|
||||
end
|
||||
|
||||
assert(const_manager.select_const_names(23).sort == names,
|
||||
'select_const_names should return all names for given value')
|
||||
|
||||
const_manager.add_const('Skidoo!', 23)
|
||||
|
||||
assert(const_manager.select_const_names(23, /^\w{1,3}$/).sort == names,
|
||||
'select_const_names should filter names with provided regex')
|
||||
|
||||
end
|
||||
|
||||
def test_is_parseable
|
||||
const_manager = WinConstManager.new
|
||||
|
||||
|
|
Loading…
Reference in New Issue