Merge pull request #96 from chao-mu/master

Updates to Railgun

[Fixes #6128] among other things.
This commit is contained in:
Tod Beardsley 2012-01-09 06:43:02 -08:00
commit 9e78eff968
10 changed files with 406 additions and 50 deletions

View File

@ -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

View File

@ -5,6 +5,8 @@ require 'test/unit'
require 'rex' require 'rex'
require 'railgun/api_constants.rb.ut' 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/buffer_item.rb.ut'
require 'railgun/dll_function.rb.ut' require 'railgun/dll_function.rb.ut'
require 'railgun/dll_helper.rb.ut' require 'railgun/dll_helper.rb.ut'

View File

@ -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

View File

@ -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

View File

@ -61,9 +61,9 @@ class Railgun
# definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'. # definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'.
# Naming is important and should follow convention. For example, if your # Naming is important and should follow convention. For example, if your
# dll's name was "my_dll" # dll's name was "my_dll"
# file name:: def_my_dll.rb # file name: def_my_dll.rb
# class name:: Def_my_dll # class name: Def_my_dll
# entry below:: 'my_dll' # entry below: 'my_dll'
# #
BUILTIN_DLLS = [ BUILTIN_DLLS = [
'kernel32', 'kernel32',
@ -104,6 +104,10 @@ class Railgun
self.dlls = {} self.dlls = {}
end end
def self.builtin_dlls
BUILTIN_DLLS
end
# #
# Return this Railgun's Util instance. # Return this Railgun's Util instance.
# #
@ -184,8 +188,8 @@ class Railgun
# For backwards compatibility, we ensure the dll is thawed # For backwards compatibility, we ensure the dll is thawed
if dll.frozen? if dll.frozen?
# dup will copy values, but not the frozen status # Duplicate not only the dll, but its functions as well. Frozen status will be lost
dll = dll.dup dll = Marshal.load(Marshal.dump(dll))
# Update local dlls with the modifiable duplicate # Update local dlls with the modifiable duplicate
dlls[dll_name] = dll dlls[dll_name] = dll
@ -277,22 +281,6 @@ class Railgun
def const(str) def const(str)
return constant_manager.parse(str) return constant_manager.parse(str)
end 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]]) # The multi-call shorthand (["kernel32", "ExitProcess", [0]])

View File

@ -120,6 +120,14 @@ class Railgun::UnitTest < Test::Unit::TestCase
assert(!unfrozen_dll.frozen?, assert(!unfrozen_dll.frozen?,
"add_function should create a local unfrozen instance that get_dll can then access") "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
end end

View File

@ -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

View File

@ -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

View File

@ -33,6 +33,7 @@ module Railgun
# Manages our library of windows constants # Manages our library of windows constants
# #
class WinConstManager class WinConstManager
attr_reader :consts
def initialize(initial_consts = {}) def initialize(initial_consts = {})
@consts = {} @consts = {}
@ -40,12 +41,10 @@ class WinConstManager
initial_consts.each_pair do |name, value| initial_consts.each_pair do |name, value|
add_const(name, value) add_const(name, value)
end end
# Load utility
end end
def add_const(name, value) def add_const(name, value)
@consts[name] = value consts[name] = value
end end
# parses a string constaining constants and returns an integer # parses a string constaining constants and returns an integer
@ -59,41 +58,38 @@ class WinConstManager
return_value = 0 return_value = 0
for one_const in s.split('|') for one_const in s.split('|')
one_const = one_const.strip() 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 return nil # at least one "Constant" is unknown to us
end end
return_value |= @consts[one_const] return_value |= consts[one_const]
end end
return return_value return return_value
end end
def is_parseable(s) def is_parseable(s)
return parse(s) != nil 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
end end
def is_parseable(s) #
return parse(s) != nil # Returns an array of constant names that have a value matching "winconst"
end # 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; end end; end; end; end; end; end

View File

@ -12,6 +12,26 @@ module Extensions
module Stdapi module Stdapi
module Railgun module Railgun
class WinConstManager::UnitTest < Test::Unit::TestCase 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 def test_is_parseable
const_manager = WinConstManager.new const_manager = WinConstManager.new